From 5ea424d0e84b50cbd55971142a21be0977381b1b Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 16:08:00 -0700 Subject: [PATCH 01/17] start of ciaro implementation. --- src/fe-gtk/xtext.c | 374 +++++++++++++++++++++++---------------------- src/fe-gtk/xtext.h | 12 +- 2 files changed, 200 insertions(+), 186 deletions(-) diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 687aaf48..fa10a947 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -54,13 +54,16 @@ #include #include #include +#include #include #else #include +#include #ifdef GDK_WINDOWING_X11 #include #endif #endif +#include /* is delimiter */ #define is_del(c) \ @@ -147,7 +150,69 @@ static void gtk_xtext_search_fini (xtext_buffer *); static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk_xtext_search_flags flags, GError **perr); static char * gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, int *ret_off, int *ret_len, GSList **slp); -#define xtext_draw_bg(xt,x,y,w,h) gdk_draw_rectangle(xt->draw_buf, xt->bgc, 1, x, y, w, h); +static inline void +xtext_set_source_color (cairo_t *cr, GdkColor *color, gdouble alpha) +{ + cairo_set_source_rgba (cr, color->red / 65535.0, color->green / 65535.0, + color->blue / 65535.0, alpha); +} + +static inline void +xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x, int y, int width, int height) +{ + cairo_save (cr); + + xtext_set_source_color (cr, color, 1.0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle (cr, (double)x, (double)y, (double)width, (double)height); + cairo_fill (cr); + + cairo_restore (cr); +} + +static inline void +xtext_draw_line (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x1, int y1, int x2, int y2) +{ + cairo_save (cr); + + /* Disable antialiasing for crispy 1-pixel lines */ + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + gdk_cairo_set_source_color (cr, color); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_line_width (cr, 1.0); + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x2, y2); + cairo_stroke (cr); + + cairo_restore (cr); +} + +static inline void +xtext_draw_bg_offset (GtkXText *xtext, int x, int y, int width, int height, int tile_x, int tile_y) +{ + cairo_t *cr = gdk_cairo_create (xtext->draw_buf); + + if (xtext->pixmap) + { + gdk_cairo_set_source_pixmap (cr, xtext->pixmap, tile_x, tile_y); + cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT); + cairo_rectangle (cr, (double)x, (double)y, (double)width, (double)height); + cairo_fill (cr); + } + else + { + xtext_draw_rectangle (xtext, cr, &xtext->bgc, x, y, width, height); + } + + cairo_destroy (cr); +} + +static inline void +xtext_draw_bg (GtkXText *xtext, int x, int y, int width, int height) +{ + xtext_draw_bg_offset (xtext, x, y, width, height, xtext->ts_x, xtext->ts_y); +} /* ======================================= */ /* ============ PANGO BACKEND ============ */ @@ -354,71 +419,45 @@ backend_get_text_width_slp (GtkXText *xtext, guchar *str, GSList *slp) return width; } -/* simplified version of gdk_draw_layout_line_with_colors() */ - -static void -xtext_draw_layout_line (GdkDrawable *drawable, - GdkGC *gc, - gint x, - gint y, - PangoLayoutLine *line) -{ - GSList *tmp_list = line->runs; - PangoRectangle logical_rect; - gint x_off = 0; - - while (tmp_list) - { - PangoLayoutRun *run = tmp_list->data; - - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - NULL, &logical_rect); - - gdk_draw_glyphs (drawable, gc, run->item->analysis.font, - x + x_off / PANGO_SCALE, y, run->glyphs); - - x_off += logical_rect.width; - tmp_list = tmp_list->next; - } -} - static void -backend_draw_text_emph (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y, +backend_draw_text_emph (GtkXText *xtext, gboolean dofill, int x, int y, char *str, int len, int str_width, int emphasis) { - GdkGCValues val; - GdkColor col; + cairo_t *cr; PangoLayoutLine *line; + cr = gdk_cairo_create (xtext->draw_buf); + pango_layout_set_attributes (xtext->layout, attr_lists[emphasis]); pango_layout_set_text (xtext->layout, str, len); if (dofill) { - gdk_gc_get_values (gc, &val); - col.pixel = val.background.pixel; - gdk_gc_set_foreground (gc, &col); - gdk_draw_rectangle (xtext->draw_buf, gc, 1, x, y - + xtext_draw_rectangle (xtext, cr, &xtext->bgc, x, y - xtext->font->ascent, str_width, xtext->fontsize); - col.pixel = val.foreground.pixel; - gdk_gc_set_foreground (gc, &col); } - line = pango_layout_get_lines (xtext->layout)->data; + gdk_cairo_set_source_color (cr, &xtext->fgc); + line = pango_layout_get_line_readonly (xtext->layout, 0); - xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line); + cairo_save (cr); + cairo_move_to (cr, x, y); + pango_cairo_show_layout_line (cr, line); + cairo_restore (cr); + + cairo_destroy (cr); } static void -xtext_set_fg (GtkXText *xtext, GdkGC *gc, int index) +xtext_set_fg (GtkXText *xtext, int index) { - gdk_gc_set_foreground (gc, &xtext->palette[index]); + xtext->fgc = xtext->palette[index]; } static void -xtext_set_bg (GtkXText *xtext, GdkGC *gc, int index) +xtext_set_bg (GtkXText *xtext, int index) { - gdk_gc_set_background (gc, &xtext->palette[index]); + xtext->bgc = xtext->palette[index]; } static void @@ -605,41 +644,6 @@ gtk_xtext_destroy (GtkObject * object) xtext->adj = NULL; } - if (xtext->bgc) - { - g_object_unref (xtext->bgc); - xtext->bgc = NULL; - } - - if (xtext->fgc) - { - g_object_unref (xtext->fgc); - xtext->fgc = NULL; - } - - if (xtext->light_gc) - { - g_object_unref (xtext->light_gc); - xtext->light_gc = NULL; - } - - if (xtext->dark_gc) - { - g_object_unref (xtext->dark_gc); - xtext->dark_gc = NULL; - } - - if (xtext->thin_gc) - { - g_object_unref (xtext->thin_gc); - xtext->thin_gc = NULL; - } - - if (xtext->marker_gc) - { - g_object_unref (xtext->marker_gc); - xtext->marker_gc = NULL; - } if (xtext->hand_cursor) { @@ -680,7 +684,6 @@ gtk_xtext_realize (GtkWidget * widget) { GtkXText *xtext; GdkWindowAttr attributes; - GdkGCValues val; GdkColor col; GdkColormap *cmap; @@ -709,53 +712,33 @@ gtk_xtext_realize (GtkWidget * widget) xtext->depth = gdk_window_get_visual (widget->window)->depth; - val.subwindow_mode = GDK_INCLUDE_INFERIORS; - val.graphics_exposures = 0; - - xtext->bgc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->fgc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->light_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->dark_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->thin_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext->marker_gc = gdk_gc_new_with_values (widget->window, &val, - GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - /* for the separator bar (light) */ col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff; gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - gdk_gc_set_foreground (xtext->light_gc, &col); + xtext->light_gc = col; /* for the separator bar (dark) */ col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111; gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - gdk_gc_set_foreground (xtext->dark_gc, &col); + xtext->dark_gc = col; /* for the separator bar (thinline) */ col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38; gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - gdk_gc_set_foreground (xtext->thin_gc, &col); + xtext->thin_gc = col; /* for the marker bar (marker) */ - gdk_gc_set_foreground (xtext->marker_gc, &xtext->palette[XTEXT_MARKER]); + xtext->marker_gc = xtext->palette[XTEXT_MARKER]; - xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); - xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); - xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + xtext_set_fg (xtext, XTEXT_FG); + xtext_set_bg (xtext, XTEXT_BG); /* draw directly to window */ xtext->draw_buf = widget->window; if (xtext->pixmap) { - gdk_gc_set_tile (xtext->bgc, xtext->pixmap); - gdk_gc_set_ts_origin (xtext->bgc, 0, 0); xtext->ts_x = xtext->ts_y = 0; - gdk_gc_set_fill (xtext->bgc, GDK_TILED); } xtext->hand_cursor = gdk_cursor_new_for_display (gdk_window_get_display (widget->window), GDK_HAND1); @@ -971,7 +954,8 @@ static void gtk_xtext_draw_sep (GtkXText * xtext, int y) { int x, height; - GdkGC *light, *dark; + GdkColor *light, *dark; + cairo_t *cr; if (y == -1) { @@ -985,31 +969,37 @@ gtk_xtext_draw_sep (GtkXText * xtext, int y) /* draw the separator line */ if (xtext->separator && xtext->buffer->indent) { - light = xtext->light_gc; - dark = xtext->dark_gc; + light = &xtext->light_gc; + dark = &xtext->dark_gc; + cr = gdk_cairo_create (xtext->draw_buf); x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); if (x < 1) + { + cairo_destroy (cr); return; + } if (xtext->thinline) { if (xtext->moving_separator) - gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + xtext_draw_line (xtext, cr, light, x, y, x, y + height); else - gdk_draw_line (xtext->draw_buf, xtext->thin_gc, x, y, x, y + height); + xtext_draw_line (xtext, cr, &xtext->thin_gc, x, y, x, y + height); } else { if (xtext->moving_separator) { - gdk_draw_line (xtext->draw_buf, light, x - 1, y, x - 1, y + height); - gdk_draw_line (xtext->draw_buf, dark, x, y, x, y + height); + xtext_draw_line (xtext, cr, light, x - 1, y, x - 1, y + height); + xtext_draw_line (xtext, cr, dark, x, y, x, y + height); } else { - gdk_draw_line (xtext->draw_buf, dark, x - 1, y, x - 1, y + height); - gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + xtext_draw_line (xtext, cr, dark, x - 1, y, x - 1, y + height); + xtext_draw_line (xtext, cr, light, x, y, x, y + height); } } + + cairo_destroy (cr); } } @@ -1017,6 +1007,7 @@ static void gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) { int x, width, render_y; + cairo_t *cr; if (!xtext->marker) return; @@ -1033,7 +1024,9 @@ gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) x = 0; width = GTK_WIDGET (xtext)->allocation.width; - gdk_draw_line (xtext->draw_buf, xtext->marker_gc, x, render_y, x + width, render_y); + cr = gdk_cairo_create (xtext->draw_buf); + xtext_draw_line (xtext, cr, &xtext->marker_gc, x, render_y, x + width, render_y); + cairo_destroy (cr); if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext))))) { @@ -2551,11 +2544,13 @@ gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len) static int gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, - int len, GdkGC *gc, int *emphasis) + int len, int *emphasis) { int str_width, dofill; GdkDrawable *pix = NULL; int dest_x = 0, dest_y = 0; + int tile_x = xtext->ts_x; + int tile_y = xtext->ts_y; if (xtext->dont_render || len < 1 || xtext->hidden) return 0; @@ -2584,8 +2579,8 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, { dest_x = x; dest_y = y - xtext->font->ascent; - - gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x - x, xtext->ts_y - dest_y); + tile_x = xtext->ts_x - x; + tile_y = xtext->ts_y - dest_y; x = 0; y = xtext->font->ascent; @@ -2598,19 +2593,19 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, if (!xtext->backcolor && xtext->pixmap) { /* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */ - xtext_draw_bg (xtext, x, y - xtext->font->ascent, str_width, - xtext->fontsize); + xtext_draw_bg_offset (xtext, x, y - xtext->font->ascent, str_width, + xtext->fontsize, tile_x, tile_y); dofill = FALSE; /* already drawn the background */ } - backend_draw_text_emph (xtext, dofill, gc, x, y, str, len, str_width, *emphasis); + backend_draw_text_emph (xtext, dofill, x, y, str, len, str_width, *emphasis); if (pix) { GdkRectangle clip; GdkRectangle dest; + cairo_t *cr; - gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x, xtext->ts_y); xtext->draw_buf = GTK_WIDGET (xtext)->window; clip.x = xtext->clip_x; clip.y = xtext->clip_y; @@ -2624,22 +2619,34 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, if (gdk_rectangle_intersect (&clip, &dest, &dest)) /* dump the DB to window, but only within the clip_x/x2/y/y2 */ - gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, - dest.x - dest_x, dest.y - dest_y, - dest.x, dest.y, dest.width, dest.height); + { + cr = gdk_cairo_create (xtext->draw_buf); + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + gdk_cairo_set_source_pixmap (cr, pix, dest_x, dest_y); + cairo_rectangle (cr, dest.x, dest.y, dest.width, dest.height); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); + } g_object_unref (pix); } if (xtext->strikethrough) { + cairo_t *cr; /* pango_attr_strikethrough_new does not render in the custom widget so we need to reinvent the wheel */ y = dest_y + (xtext->fontsize / 2); - gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + cr = gdk_cairo_create (xtext->draw_buf); + xtext_draw_line (xtext, cr, &xtext->fgc, dest_x, y, dest_x + str_width - 1, y); + cairo_destroy (cr); } if (xtext->underline) { dounder: + { + cairo_t *cr; if (pix) y = dest_y + xtext->font->ascent + 1; @@ -2649,7 +2656,10 @@ dounder: dest_x = x; } /* draw directly to window, it's out of the range of our DB */ - gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + cr = gdk_cairo_create (xtext->draw_buf); + xtext_draw_line (xtext, cr, &xtext->fgc, dest_x, y, dest_x + str_width - 1, y); + cairo_destroy (cr); + } } return str_width; @@ -2668,9 +2678,9 @@ gtk_xtext_reset (GtkXText * xtext, int mark, int attribs) { xtext->backcolor = FALSE; if (xtext->col_fore != XTEXT_FG) - xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + xtext_set_fg (xtext, XTEXT_FG); if (xtext->col_back != XTEXT_BG) - xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + xtext_set_bg (xtext, XTEXT_BG); } xtext->col_fore = XTEXT_FG; xtext->col_back = XTEXT_BG; @@ -2738,14 +2748,13 @@ gtk_xtext_search_offset (xtext_buffer *buf, textentry *ent, unsigned int off) /* render a single line, which WONT wrap, and parse mIRC colors */ -#define RENDER_FLUSH x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, emphasis) +#define RENDER_FLUSH x += gtk_xtext_render_flush (xtext, x, y, pstr, j, emphasis) static int gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, unsigned char *str, int len, int win_width, int indent, int line, int left_only, int *x_size_ret, int *emphasis) { - GdkGC *gc; int i = 0, x = indent, j = 0; unsigned char *pstr = str; int col_num, tmp; @@ -2760,13 +2769,11 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, offset = str - ent->str; - gc = xtext->fgc; /* our foreground GC */ - if (ent->mark_start != -1 && ent->mark_start <= i + offset && ent->mark_end > i + offset) { - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; mark = TRUE; } @@ -2840,7 +2847,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, col_num = col_num % XTEXT_MIRC_COLS; xtext->col_fore = col_num; if (!mark) - xtext_set_fg (xtext, gc, col_num); + xtext_set_fg (xtext, col_num); } } else { @@ -2870,7 +2877,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, else xtext->backcolor = TRUE; if (!mark) - xtext_set_bg (xtext, gc, col_num); + xtext_set_bg (xtext, col_num); xtext->col_back = col_num; } else { @@ -2880,7 +2887,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, if (col_num > XTEXT_MAX_COLOR) col_num = col_num % XTEXT_MIRC_COLS; if (!mark) - xtext_set_fg (xtext, gc, col_num); + xtext_set_fg (xtext, col_num); xtext->col_fore = col_num; } xtext->parsing_backcolor = FALSE; @@ -2904,14 +2911,14 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, { if (k & GTK_MATCH_CUR) { - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; srch_mark = TRUE; } else { - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE; srch_mark = FALSE; } @@ -2921,15 +2928,15 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, xtext->underline = (k & GTK_MATCH_CUR)? TRUE: FALSE; if (k & (GTK_MATCH_START | GTK_MATCH_MID)) { - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; srch_mark = TRUE; } if (k & GTK_MATCH_END) { - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE; srch_mark = FALSE; xtext->underline = FALSE; @@ -2952,8 +2959,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, xtext->col_back = tmp; if (!mark) { - xtext_set_fg (xtext, gc, xtext->col_fore); - xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); } if (xtext->col_back != XTEXT_BG) xtext->backcolor = TRUE; @@ -3040,7 +3047,7 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, /* have we been told to stop rendering at this point? */ if (xtext->jump_out_offset > 0 && xtext->jump_out_offset <= (i + offset)) { - gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, emphasis); + gtk_xtext_render_flush (xtext, x, y, pstr, j, emphasis); ret = 0; /* skip the rest of the lines, we're done. */ j = 0; break; @@ -3074,8 +3081,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, RENDER_FLUSH; pstr += j; j = 0; - xtext_set_bg (xtext, gc, XTEXT_MARK_BG); - xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext_set_bg (xtext, XTEXT_MARK_BG); + xtext_set_fg (xtext, XTEXT_MARK_FG); xtext->backcolor = TRUE; if (srch_underline) { @@ -3090,8 +3097,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, RENDER_FLUSH; pstr += j; j = 0; - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); if (xtext->col_back != XTEXT_BG) xtext->backcolor = TRUE; else @@ -3106,8 +3113,8 @@ gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, if (mark || srch_mark) { - xtext_set_bg (xtext, gc, xtext->col_back); - xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, xtext->col_back); + xtext_set_fg (xtext, xtext->col_fore); if (xtext->col_back != XTEXT_BG) xtext->backcolor = TRUE; else @@ -3442,11 +3449,10 @@ gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) if (gtk_widget_get_realized (GTK_WIDGET(xtext))) { - xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); - xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); - xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + xtext_set_fg (xtext, XTEXT_FG); + xtext_set_bg (xtext, XTEXT_BG); - gdk_gc_set_foreground (xtext->marker_gc, &xtext->palette[XTEXT_MARKER]); + xtext->marker_gc = xtext->palette[XTEXT_MARKER]; } xtext->col_fore = XTEXT_FG; xtext->col_back = XTEXT_BG; @@ -3532,8 +3538,6 @@ gtk_xtext_set_font (GtkXText *xtext, char *name) void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap) { - GdkGCValues val; - if (xtext->pixmap) { g_object_unref (xtext->pixmap); @@ -3548,19 +3552,8 @@ gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap) g_object_ref (pixmap); if (gtk_widget_get_realized (GTK_WIDGET(xtext))) { - gdk_gc_set_tile (xtext->bgc, pixmap); - gdk_gc_set_ts_origin (xtext->bgc, 0, 0); xtext->ts_x = xtext->ts_y = 0; - gdk_gc_set_fill (xtext->bgc, GDK_TILED); } - } else if (gtk_widget_get_realized (GTK_WIDGET(xtext))) - { - g_object_unref (xtext->bgc); - val.subwindow_mode = GDK_INCLUDE_INFERIORS; - val.graphics_exposures = 0; - xtext->bgc = gdk_gc_new_with_values (GTK_WIDGET (xtext)->window, - &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); - xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); } } @@ -3840,27 +3833,48 @@ gtk_xtext_render_page (GtkXText * xtext) if (!xtext->pixmap && abs (overlap) < height) { GdkRectangle area; + cairo_t *cr; + int src_x = 0; + int src_y = 0; + int dest_x = 0; + int dest_y = 0; + int copy_height = 0; - /* so the obscured regions are exposed */ - gdk_gc_set_exposures (xtext->fgc, TRUE); if (overlap < 1) /* DOWN */ { int remainder; - gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, - 0, -overlap, 0, 0, width, height + overlap); + src_y = -overlap; + dest_y = 0; + copy_height = height + overlap; + cr = gdk_cairo_create (xtext->draw_buf); + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + gdk_cairo_set_source_window (cr, GTK_WIDGET (xtext)->window, dest_x - src_x, dest_y - src_y); + cairo_rectangle (cr, dest_x, dest_y, width, copy_height); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); remainder = ((height - xtext->font->descent) % xtext->fontsize) + xtext->font->descent; area.y = (height + overlap) - remainder; area.height = remainder - overlap; } else { - gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, - 0, 0, 0, overlap, width, height - overlap); + src_y = 0; + dest_y = overlap; + copy_height = height - overlap; + cr = gdk_cairo_create (xtext->draw_buf); + cairo_save (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + gdk_cairo_set_source_window (cr, GTK_WIDGET (xtext)->window, dest_x - src_x, dest_y - src_y); + cairo_rectangle (cr, dest_x, dest_y, width, copy_height); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); area.y = 0; area.height = overlap; } - gdk_gc_set_exposures (xtext->fgc, FALSE); if (area.height > 0) { diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index beb77c17..e2e1940a 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -142,12 +142,12 @@ struct _GtkXText int last_win_h; int last_win_w; - GdkGC *bgc; /* backing pixmap */ - GdkGC *fgc; /* text foreground color */ - GdkGC *light_gc; /* sep bar */ - GdkGC *dark_gc; - GdkGC *thin_gc; - GdkGC *marker_gc; + GdkColor bgc; /* text background color */ + GdkColor fgc; /* text foreground color */ + GdkColor light_gc; /* sep bar */ + GdkColor dark_gc; + GdkColor thin_gc; + GdkColor marker_gc; GdkColor palette[XTEXT_COLS]; gint io_tag; /* for delayed refresh events */ From 136e63a6cdd2502adb93f73f7b1f6bdb341404f1 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 16:19:56 -0700 Subject: [PATCH 02/17] ciaro fixes --- src/fe-gtk/maingui.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index cac03b2e..3b19e44c 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -22,6 +22,7 @@ #include #include +#include #include "../common/zoitechat.h" #include "../common/fe.h" @@ -4115,9 +4116,8 @@ mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar) { - GdkGC *gc; GdkColor col; - GdkGCValues val; + cairo_t *cr; int half, width, height; int ox, oy; GdkDrawable *draw; @@ -4144,16 +4144,13 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui draw = gtk_widget_get_window (widget); } - val.subwindow_mode = GDK_INCLUDE_INFERIORS; - val.graphics_exposures = 0; - val.function = GDK_XOR; - - gc = gdk_gc_new_with_values (gtk_widget_get_window (widget), &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION); col.red = rand() % 0xffff; col.green = rand() % 0xffff; col.blue = rand() % 0xffff; - gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE); - gdk_gc_set_foreground (gc, &col); + cr = gdk_cairo_create (draw); + cairo_set_operator (cr, CAIRO_OPERATOR_XOR); + gdk_cairo_set_source_color (cr, &col); + cairo_set_line_width (cr, 1.0); half = height / 2; @@ -4171,18 +4168,20 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui if (y < half) { - gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4); - gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2); + cairo_rectangle (cr, 1 + ox, 2 + oy, width - 3, half - 4); + cairo_rectangle (cr, 0 + ox, 1 + oy, width - 1, half - 2); + cairo_stroke (cr); gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half); } else { - gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2); - gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4); + cairo_rectangle (cr, 0 + ox, half + 1 + oy, width - 1, half - 2); + cairo_rectangle (cr, 1 + ox, half + 2 + oy, width - 3, half - 4); + cairo_stroke (cr); gtk_widget_queue_draw_area (widget, ox, oy, width, half); } - g_object_unref (gc); + cairo_destroy (cr); return TRUE; } From 4bf5316cfafdee2fe760044ab8673be3e9a80b3b Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 16:34:38 -0700 Subject: [PATCH 03/17] Replaced legacy GDK rectangle drawing in the drag highlight path with Cairo calls and ensured cleanup before early return --- src/fe-gtk/maingui.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 3b19e44c..181f89db 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -4156,12 +4156,14 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui #if 0 /* are both tree/userlist on the same side? */ + GtkPaned *paned; paned = (GtkPaned *)widget->parent->parent; if (paned->child1 != NULL && paned->child2 != NULL) { - gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4); - gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2); - g_object_unref (gc); + cairo_rectangle (cr, 1 + ox, 2 + oy, width - 3, height - 4); + cairo_rectangle (cr, 0 + ox, 1 + oy, width - 1, height - 2); + cairo_stroke (cr); + cairo_destroy (cr); return TRUE; } #endif From 944ce699063b8d29817364c0b079d546b1e0c667 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 16:46:21 -0700 Subject: [PATCH 04/17] Added a Cairo color helper for drag highlight rendering and applied it to the drag motion drawing path. Replaced GDK source helpers in the text rendering pipeline with Cairo surface/color helpers for lines, backgrounds, and blits. Updated render sizing to use window dimensions and switched window copy paths to Cairo surfaces. --- src/fe-gtk/maingui.c | 9 ++++++++- src/fe-gtk/xtext.c | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 181f89db..2a9854b7 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -83,6 +83,13 @@ static void mg_emoji_button_cb (GtkWidget *widget, session_gui *gui); static GtkWidget *mg_create_emoji_menu (session_gui *gui); static void mg_emoji_insert_cb (GtkMenuItem *item, session_gui *gui); +static inline void +mg_set_source_color (cairo_t *cr, const GdkColor *color) +{ + cairo_set_source_rgba (cr, color->red / 65535.0, color->green / 65535.0, + color->blue / 65535.0, 1.0); +} + static void mg_create_entry (session *sess, GtkWidget *box); static void mg_create_search (session *sess, GtkWidget *box); #ifdef G_OS_WIN32 @@ -4149,7 +4156,7 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui col.blue = rand() % 0xffff; cr = gdk_cairo_create (draw); cairo_set_operator (cr, CAIRO_OPERATOR_XOR); - gdk_cairo_set_source_color (cr, &col); + mg_set_source_color (cr, &col); cairo_set_line_width (cr, 1.0); half = height / 2; diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index fa10a947..2986a21e 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -157,6 +157,17 @@ xtext_set_source_color (cairo_t *cr, GdkColor *color, gdouble alpha) color->blue / 65535.0, alpha); } +static cairo_surface_t * +xtext_surface_from_drawable (GdkDrawable *drawable) +{ + cairo_t *cr = gdk_cairo_create (drawable); + cairo_surface_t *surface = cairo_surface_reference (cairo_get_target (cr)); + + cairo_destroy (cr); + + return surface; +} + static inline void xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x, int y, int width, int height) { @@ -177,7 +188,7 @@ xtext_draw_line (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x1, int y1, /* Disable antialiasing for crispy 1-pixel lines */ cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); - gdk_cairo_set_source_color (cr, color); + xtext_set_source_color (cr, color, 1.0); cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_line_width (cr, 1.0); @@ -195,7 +206,10 @@ xtext_draw_bg_offset (GtkXText *xtext, int x, int y, int width, int height, int if (xtext->pixmap) { - gdk_cairo_set_source_pixmap (cr, xtext->pixmap, tile_x, tile_y); + cairo_surface_t *surface = xtext_surface_from_drawable (xtext->pixmap); + + cairo_set_source_surface (cr, surface, tile_x, tile_y); + cairo_surface_destroy (surface); cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT); cairo_rectangle (cr, (double)x, (double)y, (double)width, (double)height); cairo_fill (cr); @@ -437,7 +451,7 @@ backend_draw_text_emph (GtkXText *xtext, gboolean dofill, int x, int y, xtext->font->ascent, str_width, xtext->fontsize); } - gdk_cairo_set_source_color (cr, &xtext->fgc); + xtext_set_source_color (cr, &xtext->fgc, 1.0); line = pango_layout_get_line_readonly (xtext->layout, 0); cairo_save (cr); @@ -2620,10 +2634,14 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, if (gdk_rectangle_intersect (&clip, &dest, &dest)) /* dump the DB to window, but only within the clip_x/x2/y/y2 */ { + cairo_surface_t *surface; + cr = gdk_cairo_create (xtext->draw_buf); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - gdk_cairo_set_source_pixmap (cr, pix, dest_x, dest_y); + surface = xtext_surface_from_drawable (pix); + cairo_set_source_surface (cr, surface, dest_x, dest_y); + cairo_surface_destroy (surface); cairo_rectangle (cr, dest.x, dest.y, dest.width, dest.height); cairo_fill (cr); cairo_restore (cr); @@ -3805,7 +3823,8 @@ gtk_xtext_render_page (GtkXText * xtext) if (xtext->buffer->indent < MARGIN) xtext->buffer->indent = MARGIN; /* 2 pixels is our left margin */ - gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + width = gdk_window_get_width (GTK_WIDGET (xtext)->window); + height = gdk_window_get_height (GTK_WIDGET (xtext)->window); if (width < 34 || height < xtext->fontsize || width < xtext->buffer->indent + 32) return; @@ -3843,6 +3862,7 @@ gtk_xtext_render_page (GtkXText * xtext) if (overlap < 1) /* DOWN */ { int remainder; + cairo_surface_t *surface; src_y = -overlap; dest_y = 0; @@ -3850,7 +3870,9 @@ gtk_xtext_render_page (GtkXText * xtext) cr = gdk_cairo_create (xtext->draw_buf); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - gdk_cairo_set_source_window (cr, GTK_WIDGET (xtext)->window, dest_x - src_x, dest_y - src_y); + surface = xtext_surface_from_drawable (GTK_WIDGET (xtext)->window); + cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); + cairo_surface_destroy (surface); cairo_rectangle (cr, dest_x, dest_y, width, copy_height); cairo_fill (cr); cairo_restore (cr); @@ -3861,13 +3883,17 @@ gtk_xtext_render_page (GtkXText * xtext) area.height = remainder - overlap; } else { + cairo_surface_t *surface; + src_y = 0; dest_y = overlap; copy_height = height - overlap; cr = gdk_cairo_create (xtext->draw_buf); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - gdk_cairo_set_source_window (cr, GTK_WIDGET (xtext)->window, dest_x - src_x, dest_y - src_y); + surface = xtext_surface_from_drawable (GTK_WIDGET (xtext)->window); + cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); + cairo_surface_destroy (surface); cairo_rectangle (cr, dest_x, dest_y, width, copy_height); cairo_fill (cr); cairo_restore (cr); From 30e309853c038d1c85117dc4d67f944adf9ec9ef Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 16:56:35 -0700 Subject: [PATCH 05/17] Added the required gdk/gdk.h include before gdkcairo.h in maingui.c to satisfy GDK header include order requirements on Windows builds. Added cairo surface tracking and context creation helpers for xtext rendering, including state initialization for the new draw surface field. Replaced GdkPixmap-based offscreen text buffering with cairo surfaces and routed rendering/underline/strikethrough operations through the cairo context helper. --- src/fe-gtk/maingui.c | 1 + src/fe-gtk/xtext.c | 49 +++++++++++++++++++++++++------------------- src/fe-gtk/xtext.h | 1 + 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 2a9854b7..3bbc7824 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -21,6 +21,7 @@ #include #include +#include #include #include diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 2986a21e..ba76699c 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -168,6 +168,15 @@ xtext_surface_from_drawable (GdkDrawable *drawable) return surface; } +static cairo_t * +xtext_create_context (GtkXText *xtext) +{ + if (xtext->draw_surface) + return cairo_create (xtext->draw_surface); + + return gdk_cairo_create (xtext->draw_buf); +} + static inline void xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x, int y, int width, int height) { @@ -202,7 +211,7 @@ xtext_draw_line (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x1, int y1, static inline void xtext_draw_bg_offset (GtkXText *xtext, int x, int y, int width, int height, int tile_x, int tile_y) { - cairo_t *cr = gdk_cairo_create (xtext->draw_buf); + cairo_t *cr = xtext_create_context (xtext); if (xtext->pixmap) { @@ -440,7 +449,7 @@ backend_draw_text_emph (GtkXText *xtext, gboolean dofill, int x, int y, cairo_t *cr; PangoLayoutLine *line; - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); pango_layout_set_attributes (xtext->layout, attr_lists[emphasis]); pango_layout_set_text (xtext->layout, str, len); @@ -478,6 +487,7 @@ static void gtk_xtext_init (GtkXText * xtext) { xtext->pixmap = NULL; + xtext->draw_surface = NULL; xtext->io_tag = 0; xtext->add_io_tag = 0; xtext->scroll_tag = 0; @@ -985,7 +995,7 @@ gtk_xtext_draw_sep (GtkXText * xtext, int y) { light = &xtext->light_gc; dark = &xtext->dark_gc; - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); if (x < 1) @@ -1038,7 +1048,7 @@ gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) x = 0; width = GTK_WIDGET (xtext)->allocation.width; - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); xtext_draw_line (xtext, cr, &xtext->marker_gc, x, render_y, x + width, render_y); cairo_destroy (cr); @@ -2561,7 +2571,7 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, int len, int *emphasis) { int str_width, dofill; - GdkDrawable *pix = NULL; + cairo_surface_t *surface = NULL; int dest_x = 0, dest_y = 0; int tile_x = xtext->ts_x; int tile_y = xtext->ts_y; @@ -2588,8 +2598,9 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, goto dounder; } - pix = gdk_pixmap_new (xtext->draw_buf, str_width, xtext->fontsize, xtext->depth); - if (pix) + surface = gdk_window_create_similar_surface (GTK_WIDGET (xtext)->window, + CAIRO_CONTENT_COLOR_ALPHA, str_width, xtext->fontsize); + if (surface) { dest_x = x; dest_y = y - xtext->font->ascent; @@ -2598,7 +2609,7 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, x = 0; y = xtext->font->ascent; - xtext->draw_buf = pix; + xtext->draw_surface = surface; } dofill = TRUE; @@ -2614,13 +2625,13 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, backend_draw_text_emph (xtext, dofill, x, y, str, len, str_width, *emphasis); - if (pix) + if (surface) { GdkRectangle clip; GdkRectangle dest; cairo_t *cr; - xtext->draw_buf = GTK_WIDGET (xtext)->window; + xtext->draw_surface = NULL; clip.x = xtext->clip_x; clip.y = xtext->clip_y; clip.width = xtext->clip_x2 - xtext->clip_x; @@ -2634,20 +2645,16 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, if (gdk_rectangle_intersect (&clip, &dest, &dest)) /* dump the DB to window, but only within the clip_x/x2/y/y2 */ { - cairo_surface_t *surface; - - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - surface = xtext_surface_from_drawable (pix); cairo_set_source_surface (cr, surface, dest_x, dest_y); - cairo_surface_destroy (surface); cairo_rectangle (cr, dest.x, dest.y, dest.width, dest.height); cairo_fill (cr); cairo_restore (cr); cairo_destroy (cr); } - g_object_unref (pix); + cairo_surface_destroy (surface); } if (xtext->strikethrough) @@ -2655,7 +2662,7 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, cairo_t *cr; /* pango_attr_strikethrough_new does not render in the custom widget so we need to reinvent the wheel */ y = dest_y + (xtext->fontsize / 2); - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); xtext_draw_line (xtext, cr, &xtext->fgc, dest_x, y, dest_x + str_width - 1, y); cairo_destroy (cr); } @@ -2666,7 +2673,7 @@ dounder: { cairo_t *cr; - if (pix) + if (surface) y = dest_y + xtext->font->ascent + 1; else { @@ -2674,7 +2681,7 @@ dounder: dest_x = x; } /* draw directly to window, it's out of the range of our DB */ - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); xtext_draw_line (xtext, cr, &xtext->fgc, dest_x, y, dest_x + str_width - 1, y); cairo_destroy (cr); } @@ -3867,7 +3874,7 @@ gtk_xtext_render_page (GtkXText * xtext) src_y = -overlap; dest_y = 0; copy_height = height + overlap; - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); surface = xtext_surface_from_drawable (GTK_WIDGET (xtext)->window); @@ -3888,7 +3895,7 @@ gtk_xtext_render_page (GtkXText * xtext) src_y = 0; dest_y = overlap; copy_height = height - overlap; - cr = gdk_cairo_create (xtext->draw_buf); + cr = xtext_create_context (xtext); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); surface = xtext_surface_from_drawable (GTK_WIDGET (xtext)->window); diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index e2e1940a..a2f18a13 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -132,6 +132,7 @@ struct _GtkXText GtkAdjustment *adj; GdkPixmap *pixmap; /* 0 = use palette[19] */ GdkDrawable *draw_buf; /* points to ->window */ + cairo_surface_t *draw_surface; /* temporary surface for offscreen draws */ GdkCursor *hand_cursor; GdkCursor *resize_cursor; From 2ac523480381cf4a88527aeb998f696669f78036 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 17:34:38 -0700 Subject: [PATCH 06/17] - Added Cairo-based window snapshot conversion (with unpremultiplication) for drag icons and updated drag rendering to use the new helper instead of drawable capture. - Updated GtkXText to track the draw window and use a pixmap-to-surface helper for Cairo contexts, avoiding drawable references in the struct and rendering flow. - Added a Cairo helper for capturing window-backed surfaces and used it when blitting during scrolling, fixing the build break from the removed drawable helper. --- src/fe-gtk/maingui.c | 104 ++++++++++++++++++++++++++++++++++++++----- src/fe-gtk/xtext.c | 26 ++++++++--- src/fe-gtk/xtext.h | 2 +- 3 files changed, 113 insertions(+), 19 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 3bbc7824..5b633007 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -91,6 +91,88 @@ mg_set_source_color (cairo_t *cr, const GdkColor *color) color->blue / 65535.0, 1.0); } +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +#define MG_CAIRO_BLUE 0 +#define MG_CAIRO_GREEN 1 +#define MG_CAIRO_RED 2 +#define MG_CAIRO_ALPHA 3 +#else +#define MG_CAIRO_ALPHA 0 +#define MG_CAIRO_RED 1 +#define MG_CAIRO_GREEN 2 +#define MG_CAIRO_BLUE 3 +#endif + +static inline void +mg_unpremultiply_pixel (const guchar *src, guchar *dest) +{ + guchar alpha = src[MG_CAIRO_ALPHA]; + guchar red = src[MG_CAIRO_RED]; + guchar green = src[MG_CAIRO_GREEN]; + guchar blue = src[MG_CAIRO_BLUE]; + + if (alpha == 0) + { + dest[0] = 0; + dest[1] = 0; + dest[2] = 0; + dest[3] = 0; + return; + } + + dest[0] = (guchar)((red * 255 + alpha / 2) / alpha); + dest[1] = (guchar)((green * 255 + alpha / 2) / alpha); + dest[2] = (guchar)((blue * 255 + alpha / 2) / alpha); + dest[3] = alpha; +} + +static GdkPixbuf * +mg_pixbuf_from_window (GdkWindow *window, int width, int height) +{ + cairo_surface_t *surface; + cairo_t *cr; + GdkPixbuf *pixbuf; + guchar *surface_data; + guchar *dest; + int surface_stride; + int dest_stride; + int x; + int y; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create (surface); + gdk_cairo_set_source_window (cr, window, 0.0, 0.0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + cairo_destroy (cr); + + cairo_surface_flush (surface); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); + surface_data = cairo_image_surface_get_data (surface); + surface_stride = cairo_image_surface_get_stride (surface); + dest = gdk_pixbuf_get_pixels (pixbuf); + dest_stride = gdk_pixbuf_get_rowstride (pixbuf); + + for (y = 0; y < height; y++) + { + const guchar *src_row = surface_data + (y * surface_stride); + guchar *dest_row = dest + (y * dest_stride); + + for (x = 0; x < width; x++) + { + const guchar *src = src_row + (x * 4); + guchar *dst = dest_row + (x * 4); + + mg_unpremultiply_pixel (src, dst); + } + } + + cairo_surface_destroy (surface); + + return pixbuf; +} + static void mg_create_entry (session *sess, GtkWidget *box); static void mg_create_search (session *sess, GtkWidget *box); #ifdef G_OS_WIN32 @@ -4062,18 +4144,18 @@ gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) { int width, height; - GdkColormap *cmap; GdkPixbuf *pix, *pix2; + GdkWindow *window; /* ignore file drops */ if (!mg_is_gui_target (context)) return FALSE; - cmap = gtk_widget_get_colormap (widget); - width = gdk_window_get_width (gtk_widget_get_window (widget)); - height = gdk_window_get_height (gtk_widget_get_window (widget)); + window = gtk_widget_get_window (widget); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); - pix = gdk_pixbuf_get_from_drawable (NULL, gtk_widget_get_window (widget), cmap, 0, 0, 0, 0, width, height); + pix = mg_pixbuf_from_window (window, width, height); pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER); g_object_unref (pix); @@ -4128,7 +4210,7 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui cairo_t *cr; int half, width, height; int ox, oy; - GdkDrawable *draw; + GdkWindow *window; GtkAllocation allocation; /* ignore file drops */ @@ -4142,20 +4224,20 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui oy = allocation.y; width = allocation.width; height = allocation.height; - draw = gtk_widget_get_window (widget); + window = gtk_widget_get_window (widget); } else { ox = oy = 0; - width = gdk_window_get_width (gtk_widget_get_window (widget)); - height = gdk_window_get_height (gtk_widget_get_window (widget)); - draw = gtk_widget_get_window (widget); + window = gtk_widget_get_window (widget); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); } col.red = rand() % 0xffff; col.green = rand() % 0xffff; col.blue = rand() % 0xffff; - cr = gdk_cairo_create (draw); + cr = gdk_cairo_create (window); cairo_set_operator (cr, CAIRO_OPERATOR_XOR); mg_set_source_color (cr, &col); cairo_set_line_width (cr, 1.0); diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index ba76699c..fe57789c 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -158,9 +158,20 @@ xtext_set_source_color (cairo_t *cr, GdkColor *color, gdouble alpha) } static cairo_surface_t * -xtext_surface_from_drawable (GdkDrawable *drawable) +xtext_surface_from_pixmap (GdkPixmap *pixmap) { - cairo_t *cr = gdk_cairo_create (drawable); + cairo_t *cr = gdk_cairo_create (pixmap); + cairo_surface_t *surface = cairo_surface_reference (cairo_get_target (cr)); + + cairo_destroy (cr); + + return surface; +} + +static cairo_surface_t * +xtext_surface_from_window (GdkWindow *window) +{ + cairo_t *cr = gdk_cairo_create (window); cairo_surface_t *surface = cairo_surface_reference (cairo_get_target (cr)); cairo_destroy (cr); @@ -174,7 +185,7 @@ xtext_create_context (GtkXText *xtext) if (xtext->draw_surface) return cairo_create (xtext->draw_surface); - return gdk_cairo_create (xtext->draw_buf); + return gdk_cairo_create (xtext->draw_window); } static inline void @@ -215,7 +226,7 @@ xtext_draw_bg_offset (GtkXText *xtext, int x, int y, int width, int height, int if (xtext->pixmap) { - cairo_surface_t *surface = xtext_surface_from_drawable (xtext->pixmap); + cairo_surface_t *surface = xtext_surface_from_pixmap (xtext->pixmap); cairo_set_source_surface (cr, surface, tile_x, tile_y); cairo_surface_destroy (surface); @@ -487,6 +498,7 @@ static void gtk_xtext_init (GtkXText * xtext) { xtext->pixmap = NULL; + xtext->draw_window = NULL; xtext->draw_surface = NULL; xtext->io_tag = 0; xtext->add_io_tag = 0; @@ -758,7 +770,7 @@ gtk_xtext_realize (GtkWidget * widget) xtext_set_bg (xtext, XTEXT_BG); /* draw directly to window */ - xtext->draw_buf = widget->window; + xtext->draw_window = widget->window; if (xtext->pixmap) { @@ -3877,7 +3889,7 @@ gtk_xtext_render_page (GtkXText * xtext) cr = xtext_create_context (xtext); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - surface = xtext_surface_from_drawable (GTK_WIDGET (xtext)->window); + surface = xtext_surface_from_window (GTK_WIDGET (xtext)->window); cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); cairo_surface_destroy (surface); cairo_rectangle (cr, dest_x, dest_y, width, copy_height); @@ -3898,7 +3910,7 @@ gtk_xtext_render_page (GtkXText * xtext) cr = xtext_create_context (xtext); cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - surface = xtext_surface_from_drawable (GTK_WIDGET (xtext)->window); + surface = xtext_surface_from_window (GTK_WIDGET (xtext)->window); cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); cairo_surface_destroy (surface); cairo_rectangle (cr, dest_x, dest_y, width, copy_height); diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index a2f18a13..83e00839 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -131,7 +131,7 @@ struct _GtkXText GtkAdjustment *adj; GdkPixmap *pixmap; /* 0 = use palette[19] */ - GdkDrawable *draw_buf; /* points to ->window */ + GdkWindow *draw_window; /* points to ->window */ cairo_surface_t *draw_surface; /* temporary surface for offscreen draws */ GdkCursor *hand_cursor; GdkCursor *resize_cursor; From 24b0a3d75f8451861c86b9ee210763942e6e5f10 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 17:44:49 -0700 Subject: [PATCH 07/17] - Introduced the XTextColor struct and stored xtext palette/foreground/background colors in internal renderer state rather than GdkColor fields. - Converted palette colors and background pixmaps into cairo-ready values/surfaces for rendering (including background tiling and separator colors). --- src/fe-gtk/xtext.c | 107 ++++++++++++++++++++++----------------------- src/fe-gtk/xtext.h | 24 ++++++---- 2 files changed, 69 insertions(+), 62 deletions(-) diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index fe57789c..64b6a5b7 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -150,22 +150,24 @@ static void gtk_xtext_search_fini (xtext_buffer *); static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk_xtext_search_flags flags, GError **perr); static char * gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, int *ret_off, int *ret_len, GSList **slp); -static inline void -xtext_set_source_color (cairo_t *cr, GdkColor *color, gdouble alpha) +static inline XTextColor +xtext_color_from_gdk (const GdkColor *color) { - cairo_set_source_rgba (cr, color->red / 65535.0, color->green / 65535.0, - color->blue / 65535.0, alpha); + XTextColor result; + + result.red = color->red / 65535.0; + result.green = color->green / 65535.0; + result.blue = color->blue / 65535.0; + result.alpha = 1.0; + + return result; } -static cairo_surface_t * -xtext_surface_from_pixmap (GdkPixmap *pixmap) +static inline void +xtext_set_source_color (cairo_t *cr, const XTextColor *color, gdouble alpha) { - cairo_t *cr = gdk_cairo_create (pixmap); - cairo_surface_t *surface = cairo_surface_reference (cairo_get_target (cr)); - - cairo_destroy (cr); - - return surface; + cairo_set_source_rgba (cr, color->red, color->green, + color->blue, color->alpha * alpha); } static cairo_surface_t * @@ -189,7 +191,7 @@ xtext_create_context (GtkXText *xtext) } static inline void -xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x, int y, int width, int height) +xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, const XTextColor *color, int x, int y, int width, int height) { cairo_save (cr); @@ -202,7 +204,7 @@ xtext_draw_rectangle (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x, int } static inline void -xtext_draw_line (GtkXText *xtext, cairo_t *cr, GdkColor *color, int x1, int y1, int x2, int y2) +xtext_draw_line (GtkXText *xtext, cairo_t *cr, const XTextColor *color, int x1, int y1, int x2, int y2) { cairo_save (cr); @@ -224,12 +226,9 @@ xtext_draw_bg_offset (GtkXText *xtext, int x, int y, int width, int height, int { cairo_t *cr = xtext_create_context (xtext); - if (xtext->pixmap) + if (xtext->background_surface) { - cairo_surface_t *surface = xtext_surface_from_pixmap (xtext->pixmap); - - cairo_set_source_surface (cr, surface, tile_x, tile_y); - cairo_surface_destroy (surface); + cairo_set_source_surface (cr, xtext->background_surface, tile_x, tile_y); cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT); cairo_rectangle (cr, (double)x, (double)y, (double)width, (double)height); cairo_fill (cr); @@ -497,7 +496,7 @@ xtext_set_bg (GtkXText *xtext, int index) static void gtk_xtext_init (GtkXText * xtext) { - xtext->pixmap = NULL; + xtext->background_surface = NULL; xtext->draw_window = NULL; xtext->draw_surface = NULL; xtext->io_tag = 0; @@ -659,10 +658,10 @@ gtk_xtext_destroy (GtkObject * object) xtext->io_tag = 0; } - if (xtext->pixmap) + if (xtext->background_surface) { - g_object_unref (xtext->pixmap); - xtext->pixmap = NULL; + cairo_surface_destroy (xtext->background_surface); + xtext->background_surface = NULL; } if (xtext->font) @@ -720,8 +719,6 @@ gtk_xtext_realize (GtkWidget * widget) { GtkXText *xtext; GdkWindowAttr attributes; - GdkColor col; - GdkColormap *cmap; gtk_widget_set_realized (widget, TRUE); xtext = GTK_XTEXT (widget); @@ -736,8 +733,7 @@ gtk_xtext_realize (GtkWidget * widget) GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK; - cmap = gtk_widget_get_colormap (widget); - attributes.colormap = cmap; + attributes.colormap = gtk_widget_get_colormap (widget); attributes.visual = gtk_widget_get_visual (widget); widget->window = gdk_window_new (widget->parent->window, &attributes, @@ -749,19 +745,22 @@ gtk_xtext_realize (GtkWidget * widget) xtext->depth = gdk_window_get_visual (widget->window)->depth; /* for the separator bar (light) */ - col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff; - gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - xtext->light_gc = col; + xtext->light_gc.red = 1.0; + xtext->light_gc.green = 1.0; + xtext->light_gc.blue = 1.0; + xtext->light_gc.alpha = 1.0; /* for the separator bar (dark) */ - col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111; - gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - xtext->dark_gc = col; + xtext->dark_gc.red = 0x1111 / 65535.0; + xtext->dark_gc.green = 0x1111 / 65535.0; + xtext->dark_gc.blue = 0x1111 / 65535.0; + xtext->dark_gc.alpha = 1.0; /* for the separator bar (thinline) */ - col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38; - gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); - xtext->thin_gc = col; + xtext->thin_gc.red = 0x8e38 / 65535.0; + xtext->thin_gc.green = 0x8e38 / 65535.0; + xtext->thin_gc.blue = 0x9f38 / 65535.0; + xtext->thin_gc.alpha = 1.0; /* for the marker bar (marker) */ xtext->marker_gc = xtext->palette[XTEXT_MARKER]; @@ -772,7 +771,7 @@ gtk_xtext_realize (GtkWidget * widget) /* draw directly to window */ xtext->draw_window = widget->window; - if (xtext->pixmap) + if (xtext->background_surface) { xtext->ts_x = xtext->ts_y = 0; } @@ -990,7 +989,6 @@ static void gtk_xtext_draw_sep (GtkXText * xtext, int y) { int x, height; - GdkColor *light, *dark; cairo_t *cr; if (y == -1) @@ -1005,8 +1003,8 @@ gtk_xtext_draw_sep (GtkXText * xtext, int y) /* draw the separator line */ if (xtext->separator && xtext->buffer->indent) { - light = &xtext->light_gc; - dark = &xtext->dark_gc; + const XTextColor *light = &xtext->light_gc; + const XTextColor *dark = &xtext->dark_gc; cr = xtext_create_context (xtext); x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); @@ -2627,9 +2625,9 @@ gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, dofill = TRUE; /* backcolor is always handled by XDrawImageString */ - if (!xtext->backcolor && xtext->pixmap) + if (!xtext->backcolor && xtext->background_surface) { - /* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */ + /* draw the background surface behind the text - CAUSES FLICKER HERE!! */ xtext_draw_bg_offset (xtext, x, y - xtext->font->ascent, str_width, xtext->fontsize, tile_x, tile_y); dofill = FALSE; /* already drawn the background */ @@ -3481,7 +3479,7 @@ gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) for (i = (XTEXT_COLS-1); i >= 0; i--) { - xtext->palette[i] = palette[i]; + xtext->palette[i] = xtext_color_from_gdk (&palette[i]); } if (gtk_widget_get_realized (GTK_WIDGET(xtext))) @@ -3575,22 +3573,23 @@ gtk_xtext_set_font (GtkXText *xtext, char *name) void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap) { - if (xtext->pixmap) + if (xtext->background_surface) { - g_object_unref (xtext->pixmap); - xtext->pixmap = NULL; + cairo_surface_destroy (xtext->background_surface); + xtext->background_surface = NULL; } dontscroll (xtext->buffer); - xtext->pixmap = pixmap; - if (pixmap != 0) { - g_object_ref (pixmap); - if (gtk_widget_get_realized (GTK_WIDGET(xtext))) - { - xtext->ts_x = xtext->ts_y = 0; - } + cairo_t *cr = gdk_cairo_create (pixmap); + xtext->background_surface = cairo_surface_reference (cairo_get_target (cr)); + cairo_destroy (cr); + } + + if (pixmap != 0 && gtk_widget_get_realized (GTK_WIDGET(xtext))) + { + xtext->ts_x = xtext->ts_y = 0; } } @@ -3868,7 +3867,7 @@ gtk_xtext_render_page (GtkXText * xtext) xtext->buffer->last_pixel_pos = pos; #ifndef __APPLE__ - if (!xtext->pixmap && abs (overlap) < height) + if (!xtext->background_surface && abs (overlap) < height) { GdkRectangle area; cairo_t *cr; diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index 83e00839..789aabe8 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -55,6 +55,14 @@ typedef struct _GtkXText GtkXText; typedef struct _GtkXTextClass GtkXTextClass; typedef struct textentry textentry; +typedef struct +{ + double red; + double green; + double blue; + double alpha; +} XTextColor; + /* * offsets_t is used for retaining search information. * It is stored in the 'data' member of a GList, @@ -130,7 +138,7 @@ struct _GtkXText xtext_buffer *selection_buffer; GtkAdjustment *adj; - GdkPixmap *pixmap; /* 0 = use palette[19] */ + cairo_surface_t *background_surface; /* 0 = use palette[19] */ GdkWindow *draw_window; /* points to ->window */ cairo_surface_t *draw_surface; /* temporary surface for offscreen draws */ GdkCursor *hand_cursor; @@ -143,13 +151,13 @@ struct _GtkXText int last_win_h; int last_win_w; - GdkColor bgc; /* text background color */ - GdkColor fgc; /* text foreground color */ - GdkColor light_gc; /* sep bar */ - GdkColor dark_gc; - GdkColor thin_gc; - GdkColor marker_gc; - GdkColor palette[XTEXT_COLS]; + XTextColor bgc; /* text background color */ + XTextColor fgc; /* text foreground color */ + XTextColor light_gc; /* sep bar */ + XTextColor dark_gc; + XTextColor thin_gc; + XTextColor marker_gc; + XTextColor palette[XTEXT_COLS]; gint io_tag; /* for delayed refresh events */ gint add_io_tag; /* "" when adding new text */ From 0601be026c62fa185868942062629a78cceeb88a Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 18:19:00 -0700 Subject: [PATCH 08/17] - Converted text background loading to build Cairo surfaces directly from pixbufs for renderer use. - Updated the xtext renderer to accept Cairo background surfaces and dropped the GtkStyle attach during realize to keep rendering Cairo/Pango-focused. - Switched background surface ownership and cleanup to use cairo_surface_t across the shared state and settings update path. --- src/fe-gtk/fe-gtk.c | 2 +- src/fe-gtk/fe-gtk.h | 5 +-- src/fe-gtk/pixmaps.c | 76 ++++++++++++++++++++++++++++++++++++++++---- src/fe-gtk/pixmaps.h | 4 ++- src/fe-gtk/setup.c | 2 +- src/fe-gtk/xtext.c | 11 +++---- src/fe-gtk/xtext.h | 3 +- 7 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index da415d25..374720bb 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -58,7 +58,7 @@ #include #endif -GdkPixmap *channelwin_pix; +cairo_surface_t *channelwin_pix; #ifdef USE_LIBCANBERRA static ca_context *ca_con; diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index cfb74035..f8617517 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -30,6 +30,7 @@ #include #include +#include #ifdef HAVE_GTK_MAC #include @@ -178,8 +179,8 @@ typedef struct session_gui } session_gui; -extern GdkPixmap *channelwin_pix; -extern GdkPixmap *dialogwin_pix; +extern cairo_surface_t *channelwin_pix; +extern cairo_surface_t *dialogwin_pix; #define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e)))) #define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c index 3e613684..132fc325 100644 --- a/src/fe-gtk/pixmaps.c +++ b/src/fe-gtk/pixmaps.c @@ -28,6 +28,7 @@ #include #include +#include GdkPixbuf *pix_ulist_voice; GdkPixbuf *pix_ulist_halfop; @@ -49,26 +50,89 @@ GdkPixbuf *pix_tree_util; GdkPixbuf *pix_book; GdkPixbuf *pix_zoitechat; -static GdkPixmap * +static cairo_surface_t * +pixbuf_to_cairo_surface (GdkPixbuf *pixbuf) +{ + cairo_surface_t *surface; + gboolean has_alpha; + int width; + int height; + int src_stride; + int dest_stride; + int n_channels; + const guchar *src_pixels; + unsigned char *dest_pixels; + int x; + int y; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + src_stride = gdk_pixbuf_get_rowstride (pixbuf); + src_pixels = gdk_pixbuf_get_pixels (pixbuf); + dest_stride = cairo_image_surface_get_stride (surface); + dest_pixels = cairo_image_surface_get_data (surface); + + for (y = 0; y < height; y++) + { + const guchar *src_row = src_pixels + (y * src_stride); + guint32 *dest_row = (guint32 *)(dest_pixels + (y * dest_stride)); + + for (x = 0; x < width; x++) + { + const guchar *src = src_row + (x * n_channels); + guchar alpha = has_alpha ? src[3] : 0xff; + guchar red = src[0]; + guchar green = src[1]; + guchar blue = src[2]; + guchar premul_red = (guchar)((red * alpha + 127) / 255); + guchar premul_green = (guchar)((green * alpha + 127) / 255); + guchar premul_blue = (guchar)((blue * alpha + 127) / 255); + + dest_row[x] = ((guint32)alpha << 24) | + ((guint32)premul_red << 16) | + ((guint32)premul_green << 8) | + ((guint32)premul_blue); + } + } + + cairo_surface_mark_dirty (surface); + + return surface; +} + +static cairo_surface_t * pixmap_load_from_file_real (char *file) { GdkPixbuf *img; - GdkPixmap *pixmap; + cairo_surface_t *surface; img = gdk_pixbuf_new_from_file (file, 0); if (!img) return NULL; - gdk_pixbuf_render_pixmap_and_mask (img, &pixmap, NULL, 128); + + surface = pixbuf_to_cairo_surface (img); g_object_unref (img); - return pixmap; + return surface; } -GdkPixmap * +cairo_surface_t * pixmap_load_from_file (char *filename) { char buf[256]; - GdkPixmap *pix; + cairo_surface_t *pix; if (filename[0] == '\0') return NULL; diff --git a/src/fe-gtk/pixmaps.h b/src/fe-gtk/pixmaps.h index a8ef6009..b97fe946 100644 --- a/src/fe-gtk/pixmaps.h +++ b/src/fe-gtk/pixmaps.h @@ -20,6 +20,8 @@ #ifndef HEXCHAT_PIXMAPS_H #define HEXCHAT_PIXMAPS_H +#include + extern GdkPixbuf *pix_ulist_voice; extern GdkPixbuf *pix_ulist_halfop; extern GdkPixbuf *pix_ulist_op; @@ -40,7 +42,7 @@ extern GdkPixbuf *pix_tree_util; extern GdkPixbuf *pix_book; extern GdkPixbuf *pix_zoitechat; -extern GdkPixmap *pixmap_load_from_file (char *file); +extern cairo_surface_t *pixmap_load_from_file (char *file); extern void pixmaps_init (void); #endif diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 91f1a486..91948674 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -2449,7 +2449,7 @@ setup_apply_real (int new_pix, int do_ulist, int do_layout, int do_identd) if (new_pix) { if (channelwin_pix) - g_object_unref (channelwin_pix); + cairo_surface_destroy (channelwin_pix); channelwin_pix = pixmap_load_from_file (prefs.hex_text_background); } diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 64b6a5b7..62a7ad7d 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -780,7 +780,6 @@ gtk_xtext_realize (GtkWidget * widget) xtext->resize_cursor = gdk_cursor_new_for_display (gdk_window_get_display (widget->window), GDK_LEFT_SIDE); gdk_window_set_back_pixmap (widget->window, NULL, FALSE); - widget->style = gtk_style_attach (widget->style, widget->window); backend_init (xtext); } @@ -3571,7 +3570,7 @@ gtk_xtext_set_font (GtkXText *xtext, char *name) } void -gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap) +gtk_xtext_set_background (GtkXText * xtext, cairo_surface_t *surface) { if (xtext->background_surface) { @@ -3580,14 +3579,12 @@ gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap) } dontscroll (xtext->buffer); - if (pixmap != 0) + if (surface) { - cairo_t *cr = gdk_cairo_create (pixmap); - xtext->background_surface = cairo_surface_reference (cairo_get_target (cr)); - cairo_destroy (cr); + xtext->background_surface = cairo_surface_reference (surface); } - if (pixmap != 0 && gtk_widget_get_realized (GTK_WIDGET(xtext))) + if (surface && gtk_widget_get_realized (GTK_WIDGET(xtext))) { xtext->ts_x = xtext->ts_y = 0; } diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index 789aabe8..3663413c 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -21,6 +21,7 @@ #define HEXCHAT_XTEXT_H #include +#include #define GTK_TYPE_XTEXT (gtk_xtext_get_type ()) #define GTK_XTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText)) @@ -268,7 +269,7 @@ void gtk_xtext_append_indent (xtext_buffer *buf, unsigned char *right_text, int right_len, time_t stamp); int gtk_xtext_set_font (GtkXText *xtext, char *name); -void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap); +void gtk_xtext_set_background (GtkXText * xtext, cairo_surface_t *surface); void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]); void gtk_xtext_clear (xtext_buffer *buf, int lines); void gtk_xtext_save (GtkXText * xtext, int fh); From 5986e6a78bdd2cb5a5f183121ae6597c6c24e0d0 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 18:31:04 -0700 Subject: [PATCH 09/17] -Added the standard type attribute to the GTK file info query so file type checks no longer trigger GLib-GIO warnings. -Replaced GtkStyle application in the channel tree view with explicit base/text/font modifications to avoid style attach/detach issues on teardown. -Updated user list styling to apply the font directly instead of setting a shared GtkStyle instance. -Switched color button updates to use background modifications instead of creating per-button styles, reducing detach warnings on shutdown. --- src/fe-gtk/chanview-tree.c | 6 +++++- src/fe-gtk/gtkutil.c | 2 +- src/fe-gtk/maingui.c | 2 +- src/fe-gtk/setup.c | 15 +++------------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c index 9dd8359e..bfceeab2 100644 --- a/src/fe-gtk/chanview-tree.c +++ b/src/fe-gtk/chanview-tree.c @@ -117,7 +117,11 @@ cv_tree_init (chanview *cv) view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store)); gtk_widget_set_name (view, "zoitechat-tree"); if (cv->style) - gtk_widget_set_style (view, cv->style); + { + gtk_widget_modify_base (view, GTK_STATE_NORMAL, &cv->style->base[GTK_STATE_NORMAL]); + gtk_widget_modify_text (view, GTK_STATE_NORMAL, &cv->style->text[GTK_STATE_NORMAL]); + gtk_widget_modify_font (view, cv->style->font_desc); + } /*gtk_widget_modify_base (view, GTK_STATE_NORMAL, &colors[COL_BG]);*/ gtk_widget_set_can_focus (view, FALSE); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index 29a2c9a6..22463555 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -95,7 +95,7 @@ gtkutil_check_file (char *filename, struct file_req *freq) } else { - GFileInfo *fi = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, NULL); + GFileInfo *fi = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (fi != NULL) { diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 5b633007..a471af7b 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -2587,7 +2587,7 @@ mg_create_userlist (session_gui *gui, GtkWidget *box) if (prefs.hex_gui_ulist_style) { - gtk_widget_set_style (ulist, input_style); + gtk_widget_modify_font (ulist, input_style->font_desc); } /* diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 91948674..7c9d7516 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -1454,8 +1454,6 @@ setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) GtkColorSelectionDialog *cdialog = GTK_COLOR_SELECTION_DIALOG (dialog); GdkColor *col; GdkColor old_color; - GtkStyle *style; - col = g_object_get_data (G_OBJECT (button), "c"); old_color = *col; @@ -1473,10 +1471,7 @@ setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE); - style = gtk_style_new (); - style->bg[0] = *col; - gtk_widget_set_style (button, style); - g_object_unref (style); + gtk_widget_modify_bg (button, GTK_STATE_NORMAL, col); /* is this line correct?? */ gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1); @@ -1528,7 +1523,6 @@ static void setup_create_color_button (GtkWidget *table, int num, int row, int col) { GtkWidget *but; - GtkStyle *style; char buf[64]; if (num > 31) @@ -1544,10 +1538,7 @@ setup_create_color_button (GtkWidget *table, int num, int row, int col) GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num)); - style = gtk_style_new (); - style->bg[GTK_STATE_NORMAL] = colors[num]; - gtk_widget_set_style (but, style); - g_object_unref (style); + gtk_widget_modify_bg (but, GTK_STATE_NORMAL, &colors[num]); /* Track all color selector widgets (used for dark mode UI behavior). */ color_selector_widgets = g_slist_prepend (color_selector_widgets, but); @@ -2372,7 +2363,7 @@ setup_apply_to_sess (session_gui *gui) chanview_apply_theme ((chanview *) gui->chanview); if (prefs.hex_gui_ulist_style) - gtk_widget_set_style (gui->user_tree, input_style); + gtk_widget_modify_font (gui->user_tree, input_style->font_desc); if (prefs.hex_gui_ulist_style || prefs.hex_gui_dark_mode) { From 4ac836fc661f9ddeeb334b103bea0db26ac33f5a Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 21:39:45 -0700 Subject: [PATCH 10/17] - Added a shared XTextColor definition and a palette conversion helper for Cairo-ready colors, decoupling renderer palettes from GdkColor usage in the API surface. - Updated xtext palette APIs to accept internal color arrays directly for rendering setup. - Wired xtext palette creation through UI entry points to use the new palette helper when constructing or refreshing text views. --- src/fe-gtk/fkeys.c | 4 +++- src/fe-gtk/maingui.c | 8 ++++++-- src/fe-gtk/palette.c | 25 ++++++++++++++++++++++++- src/fe-gtk/palette.h | 6 ++++++ src/fe-gtk/rawlog.c | 4 +++- src/fe-gtk/textgui.c | 4 +++- src/fe-gtk/xtext-color.h | 20 ++++++++++++++++++++ src/fe-gtk/xtext.c | 19 +++---------------- src/fe-gtk/xtext.h | 13 +++---------- 9 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 src/fe-gtk/xtext-color.h diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c index ac281764..bef7e9b5 100644 --- a/src/fe-gtk/fkeys.c +++ b/src/fe-gtk/fkeys.c @@ -803,6 +803,7 @@ key_dialog_show () GtkWidget *vbox, *box; GtkWidget *view, *xtext; GtkListStore *store; + XTextColor xtext_palette[XTEXT_COLS]; char buf[128]; if (key_dialog) @@ -816,7 +817,8 @@ key_dialog_show () NULL, 600, 360, &vbox, 0); view = key_dialog_treeview_new (vbox); - xtext = gtk_xtext_new (colors, 0); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + xtext = gtk_xtext_new (xtext_palette, 0); gtk_box_pack_start (GTK_BOX (vbox), xtext, FALSE, TRUE, 2); gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font); diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index a471af7b..f6f6214b 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -2405,8 +2405,10 @@ mg_update_xtext (GtkWidget *wid) { GtkXText *xtext = GTK_XTEXT (wid); const gchar *font_name; + XTextColor xtext_palette[XTEXT_COLS]; - gtk_xtext_set_palette (xtext, colors); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + gtk_xtext_set_palette (xtext, xtext_palette); gtk_xtext_set_max_lines (xtext, prefs.hex_text_max_lines); gtk_xtext_set_background (xtext, channelwin_pix); gtk_xtext_set_wordwrap (xtext, prefs.hex_text_wordwrap); @@ -2431,6 +2433,7 @@ mg_create_textarea (session *sess, GtkWidget *box) { GtkWidget *inbox, *vbox, *frame; GtkXText *xtext; + XTextColor xtext_palette[XTEXT_COLS]; session_gui *gui = sess->gui; static const GtkTargetEntry dnd_targets[] = { @@ -2451,7 +2454,8 @@ mg_create_textarea (session *sess, GtkWidget *box) gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (inbox), frame); - gui->xtext = gtk_xtext_new (colors, TRUE); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + gui->xtext = gtk_xtext_new (xtext_palette, TRUE); xtext = GTK_XTEXT (gui->xtext); gtk_xtext_set_max_indent (xtext, prefs.hex_text_max_indent); gtk_xtext_set_thin_separator (xtext, prefs.hex_text_thin_sep); diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c index a973bcc8..e35f607f 100644 --- a/src/fe-gtk/palette.c +++ b/src/fe-gtk/palette.c @@ -38,6 +38,18 @@ #include "../common/cfgfiles.h" #include "../common/typedef.h" +static XTextColor +palette_color_from_gdk (const GdkColor *color) +{ + XTextColor result; + + result.red = color->red / 65535.0; + result.green = color->green / 65535.0; + result.blue = color->blue / 65535.0; + result.alpha = 1.0; + + return result; +} GdkColor colors[] = { /* colors for xtext */ @@ -143,6 +155,18 @@ static const GdkColor dark_colors[MAX_COL + 1] = { {0, 0xf4f4, 0x4747, 0x4747}, /* 41 COL_SPELL (spellcheck underline) */ }; +void +palette_get_xtext_colors (XTextColor *palette, size_t palette_len) +{ + size_t i; + size_t count = palette_len < G_N_ELEMENTS (colors) ? palette_len : G_N_ELEMENTS (colors); + + for (i = 0; i < count; i++) + { + palette[i] = palette_color_from_gdk (&colors[i]); + } +} + void palette_user_set_color (int idx, const GdkColor *col) { @@ -399,4 +423,3 @@ palette_apply_dark_mode (gboolean enable) return changed; } - diff --git a/src/fe-gtk/palette.h b/src/fe-gtk/palette.h index 7598ed63..3c09e957 100644 --- a/src/fe-gtk/palette.h +++ b/src/fe-gtk/palette.h @@ -20,6 +20,10 @@ #ifndef HEXCHAT_PALETTE_H #define HEXCHAT_PALETTE_H +#include + +#include "xtext-color.h" + extern GdkColor colors[]; #define COL_MARK_FG 32 @@ -59,4 +63,6 @@ void palette_dark_set_color (int idx, const GdkColor *col); */ gboolean palette_apply_dark_mode (gboolean enable); +void palette_get_xtext_colors (XTextColor *palette, size_t palette_len); + #endif diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c index 194e5108..284b5c6d 100644 --- a/src/fe-gtk/rawlog.c +++ b/src/fe-gtk/rawlog.c @@ -101,6 +101,7 @@ void open_rawlog (struct server *serv) { GtkWidget *bbox, *scrolledwindow, *vbox; + XTextColor xtext_palette[XTEXT_COLS]; char tbuf[256]; if (serv->gui->rawlog_window) @@ -120,7 +121,8 @@ open_rawlog (struct server *serv) gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (vbox), scrolledwindow); - serv->gui->rawlog_textlist = gtk_xtext_new (colors, 0); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + serv->gui->rawlog_textlist = gtk_xtext_new (xtext_palette, 0); gtk_container_add (GTK_CONTAINER (scrolledwindow), serv->gui->rawlog_textlist); gtk_xtext_set_font (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.hex_text_font); GTK_XTEXT (serv->gui->rawlog_textlist)->ignore_hidden = 1; diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c index 9b8c78d6..1e9e956f 100644 --- a/src/fe-gtk/textgui.c +++ b/src/fe-gtk/textgui.c @@ -438,6 +438,7 @@ void pevent_dialog_show () { GtkWidget *vbox, *hbox, *wid, *pane; + XTextColor xtext_palette[XTEXT_COLS]; if (pevent_dialog) { @@ -462,7 +463,8 @@ pevent_dialog_show () gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (wid), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); gtk_box_pack_start (GTK_BOX (vbox), wid, FALSE, TRUE, 0); - pevent_dialog_twid = gtk_xtext_new (colors, 0); + palette_get_xtext_colors (xtext_palette, XTEXT_COLS); + pevent_dialog_twid = gtk_xtext_new (xtext_palette, 0); gtk_widget_set_sensitive (pevent_dialog_twid, FALSE); gtk_widget_set_size_request (pevent_dialog_twid, -1, 75); gtk_container_add (GTK_CONTAINER (wid), pevent_dialog_twid); diff --git a/src/fe-gtk/xtext-color.h b/src/fe-gtk/xtext-color.h new file mode 100644 index 00000000..4696c134 --- /dev/null +++ b/src/fe-gtk/xtext-color.h @@ -0,0 +1,20 @@ +/* ZoiteChat + * + * 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. + */ + +#ifndef HEXCHAT_XTEXT_COLOR_H +#define HEXCHAT_XTEXT_COLOR_H + +typedef struct +{ + double red; + double green; + double blue; + double alpha; +} XTextColor; + +#endif diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 62a7ad7d..a23406e6 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -150,19 +150,6 @@ static void gtk_xtext_search_fini (xtext_buffer *); static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gtk_xtext_search_flags flags, GError **perr); static char * gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, int *ret_off, int *ret_len, GSList **slp); -static inline XTextColor -xtext_color_from_gdk (const GdkColor *color) -{ - XTextColor result; - - result.red = color->red / 65535.0; - result.green = color->green / 65535.0; - result.blue = color->blue / 65535.0; - result.alpha = 1.0; - - return result; -} - static inline void xtext_set_source_color (cairo_t *cr, const XTextColor *color, gdouble alpha) { @@ -619,7 +606,7 @@ gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext) } GtkWidget * -gtk_xtext_new (GdkColor palette[], int separator) +gtk_xtext_new (const XTextColor *palette, int separator) { GtkXText *xtext; @@ -3472,13 +3459,13 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line, } void -gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) +gtk_xtext_set_palette (GtkXText * xtext, const XTextColor *palette) { int i; for (i = (XTEXT_COLS-1); i >= 0; i--) { - xtext->palette[i] = xtext_color_from_gdk (&palette[i]); + xtext->palette[i] = palette[i]; } if (gtk_widget_get_realized (GTK_WIDGET(xtext))) diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index 3663413c..1d30bbe9 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -22,6 +22,7 @@ #include #include +#include "xtext-color.h" #define GTK_TYPE_XTEXT (gtk_xtext_get_type ()) #define GTK_XTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText)) @@ -56,14 +57,6 @@ typedef struct _GtkXText GtkXText; typedef struct _GtkXTextClass GtkXTextClass; typedef struct textentry textentry; -typedef struct -{ - double red; - double green; - double blue; - double alpha; -} XTextColor; - /* * offsets_t is used for retaining search information. * It is stored in the 'data' member of a GList, @@ -262,7 +255,7 @@ struct _GtkXTextClass void (*set_scroll_adjustments) (GtkXText *xtext, GtkAdjustment *hadj, GtkAdjustment *vadj); }; -GtkWidget *gtk_xtext_new (GdkColor palette[], int separator); +GtkWidget *gtk_xtext_new (const XTextColor *palette, int separator); void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp); void gtk_xtext_append_indent (xtext_buffer *buf, unsigned char *left_text, int left_len, @@ -270,7 +263,7 @@ void gtk_xtext_append_indent (xtext_buffer *buf, time_t stamp); int gtk_xtext_set_font (GtkXText *xtext, char *name); void gtk_xtext_set_background (GtkXText * xtext, cairo_surface_t *surface); -void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]); +void gtk_xtext_set_palette (GtkXText * xtext, const XTextColor *palette); void gtk_xtext_clear (xtext_buffer *buf, int lines); void gtk_xtext_save (GtkXText * xtext, int fh); void gtk_xtext_refresh (GtkXText * xtext); From c1f855c2ab3efed4b31ad95a98e814b84cddcb79 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 21:56:00 -0700 Subject: [PATCH 11/17] - Updated main GUI color helpers and tab palette generation to use XTextColor, and simplified drag icon snapshotting to use gdk_pixbuf_get_from_window with a null guard. - Added pixbuf-based cairo surface capture for xtext window scrolling with a fallback to full redraw when capture fails. --- src/fe-gtk/maingui.c | 122 +++++++++++++++---------------------------- src/fe-gtk/xtext.c | 96 ++++++++++++++++++++++++++++++++-- 2 files changed, 134 insertions(+), 84 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index f6f6214b..25ca42ee 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "../common/zoitechat.h" #include "../common/fe.h" @@ -85,92 +86,42 @@ static GtkWidget *mg_create_emoji_menu (session_gui *gui); static void mg_emoji_insert_cb (GtkMenuItem *item, session_gui *gui); static inline void -mg_set_source_color (cairo_t *cr, const GdkColor *color) +mg_set_source_color (cairo_t *cr, const XTextColor *color) { - cairo_set_source_rgba (cr, color->red / 65535.0, color->green / 65535.0, - color->blue / 65535.0, 1.0); + cairo_set_source_rgba (cr, color->red, color->green, color->blue, color->alpha); } -#if G_BYTE_ORDER == G_LITTLE_ENDIAN -#define MG_CAIRO_BLUE 0 -#define MG_CAIRO_GREEN 1 -#define MG_CAIRO_RED 2 -#define MG_CAIRO_ALPHA 3 -#else -#define MG_CAIRO_ALPHA 0 -#define MG_CAIRO_RED 1 -#define MG_CAIRO_GREEN 2 -#define MG_CAIRO_BLUE 3 -#endif - -static inline void -mg_unpremultiply_pixel (const guchar *src, guchar *dest) +static inline guint16 +mg_color_component_to_pango (double value) { - guchar alpha = src[MG_CAIRO_ALPHA]; - guchar red = src[MG_CAIRO_RED]; - guchar green = src[MG_CAIRO_GREEN]; - guchar blue = src[MG_CAIRO_BLUE]; + if (value < 0.0) + value = 0.0; + if (value > 1.0) + value = 1.0; - if (alpha == 0) - { - dest[0] = 0; - dest[1] = 0; - dest[2] = 0; - dest[3] = 0; - return; - } - - dest[0] = (guchar)((red * 255 + alpha / 2) / alpha); - dest[1] = (guchar)((green * 255 + alpha / 2) / alpha); - dest[2] = (guchar)((blue * 255 + alpha / 2) / alpha); - dest[3] = alpha; + return (guint16)(value * 65535.0 + 0.5); } static GdkPixbuf * mg_pixbuf_from_window (GdkWindow *window, int width, int height) { - cairo_surface_t *surface; - cairo_t *cr; - GdkPixbuf *pixbuf; - guchar *surface_data; - guchar *dest; - int surface_stride; - int dest_stride; - int x; - int y; + GdkDrawable *drawable; + int src_width; + int src_height; - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); - cr = cairo_create (surface); - gdk_cairo_set_source_window (cr, window, 0.0, 0.0); - cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); - cairo_paint (cr); - cairo_destroy (cr); + drawable = GDK_DRAWABLE (window); + if (!drawable) + return NULL; - cairo_surface_flush (surface); - - pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); - surface_data = cairo_image_surface_get_data (surface); - surface_stride = cairo_image_surface_get_stride (surface); - dest = gdk_pixbuf_get_pixels (pixbuf); - dest_stride = gdk_pixbuf_get_rowstride (pixbuf); - - for (y = 0; y < height; y++) + gdk_drawable_get_size (drawable, &src_width, &src_height); + if (width <= 0 || height <= 0) { - const guchar *src_row = surface_data + (y * surface_stride); - guchar *dest_row = dest + (y * dest_stride); - - for (x = 0; x < width; x++) - { - const guchar *src = src_row + (x * 4); - guchar *dst = dest_row + (x * 4); - - mg_unpremultiply_pixel (src, dst); - } + width = src_width; + height = src_height; } - cairo_surface_destroy (surface); - - return pixbuf; + return gdk_pixbuf_get_from_drawable (NULL, drawable, NULL, + 0, 0, 0, 0, width, height); } static void mg_create_entry (session *sess, GtkWidget *box); @@ -197,7 +148,7 @@ static PangoAttrList *newmsg_list; static PangoAttrList *plain_list = NULL; static PangoAttrList * -mg_attr_list_create (GdkColor *col, int size) +mg_attr_list_create (const XTextColor *col, int size) { PangoAttribute *attr; PangoAttrList *list; @@ -206,7 +157,10 @@ mg_attr_list_create (GdkColor *col, int size) if (col) { - attr = pango_attr_foreground_new (col->red, col->green, col->blue); + attr = pango_attr_foreground_new ( + mg_color_component_to_pango (col->red), + mg_color_component_to_pango (col->green), + mg_color_component_to_pango (col->blue)); attr->start_index = 0; attr->end_index = 0xffff; pango_attr_list_insert (list, attr); @@ -226,6 +180,8 @@ mg_attr_list_create (GdkColor *col, int size) static void mg_create_tab_colors (void) { + XTextColor gui_palette[MAX_COL + 1]; + if (plain_list) { pango_attr_list_unref (plain_list); @@ -235,11 +191,12 @@ mg_create_tab_colors (void) pango_attr_list_unref (away_list); } + palette_get_xtext_colors (gui_palette, G_N_ELEMENTS (gui_palette)); plain_list = mg_attr_list_create (NULL, prefs.hex_gui_tab_small); - newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.hex_gui_tab_small); - nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.hex_gui_tab_small); - newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.hex_gui_tab_small); - away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE); + newdata_list = mg_attr_list_create (&gui_palette[COL_NEW_DATA], prefs.hex_gui_tab_small); + nickseen_list = mg_attr_list_create (&gui_palette[COL_HILIGHT], prefs.hex_gui_tab_small); + newmsg_list = mg_attr_list_create (&gui_palette[COL_NEW_MSG], prefs.hex_gui_tab_small); + away_list = mg_attr_list_create (&gui_palette[COL_AWAY], FALSE); } static void @@ -4160,6 +4117,8 @@ mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) height = gdk_window_get_height (window); pix = mg_pixbuf_from_window (window, width, height); + if (!pix) + return FALSE; pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER); g_object_unref (pix); @@ -4210,7 +4169,7 @@ mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar) { - GdkColor col; + XTextColor col; cairo_t *cr; int half, width, height; int ox, oy; @@ -4238,9 +4197,10 @@ mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, gui height = gdk_window_get_height (window); } - col.red = rand() % 0xffff; - col.green = rand() % 0xffff; - col.blue = rand() % 0xffff; + col.red = (double)rand () / (double)RAND_MAX; + col.green = (double)rand () / (double)RAND_MAX; + col.blue = (double)rand () / (double)RAND_MAX; + col.alpha = 1.0; cr = gdk_cairo_create (window); cairo_set_operator (cr, CAIRO_OPERATOR_XOR); mg_set_source_color (cr, &col); diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index a23406e6..8094fa95 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -59,6 +59,7 @@ #else #include #include +#include #ifdef GDK_WINDOWING_X11 #include #endif @@ -157,13 +158,89 @@ xtext_set_source_color (cairo_t *cr, const XTextColor *color, gdouble alpha) color->blue, color->alpha * alpha); } +static cairo_surface_t * +xtext_surface_from_pixbuf (GdkPixbuf *pixbuf) +{ + cairo_surface_t *surface; + gboolean has_alpha; + int width; + int height; + int src_stride; + int dest_stride; + int n_channels; + const guchar *src_pixels; + unsigned char *dest_pixels; + int x; + int y; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + src_stride = gdk_pixbuf_get_rowstride (pixbuf); + src_pixels = gdk_pixbuf_get_pixels (pixbuf); + dest_stride = cairo_image_surface_get_stride (surface); + dest_pixels = cairo_image_surface_get_data (surface); + + for (y = 0; y < height; y++) + { + const guchar *src_row = src_pixels + (y * src_stride); + guint32 *dest_row = (guint32 *)(dest_pixels + (y * dest_stride)); + + for (x = 0; x < width; x++) + { + const guchar *src = src_row + (x * n_channels); + guchar alpha = has_alpha ? src[3] : 0xff; + guchar red = src[0]; + guchar green = src[1]; + guchar blue = src[2]; + guchar premul_red = (guchar)((red * alpha + 127) / 255); + guchar premul_green = (guchar)((green * alpha + 127) / 255); + guchar premul_blue = (guchar)((blue * alpha + 127) / 255); + + dest_row[x] = ((guint32)alpha << 24) | + ((guint32)premul_red << 16) | + ((guint32)premul_green << 8) | + ((guint32)premul_blue); + } + } + + cairo_surface_mark_dirty (surface); + + return surface; +} + static cairo_surface_t * xtext_surface_from_window (GdkWindow *window) { - cairo_t *cr = gdk_cairo_create (window); - cairo_surface_t *surface = cairo_surface_reference (cairo_get_target (cr)); + cairo_surface_t *surface = NULL; + GdkPixbuf *pixbuf; + GdkDrawable *drawable; + int width; + int height; - cairo_destroy (cr); + drawable = GDK_DRAWABLE (window); + if (!drawable) + return NULL; + + gdk_drawable_get_size (drawable, &width, &height); + pixbuf = gdk_pixbuf_get_from_drawable (NULL, drawable, NULL, + 0, 0, 0, 0, width, height); + if (pixbuf) + { + surface = xtext_surface_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + } return surface; } @@ -3873,6 +3950,12 @@ gtk_xtext_render_page (GtkXText * xtext) cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); surface = xtext_surface_from_window (GTK_WIDGET (xtext)->window); + if (!surface) + { + cairo_restore (cr); + cairo_destroy (cr); + goto full_redraw; + } cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); cairo_surface_destroy (surface); cairo_rectangle (cr, dest_x, dest_y, width, copy_height); @@ -3894,6 +3977,12 @@ gtk_xtext_render_page (GtkXText * xtext) cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); surface = xtext_surface_from_window (GTK_WIDGET (xtext)->window); + if (!surface) + { + cairo_restore (cr); + cairo_destroy (cr); + goto full_redraw; + } cairo_set_source_surface (cr, surface, dest_x - src_x, dest_y - src_y); cairo_surface_destroy (surface); cairo_rectangle (cr, dest_x, dest_y, width, copy_height); @@ -3915,6 +4004,7 @@ gtk_xtext_render_page (GtkXText * xtext) } #endif +full_redraw: width -= MARGIN; lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1; From cf41615cb32d10907737e6743b00cb0a1c72d10f Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 22:25:12 -0700 Subject: [PATCH 12/17] =?UTF-8?q?-=20Reworked=20GTK=20window=20snapshottin?= =?UTF-8?q?g=20in=20the=20main=20GUI=20to=20capture=20into=20cairo=20surfa?= =?UTF-8?q?ces=20and=20convert=20via=20gdk=5Fpixbuf=5Fget=5Ffrom=5Fsurface?= =?UTF-8?q?.=20-=20Added=20a=20Cairo=20surface=20=E2=86=92=20RGBA=20pixbuf?= =?UTF-8?q?=20conversion=20helper=20that=20unpremultiplies=20ARGB32=20data?= =?UTF-8?q?=20for=20GTK2=20compatibility=20while=20keeping=20window=20snap?= =?UTF-8?q?shots=20Cairo-based.=20-=20Updated=20window=20snapshotting=20to?= =?UTF-8?q?=20use=20the=20new=20Cairo=20surface=20conversion=20instead=20o?= =?UTF-8?q?f=20gdk=5Fpixbuf=5Fget=5Ffrom=5Fsurface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fe-gtk/maingui.c | 103 ++++++++++++++++++++++++++++++++++++++++--- src/fe-gtk/xtext.c | 83 ++++++---------------------------- 2 files changed, 110 insertions(+), 76 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 25ca42ee..31f92f26 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -102,26 +102,117 @@ mg_color_component_to_pango (double value) return (guint16)(value * 65535.0 + 0.5); } +static void +mg_pixbuf_destroy (guchar *pixels, gpointer data) +{ + g_free (pixels); +} + +static GdkPixbuf * +mg_pixbuf_from_surface (cairo_surface_t *surface, int width, int height) +{ + const unsigned char *src; + int src_stride; + int rowstride; + guchar *pixels; + int x; + int y; + + if (!surface || width <= 0 || height <= 0) + return NULL; + + src = cairo_image_surface_get_data (surface); + src_stride = cairo_image_surface_get_stride (surface); + rowstride = width * 4; + pixels = g_malloc ((gsize)rowstride * height); + + for (y = 0; y < height; y++) + { + const unsigned char *src_row = src + (y * src_stride); + guchar *dest_row = pixels + (y * rowstride); + + for (x = 0; x < width; x++) + { + guint8 a; + guint8 r; + guint8 g; + guint8 b; + const unsigned char *src_px = src_row + (x * 4); + guchar *dest_px = dest_row + (x * 4); + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + b = src_px[0]; + g = src_px[1]; + r = src_px[2]; + a = src_px[3]; +#else + a = src_px[0]; + r = src_px[1]; + g = src_px[2]; + b = src_px[3]; +#endif + + if (a) + { + r = (guint8)((r * 255 + (a / 2)) / a); + g = (guint8)((g * 255 + (a / 2)) / a); + b = (guint8)((b * 255 + (a / 2)) / a); + } + else + { + r = g = b = 0; + } + + dest_px[0] = r; + dest_px[1] = g; + dest_px[2] = b; + dest_px[3] = a; + } + } + + return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8, + width, height, rowstride, mg_pixbuf_destroy, NULL); +} + static GdkPixbuf * mg_pixbuf_from_window (GdkWindow *window, int width, int height) { - GdkDrawable *drawable; int src_width; int src_height; + cairo_surface_t *surface; + cairo_t *cr; + GdkPixbuf *pixbuf; - drawable = GDK_DRAWABLE (window); - if (!drawable) + if (!window) return NULL; - gdk_drawable_get_size (drawable, &src_width, &src_height); + src_width = gdk_window_get_width (window); + src_height = gdk_window_get_height (window); if (width <= 0 || height <= 0) { width = src_width; height = src_height; } - return gdk_pixbuf_get_from_drawable (NULL, drawable, NULL, - 0, 0, 0, 0, width, height); + if (width <= 0 || height <= 0) + return NULL; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return NULL; + } + + cr = cairo_create (surface); + gdk_cairo_set_source_window (cr, window, 0.0, 0.0); + cairo_paint (cr); + cairo_destroy (cr); + + pixbuf = mg_pixbuf_from_surface (surface, width, height); + cairo_surface_destroy (surface); + + return pixbuf; } static void mg_create_entry (session *sess, GtkWidget *box); diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 8094fa95..8f3cc190 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -59,7 +59,6 @@ #else #include #include -#include #ifdef GDK_WINDOWING_X11 #include #endif @@ -159,26 +158,20 @@ xtext_set_source_color (cairo_t *cr, const XTextColor *color, gdouble alpha) } static cairo_surface_t * -xtext_surface_from_pixbuf (GdkPixbuf *pixbuf) +xtext_surface_from_window (GdkWindow *window) { - cairo_surface_t *surface; - gboolean has_alpha; + cairo_surface_t *surface = NULL; int width; int height; - int src_stride; - int dest_stride; - int n_channels; - const guchar *src_pixels; - unsigned char *dest_pixels; - int x; - int y; + cairo_t *cr; - g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + if (!window) + return NULL; - width = gdk_pixbuf_get_width (pixbuf); - height = gdk_pixbuf_get_height (pixbuf); - has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); - n_channels = gdk_pixbuf_get_n_channels (pixbuf); + width = gdk_window_get_width (window); + height = gdk_window_get_height (window); + if (width <= 0 || height <= 0) + return NULL; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) @@ -187,60 +180,10 @@ xtext_surface_from_pixbuf (GdkPixbuf *pixbuf) return NULL; } - src_stride = gdk_pixbuf_get_rowstride (pixbuf); - src_pixels = gdk_pixbuf_get_pixels (pixbuf); - dest_stride = cairo_image_surface_get_stride (surface); - dest_pixels = cairo_image_surface_get_data (surface); - - for (y = 0; y < height; y++) - { - const guchar *src_row = src_pixels + (y * src_stride); - guint32 *dest_row = (guint32 *)(dest_pixels + (y * dest_stride)); - - for (x = 0; x < width; x++) - { - const guchar *src = src_row + (x * n_channels); - guchar alpha = has_alpha ? src[3] : 0xff; - guchar red = src[0]; - guchar green = src[1]; - guchar blue = src[2]; - guchar premul_red = (guchar)((red * alpha + 127) / 255); - guchar premul_green = (guchar)((green * alpha + 127) / 255); - guchar premul_blue = (guchar)((blue * alpha + 127) / 255); - - dest_row[x] = ((guint32)alpha << 24) | - ((guint32)premul_red << 16) | - ((guint32)premul_green << 8) | - ((guint32)premul_blue); - } - } - - cairo_surface_mark_dirty (surface); - - return surface; -} - -static cairo_surface_t * -xtext_surface_from_window (GdkWindow *window) -{ - cairo_surface_t *surface = NULL; - GdkPixbuf *pixbuf; - GdkDrawable *drawable; - int width; - int height; - - drawable = GDK_DRAWABLE (window); - if (!drawable) - return NULL; - - gdk_drawable_get_size (drawable, &width, &height); - pixbuf = gdk_pixbuf_get_from_drawable (NULL, drawable, NULL, - 0, 0, 0, 0, width, height); - if (pixbuf) - { - surface = xtext_surface_from_pixbuf (pixbuf); - g_object_unref (pixbuf); - } + cr = cairo_create (surface); + gdk_cairo_set_source_window (cr, window, 0.0, 0.0); + cairo_paint (cr); + cairo_destroy (cr); return surface; } From 8d275ddb3105a955b26f9b2a61d07256d25ec1c7 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 22:52:32 -0700 Subject: [PATCH 13/17] - Added dark mode mode constants, config storage as an integer, and a helper to resolve Auto/Dark/Light using system preferences where available. - Replaced the dark mode checkbox with an Auto/Dark/Light color mode selector and ensured palette edits use the resolved mode. - Applied the resolved color mode consistently across palette saving and GTK styling in the user list and channel tree/theme application paths. --- src/common/cfgfiles.c | 2 +- src/common/zoitechat.h | 4 +++ src/fe-gtk/chanview.c | 2 +- src/fe-gtk/fe-gtk.c | 55 +++++++++++++++++++++++++++++++++++++++++- src/fe-gtk/fe-gtk.h | 3 +++ src/fe-gtk/maingui.c | 2 +- src/fe-gtk/palette.c | 9 ++++--- 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index f12b38e3..9787f95e 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -437,7 +437,7 @@ const struct prefs vars[] = {"gui_tab_dialogs", P_OFFINT (hex_gui_tab_dialogs), 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_dark_mode", P_OFFINT (hex_gui_dark_mode), TYPE_BOOL}, + {"gui_dark_mode", P_OFFINT (hex_gui_dark_mode), 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_newtofront", P_OFFINT (hex_gui_tab_newtofront), TYPE_INT}, diff --git a/src/common/zoitechat.h b/src/common/zoitechat.h index 893beea5..f8f0b58f 100644 --- a/src/common/zoitechat.h +++ b/src/common/zoitechat.h @@ -83,6 +83,10 @@ gboolean zoitechat_import_theme (const char *path, GError **error); #define USERNAMELEN 10 #define HIDDEN_CHAR 8 /* invisible character for xtext */ +#define ZOITECHAT_DARK_MODE_AUTO 0 +#define ZOITECHAT_DARK_MODE_DARK 1 +#define ZOITECHAT_DARK_MODE_LIGHT 2 + struct nbexec { int myfd; diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c index ea5113b7..4dc6547a 100644 --- a/src/fe-gtk/chanview.c +++ b/src/fe-gtk/chanview.c @@ -122,7 +122,7 @@ chanview_apply_theme (chanview *cv) return; w = GTK_WIDGET (tv->tree); - if (prefs.hex_gui_dark_mode) + if (fe_dark_mode_is_enabled ()) { gtk_widget_modify_base (w, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_text (w, GTK_STATE_NORMAL, &colors[COL_FG]); diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index 374720bb..5e4b2306 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -270,6 +270,59 @@ static const char adwaita_workaround_rc[] = "}" "widget \"*.zoitechat-inputbox\" style \"zoitechat-input-workaround\""; +static gboolean +fe_system_prefers_dark (void) +{ + GtkSettings *settings = gtk_settings_get_default (); + gboolean prefer_dark = FALSE; + char *theme_name = NULL; + + if (!settings) + return FALSE; + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (settings), + "gtk-application-prefer-dark-theme")) + { + g_object_get (settings, "gtk-application-prefer-dark-theme", &prefer_dark, NULL); + } + + if (!prefer_dark) + { + g_object_get (settings, "gtk-theme-name", &theme_name, NULL); + if (theme_name) + { + char *lower = g_ascii_strdown (theme_name, -1); + if (g_str_has_suffix (lower, "-dark") || g_strrstr (lower, "dark")) + prefer_dark = TRUE; + g_free (lower); + g_free (theme_name); + } + } + + return prefer_dark; +} + +gboolean +fe_dark_mode_is_enabled_for (unsigned int mode) +{ + switch (mode) + { + case ZOITECHAT_DARK_MODE_DARK: + return TRUE; + case ZOITECHAT_DARK_MODE_LIGHT: + return FALSE; + case ZOITECHAT_DARK_MODE_AUTO: + default: + return fe_system_prefers_dark (); + } +} + +gboolean +fe_dark_mode_is_enabled (void) +{ + return fe_dark_mode_is_enabled_for (prefs.hex_gui_dark_mode); +} + GtkStyle * create_input_style (GtkStyle *style) { @@ -317,7 +370,7 @@ void fe_init (void) { palette_load (); - palette_apply_dark_mode (prefs.hex_gui_dark_mode); + palette_apply_dark_mode (fe_dark_mode_is_enabled ()); key_init (); pixmaps_init (); diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index f8617517..a0bf7c26 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -182,6 +182,9 @@ typedef struct session_gui extern cairo_surface_t *channelwin_pix; extern cairo_surface_t *dialogwin_pix; +gboolean fe_dark_mode_is_enabled (void); +gboolean fe_dark_mode_is_enabled_for (unsigned int mode); + #define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e)))) #define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) #define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_editable_set_editable(GTK_EDITABLE(e),v) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 31f92f26..d410a940 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -2650,7 +2650,7 @@ mg_create_userlist (session_gui *gui, GtkWidget *box) * - 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) + if (prefs.hex_gui_ulist_style || fe_dark_mode_is_enabled ()) { gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_text (ulist, GTK_STATE_NORMAL, &colors[COL_FG]); diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c index e35f607f..f2af5bd1 100644 --- a/src/fe-gtk/palette.c +++ b/src/fe-gtk/palette.c @@ -311,20 +311,21 @@ palette_save (void) char prefname[256]; const GdkColor *lightpal = colors; const GdkColor *darkpal = NULL; + gboolean dark_mode_active = fe_dark_mode_is_enabled (); /* If we're currently in dark mode, keep colors.conf's legacy keys as the user's light palette. */ - if (prefs.hex_gui_dark_mode && user_colors_valid) + if (dark_mode_active && user_colors_valid) lightpal = user_colors; /* If we're currently in light mode, ensure the snapshot stays in sync. */ - if (!prefs.hex_gui_dark_mode) + if (!dark_mode_active) { memcpy (user_colors, colors, sizeof (user_colors)); user_colors_valid = TRUE; } /* If dark mode is enabled but we haven't snapshotted a custom dark palette yet, capture it now. */ - if (prefs.hex_gui_dark_mode && !dark_user_colors_valid) + if (dark_mode_active && !dark_user_colors_valid) { memcpy (dark_user_colors, colors, sizeof (dark_user_colors)); dark_user_colors_valid = TRUE; @@ -332,7 +333,7 @@ palette_save (void) if (dark_user_colors_valid) darkpal = dark_user_colors; - else if (prefs.hex_gui_dark_mode) + else if (dark_mode_active) darkpal = colors; /* current dark palette (likely defaults) */ fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); From 7279e3592f7f34a185380d74abb0059e57603eac Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 23:09:38 -0700 Subject: [PATCH 14/17] =?UTF-8?q?-=20Replaced=20the=20dark=20mode=20checkb?= =?UTF-8?q?ox=20with=20an=20Auto/Dark/Light=20selector=20in=20Preferences?= =?UTF-8?q?=20=E2=86=92=20Colors=20and=20added=20the=20new=20mode=20labels?= =?UTF-8?q?/setting=20metadata=20to=20match=20the=20combo=20box=20UI.=20-?= =?UTF-8?q?=20Updated=20palette=20handling=20to=20respect=20the=20effectiv?= =?UTF-8?q?e=20dark=20mode=20(including=20auto)=20when=20saving=20colors,?= =?UTF-8?q?=20applying=20themes,=20and=20refreshing=20user=20list=20stylin?= =?UTF-8?q?g.=20-=20Added=20auto=20dark-mode=20tracking=20that=20listens?= =?UTF-8?q?=20to=20system=20theme=20changes=20and=20reapplies=20palette/st?= =?UTF-8?q?yles=20live=20when=20Auto=20is=20selected,=20so=20updates=20hap?= =?UTF-8?q?pen=20without=20restart=20(including=20channel=20list=20styling?= =?UTF-8?q?=20updates=20via=20setup=5Fapply=5Freal).=20-=20Synced=20the=20?= =?UTF-8?q?stored=20auto=20dark-mode=20state=20when=20preferences=20are=20?= =?UTF-8?q?applied,=20keeping=20Auto=20mode=20consistent=20after=20manual?= =?UTF-8?q?=20changes.=20-=20Exposed=20a=20helper=20for=20keeping=20the=20?= =?UTF-8?q?Auto=20state=20synchronized=20from=20the=20GTK=20layer=20to=20p?= =?UTF-8?q?references=20handling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fe-gtk/fe-gtk.c | 41 ++++++++++++ src/fe-gtk/fe-gtk.h | 1 + src/fe-gtk/setup.c | 153 +++++++++++++++++++++++++++----------------- 3 files changed, 136 insertions(+), 59 deletions(-) diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index 5e4b2306..04d0d265 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -302,6 +302,35 @@ fe_system_prefers_dark (void) return prefer_dark; } +static gboolean auto_dark_mode_enabled = FALSE; + +static void +fe_auto_dark_mode_changed (GtkSettings *settings, GParamSpec *pspec, gpointer data) +{ + gboolean enabled; + + (void) settings; + (void) pspec; + (void) data; + + if (prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO) + return; + + enabled = fe_system_prefers_dark (); + if (enabled == auto_dark_mode_enabled) + return; + + auto_dark_mode_enabled = enabled; + palette_apply_dark_mode (enabled); + setup_apply_real (0, TRUE, FALSE, FALSE); +} + +void +fe_set_auto_dark_mode_state (gboolean enabled) +{ + auto_dark_mode_enabled = enabled; +} + gboolean fe_dark_mode_is_enabled_for (unsigned int mode) { @@ -369,6 +398,8 @@ create_input_style (GtkStyle *style) void fe_init (void) { + GtkSettings *settings; + palette_load (); palette_apply_dark_mode (fe_dark_mode_is_enabled ()); key_init (); @@ -379,6 +410,16 @@ fe_init (void) #endif channelwin_pix = pixmap_load_from_file (prefs.hex_text_background); input_style = create_input_style (gtk_style_new ()); + + settings = gtk_settings_get_default (); + if (settings) + { + auto_dark_mode_enabled = fe_system_prefers_dark (); + g_signal_connect (settings, "notify::gtk-application-prefer-dark-theme", + G_CALLBACK (fe_auto_dark_mode_changed), NULL); + g_signal_connect (settings, "notify::gtk-theme-name", + G_CALLBACK (fe_auto_dark_mode_changed), NULL); + } } #ifdef HAVE_GTK_MAC diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index a0bf7c26..12cc5d19 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -184,6 +184,7 @@ extern cairo_surface_t *dialogwin_pix; gboolean fe_dark_mode_is_enabled (void); gboolean fe_dark_mode_is_enabled_for (unsigned int mode); +void fe_set_auto_dark_mode_state (gboolean enabled); #define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e)))) #define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 7c9d7516..d3af273f 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -58,7 +58,6 @@ static int last_selected_row = 0; /* sound row */ static gboolean color_change; static struct zoitechatprefs setup_prefs; static GSList *color_selector_widgets; -static GtkWidget *dark_mode_toggle_widget; static GtkWidget *cancel_button; static GtkWidget *font_dialog = NULL; void setup_apply_real (int new_pix, int do_ulist, int do_layout, int do_identd); @@ -348,22 +347,30 @@ static const setting tabs_settings[] = static const setting color_settings[] = { - {ST_TOGGLE, N_("Messages"), P_OFFINTNL(hex_text_stripcolor_msg), 0, 0, 0}, - {ST_TOGGLE, N_("Scrollback"), P_OFFINTNL(hex_text_stripcolor_replay), 0, 0, 0}, - {ST_TOGGLE, N_("Topic"), P_OFFINTNL(hex_text_stripcolor_topic), 0, 0, 0}, + {ST_TOGGLE, N_("Messages"), P_OFFINTNL(hex_text_stripcolor_msg), 0, 0, 0}, + {ST_TOGGLE, N_("Scrollback"), P_OFFINTNL(hex_text_stripcolor_replay), 0, 0, 0}, + {ST_TOGGLE, N_("Topic"), P_OFFINTNL(hex_text_stripcolor_topic), 0, 0, 0}, - {ST_END, 0, 0, 0, 0, 0} + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const dark_mode_modes[] = +{ + N_("Auto (system)"), + N_("Dark"), + N_("Light"), + NULL }; static const setting dark_mode_setting = { - ST_TOGGLE, - N_("Enable dark mode"), - P_OFFINTNL(hex_gui_dark_mode), - N_("Applies ZoiteChat's built-in dark palette to the chat buffer, channel list, and user list.\n" - "This includes message colors, selection colors, and interface highlights.\n"), - 0, - 0 + ST_MENU, + N_("Dark mode:"), + P_OFFINTNL(hex_gui_dark_mode), + N_("Choose how ZoiteChat selects its color palette for the chat buffer, channel list, and user list.\n" + "This includes message colors, selection colors, and interface highlights.\n"), + dark_mode_modes, + 0 }; static const char *const dccaccept[] = @@ -1440,12 +1447,41 @@ setup_color_selectors_set_sensitive (gboolean sensitive) } static void -setup_dark_mode_ui_toggle_cb (GtkToggleButton *but, gpointer userdata) +setup_dark_mode_menu_cb (GtkWidget *cbox, const setting *set) { - (void) but; - (void) userdata; - /* Keep color selectors usable even when dark mode is enabled. */ - setup_color_selectors_set_sensitive (TRUE); + setup_menu_cb (cbox, set); + /* Keep color selectors usable even when dark mode is enabled. */ + setup_color_selectors_set_sensitive (TRUE); +} + +static GtkWidget * +setup_create_dark_mode_menu (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid, *cbox, *box; + const char **text = (const char **)set->list; + int i; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + cbox = gtk_combo_box_text_new (); + + for (i = 0; text[i]; i++) + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _(text[i])); + + gtk_combo_box_set_active (GTK_COMBO_BOX (cbox), + setup_get_int (&setup_prefs, set) - set->extra); + g_signal_connect (G_OBJECT (cbox), "changed", + G_CALLBACK (setup_dark_mode_menu_cb), (gpointer)set); + + box = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0); + gtk_table_attach (GTK_TABLE (table), box, 3, 4, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + return cbox; } static void @@ -1476,11 +1512,11 @@ setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) /* is this line correct?? */ gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1); - /* Persist custom colors for the palette the user is editing. */ - if (setup_prefs.hex_gui_dark_mode) - palette_dark_set_color ((int)(col - colors), col); - else - palette_user_set_color ((int)(col - colors), col); + /* Persist custom colors for the palette the user is editing. */ + if (fe_dark_mode_is_enabled_for (setup_prefs.hex_gui_dark_mode)) + palette_dark_set_color ((int)(col - colors), col); + else + palette_user_set_color ((int)(col - colors), col); gtk_widget_destroy (dialog); } @@ -1571,11 +1607,10 @@ setup_create_other_color (char *text, int num, int row, GtkWidget *tab) static GtkWidget * setup_create_color_page (void) { - color_selector_widgets = NULL; - dark_mode_toggle_widget = NULL; + color_selector_widgets = NULL; - GtkWidget *tab, *box, *label; - int i; + GtkWidget *tab, *box, *label; + int i; box = gtk_vbox_new (FALSE, 0); gtk_container_set_border_width (GTK_CONTAINER (box), 6); @@ -1616,15 +1651,13 @@ setup_create_color_page (void) setup_create_other_color (_("New data:"), COL_NEW_DATA, 9, tab); setup_create_other_colorR (_("Marker line:"), COL_MARKER, 9, tab); - setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab); - setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); - setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); - setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab); - dark_mode_toggle_widget = setup_create_toggleL (tab, 13, &dark_mode_setting); - g_signal_connect (G_OBJECT (dark_mode_toggle_widget), "toggled", - G_CALLBACK (setup_dark_mode_ui_toggle_cb), NULL); - setup_color_selectors_set_sensitive (TRUE); - setup_create_header (tab, 15, N_("Color Stripping")); + setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab); + setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); + setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); + setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab); + setup_create_dark_mode_menu (tab, 13, &dark_mode_setting); + setup_color_selectors_set_sensitive (TRUE); + setup_create_header (tab, 15, N_("Color Stripping")); /* label = gtk_label_new (_("Strip colors from:")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); @@ -1800,9 +1833,9 @@ setup_theme_apply_cb (GtkWidget *button, gpointer user_data) g_unlink (events_dest); } - palette_load (); - palette_apply_dark_mode (prefs.hex_gui_dark_mode); - color_change = TRUE; + palette_load (); + palette_apply_dark_mode (fe_dark_mode_is_enabled ()); + color_change = TRUE; setup_apply_real (0, TRUE, FALSE, FALSE); setup_theme_show_message (GTK_MESSAGE_INFO, _("Theme applied. Some changes may require a restart to take full effect.")); @@ -2365,13 +2398,13 @@ setup_apply_to_sess (session_gui *gui) if (prefs.hex_gui_ulist_style) gtk_widget_modify_font (gui->user_tree, input_style->font_desc); - 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); + if (prefs.hex_gui_ulist_style || fe_dark_mode_is_enabled ()) + { + gtk_widget_modify_base (gui->user_tree, GTK_STATE_NORMAL, &colors[COL_BG]); + if (fe_dark_mode_is_enabled ()) + 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 { @@ -2570,14 +2603,17 @@ setup_apply (struct zoitechatprefs *pr) * "Dark mode" applies ZoiteChat's built-in dark palette to the chat views. * * 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; - } + * We MUST run palette_apply_dark_mode() when the setting changes, otherwise + * the preference flips but the palette stays the same (aka: "nothing happens"). + */ + { + gboolean pal_changed = palette_apply_dark_mode (fe_dark_mode_is_enabled_for (prefs.hex_gui_dark_mode)); + if (prefs.hex_gui_dark_mode != old_dark_mode || pal_changed) + color_change = TRUE; + } + + if (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO) + fe_set_auto_dark_mode_state (fe_dark_mode_is_enabled_for (ZOITECHAT_DARK_MODE_AUTO)); #ifdef WIN32 /* merge hex_font_main and hex_font_alternative into hex_font_normal */ @@ -2674,12 +2710,11 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin) { *swin = NULL; - if (color_selector_widgets) - { - g_slist_free (color_selector_widgets); - color_selector_widgets = NULL; - dark_mode_toggle_widget = NULL; - } + if (color_selector_widgets) + { + g_slist_free (color_selector_widgets); + color_selector_widgets = NULL; + } if (font_dialog) { From cb8b8bd4ccd2b82346ffd78837eb5f654cb3fb57 Mon Sep 17 00:00:00 2001 From: deepend Date: Sat, 17 Jan 2026 23:50:44 -0700 Subject: [PATCH 15/17] - Added a helper to apply the selected palette color across all GTK button states so color selector backgrounds stay consistent in preferences (GTK Ciara compatible). - Reused the helper when initializing and updating color selector buttons to keep the UI in sync with chosen colors. --- src/fe-gtk/setup.c | 51 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index d3af273f..8acafa8d 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -1484,6 +1484,30 @@ setup_create_dark_mode_menu (GtkWidget *table, int row, const setting *set) return cbox; } +static void +setup_color_button_apply (GtkWidget *button, const GdkColor *color) +{ + GtkWidget *target = g_object_get_data (G_OBJECT (button), "zoitechat-color-box"); + GtkStateType states[] = { + GTK_STATE_NORMAL, + GTK_STATE_PRELIGHT, + GTK_STATE_ACTIVE, + GTK_STATE_SELECTED, + GTK_STATE_INSENSITIVE + }; + guint i; + GtkWidget *apply_widget = GTK_IS_WIDGET (target) ? target : button; + + for (i = 0; i < G_N_ELEMENTS (states); i++) + gtk_widget_modify_bg (apply_widget, states[i], color); + + if (apply_widget != button) + for (i = 0; i < G_N_ELEMENTS (states); i++) + gtk_widget_modify_bg (button, states[i], color); + + gtk_widget_queue_draw (button); +} + static void setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) { @@ -1507,7 +1531,7 @@ setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE); - gtk_widget_modify_bg (button, GTK_STATE_NORMAL, col); + setup_color_button_apply (button, col); /* is this line correct?? */ gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1); @@ -1559,22 +1583,41 @@ static void setup_create_color_button (GtkWidget *table, int num, int row, int col) { GtkWidget *but; + GtkWidget *label; + GtkWidget *box; + GtkWidget *alignment; char buf[64]; if (num > 31) strcpy (buf, " "); + else if (num < 10) + sprintf (buf, " %d", num); else /* 12345678901 23456789 01 23456789 */ sprintf (buf, "%d", num); - but = gtk_button_new_with_label (" "); - gtk_label_set_markup (GTK_LABEL (gtk_bin_get_child (GTK_BIN (but))), buf); + but = gtk_button_new (); + label = gtk_label_new (" "); + gtk_label_set_markup (GTK_LABEL (label), buf); + box = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (box), TRUE); + gtk_container_add (GTK_CONTAINER (box), label); + gtk_container_add (GTK_CONTAINER (but), box); + alignment = gtk_bin_get_child (GTK_BIN (but)); + if (GTK_IS_ALIGNMENT (alignment)) + { + gtk_alignment_set (GTK_ALIGNMENT (alignment), 0.5, 0.5, 1.0, 1.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 0); + } + gtk_widget_show (label); + gtk_widget_show (box); /* win32 build uses this to turn off themeing */ g_object_set_data (G_OBJECT (but), "zoitechat-color", (gpointer)1); + g_object_set_data (G_OBJECT (but), "zoitechat-color-box", box); gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1, GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num)); - gtk_widget_modify_bg (but, GTK_STATE_NORMAL, &colors[num]); + setup_color_button_apply (but, &colors[num]); /* Track all color selector widgets (used for dark mode UI behavior). */ color_selector_widgets = g_slist_prepend (color_selector_widgets, but); From 35ecc2c6434efe51821625e66e552315f9d704e1 Mon Sep 17 00:00:00 2001 From: deepend Date: Sun, 18 Jan 2026 01:14:49 -0700 Subject: [PATCH 16/17] Standardized non-numbered color selector button labels to use two figure spaces so their width matches the numbered palette buttons. --- src/fe-gtk/setup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 8acafa8d..a4d028b9 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -1589,7 +1589,7 @@ setup_create_color_button (GtkWidget *table, int num, int row, int col) char buf[64]; if (num > 31) - strcpy (buf, " "); + strcpy (buf, "  "); else if (num < 10) sprintf (buf, " %d", num); else @@ -1614,7 +1614,7 @@ setup_create_color_button (GtkWidget *table, int num, int row, int col) g_object_set_data (G_OBJECT (but), "zoitechat-color", (gpointer)1); g_object_set_data (G_OBJECT (but), "zoitechat-color-box", box); gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1, - GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + GTK_SHRINK, GTK_SHRINK, 0, 0); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num)); setup_color_button_apply (but, &colors[num]); From 6137fdbd914a0896675cf311f849ecce1c15144a Mon Sep 17 00:00:00 2001 From: deepend Date: Sun, 18 Jan 2026 01:21:51 -0700 Subject: [PATCH 17/17] Updated the channel list theme application to honor explicit light mode selection so it applies the light palette instead of staying dark. --- src/fe-gtk/chanview.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c index 4dc6547a..c4520df3 100644 --- a/src/fe-gtk/chanview.c +++ b/src/fe-gtk/chanview.c @@ -122,7 +122,7 @@ chanview_apply_theme (chanview *cv) return; w = GTK_WIDGET (tv->tree); - if (fe_dark_mode_is_enabled ()) + if (fe_dark_mode_is_enabled () || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_LIGHT) { gtk_widget_modify_base (w, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_text (w, GTK_STATE_NORMAL, &colors[COL_FG]);