From 96af9bdde6393336bafb0ba44935b2886a29014b Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Mon, 16 Mar 2026 21:55:36 -0600 Subject: [PATCH 01/13] fix: surface zoitechat.conf/colors.conf save failures; add missing theme prefs fwd decl --- src/common/zoitechat.c | 3 +- src/fe-gtk/chanlist.c | 12 ++++--- src/fe-gtk/maingui.c | 3 +- src/fe-gtk/servlistgui.c | 3 +- src/fe-gtk/setup.c | 6 ++-- .../theme/tests/test-theme-access-routing.c | 3 +- .../tests/test-theme-manager-auto-refresh.c | 3 +- .../test-theme-manager-dispatch-routing.c | 3 +- .../theme/tests/test-theme-manager-policy.c | 3 +- .../test-theme-preferences-gtk3-populate.c | 3 +- src/fe-gtk/theme/theme-manager.c | 4 +-- src/fe-gtk/theme/theme-manager.h | 2 +- src/fe-gtk/theme/theme-preferences.c | 13 +++++--- src/fe-gtk/theme/theme-runtime.c | 31 +++++++++++++------ src/fe-gtk/theme/theme-runtime.h | 2 +- 15 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/common/zoitechat.c b/src/common/zoitechat.c index 96d769c2..2aa36e90 100644 --- a/src/common/zoitechat.c +++ b/src/common/zoitechat.c @@ -1104,7 +1104,8 @@ zoitechat_exit (void) plugin_kill_all (); fe_cleanup (); - save_config (); + if (!save_config ()) + g_printerr ("Could not save zoitechat.conf.\n"); if (prefs.save_pevents) { pevent_save (NULL); diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c index 394d0f2d..a26ca697 100644 --- a/src/fe-gtk/chanlist.c +++ b/src/fe-gtk/chanlist.c @@ -650,7 +650,8 @@ chanlist_minusers (GtkSpinButton *wid, server *serv) { serv->gui->chanlist_minusers = gtk_spin_button_get_value_as_int (wid); prefs.hex_gui_chanlist_minusers = serv->gui->chanlist_minusers; - save_config(); + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); if (serv->gui->chanlist_minusers < serv->gui->chanlist_minusers_downloaded) { @@ -672,7 +673,8 @@ chanlist_maxusers (GtkSpinButton *wid, server *serv) { serv->gui->chanlist_maxusers = gtk_spin_button_get_value_as_int (wid); prefs.hex_gui_chanlist_maxusers = serv->gui->chanlist_maxusers; - save_config(); + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); } static void @@ -893,7 +895,8 @@ chanlist_opengui (server *serv, int do_refresh) if (prefs.hex_gui_chanlist_minusers < 1 || prefs.hex_gui_chanlist_minusers > 999999) { prefs.hex_gui_chanlist_minusers = 5; - save_config(); + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); } serv->gui->chanlist_minusers = prefs.hex_gui_chanlist_minusers; @@ -904,7 +907,8 @@ chanlist_opengui (server *serv, int do_refresh) if (prefs.hex_gui_chanlist_maxusers < 1 || prefs.hex_gui_chanlist_maxusers > 999999) { prefs.hex_gui_chanlist_maxusers = 9999; - save_config(); + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); } serv->gui->chanlist_maxusers = prefs.hex_gui_chanlist_maxusers; diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index e6e27df7..4fb5a599 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -3683,7 +3683,8 @@ static void search_set_option (GtkToggleButton *but, guint *pref) { *pref = gtk_toggle_button_get_active(but); - save_config(); + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); } void diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index 855bcbe6..38a0d514 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -1002,7 +1002,8 @@ servlist_savegui (void) sp[0] = 0; /* spaces will break the login */ /* strcpy (prefs.hex_irc_real_name, gtk_entry_get_text (GTK_ENTRY (entry_greal))); */ servlist_save (); - save_config (); /* For nicks stored in zoitechat.conf */ + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); return 0; } diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index ec5c3b3c..d980aa70 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -2193,8 +2193,10 @@ setup_ok_cb (GtkWidget *but, GtkWidget *win) { gtk_widget_destroy (win); setup_apply (&setup_prefs); - save_config (); - theme_manager_save_preferences (); + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_ERROR); + if (!theme_manager_save_preferences ()) + fe_message (_("Could not save colors.conf."), FE_MSG_ERROR); } static GtkWidget * diff --git a/src/fe-gtk/theme/tests/test-theme-access-routing.c b/src/fe-gtk/theme/tests/test-theme-access-routing.c index 0fc899b5..b6818c50 100644 --- a/src/fe-gtk/theme/tests/test-theme-access-routing.c +++ b/src/fe-gtk/theme/tests/test-theme-access-routing.c @@ -57,9 +57,10 @@ theme_runtime_load (void) { } -void +gboolean theme_runtime_save (void) { + return TRUE; } void diff --git a/src/fe-gtk/theme/tests/test-theme-manager-auto-refresh.c b/src/fe-gtk/theme/tests/test-theme-manager-auto-refresh.c index 8331b311..8f6c5731 100644 --- a/src/fe-gtk/theme/tests/test-theme-manager-auto-refresh.c +++ b/src/fe-gtk/theme/tests/test-theme-manager-auto-refresh.c @@ -119,8 +119,9 @@ void theme_runtime_load (void) { } -void theme_runtime_save (void) +gboolean theme_runtime_save (void) { + return TRUE; } gboolean theme_runtime_is_dark_active (void) diff --git a/src/fe-gtk/theme/tests/test-theme-manager-dispatch-routing.c b/src/fe-gtk/theme/tests/test-theme-manager-dispatch-routing.c index a3c34bc0..d3f52378 100644 --- a/src/fe-gtk/theme/tests/test-theme-manager-dispatch-routing.c +++ b/src/fe-gtk/theme/tests/test-theme-manager-dispatch-routing.c @@ -112,8 +112,9 @@ void theme_runtime_load (void) { } -void theme_runtime_save (void) +gboolean theme_runtime_save (void) { + return TRUE; } gboolean theme_runtime_is_dark_active (void) diff --git a/src/fe-gtk/theme/tests/test-theme-manager-policy.c b/src/fe-gtk/theme/tests/test-theme-manager-policy.c index d5d2b60d..7d395baa 100644 --- a/src/fe-gtk/theme/tests/test-theme-manager-policy.c +++ b/src/fe-gtk/theme/tests/test-theme-manager-policy.c @@ -122,8 +122,9 @@ void theme_runtime_load (void) { } -void theme_runtime_save (void) +gboolean theme_runtime_save (void) { + return TRUE; } gboolean theme_runtime_is_dark_active (void) diff --git a/src/fe-gtk/theme/tests/test-theme-preferences-gtk3-populate.c b/src/fe-gtk/theme/tests/test-theme-preferences-gtk3-populate.c index 36863658..31ef4fa8 100644 --- a/src/fe-gtk/theme/tests/test-theme-preferences-gtk3-populate.c +++ b/src/fe-gtk/theme/tests/test-theme-preferences-gtk3-populate.c @@ -66,9 +66,10 @@ theme_manager_reset_mode_colors (unsigned int mode, gboolean *palette_changed) *palette_changed = FALSE; } -void +gboolean theme_manager_save_preferences (void) { + return TRUE; } ThemePaletteBehavior diff --git a/src/fe-gtk/theme/theme-manager.c b/src/fe-gtk/theme/theme-manager.c index 1885d626..cf4d97b1 100644 --- a/src/fe-gtk/theme/theme-manager.c +++ b/src/fe-gtk/theme/theme-manager.c @@ -284,10 +284,10 @@ theme_manager_commit_preferences (unsigned int old_mode, gboolean *color_change) fe_set_auto_dark_mode_state (theme_policy_is_dark_mode_active (ZOITECHAT_DARK_MODE_AUTO)); } -void +gboolean theme_manager_save_preferences (void) { - theme_runtime_save (); + return theme_runtime_save (); } gboolean diff --git a/src/fe-gtk/theme/theme-manager.h b/src/fe-gtk/theme/theme-manager.h index 7f700054..9d6a115a 100644 --- a/src/fe-gtk/theme/theme-manager.h +++ b/src/fe-gtk/theme/theme-manager.h @@ -43,7 +43,7 @@ void theme_manager_set_mode (unsigned int mode, gboolean *palette_changed); void theme_manager_set_token_color (unsigned int mode, ThemeSemanticToken token, const GdkRGBA *color, gboolean *palette_changed); void theme_manager_reset_mode_colors (unsigned int mode, gboolean *palette_changed); void theme_manager_commit_preferences (unsigned int old_mode, gboolean *color_change); -void theme_manager_save_preferences (void); +gboolean theme_manager_save_preferences (void); gboolean theme_changed_event_has_reason (const ThemeChangedEvent *event, ThemeChangedReason reason); void theme_manager_apply_and_dispatch (unsigned int mode, ThemeChangedReason reasons, gboolean *palette_changed); void theme_manager_dispatch_changed (ThemeChangedReason reasons); diff --git a/src/fe-gtk/theme/theme-preferences.c b/src/fe-gtk/theme/theme-preferences.c index c91e712c..af165a75 100644 --- a/src/fe-gtk/theme/theme-preferences.c +++ b/src/fe-gtk/theme/theme-preferences.c @@ -70,6 +70,9 @@ typedef struct #define COLOR_MANAGER_RESPONSE_RESET 1 +static void +theme_preferences_show_import_error (GtkWidget *button, const char *message); + static void theme_preferences_manager_row_free (gpointer data) { @@ -735,8 +738,9 @@ theme_preferences_manage_colors_cb (GtkWidget *button, gpointer user_data) gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); - if (color_change_flag && *color_change_flag != old_changed) - theme_manager_save_preferences (); + if (color_change_flag && *color_change_flag != old_changed && + !theme_manager_save_preferences ()) + theme_preferences_show_import_error (button, _("Could not save colors.conf.")); } static void @@ -912,8 +916,9 @@ theme_preferences_import_colors_conf_cb (GtkWidget *button, gpointer user_data) if (!any_imported) theme_preferences_show_import_error (button, _("No importable colors were found in that colors.conf file.")); - else if (color_change_flag && *color_change_flag != old_changed) - theme_manager_save_preferences (); + else if (color_change_flag && *color_change_flag != old_changed && + !theme_manager_save_preferences ()) + theme_preferences_show_import_error (button, _("Could not save colors.conf.")); g_free (cfg); g_free (path); diff --git a/src/fe-gtk/theme/theme-runtime.c b/src/fe-gtk/theme/theme-runtime.c index 6990c380..4f548ae6 100644 --- a/src/fe-gtk/theme/theme-runtime.c +++ b/src/fe-gtk/theme/theme-runtime.c @@ -276,7 +276,7 @@ theme_runtime_load_migrated_legacy_color (char *cfg, return palette_read_legacy_color (cfg, mode->legacy_prefix, def->legacy_index, out_color); } -static void +static gboolean palette_write_token_color (int fh, const char *mode_name, const ThemePaletteTokenDef *def, const GdkRGBA *color) { char prefname[256]; @@ -284,13 +284,13 @@ palette_write_token_color (int fh, const char *mode_name, const ThemePaletteToke guint16 green; guint16 blue; - g_return_if_fail (mode_name != NULL); - g_return_if_fail (def != NULL); - g_return_if_fail (color != NULL); + g_return_val_if_fail (mode_name != NULL, FALSE); + g_return_val_if_fail (def != NULL, FALSE); + g_return_val_if_fail (color != NULL, FALSE); g_snprintf (prefname, sizeof prefname, "theme.mode.%s.token.%s", mode_name, def->name); theme_palette_color_get_rgb16 (color, &red, &green, &blue); - cfg_put_color (fh, red, green, blue, prefname); + return cfg_put_color (fh, red, green, blue, prefname); } @@ -449,7 +449,7 @@ theme_runtime_load (void) user_colors_valid = TRUE; } -void +gboolean theme_runtime_save (void) { size_t i; @@ -487,9 +487,13 @@ theme_runtime_save (void) fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); if (fh == -1) - return; + return FALSE; - cfg_put_int (fh, THEME_PALETTE_MIGRATION_MARKER_VALUE, (char *) THEME_PALETTE_MIGRATION_MARKER_KEY); + if (!cfg_put_int (fh, THEME_PALETTE_MIGRATION_MARKER_VALUE, (char *) THEME_PALETTE_MIGRATION_MARKER_KEY)) + { + close (fh); + return FALSE; + } for (i = 0; i < mode_count; i++) { @@ -507,11 +511,18 @@ theme_runtime_save (void) if (!custom_tokens[def->token]) continue; g_assert (theme_palette_get_color (modes[i].palette, def->token, &color)); - palette_write_token_color (fh, modes[i].mode_name, def, &color); + if (!palette_write_token_color (fh, modes[i].mode_name, def, &color)) + { + close (fh); + return FALSE; + } } } - close (fh); + if (close (fh) == -1) + return FALSE; + + return TRUE; } static gboolean diff --git a/src/fe-gtk/theme/theme-runtime.h b/src/fe-gtk/theme/theme-runtime.h index 483c3af8..fb8f7eb0 100644 --- a/src/fe-gtk/theme/theme-runtime.h +++ b/src/fe-gtk/theme/theme-runtime.h @@ -18,7 +18,7 @@ typedef struct } ThemeGtkPaletteMap; void theme_runtime_load (void); -void theme_runtime_save (void); +gboolean theme_runtime_save (void); gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed); gboolean theme_runtime_apply_dark_mode (gboolean enable); void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col); From 896a761e24a92662bab2d5c012bc919c835104bb Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Mon, 16 Mar 2026 21:57:21 -0600 Subject: [PATCH 02/13] perf: debounce GUI config saves; flush pending write on close --- src/fe-gtk/maingui.c | 163 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 149 insertions(+), 14 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 4fb5a599..0d76f325 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -173,6 +173,55 @@ enum static void mg_apply_emoji_fallback_widget (GtkWidget *widget); +#define MG_CONFIG_SAVE_DEBOUNCE_MS 250 + +static guint mg_config_save_source_id = 0; +static gboolean mg_config_prefs_dirty = FALSE; + +static gboolean +mg_config_save_timeout_cb (gpointer userdata) +{ + mg_config_save_source_id = 0; + + if (!mg_config_prefs_dirty) + return G_SOURCE_REMOVE; + + save_config (); + mg_config_prefs_dirty = FALSE; + + return G_SOURCE_REMOVE; +} + +static void +mg_schedule_config_save (void) +{ + if (!mg_config_prefs_dirty) + return; + + if (mg_config_save_source_id != 0) + g_source_remove (mg_config_save_source_id); + + mg_config_save_source_id = g_timeout_add (MG_CONFIG_SAVE_DEBOUNCE_MS, + mg_config_save_timeout_cb, + NULL); +} + +static void +mg_flush_config_save (void) +{ + if (mg_config_save_source_id != 0) + { + g_source_remove (mg_config_save_source_id); + mg_config_save_source_id = 0; + } + + if (mg_config_prefs_dirty) + { + save_config (); + mg_config_prefs_dirty = FALSE; + } +} + static inline void mg_set_source_color (cairo_t *cr, const XTextColor *color) { @@ -761,6 +810,10 @@ fe_set_title (session *sess) static gboolean mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata) { + guint win_state; + guint win_fullscreen; + gboolean changed = FALSE; + if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) && prefs.hex_gui_tray_minimize && prefs.hex_gui_tray && @@ -769,13 +822,31 @@ mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata tray_toggle_visibility (TRUE); } - prefs.hex_gui_win_state = 0; - if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) - prefs.hex_gui_win_state = 1; + win_state = 0; + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) + win_state = 1; - prefs.hex_gui_win_fullscreen = 0; - if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) - prefs.hex_gui_win_fullscreen = 1; + win_fullscreen = 0; + if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) + win_fullscreen = 1; + + if (prefs.hex_gui_win_state != win_state) + { + prefs.hex_gui_win_state = win_state; + changed = TRUE; + } + + if (prefs.hex_gui_win_fullscreen != win_fullscreen) + { + prefs.hex_gui_win_fullscreen = win_fullscreen; + changed = TRUE; + } + + if (changed) + { + mg_config_prefs_dirty = TRUE; + mg_schedule_config_save (); + } menu_set_fullscreen (current_sess->gui, prefs.hex_gui_win_fullscreen); @@ -789,17 +860,46 @@ mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata static gboolean mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) { + gboolean changed = FALSE; + if (sess == NULL) /* for the main_window */ { if (mg_gui) { if (prefs.hex_gui_win_save && !prefs.hex_gui_win_state && !prefs.hex_gui_win_fullscreen) { + int win_left; + int win_top; + int win_width; + int win_height; + sess = current_sess; - gtk_window_get_position (GTK_WINDOW (wid), &prefs.hex_gui_win_left, - &prefs.hex_gui_win_top); - gtk_window_get_size (GTK_WINDOW (wid), &prefs.hex_gui_win_width, - &prefs.hex_gui_win_height); + gtk_window_get_position (GTK_WINDOW (wid), &win_left, &win_top); + gtk_window_get_size (GTK_WINDOW (wid), &win_width, &win_height); + + if (prefs.hex_gui_win_left != win_left) + { + prefs.hex_gui_win_left = win_left; + changed = TRUE; + } + + if (prefs.hex_gui_win_top != win_top) + { + prefs.hex_gui_win_top = win_top; + changed = TRUE; + } + + if (prefs.hex_gui_win_width != win_width) + { + prefs.hex_gui_win_width = win_width; + changed = TRUE; + } + + if (prefs.hex_gui_win_height != win_height) + { + prefs.hex_gui_win_height = win_height; + changed = TRUE; + } } } } @@ -808,13 +908,46 @@ mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) { if (sess->type == SESS_DIALOG && prefs.hex_gui_win_save) { - gtk_window_get_position (GTK_WINDOW (wid), &prefs.hex_gui_dialog_left, - &prefs.hex_gui_dialog_top); - gtk_window_get_size (GTK_WINDOW (wid), &prefs.hex_gui_dialog_width, - &prefs.hex_gui_dialog_height); + int dialog_left; + int dialog_top; + int dialog_width; + int dialog_height; + + gtk_window_get_position (GTK_WINDOW (wid), &dialog_left, &dialog_top); + gtk_window_get_size (GTK_WINDOW (wid), &dialog_width, &dialog_height); + + if (prefs.hex_gui_dialog_left != dialog_left) + { + prefs.hex_gui_dialog_left = dialog_left; + changed = TRUE; + } + + if (prefs.hex_gui_dialog_top != dialog_top) + { + prefs.hex_gui_dialog_top = dialog_top; + changed = TRUE; + } + + if (prefs.hex_gui_dialog_width != dialog_width) + { + prefs.hex_gui_dialog_width = dialog_width; + changed = TRUE; + } + + if (prefs.hex_gui_dialog_height != dialog_height) + { + prefs.hex_gui_dialog_height = dialog_height; + changed = TRUE; + } } } + if (changed) + { + mg_config_prefs_dirty = TRUE; + mg_schedule_config_save (); + } + return FALSE; } @@ -2339,6 +2472,8 @@ mg_tabwindow_kill_cb (GtkWidget *win, gpointer userdata) GSList *list, *next; session *sess; + mg_flush_config_save (); + zoitechat_is_quitting = TRUE; /* see if there's any non-tab windows left */ From 9e808c57b4dcc678f561b032194614656c192637 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Mon, 16 Mar 2026 22:45:30 -0600 Subject: [PATCH 03/13] refactor: coord prefs/theme saves in one staged path; drop broken manager wrappers --- src/common/cfgfiles.c | 128 +++++++++++++++++--------- src/common/cfgfiles.h | 3 + src/fe-gtk/fe-gtk.vcxproj | 2 + src/fe-gtk/fe-gtk.vcxproj.filters | 8 +- src/fe-gtk/maingui.c | 31 ++++++- src/fe-gtk/meson.build | 1 + src/fe-gtk/preferences-persistence.c | 66 ++++++++++++++ src/fe-gtk/preferences-persistence.h | 18 ++++ src/fe-gtk/setup.c | 20 ++++- src/fe-gtk/theme/theme-runtime.c | 130 ++++++++++++++++++++++++--- src/fe-gtk/theme/theme-runtime.h | 3 + 11 files changed, 346 insertions(+), 64 deletions(-) create mode 100644 src/fe-gtk/preferences-persistence.c create mode 100644 src/fe-gtk/preferences-persistence.h diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index 64d9ca4a..e4b16956 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -1009,15 +1009,51 @@ load_config (void) return 0; } -int -save_config (void) +static int +save_config_write_to_fd (int fh) { - int fh, i; - char *config, *new_config; + int i; + + if (!cfg_put_str (fh, "version", PACKAGE_VERSION)) + return 0; + + i = 0; + do + { + switch (vars[i].type) + { + case TYPE_STR: + if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset)) + return 0; + break; + case TYPE_INT: + case TYPE_BOOL: + if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name)) + return 0; + } + + if (vars[i].after_update != NULL) + vars[i].after_update(); + i++; + } + while (vars[i].name); + + return 1; +} + +int +save_config_prepare (char **temp_path) +{ + int fh; + char *config; + char *new_config; if (check_config_dir () != 0) make_config_dirs (); + if (!temp_path) + return 0; + config = default_file (); new_config = g_strconcat (config, ".new", NULL); @@ -1028,63 +1064,67 @@ save_config (void) return 0; } - if (!cfg_put_str (fh, "version", PACKAGE_VERSION)) + if (!save_config_write_to_fd (fh)) { close (fh); g_free (new_config); return 0; } - i = 0; - do - { - switch (vars[i].type) - { - case TYPE_STR: - if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset)) - { - close (fh); - g_free (new_config); - return 0; - } - break; - case TYPE_INT: - case TYPE_BOOL: - if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name)) - { - close (fh); - g_free (new_config); - return 0; - } - } - - if (vars[i].after_update != NULL) - { - vars[i].after_update(); - } - i++; - } - while (vars[i].name); - if (close (fh) == -1) { g_free (new_config); return 0; } -#ifdef WIN32 - g_unlink (config); /* win32 can't rename to an existing file */ -#endif - if (g_rename (new_config, config) == -1) - { - g_free (new_config); + *temp_path = new_config; + return 1; +} + +int +save_config_finalize (const char *temp_path) +{ + char *config; + + if (!temp_path) + return 0; + + config = default_file (); +#ifdef WIN32 + g_unlink (config); +#endif + if (g_rename (temp_path, config) == -1) return 0; - } - g_free (new_config); return 1; } +void +save_config_discard (const char *temp_path) +{ + if (!temp_path) + return; + + g_unlink (temp_path); +} + +int +save_config (void) +{ + char *temp_path = NULL; + int result; + + if (!save_config_prepare (&temp_path)) + return 0; + + result = save_config_finalize (temp_path); + if (!result) + save_config_discard (temp_path); + g_free (temp_path); + + return result; +} + static void set_showval (session *sess, const struct prefs *var, char *tbuf) { diff --git a/src/common/cfgfiles.h b/src/common/cfgfiles.h index eaac2f32..55dbc38d 100644 --- a/src/common/cfgfiles.h +++ b/src/common/cfgfiles.h @@ -43,6 +43,9 @@ int make_config_dirs (void); int make_dcc_dirs (void); int load_config (void); int save_config (void); +int save_config_prepare (char **temp_path); +int save_config_finalize (const char *temp_path); +void save_config_discard (const char *temp_path); void list_free (GSList ** list); void list_loadconf (char *file, GSList ** list, char *defaultconf); int list_delentry (GSList ** list, char *name); diff --git a/src/fe-gtk/fe-gtk.vcxproj b/src/fe-gtk/fe-gtk.vcxproj index 63fc5cf1..a53f9314 100644 --- a/src/fe-gtk/fe-gtk.vcxproj +++ b/src/fe-gtk/fe-gtk.vcxproj @@ -61,6 +61,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out- + @@ -107,6 +108,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out- + diff --git a/src/fe-gtk/fe-gtk.vcxproj.filters b/src/fe-gtk/fe-gtk.vcxproj.filters index 0267bc56..66288085 100644 --- a/src/fe-gtk/fe-gtk.vcxproj.filters +++ b/src/fe-gtk/fe-gtk.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -51,6 +51,9 @@ Header Files + + Header Files + Header Files @@ -182,6 +185,9 @@ Source Files + + Source Files + Source Files diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 0d76f325..5a520b7a 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -52,6 +52,7 @@ #include "theme/theme-palette.h" #include "maingui.h" #include "menu.h" +#include "preferences-persistence.h" #include "fkeys.h" #include "userlistgui.h" #include "chanview.h" @@ -178,15 +179,37 @@ static void mg_apply_emoji_fallback_widget (GtkWidget *widget); static guint mg_config_save_source_id = 0; static gboolean mg_config_prefs_dirty = FALSE; +static void +mg_show_save_failure (const PreferencesPersistenceResult *save_result) +{ + char buffer[192]; + + if (!save_result || save_result->success) + return; + + if (save_result->partial_failure) + { + fe_message (_("Could not fully save preferences. zoitechat.conf was written, but colors.conf failed. Retry is possible."), FE_MSG_ERROR); + return; + } + + g_snprintf (buffer, sizeof (buffer), _("Could not save preferences (%s). Retry is possible."), save_result->failed_file ? save_result->failed_file : _("unknown file")); + fe_message (buffer, FE_MSG_ERROR); +} + static gboolean mg_config_save_timeout_cb (gpointer userdata) { + PreferencesPersistenceResult save_result; + mg_config_save_source_id = 0; if (!mg_config_prefs_dirty) return G_SOURCE_REMOVE; - save_config (); + save_result = preferences_persistence_save_all (); + if (!save_result.success) + mg_show_save_failure (&save_result); mg_config_prefs_dirty = FALSE; return G_SOURCE_REMOVE; @@ -209,6 +232,8 @@ mg_schedule_config_save (void) static void mg_flush_config_save (void) { + PreferencesPersistenceResult save_result; + if (mg_config_save_source_id != 0) { g_source_remove (mg_config_save_source_id); @@ -217,7 +242,9 @@ mg_flush_config_save (void) if (mg_config_prefs_dirty) { - save_config (); + save_result = preferences_persistence_save_all (); + if (!save_result.success) + mg_show_save_failure (&save_result); mg_config_prefs_dirty = FALSE; } } diff --git a/src/fe-gtk/meson.build b/src/fe-gtk/meson.build index 90fe134e..561016ae 100644 --- a/src/fe-gtk/meson.build +++ b/src/fe-gtk/meson.build @@ -28,6 +28,7 @@ zoitechat_gtk_sources = [ 'maingui.c', 'notifygui.c', 'pixmaps.c', + 'preferences-persistence.c', 'plugin-tray.c', 'plugin-notification.c', 'rawlog.c', diff --git a/src/fe-gtk/preferences-persistence.c b/src/fe-gtk/preferences-persistence.c new file mode 100644 index 00000000..aa6ac597 --- /dev/null +++ b/src/fe-gtk/preferences-persistence.c @@ -0,0 +1,66 @@ +#include "preferences-persistence.h" + +#include "../common/cfgfiles.h" +#include "theme/theme-runtime.h" + +PreferencesPersistenceResult +preferences_persistence_save_all (void) +{ + PreferencesPersistenceResult result; + char *config_temp; + char *theme_temp; + + result.success = FALSE; + result.retry_possible = TRUE; + result.partial_failure = FALSE; + result.config_failed = FALSE; + result.theme_failed = FALSE; + result.failed_file = NULL; + config_temp = NULL; + theme_temp = NULL; + + if (!save_config_prepare (&config_temp)) + { + result.config_failed = TRUE; + goto done; + } + + if (!theme_runtime_save_prepare (&theme_temp)) + { + result.theme_failed = TRUE; + goto done; + } + + if (!save_config_finalize (config_temp)) + { + result.config_failed = TRUE; + goto done; + } + + if (!theme_runtime_save_finalize (theme_temp)) + { + result.theme_failed = TRUE; + result.partial_failure = TRUE; + goto done; + } + + result.success = TRUE; + + done: + if (!result.success) + { + if (result.config_failed && result.theme_failed) + result.failed_file = "zoitechat.conf and colors.conf"; + else if (result.config_failed) + result.failed_file = "zoitechat.conf"; + else if (result.theme_failed) + result.failed_file = "colors.conf"; + } + + save_config_discard (config_temp); + theme_runtime_save_discard (theme_temp); + g_free (config_temp); + g_free (theme_temp); + + return result; +} diff --git a/src/fe-gtk/preferences-persistence.h b/src/fe-gtk/preferences-persistence.h new file mode 100644 index 00000000..9a4c1f5a --- /dev/null +++ b/src/fe-gtk/preferences-persistence.h @@ -0,0 +1,18 @@ +#ifndef ZOITECHAT_PREFERENCES_PERSISTENCE_H +#define ZOITECHAT_PREFERENCES_PERSISTENCE_H + +#include + +typedef struct +{ + gboolean success; + gboolean retry_possible; + gboolean partial_failure; + gboolean config_failed; + gboolean theme_failed; + const char *failed_file; +} PreferencesPersistenceResult; + +PreferencesPersistenceResult preferences_persistence_save_all (void); + +#endif diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index d980aa70..3a73edfe 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -40,6 +40,7 @@ #include "maingui.h" #include "pixmaps.h" #include "menu.h" +#include "preferences-persistence.h" #include "plugin-tray.h" #include "notifications/notification-backend.h" @@ -2191,12 +2192,23 @@ setup_apply (struct zoitechatprefs *pr) static void setup_ok_cb (GtkWidget *but, GtkWidget *win) { + PreferencesPersistenceResult save_result; + char buffer[192]; + gtk_widget_destroy (win); setup_apply (&setup_prefs); - if (!save_config ()) - fe_message (_("Could not save zoitechat.conf."), FE_MSG_ERROR); - if (!theme_manager_save_preferences ()) - fe_message (_("Could not save colors.conf."), FE_MSG_ERROR); + save_result = preferences_persistence_save_all (); + if (save_result.success) + return; + + if (save_result.partial_failure) + { + fe_message (_("Preferences were partially saved. zoitechat.conf succeeded, colors.conf failed. Retry is possible."), FE_MSG_ERROR); + return; + } + + g_snprintf (buffer, sizeof (buffer), _("Could not save preferences (%s). Retry is possible."), save_result.failed_file ? save_result.failed_file : _("unknown file")); + fe_message (buffer, FE_MSG_ERROR); } static GtkWidget * diff --git a/src/fe-gtk/theme/theme-runtime.c b/src/fe-gtk/theme/theme-runtime.c index 4f548ae6..f7765399 100644 --- a/src/fe-gtk/theme/theme-runtime.c +++ b/src/fe-gtk/theme/theme-runtime.c @@ -449,12 +449,11 @@ theme_runtime_load (void) user_colors_valid = TRUE; } -gboolean -theme_runtime_save (void) +static gboolean +theme_runtime_save_to_fd (int fh) { size_t i; size_t j; - int fh; ThemePalettePersistenceMode modes[] = { { "light", "color", &light_palette, &user_colors_valid }, { "dark", "dark_color", &dark_palette, &dark_user_colors_valid }, @@ -485,15 +484,8 @@ theme_runtime_save (void) else modes[1].palette = &dark_palette; - fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); - if (fh == -1) - return FALSE; - if (!cfg_put_int (fh, THEME_PALETTE_MIGRATION_MARKER_VALUE, (char *) THEME_PALETTE_MIGRATION_MARKER_KEY)) - { - close (fh); return FALSE; - } for (i = 0; i < mode_count; i++) { @@ -512,19 +504,131 @@ theme_runtime_save (void) continue; g_assert (theme_palette_get_color (modes[i].palette, def->token, &color)); if (!palette_write_token_color (fh, modes[i].mode_name, def, &color)) - { - close (fh); return FALSE; - } } } + return TRUE; +} + +gboolean +theme_runtime_save_prepare (char **temp_path) +{ + int fh; + const char *temp_name; + + if (!temp_path) + return FALSE; + + temp_name = "colors.conf.new"; + fh = zoitechat_open_file (temp_name, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh == -1) + return FALSE; + + if (!theme_runtime_save_to_fd (fh)) + { + close (fh); + return FALSE; + } + if (close (fh) == -1) return FALSE; + *temp_path = g_strdup (temp_name); return TRUE; } +gboolean +theme_runtime_save_finalize (const char *temp_path) +{ + int src_fh; + int dst_fh; + char buffer[4096]; + ssize_t read_len; + + if (!temp_path) + return FALSE; + + src_fh = zoitechat_open_file (temp_path, O_RDONLY, 0, XOF_DOMODE); + if (src_fh == -1) + return FALSE; + + dst_fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (dst_fh == -1) + { + close (src_fh); + return FALSE; + } + + while ((read_len = read (src_fh, buffer, sizeof (buffer))) > 0) + { + ssize_t offset; + + offset = 0; + while (offset < read_len) + { + ssize_t written; + + written = write (dst_fh, buffer + offset, (size_t) (read_len - offset)); + if (written <= 0) + { + close (src_fh); + close (dst_fh); + return FALSE; + } + offset += written; + } + } + + if (read_len < 0) + { + close (src_fh); + close (dst_fh); + return FALSE; + } + + if (close (src_fh) == -1) + { + close (dst_fh); + return FALSE; + } + + if (close (dst_fh) == -1) + return FALSE; + + return TRUE; +} + +void +theme_runtime_save_discard (const char *temp_path) +{ + int fh; + + if (!temp_path) + return; + + fh = zoitechat_open_file (temp_path, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + close (fh); +} + +gboolean +theme_runtime_save (void) +{ + char *temp_path = NULL; + gboolean result; + + if (!theme_runtime_save_prepare (&temp_path)) + return FALSE; + + result = theme_runtime_save_finalize (temp_path); + if (!result) + theme_runtime_save_discard (temp_path); + g_free (temp_path); + + return result; +} + static gboolean palette_color_eq (const GdkRGBA *a, const GdkRGBA *b) { diff --git a/src/fe-gtk/theme/theme-runtime.h b/src/fe-gtk/theme/theme-runtime.h index fb8f7eb0..d20aa6b2 100644 --- a/src/fe-gtk/theme/theme-runtime.h +++ b/src/fe-gtk/theme/theme-runtime.h @@ -19,6 +19,9 @@ typedef struct void theme_runtime_load (void); gboolean theme_runtime_save (void); +gboolean theme_runtime_save_prepare (char **temp_path); +gboolean theme_runtime_save_finalize (const char *temp_path); +void theme_runtime_save_discard (const char *temp_path); gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed); gboolean theme_runtime_apply_dark_mode (gboolean enable); void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col); From 48b551b188496a24a88995c41a835a34d25192da Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 09:35:20 -0600 Subject: [PATCH 04/13] refactor: stage theme saves w/ atomic rename; add finalize/discard persistence tests --- .../tests/test-theme-runtime-persistence.c | 64 +++++++++++++- src/fe-gtk/theme/theme-runtime.c | 86 +++++++------------ 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/src/fe-gtk/theme/tests/test-theme-runtime-persistence.c b/src/fe-gtk/theme/tests/test-theme-runtime-persistence.c index 8b211125..b5308a68 100644 --- a/src/fe-gtk/theme/tests/test-theme-runtime-persistence.c +++ b/src/fe-gtk/theme/tests/test-theme-runtime-persistence.c @@ -19,6 +19,12 @@ struct zoitechatprefs prefs; static char *test_home_dir; +static char * +test_home_path (const char *file) +{ + return g_build_filename (test_home_dir, file, NULL); +} + static gboolean read_line_value (const char *cfg, const char *key, char *out, gsize out_len) { @@ -97,6 +103,12 @@ cfg_put_int (int fh, int value, char *var) return write (fh, line, (size_t) len) == len; } +char * +get_xdir (void) +{ + return test_home_dir; +} + int zoitechat_open_file (const char *file, int flags, int mode, int xof_flags) { @@ -145,7 +157,7 @@ read_colors_conf (void) gsize length = 0; gboolean ok; - path = g_build_filename (test_home_dir, "colors.conf", NULL); + path = test_home_path ("colors.conf"); ok = g_file_get_contents (path, &content, &length, NULL); g_free (path); g_assert_true (ok); @@ -222,7 +234,7 @@ test_loads_legacy_color_keys_via_migration_loader (void) gboolean ok; setup_temp_home (); - path = g_build_filename (test_home_dir, "colors.conf", NULL); + path = test_home_path ("colors.conf"); ok = g_file_set_contents (path, legacy_cfg, -1, NULL); g_free (path); g_assert_true (ok); @@ -336,6 +348,50 @@ test_gtk_map_uses_theme_defaults_until_custom_token_is_set (void) g_assert_true (colors_equal (&values.foreground, &custom)); } + +static void +test_save_finalize_replaces_colors_conf_atomically (void) +{ + char *path; + char *temp_path = NULL; + char *cfg = NULL; + gboolean ok; + + setup_temp_home (); + path = test_home_path ("colors.conf"); + ok = g_file_set_contents (path, "theme.mode.light.token.mirc_0 = 0000 0000 0000\n", -1, NULL); + g_assert_true (ok); + + theme_runtime_load (); + g_assert_true (theme_runtime_save_prepare (&temp_path)); + g_assert_nonnull (temp_path); + g_assert_nonnull (g_strrstr (temp_path, "colors.conf.new.")); + g_assert_true (g_file_test (temp_path, G_FILE_TEST_EXISTS)); + g_assert_true (theme_runtime_save_finalize (temp_path)); + g_assert_false (g_file_test (temp_path, G_FILE_TEST_EXISTS)); + ok = g_file_get_contents (path, &cfg, NULL, NULL); + g_assert_true (ok); + g_assert_nonnull (g_strstr_len (cfg, -1, "theme.palette.semantic_migrated = 1")); + g_free (cfg); + g_free (temp_path); + g_free (path); +} + +static void +test_save_discard_unlinks_temp_file (void) +{ + char *temp_path = NULL; + + setup_temp_home (); + theme_runtime_load (); + g_assert_true (theme_runtime_save_prepare (&temp_path)); + g_assert_nonnull (temp_path); + g_assert_true (g_file_test (temp_path, G_FILE_TEST_EXISTS)); + theme_runtime_save_discard (temp_path); + g_assert_false (g_file_test (temp_path, G_FILE_TEST_EXISTS)); + g_free (temp_path); +} + static void test_save_writes_only_custom_token_keys (void) { @@ -368,6 +424,10 @@ main (int argc, char **argv) test_gtk_map_colors_blend_with_palette_without_transparency); g_test_add_func ("/theme/runtime/gtk_map_uses_theme_defaults_until_custom_token_is_set", test_gtk_map_uses_theme_defaults_until_custom_token_is_set); + g_test_add_func ("/theme/runtime/save_finalize_replaces_colors_conf_atomically", + test_save_finalize_replaces_colors_conf_atomically); + g_test_add_func ("/theme/runtime/save_discard_unlinks_temp_file", + test_save_discard_unlinks_temp_file); g_test_add_func ("/theme/runtime/save_writes_only_custom_token_keys", test_save_writes_only_custom_token_keys); return g_test_run (); diff --git a/src/fe-gtk/theme/theme-runtime.c b/src/fe-gtk/theme/theme-runtime.c index f7765399..c2dc9fb6 100644 --- a/src/fe-gtk/theme/theme-runtime.c +++ b/src/fe-gtk/theme/theme-runtime.c @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef WIN32 #include @@ -515,86 +516,61 @@ gboolean theme_runtime_save_prepare (char **temp_path) { int fh; - const char *temp_name; + char *temp_name; + const char *config_dir; if (!temp_path) return FALSE; - temp_name = "colors.conf.new"; - fh = zoitechat_open_file (temp_name, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); - if (fh == -1) + config_dir = get_xdir (); + if (!config_dir) return FALSE; + temp_name = g_build_filename (config_dir, "colors.conf.new.XXXXXX", NULL); + fh = g_mkstemp (temp_name); + if (fh == -1) + { + g_free (temp_name); + return FALSE; + } + if (!theme_runtime_save_to_fd (fh)) { close (fh); + g_unlink (temp_name); + g_free (temp_name); return FALSE; } if (close (fh) == -1) + { + g_unlink (temp_name); + g_free (temp_name); return FALSE; + } - *temp_path = g_strdup (temp_name); + *temp_path = temp_name; return TRUE; } gboolean theme_runtime_save_finalize (const char *temp_path) { - int src_fh; - int dst_fh; - char buffer[4096]; - ssize_t read_len; + char *config_path; if (!temp_path) return FALSE; - src_fh = zoitechat_open_file (temp_path, O_RDONLY, 0, XOF_DOMODE); - if (src_fh == -1) - return FALSE; - - dst_fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); - if (dst_fh == -1) + config_path = g_build_filename (get_xdir (), "colors.conf", NULL); +#ifdef WIN32 + g_unlink (config_path); +#endif + if (g_rename (temp_path, config_path) == -1) { - close (src_fh); + g_free (config_path); return FALSE; } - - while ((read_len = read (src_fh, buffer, sizeof (buffer))) > 0) - { - ssize_t offset; - - offset = 0; - while (offset < read_len) - { - ssize_t written; - - written = write (dst_fh, buffer + offset, (size_t) (read_len - offset)); - if (written <= 0) - { - close (src_fh); - close (dst_fh); - return FALSE; - } - offset += written; - } - } - - if (read_len < 0) - { - close (src_fh); - close (dst_fh); - return FALSE; - } - - if (close (src_fh) == -1) - { - close (dst_fh); - return FALSE; - } - - if (close (dst_fh) == -1) - return FALSE; + g_free (config_path); return TRUE; } @@ -602,14 +578,10 @@ theme_runtime_save_finalize (const char *temp_path) void theme_runtime_save_discard (const char *temp_path) { - int fh; - if (!temp_path) return; - fh = zoitechat_open_file (temp_path, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); - if (fh != -1) - close (fh); + g_unlink (temp_path); } gboolean From 854a91391176a17e2fd7fe510320ff298d02490a Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 09:43:50 -0600 Subject: [PATCH 05/13] refactor: stage theme prefs edits; preview live, commit on OK, discard on cancel --- src/fe-gtk/setup.c | 5 +- src/fe-gtk/theme/theme-preferences.c | 224 +++++++++++++++++++++------ src/fe-gtk/theme/theme-preferences.h | 3 + 3 files changed, 184 insertions(+), 48 deletions(-) diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 3a73edfe..2a19cd92 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -2195,9 +2195,10 @@ setup_ok_cb (GtkWidget *but, GtkWidget *win) PreferencesPersistenceResult save_result; char buffer[192]; - gtk_widget_destroy (win); + theme_preferences_stage_commit (); setup_apply (&setup_prefs); save_result = preferences_persistence_save_all (); + gtk_widget_destroy (win); if (save_result.success) return; @@ -2256,6 +2257,7 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin) { *swin = NULL; + theme_preferences_stage_discard (); if (font_dialog) { @@ -2276,6 +2278,7 @@ setup_open (void) memcpy (&setup_prefs, &prefs, sizeof (prefs)); color_change = FALSE; + theme_preferences_stage_begin (); setup_window = setup_window_open (); g_signal_connect (G_OBJECT (setup_window), "destroy", diff --git a/src/fe-gtk/theme/theme-preferences.c b/src/fe-gtk/theme/theme-preferences.c index af165a75..352817ee 100644 --- a/src/fe-gtk/theme/theme-preferences.c +++ b/src/fe-gtk/theme/theme-preferences.c @@ -70,6 +70,139 @@ typedef struct #define COLOR_MANAGER_RESPONSE_RESET 1 +typedef struct +{ + gboolean active; + gboolean changed; + gboolean snapshot_valid[THEME_TOKEN_COUNT]; + gboolean staged_valid[THEME_TOKEN_COUNT]; + GdkRGBA snapshot[THEME_TOKEN_COUNT]; + GdkRGBA staged[THEME_TOKEN_COUNT]; +} theme_preferences_stage_state; + +static theme_preferences_stage_state theme_preferences_stage; + +static gboolean +theme_preferences_staged_get_color (ThemeSemanticToken token, GdkRGBA *rgba) +{ + if (token < 0 || token >= THEME_TOKEN_COUNT || !rgba) + return FALSE; + + if (theme_preferences_stage.active && theme_preferences_stage.staged_valid[token]) + { + *rgba = theme_preferences_stage.staged[token]; + return TRUE; + } + + return theme_get_color (token, rgba); +} + +static void +theme_preferences_stage_recompute_changed (void) +{ + ThemeSemanticToken token; + + theme_preferences_stage.changed = FALSE; + for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) + { + if (!theme_preferences_stage.snapshot_valid[token] || !theme_preferences_stage.staged_valid[token]) + continue; + if (!gdk_rgba_equal (&theme_preferences_stage.snapshot[token], &theme_preferences_stage.staged[token])) + { + theme_preferences_stage.changed = TRUE; + return; + } + } +} + +static void +theme_preferences_stage_sync_runtime_to_snapshot (void) +{ + ThemeSemanticToken token; + + for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) + { + if (theme_preferences_stage.snapshot_valid[token]) + theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, + &theme_preferences_stage.snapshot[token], NULL); + } +} + +static void +theme_preferences_stage_sync_runtime_to_staged (void) +{ + ThemeSemanticToken token; + + for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) + { + if (theme_preferences_stage.staged_valid[token]) + theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, + &theme_preferences_stage.staged[token], NULL); + } +} + +static void +theme_preferences_staged_set_color (ThemeSemanticToken token, const GdkRGBA *rgba, + gboolean *color_change_flag, gboolean live_preview) +{ + if (token < 0 || token >= THEME_TOKEN_COUNT || !rgba) + return; + + if (theme_preferences_stage.active) + { + theme_preferences_stage.staged[token] = *rgba; + theme_preferences_stage.staged_valid[token] = TRUE; + theme_preferences_stage_recompute_changed (); + if (color_change_flag) + *color_change_flag = theme_preferences_stage.changed; + } + + if (live_preview) + theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, rgba, NULL); +} + +void +theme_preferences_stage_begin (void) +{ + ThemeSemanticToken token; + + memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); + theme_preferences_stage.active = TRUE; + + for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) + { + GdkRGBA rgba; + + if (!theme_preferences_staged_get_color (token, &rgba)) + continue; + + theme_preferences_stage.snapshot[token] = rgba; + theme_preferences_stage.staged[token] = rgba; + theme_preferences_stage.snapshot_valid[token] = TRUE; + theme_preferences_stage.staged_valid[token] = TRUE; + } +} + +void +theme_preferences_stage_commit (void) +{ + if (!theme_preferences_stage.active) + return; + + theme_preferences_stage_sync_runtime_to_staged (); + memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); +} + +void +theme_preferences_stage_discard (void) +{ + if (!theme_preferences_stage.active) + return; + + theme_preferences_stage_sync_runtime_to_snapshot (); + memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); +} + static void theme_preferences_show_import_error (GtkWidget *button, const char *message); @@ -116,16 +249,16 @@ theme_preferences_manager_update_preview (theme_color_manager_ui *ui) if (!ui) return; - if (!theme_get_color (THEME_TOKEN_TEXT_FOREGROUND, &text_fg) - || !theme_get_color (THEME_TOKEN_TEXT_BACKGROUND, &text_bg) - || !theme_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &sel_fg) - || !theme_get_color (THEME_TOKEN_SELECTION_BACKGROUND, &sel_bg) - || !theme_get_color (THEME_TOKEN_MARKER, &marker) - || !theme_get_color (THEME_TOKEN_TAB_NEW_DATA, &tab_new_data) - || !theme_get_color (THEME_TOKEN_TAB_NEW_MESSAGE, &tab_new_message) - || !theme_get_color (THEME_TOKEN_TAB_HIGHLIGHT, &tab_highlight) - || !theme_get_color (THEME_TOKEN_TAB_AWAY, &tab_away) - || !theme_get_color (THEME_TOKEN_SPELL, &spell)) + if (!theme_preferences_staged_get_color (THEME_TOKEN_TEXT_FOREGROUND, &text_fg) + || !theme_preferences_staged_get_color (THEME_TOKEN_TEXT_BACKGROUND, &text_bg) + || !theme_preferences_staged_get_color (THEME_TOKEN_SELECTION_FOREGROUND, &sel_fg) + || !theme_preferences_staged_get_color (THEME_TOKEN_SELECTION_BACKGROUND, &sel_bg) + || !theme_preferences_staged_get_color (THEME_TOKEN_MARKER, &marker) + || !theme_preferences_staged_get_color (THEME_TOKEN_TAB_NEW_DATA, &tab_new_data) + || !theme_preferences_staged_get_color (THEME_TOKEN_TAB_NEW_MESSAGE, &tab_new_message) + || !theme_preferences_staged_get_color (THEME_TOKEN_TAB_HIGHLIGHT, &tab_highlight) + || !theme_preferences_staged_get_color (THEME_TOKEN_TAB_AWAY, &tab_away) + || !theme_preferences_staged_get_color (THEME_TOKEN_SPELL, &spell)) return; gtkutil_apply_palette (ui->preview_window, &text_bg, &text_fg, NULL); @@ -305,15 +438,12 @@ theme_preferences_color_response_cb (GtkDialog *dialog, gint response_id, gpoint if (response_id == GTK_RESPONSE_OK) { GdkRGBA rgba; - gboolean changed = FALSE; gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog), &rgba); - theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, - data->token, - &rgba, - &changed); - if (data->color_change_flag) - *data->color_change_flag = *data->color_change_flag || changed; + theme_preferences_staged_set_color (data->token, + &rgba, + data->color_change_flag, + TRUE); theme_preferences_color_button_apply (data->button, &rgba); theme_preferences_manager_update_preview ((theme_color_manager_ui *) data->manager_ui); } @@ -332,7 +462,7 @@ theme_preferences_color_cb (GtkWidget *button, gpointer userdata) token = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "zoitechat-theme-token")); - if (!theme_get_color (token, &rgba)) + if (!theme_preferences_staged_get_color (token, &rgba)) return; dialog = gtk_color_chooser_dialog_new (_("Select color"), GTK_WINDOW (userdata)); theme_manager_attach_window (dialog); @@ -408,14 +538,7 @@ theme_preferences_manager_row_apply (theme_color_manager_row *row, const GdkRGBA static void theme_preferences_manager_row_commit (theme_color_manager_row *row, const GdkRGBA *rgba) { - gboolean changed = FALSE; - - theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, - row->token, - rgba, - &changed); - if (row->color_change_flag) - *row->color_change_flag = *row->color_change_flag || changed; + theme_preferences_staged_set_color (row->token, rgba, row->color_change_flag, TRUE); theme_preferences_manager_row_apply (row, rgba); theme_preferences_manager_update_preview ((theme_color_manager_ui *) row->manager_ui); } @@ -428,7 +551,7 @@ theme_preferences_manager_entry_commit (theme_color_manager_row *row) if (!gdk_rgba_parse (&rgba, text)) { - if (theme_get_color (row->token, &rgba)) + if (theme_preferences_staged_get_color (row->token, &rgba)) theme_preferences_manager_row_apply (row, &rgba); return; } @@ -486,7 +609,7 @@ theme_preferences_manager_pick_cb (GtkWidget *button, gpointer user_data) GdkRGBA rgba; theme_manager_live_picker_data *data; - if (!theme_get_color (row->token, &rgba)) + if (!theme_preferences_staged_get_color (row->token, &rgba)) return; dialog = gtk_color_chooser_dialog_new (_("Select color"), row->parent); @@ -545,7 +668,7 @@ theme_preferences_manager_refresh_rows (theme_color_manager_ui *ui) theme_color_manager_row *row = g_ptr_array_index (ui->rows, i); GdkRGBA rgba; - if (theme_get_color (row->token, &rgba)) + if (theme_preferences_staged_get_color (row->token, &rgba)) theme_preferences_manager_row_apply (row, &rgba); } @@ -564,7 +687,24 @@ theme_preferences_manager_dialog_response_cb (GtkDialog *dialog, gint response_i gboolean changed = FALSE; theme_manager_reset_mode_colors (ZOITECHAT_DARK_MODE_LIGHT, &changed); - if (ui->color_change_flag) + if (theme_preferences_stage.active) + { + ThemeSemanticToken token; + + for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) + { + GdkRGBA rgba; + + if (!theme_get_color (token, &rgba)) + continue; + theme_preferences_stage.staged[token] = rgba; + theme_preferences_stage.staged_valid[token] = TRUE; + } + theme_preferences_stage_recompute_changed (); + if (ui->color_change_flag) + *ui->color_change_flag = theme_preferences_stage.changed; + } + else if (ui->color_change_flag) *ui->color_change_flag = *ui->color_change_flag || changed; } @@ -698,7 +838,7 @@ theme_preferences_create_color_manager_dialog (GtkWindow *parent, gboolean *colo g_free (token_code); g_free (search_text); - if (theme_get_color (token, &rgba)) + if (theme_preferences_staged_get_color (token, &rgba)) theme_preferences_manager_row_apply (row, &rgba); g_signal_connect (G_OBJECT (button), "clicked", @@ -727,20 +867,15 @@ static void theme_preferences_manage_colors_cb (GtkWidget *button, gpointer user_data) { gboolean *color_change_flag = user_data; - gboolean old_changed = FALSE; GtkWidget *dialog; - if (color_change_flag) - old_changed = *color_change_flag; - dialog = theme_preferences_create_color_manager_dialog (GTK_WINDOW (gtk_widget_get_toplevel (button)), color_change_flag); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); - if (color_change_flag && *color_change_flag != old_changed && - !theme_manager_save_preferences ()) - theme_preferences_show_import_error (button, _("Could not save colors.conf.")); + if (color_change_flag) + *color_change_flag = theme_preferences_stage.active ? theme_preferences_stage.changed : *color_change_flag; } static void @@ -869,12 +1004,8 @@ theme_preferences_import_colors_conf_cb (GtkWidget *button, gpointer user_data) char *cfg; GError *error = NULL; gboolean any_imported = FALSE; - gboolean old_changed = FALSE; ThemeSemanticToken token; - if (color_change_flag) - old_changed = *color_change_flag; - dialog = gtk_file_chooser_dialog_new (_("Import colors.conf colors"), GTK_WINDOW (gtk_widget_get_toplevel (button)), GTK_FILE_CHOOSER_ACTION_OPEN, @@ -910,15 +1041,14 @@ theme_preferences_import_colors_conf_cb (GtkWidget *button, gpointer user_data) if (!theme_preferences_read_import_color (cfg, token, &rgba)) continue; - theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, &rgba, color_change_flag); + theme_preferences_staged_set_color (token, &rgba, color_change_flag, TRUE); any_imported = TRUE; } if (!any_imported) theme_preferences_show_import_error (button, _("No importable colors were found in that colors.conf file.")); - else if (color_change_flag && *color_change_flag != old_changed && - !theme_manager_save_preferences ()) - theme_preferences_show_import_error (button, _("Could not save colors.conf.")); + else if (color_change_flag) + *color_change_flag = theme_preferences_stage.active ? theme_preferences_stage.changed : *color_change_flag; g_free (cfg); g_free (path); @@ -962,7 +1092,7 @@ theme_preferences_create_color_button (GtkWidget *table, g_object_set_data (G_OBJECT (but), "zoitechat-theme-color-change", color_change_flag); gtk_grid_attach (GTK_GRID (table), but, col, row, 1, 1); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (theme_preferences_color_cb), parent); - if (theme_get_color (token, &color)) + if (theme_preferences_staged_get_color (token, &color)) theme_preferences_color_button_apply (but, &color); } diff --git a/src/fe-gtk/theme/theme-preferences.h b/src/fe-gtk/theme/theme-preferences.h index d2d15a07..428adaca 100644 --- a/src/fe-gtk/theme/theme-preferences.h +++ b/src/fe-gtk/theme/theme-preferences.h @@ -13,5 +13,8 @@ GtkWidget *theme_preferences_create_color_page (GtkWindow *parent, struct zoitechatprefs *setup_prefs, gboolean *color_change_flag); void theme_preferences_apply_to_session (session_gui *gui, InputStyle *input_style); +void theme_preferences_stage_begin (void); +void theme_preferences_stage_commit (void); +void theme_preferences_stage_discard (void); #endif From 592d74e78853a099c980c2dcc302779960fa3f80 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 09:53:56 -0600 Subject: [PATCH 06/13] fix: preview staged theme values from staged model, not raw callback input --- src/fe-gtk/theme/theme-preferences.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/fe-gtk/theme/theme-preferences.c b/src/fe-gtk/theme/theme-preferences.c index 352817ee..8ad1acd7 100644 --- a/src/fe-gtk/theme/theme-preferences.c +++ b/src/fe-gtk/theme/theme-preferences.c @@ -145,6 +145,8 @@ static void theme_preferences_staged_set_color (ThemeSemanticToken token, const GdkRGBA *rgba, gboolean *color_change_flag, gboolean live_preview) { + const GdkRGBA *preview_color = rgba; + if (token < 0 || token >= THEME_TOKEN_COUNT || !rgba) return; @@ -155,10 +157,12 @@ theme_preferences_staged_set_color (ThemeSemanticToken token, const GdkRGBA *rgb theme_preferences_stage_recompute_changed (); if (color_change_flag) *color_change_flag = theme_preferences_stage.changed; + + preview_color = &theme_preferences_stage.staged[token]; } if (live_preview) - theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, rgba, NULL); + theme_manager_set_token_color (ZOITECHAT_DARK_MODE_LIGHT, token, preview_color, NULL); } void From 0155b07c9d3c1d8138d6be079204e6371e86d3ac Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 10:07:34 -0600 Subject: [PATCH 07/13] refactor: route exit saves through FE persistence wrapper; keep text mode simple --- src/common/fe.h | 9 +++++++++ src/common/zoitechat.c | 14 ++++++++++++-- src/fe-gtk/fe-gtk.c | 16 ++++++++++++++++ src/fe-text/fe-text.c | 13 +++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/common/fe.h b/src/common/fe.h index d9cd23ee..e456ad73 100644 --- a/src/common/fe.h +++ b/src/common/fe.h @@ -44,11 +44,20 @@ typedef struct char *icon; /* filename */ } menu_entry; +typedef struct +{ + gboolean success; + gboolean partial_failure; + gboolean config_failed; + gboolean theme_failed; +} fe_preferences_save_result; + int fe_args (int argc, char *argv[]); void fe_init (void); void fe_main (void); void fe_cleanup (void); void fe_exit (void); +fe_preferences_save_result fe_preferences_persistence_save_all (void); int fe_timeout_add (int interval, void *callback, void *userdata); int fe_timeout_add_seconds (int interval, void *callback, void *userdata); void fe_timeout_remove (int tag); diff --git a/src/common/zoitechat.c b/src/common/zoitechat.c index 2aa36e90..4d8ca3fa 100644 --- a/src/common/zoitechat.c +++ b/src/common/zoitechat.c @@ -1104,8 +1104,18 @@ zoitechat_exit (void) plugin_kill_all (); fe_cleanup (); - if (!save_config ()) - g_printerr ("Could not save zoitechat.conf.\n"); + { + fe_preferences_save_result save_result; + + save_result = fe_preferences_persistence_save_all (); + if (!save_result.success) + { + if (save_result.partial_failure || (!save_result.config_failed && save_result.theme_failed)) + g_printerr ("Could not fully save preferences. zoitechat.conf was written, but colors.conf failed.\n"); + else + g_printerr ("Could not save zoitechat.conf.\n"); + } + } if (prefs.save_pevents) { pevent_save (NULL); diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index 863f189f..9c48a44d 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -59,6 +59,7 @@ #include "plugin-notification.h" #include "theme/theme-manager.h" #include "theme/theme-application.h" +#include "preferences-persistence.h" #ifdef USE_LIBCANBERRA #include @@ -585,6 +586,21 @@ fe_cleanup (void) { } +fe_preferences_save_result +fe_preferences_persistence_save_all (void) +{ + PreferencesPersistenceResult save_result; + fe_preferences_save_result result; + + save_result = preferences_persistence_save_all (); + result.success = save_result.success; + result.partial_failure = save_result.partial_failure; + result.config_failed = save_result.config_failed; + result.theme_failed = save_result.theme_failed; + + return result; +} + void fe_exit (void) { diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c index 53d85f30..e73bd585 100644 --- a/src/fe-text/fe-text.c +++ b/src/fe-text/fe-text.c @@ -581,6 +581,19 @@ fe_exit (void) g_main_loop_quit(main_loop); } +fe_preferences_save_result +fe_preferences_persistence_save_all (void) +{ + fe_preferences_save_result result; + + result.success = save_config () != 0; + result.partial_failure = FALSE; + result.config_failed = !result.success; + result.theme_failed = FALSE; + + return result; +} + void fe_new_server (struct server *serv) { From e90b68967cacc21c2e15accf6238fe1407e318b4 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 10:11:41 -0600 Subject: [PATCH 08/13] refactor: split mg_configure_cb main/dialog geometry paths; drop sess mutation --- src/fe-gtk/maingui.c | 61 ++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 5a520b7a..2f4a5d60 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -889,7 +889,7 @@ mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) { gboolean changed = FALSE; - if (sess == NULL) /* for the main_window */ + if (sess == NULL) { if (mg_gui) { @@ -900,7 +900,6 @@ mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) int win_width; int win_height; - sess = current_sess; gtk_window_get_position (GTK_WINDOW (wid), &win_left, &win_top); gtk_window_get_size (GTK_WINDOW (wid), &win_width, &win_height); @@ -930,42 +929,38 @@ mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) } } } - - if (sess) + else if (sess->type == SESS_DIALOG && prefs.hex_gui_win_save) { - if (sess->type == SESS_DIALOG && prefs.hex_gui_win_save) + int dialog_left; + int dialog_top; + int dialog_width; + int dialog_height; + + gtk_window_get_position (GTK_WINDOW (wid), &dialog_left, &dialog_top); + gtk_window_get_size (GTK_WINDOW (wid), &dialog_width, &dialog_height); + + if (prefs.hex_gui_dialog_left != dialog_left) { - int dialog_left; - int dialog_top; - int dialog_width; - int dialog_height; + prefs.hex_gui_dialog_left = dialog_left; + changed = TRUE; + } - gtk_window_get_position (GTK_WINDOW (wid), &dialog_left, &dialog_top); - gtk_window_get_size (GTK_WINDOW (wid), &dialog_width, &dialog_height); + if (prefs.hex_gui_dialog_top != dialog_top) + { + prefs.hex_gui_dialog_top = dialog_top; + changed = TRUE; + } - if (prefs.hex_gui_dialog_left != dialog_left) - { - prefs.hex_gui_dialog_left = dialog_left; - changed = TRUE; - } + if (prefs.hex_gui_dialog_width != dialog_width) + { + prefs.hex_gui_dialog_width = dialog_width; + changed = TRUE; + } - if (prefs.hex_gui_dialog_top != dialog_top) - { - prefs.hex_gui_dialog_top = dialog_top; - changed = TRUE; - } - - if (prefs.hex_gui_dialog_width != dialog_width) - { - prefs.hex_gui_dialog_width = dialog_width; - changed = TRUE; - } - - if (prefs.hex_gui_dialog_height != dialog_height) - { - prefs.hex_gui_dialog_height = dialog_height; - changed = TRUE; - } + if (prefs.hex_gui_dialog_height != dialog_height) + { + prefs.hex_gui_dialog_height = dialog_height; + changed = TRUE; } } From 0cfb63f301b06eb05580ff52f7bc58a4c38ab4ab Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 10:30:54 -0600 Subject: [PATCH 09/13] style: normalize GTK signal names to canonical dash-case --- src/fe-gtk/ascii.c | 2 +- src/fe-gtk/chanlist.c | 6 +++--- src/fe-gtk/chanview-tabs.c | 12 ++++++------ src/fe-gtk/chanview-tree.c | 10 +++++----- src/fe-gtk/dccgui.c | 2 +- src/fe-gtk/editlist.c | 2 +- src/fe-gtk/fkeys.c | 2 +- src/fe-gtk/gtkutil.c | 2 +- src/fe-gtk/joind.c | 2 +- src/fe-gtk/maingui.c | 34 +++++++++++++++++----------------- src/fe-gtk/rawlog.c | 2 +- src/fe-gtk/servlistgui.c | 16 ++++++++-------- src/fe-gtk/setup.c | 6 +++--- src/fe-gtk/urlgrab.c | 2 +- src/fe-gtk/userlistgui.c | 18 +++++++++--------- 15 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/fe-gtk/ascii.c b/src/fe-gtk/ascii.c index 5e2ca75c..9725480f 100644 --- a/src/fe-gtk/ascii.c +++ b/src/fe-gtk/ascii.c @@ -153,7 +153,7 @@ ascii_open (void) gtk_widget_set_size_request (but, 28, -1); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (ascii_click), NULL); - g_signal_connect (G_OBJECT (but), "enter_notify_event", + g_signal_connect (G_OBJECT (but), "enter-notify-event", G_CALLBACK (ascii_enter), label); gtk_box_pack_start (GTK_BOX (hbox), but, 0, 0, 0); gtk_widget_show (but); diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c index a26ca697..e550006a 100644 --- a/src/fe-gtk/chanlist.c +++ b/src/fe-gtk/chanlist.c @@ -936,7 +936,7 @@ chanlist_opengui (server *serv, int do_refresh) GTK_SHADOW_IN); serv->gui->chanlist_list = view; - g_signal_connect (G_OBJECT (view), "row_activated", + g_signal_connect (G_OBJECT (view), "row-activated", G_CALLBACK (chanlist_dclick_cb), serv); g_signal_connect (G_OBJECT (view), "button-press-event", G_CALLBACK (chanlist_button_cb), serv); @@ -1003,7 +1003,7 @@ chanlist_opengui (server *serv, int do_refresh) wid = gtk_spin_button_new_with_range (1, 999999, 1); gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), serv->gui->chanlist_minusers); - g_signal_connect (G_OBJECT (wid), "value_changed", + g_signal_connect (G_OBJECT (wid), "value-changed", G_CALLBACK (chanlist_minusers), serv); gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); gtk_widget_show (wid); @@ -1016,7 +1016,7 @@ chanlist_opengui (server *serv, int do_refresh) wid = gtk_spin_button_new_with_range (1, 999999, 1); gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), serv->gui->chanlist_maxusers); - g_signal_connect (G_OBJECT (wid), "value_changed", + g_signal_connect (G_OBJECT (wid), "value-changed", G_CALLBACK (chanlist_maxusers), serv); gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); gtk_widget_show (wid); diff --git a/src/fe-gtk/chanview-tabs.c b/src/fe-gtk/chanview-tabs.c index 0c526a5f..d49ab825 100644 --- a/src/fe-gtk/chanview-tabs.c +++ b/src/fe-gtk/chanview-tabs.c @@ -371,7 +371,7 @@ make_sbutton (GtkArrowType type, void *click_cb, void *userdata) gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (click_cb), userdata); - g_signal_connect (G_OBJECT (button), "scroll_event", + g_signal_connect (G_OBJECT (button), "scroll-event", G_CALLBACK (tab_scroll_cb), userdata); gtk_widget_show (arrow); @@ -395,7 +395,7 @@ cv_tabs_init (chanview *cv) outer = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); } ((tabview *)cv)->outer = outer; - g_signal_connect (G_OBJECT (outer), "size_allocate", + g_signal_connect (G_OBJECT (outer), "size-allocate", G_CALLBACK (cv_tabs_sizealloc), cv); gtk_widget_show (outer); @@ -405,7 +405,7 @@ cv_tabs_init (chanview *cv) gtk_widget_set_size_request (viewport, -1, 1); else gtk_widget_set_size_request (viewport, 1, -1); - g_signal_connect (G_OBJECT (viewport), "scroll_event", + g_signal_connect (G_OBJECT (viewport), "scroll-event", G_CALLBACK (tab_scroll_cb), cv); gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0); gtk_widget_show (viewport); @@ -671,12 +671,12 @@ cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent) gtk_widget_set_name (but, "zoitechat-tab"); g_object_set_data (G_OBJECT (but), "c", ch); /* used to trap right-clicks */ - g_signal_connect (G_OBJECT (but), "button_press_event", + g_signal_connect (G_OBJECT (but), "button-press-event", G_CALLBACK (tab_click_cb), ch); /* avoid prelights */ - g_signal_connect (G_OBJECT (but), "enter_notify_event", + g_signal_connect (G_OBJECT (but), "enter-notify-event", G_CALLBACK (tab_ignore_cb), NULL); - g_signal_connect (G_OBJECT (but), "leave_notify_event", + g_signal_connect (G_OBJECT (but), "leave-notify-event", G_CALLBACK (tab_ignore_cb), NULL); g_signal_connect (G_OBJECT (but), "pressed", G_CALLBACK (tab_pressed_cb), ch); diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c index 5bb9cf1d..57c7454c 100644 --- a/src/fe-gtk/chanview-tree.c +++ b/src/fe-gtk/chanview-tree.c @@ -199,20 +199,20 @@ cv_tree_init (chanview *cv) G_CALLBACK (cv_tree_click_cb), cv); g_signal_connect (G_OBJECT (view), "row-activated", G_CALLBACK (cv_tree_activated_cb), NULL); - g_signal_connect (G_OBJECT (view), "scroll_event", + g_signal_connect (G_OBJECT (view), "scroll-event", G_CALLBACK (cv_tree_scroll_event_cb), NULL); gtk_drag_dest_set (view, GTK_DEST_DEFAULT_ALL, dnd_dest_target, 1, GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); gtk_drag_source_set (view, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_COPY); - g_signal_connect (G_OBJECT (view), "drag_begin", + g_signal_connect (G_OBJECT (view), "drag-begin", G_CALLBACK (mg_drag_begin_cb), NULL); - g_signal_connect (G_OBJECT (view), "drag_drop", + g_signal_connect (G_OBJECT (view), "drag-drop", G_CALLBACK (mg_drag_drop_cb), NULL); - g_signal_connect (G_OBJECT (view), "drag_motion", + g_signal_connect (G_OBJECT (view), "drag-motion", G_CALLBACK (mg_drag_motion_cb), NULL); - g_signal_connect (G_OBJECT (view), "drag_end", + g_signal_connect (G_OBJECT (view), "drag-end", G_CALLBACK (mg_drag_end_cb), NULL); ((treeview *)cv)->tree = GTK_TREE_VIEW (view); diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c index 3be9fcd3..f7fad6b1 100644 --- a/src/fe-gtk/dccgui.c +++ b/src/fe-gtk/dccgui.c @@ -863,7 +863,7 @@ fe_dcc_open_recv_win (int passive) gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE); if (!prefs.hex_gui_tab_utils) - g_signal_connect (G_OBJECT (dccfwin.window), "configure_event", + g_signal_connect (G_OBJECT (dccfwin.window), "configure-event", G_CALLBACK (dcc_configure_cb), 0); g_signal_connect (G_OBJECT (dccfwin.sel), "changed", G_CALLBACK (dcc_row_cb), NULL); diff --git a/src/fe-gtk/editlist.c b/src/fe-gtk/editlist.c index 27e8b3c0..31d67d40 100644 --- a/src/fe-gtk/editlist.c +++ b/src/fe-gtk/editlist.c @@ -290,7 +290,7 @@ editlist_treeview_new (GtkWidget *box, char *title1, char *title2) gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE); gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), TRUE); - g_signal_connect (G_OBJECT (view), "key_press_event", + g_signal_connect (G_OBJECT (view), "key-press-event", G_CALLBACK (editlist_keypress), NULL); gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (view), GTK_TREE_VIEW_GRID_LINES_HORIZONTAL); diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c index 13aea5fd..5ca2b7db 100644 --- a/src/fe-gtk/fkeys.c +++ b/src/fe-gtk/fkeys.c @@ -470,7 +470,7 @@ key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess) return 1; case 2: g_signal_stop_emission_by_name (G_OBJECT (wid), - "key_press_event"); + "key-press-event"); return 1; } } diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index b1ed5e23..d18baacc 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -662,7 +662,7 @@ gtkutil_esc_destroy (GtkWidget * win, GdkEventKey * key, gpointer userdata) void gtkutil_destroy_on_esc (GtkWidget *win) { - g_signal_connect (G_OBJECT (win), "key_press_event", G_CALLBACK (gtkutil_esc_destroy), win); + g_signal_connect (G_OBJECT (win), "key-press-event", G_CALLBACK (gtkutil_esc_destroy), win); } void diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c index 3b05d394..f1204e62 100644 --- a/src/fe-gtk/joind.c +++ b/src/fe-gtk/joind.c @@ -241,7 +241,7 @@ joind_show_dialog (server *serv) g_signal_connect (G_OBJECT (dialog1), "destroy", G_CALLBACK (joind_destroy_cb), serv); - g_signal_connect (G_OBJECT (entry1), "focus_in_event", + g_signal_connect (G_OBJECT (entry1), "focus-in-event", G_CALLBACK (joind_entryfocus_cb), serv); g_signal_connect (G_OBJECT (entry1), "activate", G_CALLBACK (joind_entryenter_cb), okbutton1); diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 2f4a5d60..8f9a9305 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -2806,7 +2806,7 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) mg_apply_emoji_fallback_widget (gui->key_entry); g_signal_connect (G_OBJECT (gui->key_entry), "activate", G_CALLBACK (mg_key_entry_cb), NULL); - g_signal_connect (G_OBJECT (gui->key_entry), "key_press_event", + g_signal_connect (G_OBJECT (gui->key_entry), "key-press-event", G_CALLBACK (mg_entry_select_all), NULL); if (prefs.hex_gui_input_style) @@ -2821,7 +2821,7 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) mg_apply_emoji_fallback_widget (gui->limit_entry); g_signal_connect (G_OBJECT (gui->limit_entry), "activate", G_CALLBACK (mg_limit_entry_cb), NULL); - g_signal_connect (G_OBJECT (gui->limit_entry), "key_press_event", + g_signal_connect (G_OBJECT (gui->limit_entry), "key-press-event", G_CALLBACK (mg_entry_select_all), NULL); if (prefs.hex_gui_input_style) @@ -3113,18 +3113,18 @@ mg_create_textarea (session *sess, GtkWidget *box) gtk_drag_dest_set (gui->vscrollbar, 5, dnd_dest_targets, 2, GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); - g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_begin", + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag-begin", G_CALLBACK (mg_drag_begin_cb), NULL); - g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_drop", + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag-drop", G_CALLBACK (mg_drag_drop_cb), NULL); - g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_motion", + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag-motion", G_CALLBACK (mg_drag_motion_cb), gui->vscrollbar); - g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_end", + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag-end", G_CALLBACK (mg_drag_end_cb), NULL); gtk_drag_dest_set (gui->xtext, GTK_DEST_DEFAULT_ALL, dnd_targets, 1, GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); - g_signal_connect (G_OBJECT (gui->xtext), "drag_data_received", + g_signal_connect (G_OBJECT (gui->xtext), "drag-data-received", G_CALLBACK (mg_dialog_dnd_drop), NULL); } @@ -3897,7 +3897,7 @@ mg_create_search(session *sess, GtkWidget *box) gtk_widget_set_size_request (gui->shentry, 180, -1); mg_apply_emoji_fallback_widget (entry); gui->search_changed_signal = g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(search_handle_change), sess); - g_signal_connect (G_OBJECT (entry), "key_press_event", G_CALLBACK (search_handle_esc), sess); + g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (search_handle_esc), sess); g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(mg_search_handle_next), sess); gtk_entry_set_icon_activatable (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY, FALSE); gtk_entry_set_icon_tooltip_text (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, _("Search hit end or not found.")); @@ -3979,11 +3979,11 @@ mg_create_entry (session *sess, GtkWidget *box) gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); gtk_widget_set_name (entry, "zoitechat-inputbox"); - g_signal_connect (G_OBJECT (entry), "key_press_event", + g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (key_handle_key_press), NULL); - g_signal_connect (G_OBJECT (entry), "focus_in_event", + g_signal_connect (G_OBJECT (entry), "focus-in-event", G_CALLBACK (mg_inputbox_focus), gui); - g_signal_connect (G_OBJECT (entry), "populate_popup", + g_signal_connect (G_OBJECT (entry), "populate-popup", G_CALLBACK (mg_inputbox_rightclick), NULL); g_signal_connect (G_OBJECT (entry), "word-check", G_CALLBACK (mg_spellcheck_cb), NULL); @@ -4146,11 +4146,11 @@ mg_create_topwindow (session *sess) gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); gtk_widget_set_opacity (win, (prefs.hex_gui_transparency / 255.)); - g_signal_connect (G_OBJECT (win), "focus_in_event", + g_signal_connect (G_OBJECT (win), "focus-in-event", G_CALLBACK (mg_topwin_focus_cb), sess); g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (mg_topdestroy_cb), sess); - g_signal_connect (G_OBJECT (win), "configure_event", + g_signal_connect (G_OBJECT (win), "configure-event", G_CALLBACK (mg_configure_cb), sess); @@ -4336,15 +4336,15 @@ mg_create_tabwindow (session *sess) gtk_widget_set_opacity (win, (prefs.hex_gui_transparency / 255.)); gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); - g_signal_connect (G_OBJECT (win), "delete_event", + g_signal_connect (G_OBJECT (win), "delete-event", G_CALLBACK (mg_tabwindow_de_cb), 0); g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (mg_tabwindow_kill_cb), 0); - g_signal_connect (G_OBJECT (win), "focus_in_event", + g_signal_connect (G_OBJECT (win), "focus-in-event", G_CALLBACK (mg_tabwin_focus_cb), NULL); - g_signal_connect (G_OBJECT (win), "configure_event", + g_signal_connect (G_OBJECT (win), "configure-event", G_CALLBACK (mg_configure_cb), NULL); - g_signal_connect (G_OBJECT (win), "window_state_event", + g_signal_connect (G_OBJECT (win), "window-state-event", G_CALLBACK (mg_windowstate_cb), NULL); diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c index 15dcf4d8..305d955d 100644 --- a/src/fe-gtk/rawlog.c +++ b/src/fe-gtk/rawlog.c @@ -192,7 +192,7 @@ open_rawlog (struct server *serv) serv, _("Save As...")); /* Copy selection to clipboard when Ctrl+Shift+C is pressed AND text auto-copy is disabled */ - g_signal_connect (G_OBJECT (serv->gui->rawlog_window), "key_press_event", G_CALLBACK (rawlog_key_cb), serv->gui->rawlog_textlist); + g_signal_connect (G_OBJECT (serv->gui->rawlog_window), "key-press-event", G_CALLBACK (rawlog_key_cb), serv->gui->rawlog_textlist); g_object_set_data (G_OBJECT (serv->gui->rawlog_window), RAWLOG_THEME_LISTENER_ID_KEY, GUINT_TO_POINTER (theme_listener_register ("rawlog.window", rawlog_theme_changed, serv->gui->rawlog_window))); g_signal_connect (G_OBJECT (serv->gui->rawlog_window), "destroy", G_CALLBACK (rawlog_theme_destroy_cb), NULL); diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index 38a0d514..254aa76c 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -763,9 +763,9 @@ servlist_edit_cb (GtkWidget *but, gpointer none) servlist_servers_populate (selected_net, edit_trees[SERVER_TREE]); servlist_channels_populate (selected_net, edit_trees[CHANNEL_TREE]); servlist_commands_populate (selected_net, edit_trees[CMD_TREE]); - g_signal_connect (G_OBJECT (edit_win), "delete_event", + g_signal_connect (G_OBJECT (edit_win), "delete-event", G_CALLBACK (servlist_editwin_delete_cb), 0); - g_signal_connect (G_OBJECT (edit_win), "configure_event", + g_signal_connect (G_OBJECT (edit_win), "configure-event", G_CALLBACK (servlist_edit_configure_cb), 0); gtk_widget_show (edit_win); } @@ -1839,7 +1839,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) model = GTK_TREE_MODEL (store); edit_trees[SERVER_TREE] = treeview_servers = gtk_tree_view_new_with_model (model); - g_signal_connect (G_OBJECT (treeview_servers), "key_press_event", + g_signal_connect (G_OBJECT (treeview_servers), "key-press-event", G_CALLBACK (servlist_keypress_cb), notebook); g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview_servers))), "changed", G_CALLBACK (servlist_server_row_cb), NULL); @@ -1864,7 +1864,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) model = GTK_TREE_MODEL (store); edit_trees[CHANNEL_TREE] = treeview_channels = gtk_tree_view_new_with_model (model); - g_signal_connect (G_OBJECT (treeview_channels), "key_press_event", + g_signal_connect (G_OBJECT (treeview_channels), "key-press-event", G_CALLBACK (servlist_keypress_cb), notebook); g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview_channels))), "changed", G_CALLBACK (servlist_channel_row_cb), NULL); @@ -1901,7 +1901,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) model = GTK_TREE_MODEL (store); edit_trees[CMD_TREE] = treeview_commands = gtk_tree_view_new_with_model (model); - g_signal_connect (G_OBJECT (treeview_commands), "key_press_event", + g_signal_connect (G_OBJECT (treeview_commands), "key-press-event", G_CALLBACK (servlist_keypress_cb), notebook); g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview_commands))), "changed", G_CALLBACK (servlist_command_row_cb), NULL); @@ -2357,13 +2357,13 @@ fe_serverlist_open (session *sess) servlist_networks_populate (networks_tree, network_list); - g_signal_connect (G_OBJECT (serverlist_win), "delete_event", + g_signal_connect (G_OBJECT (serverlist_win), "delete-event", G_CALLBACK (servlist_delete_cb), 0); - g_signal_connect (G_OBJECT (serverlist_win), "configure_event", + g_signal_connect (G_OBJECT (serverlist_win), "configure-event", G_CALLBACK (servlist_configure_cb), 0); g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree))), "changed", G_CALLBACK (servlist_network_row_cb), NULL); - g_signal_connect (G_OBJECT (networks_tree), "key_press_event", + g_signal_connect (G_OBJECT (networks_tree), "key-press-event", G_CALLBACK (servlist_net_keypress_cb), networks_tree); gtk_widget_show (serverlist_win); diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 2a19cd92..8dd35c94 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -894,7 +894,7 @@ setup_create_spin (GtkWidget *table, int row, const setting *set) gtk_widget_set_tooltip_text (wid, _(set->tooltip)); gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), setup_get_int (&setup_prefs, set)); - g_signal_connect (G_OBJECT (wid), "value_changed", + g_signal_connect (G_OBJECT (wid), "value-changed", G_CALLBACK (setup_spin_cb), (gpointer)set); gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0); @@ -950,7 +950,7 @@ setup_create_hscale (GtkWidget *table, int row, const setting *set) wid = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0., 255., 1.); gtk_scale_set_value_pos (GTK_SCALE (wid), GTK_POS_RIGHT); gtk_range_set_value (GTK_RANGE (wid), setup_get_int (&setup_prefs, set)); - g_signal_connect (G_OBJECT(wid), "value_changed", + g_signal_connect (G_OBJECT(wid), "value-changed", G_CALLBACK (setup_hscale_cb), (gpointer)set); setup_table_attach (table, wid, 3, 6, row, row + 1, TRUE, FALSE, SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0); @@ -1966,7 +1966,7 @@ setup_create_tree (GtkWidget *box, GtkWidget *book) gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); gtk_tree_selection_set_select_function (sel, setup_tree_select_filter, NULL, NULL); - g_signal_connect (G_OBJECT (tree), "cursor_changed", + g_signal_connect (G_OBJECT (tree), "cursor-changed", G_CALLBACK (setup_tree_cb), book); renderer = gtk_cell_renderer_text_new (); diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c index 7de40993..0c03b72b 100644 --- a/src/fe-gtk/urlgrab.c +++ b/src/fe-gtk/urlgrab.c @@ -99,7 +99,7 @@ url_treeview_new (GtkWidget *box) scroll = gtk_widget_get_parent (view); gtk_widget_set_hexpand (scroll, TRUE); gtk_widget_set_vexpand (scroll, TRUE); - g_signal_connect (G_OBJECT (view), "button_press_event", + g_signal_connect (G_OBJECT (view), "button-press-event", G_CALLBACK (url_treeview_url_clicked_cb), NULL); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); gtk_widget_show (view); diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c index efd5787c..fdc8b8d5 100644 --- a/src/fe-gtk/userlistgui.c +++ b/src/fe-gtk/userlistgui.c @@ -865,26 +865,26 @@ userlist_create (GtkWidget *box) gtk_drag_source_set (treeview, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_MOVE); /* file DND (for DCC) */ - g_signal_connect (G_OBJECT (treeview), "drag_motion", + g_signal_connect (G_OBJECT (treeview), "drag-motion", G_CALLBACK (userlist_dnd_motion), treeview); - g_signal_connect (G_OBJECT (treeview), "drag_leave", + g_signal_connect (G_OBJECT (treeview), "drag-leave", G_CALLBACK (userlist_dnd_leave), 0); - g_signal_connect (G_OBJECT (treeview), "drag_data_received", + g_signal_connect (G_OBJECT (treeview), "drag-data-received", G_CALLBACK (userlist_dnd_drop), treeview); - g_signal_connect (G_OBJECT (treeview), "button_press_event", + g_signal_connect (G_OBJECT (treeview), "button-press-event", G_CALLBACK (userlist_click_cb), 0); - g_signal_connect (G_OBJECT (treeview), "key_press_event", + g_signal_connect (G_OBJECT (treeview), "key-press-event", G_CALLBACK (userlist_key_cb), 0); /* tree/chanview DND */ - g_signal_connect (G_OBJECT (treeview), "drag_begin", + g_signal_connect (G_OBJECT (treeview), "drag-begin", G_CALLBACK (mg_drag_begin_cb), NULL); - g_signal_connect (G_OBJECT (treeview), "drag_drop", + g_signal_connect (G_OBJECT (treeview), "drag-drop", G_CALLBACK (mg_drag_drop_cb), NULL); - g_signal_connect (G_OBJECT (treeview), "drag_motion", + g_signal_connect (G_OBJECT (treeview), "drag-motion", G_CALLBACK (mg_drag_motion_cb), NULL); - g_signal_connect (G_OBJECT (treeview), "drag_end", + g_signal_connect (G_OBJECT (treeview), "drag-end", G_CALLBACK (mg_drag_end_cb), NULL); userlist_add_columns (GTK_TREE_VIEW (treeview)); From b7e4548d2a9150ff44cc24a04b359ef312a1ab95 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 11:01:40 -0600 Subject: [PATCH 10/13] refactor: setup OK acts transactional now; apply staged theme before save, roll back on fail --- src/fe-gtk/setup.c | 12 ++++++++++-- src/fe-gtk/theme/theme-preferences.c | 11 ++++++++++- src/fe-gtk/theme/theme-preferences.h | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 8dd35c94..985d9e11 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -2193,14 +2193,22 @@ static void setup_ok_cb (GtkWidget *but, GtkWidget *win) { PreferencesPersistenceResult save_result; + struct zoitechatprefs old_prefs; char buffer[192]; - theme_preferences_stage_commit (); + memcpy (&old_prefs, &prefs, sizeof (prefs)); + theme_preferences_stage_apply (); setup_apply (&setup_prefs); save_result = preferences_persistence_save_all (); - gtk_widget_destroy (win); if (save_result.success) + { + theme_preferences_stage_commit (); + gtk_widget_destroy (win); return; + } + + memcpy (&prefs, &old_prefs, sizeof (prefs)); + theme_preferences_stage_discard (); if (save_result.partial_failure) { diff --git a/src/fe-gtk/theme/theme-preferences.c b/src/fe-gtk/theme/theme-preferences.c index 8ad1acd7..3e83bf12 100644 --- a/src/fe-gtk/theme/theme-preferences.c +++ b/src/fe-gtk/theme/theme-preferences.c @@ -188,12 +188,21 @@ theme_preferences_stage_begin (void) } void -theme_preferences_stage_commit (void) +theme_preferences_stage_apply (void) { if (!theme_preferences_stage.active) return; theme_preferences_stage_sync_runtime_to_staged (); +} + +void +theme_preferences_stage_commit (void) +{ + if (!theme_preferences_stage.active) + return; + + theme_preferences_stage_apply (); memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); } diff --git a/src/fe-gtk/theme/theme-preferences.h b/src/fe-gtk/theme/theme-preferences.h index 428adaca..5e3dbad0 100644 --- a/src/fe-gtk/theme/theme-preferences.h +++ b/src/fe-gtk/theme/theme-preferences.h @@ -14,6 +14,7 @@ GtkWidget *theme_preferences_create_color_page (GtkWindow *parent, gboolean *color_change_flag); void theme_preferences_apply_to_session (session_gui *gui, InputStyle *input_style); void theme_preferences_stage_begin (void); +void theme_preferences_stage_apply (void); void theme_preferences_stage_commit (void); void theme_preferences_stage_discard (void); From b32f6522ac033cf22f993baff79d4d61c3d44d8f Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Tue, 17 Mar 2026 13:09:40 -0600 Subject: [PATCH 11/13] Persist GTK userlist column widths --- src/common/cfgfiles.c | 10 +++++++ src/common/zoitechat.h | 5 ++++ src/fe-gtk/chanlist.c | 64 ++++++++++++++++++++++++++++++++++++++++ src/fe-gtk/userlistgui.c | 36 ++++++++++++++++++++-- 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index e4b16956..bd35745f 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -407,6 +407,9 @@ const struct prefs vars[] = {"gui_autoopen_send", P_OFFINT (hex_gui_autoopen_send), TYPE_BOOL}, {"gui_chanlist_maxusers", P_OFFINT (hex_gui_chanlist_maxusers), TYPE_INT}, {"gui_chanlist_minusers", P_OFFINT (hex_gui_chanlist_minusers), TYPE_INT}, + {"gui_chanlist_width_channel", P_OFFINT (hex_gui_chanlist_width_channel), TYPE_INT}, + {"gui_chanlist_width_topic", P_OFFINT (hex_gui_chanlist_width_topic), TYPE_INT}, + {"gui_chanlist_width_users", P_OFFINT (hex_gui_chanlist_width_users), TYPE_INT}, {"gui_compact", P_OFFINT (hex_gui_compact), TYPE_BOOL}, {"gui_dialog_height", P_OFFINT (hex_gui_dialog_height), TYPE_INT}, {"gui_dialog_left", P_OFFINT (hex_gui_dialog_left), TYPE_INT}, @@ -466,6 +469,8 @@ const struct prefs vars[] = {"gui_ulist_hide", P_OFFINT (hex_gui_ulist_hide), TYPE_BOOL}, {"gui_ulist_icons", P_OFFINT (hex_gui_ulist_icons), TYPE_BOOL}, {"gui_ulist_pos", P_OFFINT (hex_gui_ulist_pos), TYPE_INT}, + {"gui_ulist_nick_width", P_OFFINT (hex_gui_ulist_nick_width), TYPE_INT}, + {"gui_ulist_host_width", P_OFFINT (hex_gui_ulist_host_width), TYPE_INT}, {"gui_ulist_show_hosts", P_OFFINT(hex_gui_ulist_show_hosts), TYPE_BOOL}, {"gui_ulist_sort", P_OFFINT (hex_gui_ulist_sort), TYPE_INT}, {"gui_ulist_style", P_OFFINT (hex_gui_ulist_style), TYPE_BOOL}, @@ -824,6 +829,9 @@ load_default_config(void) prefs.hex_flood_msg_time = 30; prefs.hex_gui_chanlist_maxusers = 9999; prefs.hex_gui_chanlist_minusers = 5; + prefs.hex_gui_chanlist_width_channel = 0; + prefs.hex_gui_chanlist_width_topic = 0; + prefs.hex_gui_chanlist_width_users = 0; prefs.hex_gui_dialog_height = 256; prefs.hex_gui_dialog_width = 500; prefs.hex_gui_lagometer = 1; @@ -837,6 +845,8 @@ load_default_config(void) prefs.hex_gui_tab_trunc = 20; prefs.hex_gui_throttlemeter = 1; prefs.hex_gui_ulist_pos = 3; + prefs.hex_gui_ulist_nick_width = 0; + prefs.hex_gui_ulist_host_width = 0; prefs.hex_gui_win_height = 400; prefs.hex_gui_win_width = 640; prefs.hex_irc_ban_type = 1; diff --git a/src/common/zoitechat.h b/src/common/zoitechat.h index 7693f53b..61ad8c8d 100644 --- a/src/common/zoitechat.h +++ b/src/common/zoitechat.h @@ -248,6 +248,9 @@ struct zoitechatprefs int hex_flood_msg_time; int hex_gui_chanlist_maxusers; int hex_gui_chanlist_minusers; + int hex_gui_chanlist_width_channel; + int hex_gui_chanlist_width_topic; + int hex_gui_chanlist_width_users; int hex_gui_dialog_height; int hex_gui_dialog_left; int hex_gui_dialog_top; @@ -269,6 +272,8 @@ struct zoitechatprefs int hex_gui_transparency; int hex_gui_throttlemeter; int hex_gui_ulist_pos; + int hex_gui_ulist_nick_width; + int hex_gui_ulist_host_width; int hex_gui_ulist_sort; int hex_gui_url_mod; int hex_gui_win_height; diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c index e550006a..09440273 100644 --- a/src/fe-gtk/chanlist.c +++ b/src/fe-gtk/chanlist.c @@ -74,6 +74,19 @@ chanlistrow; #define GET_MODEL(xserv) (gtk_tree_view_get_model(GTK_TREE_VIEW(xserv->gui->chanlist_list))) +#define CHANLIST_COL_WIDTH_MIN_CHANNEL 60 +#define CHANLIST_COL_WIDTH_MIN_USERS 40 +#define CHANLIST_COL_WIDTH_MIN_TOPIC 60 + +static int +chanlist_clamp_width (int width, int min_width) +{ + if (width < min_width) + return min_width; + + return width; +} + static void chanlist_set_label_alignment (GtkWidget *widget) { @@ -805,6 +818,26 @@ chanlist_button_cb (GtkTreeView *tree, GdkEventButton *event, server *serv) static void chanlist_destroy_widget (GtkWidget *wid, server *serv) { + GtkTreeViewColumn *column; + + column = gtk_tree_view_get_column (GTK_TREE_VIEW (serv->gui->chanlist_list), COL_CHANNEL); + if (column) + prefs.hex_gui_chanlist_width_channel = + chanlist_clamp_width (gtk_tree_view_column_get_width (column), CHANLIST_COL_WIDTH_MIN_CHANNEL); + + column = gtk_tree_view_get_column (GTK_TREE_VIEW (serv->gui->chanlist_list), COL_USERS); + if (column) + prefs.hex_gui_chanlist_width_users = + chanlist_clamp_width (gtk_tree_view_column_get_width (column), CHANLIST_COL_WIDTH_MIN_USERS); + + column = gtk_tree_view_get_column (GTK_TREE_VIEW (serv->gui->chanlist_list), COL_TOPIC); + if (column) + prefs.hex_gui_chanlist_width_topic = + chanlist_clamp_width (gtk_tree_view_column_get_width (column), CHANLIST_COL_WIDTH_MIN_TOPIC); + + if (!save_config ()) + fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN); + custom_list_clear ((CustomList *)GET_MODEL (serv)); chanlist_data_free (serv); @@ -944,6 +977,37 @@ chanlist_opengui (server *serv, int do_refresh) chanlist_add_column (view, COL_CHANNEL, 96, _("Channel"), FALSE); chanlist_add_column (view, COL_USERS, 50, _("Users"), TRUE); chanlist_add_column (view, COL_TOPIC, 50, _("Topic"), FALSE); + + if (prefs.hex_gui_chanlist_width_channel > 0) + { + GtkTreeViewColumn *column = gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_CHANNEL); + if (column) + gtk_tree_view_column_set_fixed_width (column, + chanlist_clamp_width (prefs.hex_gui_chanlist_width_channel, CHANLIST_COL_WIDTH_MIN_CHANNEL)); + } + + if (prefs.hex_gui_chanlist_width_users > 0) + { + GtkTreeViewColumn *column = gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_USERS); + if (column) + { + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_fixed_width (column, + chanlist_clamp_width (prefs.hex_gui_chanlist_width_users, CHANLIST_COL_WIDTH_MIN_USERS)); + gtk_tree_view_column_set_resizable (column, FALSE); + } + } + + if (prefs.hex_gui_chanlist_width_topic > 0) + { + GtkTreeViewColumn *column = gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_TOPIC); + if (column) + { + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_fixed_width (column, + chanlist_clamp_width (prefs.hex_gui_chanlist_width_topic, CHANLIST_COL_WIDTH_MIN_TOPIC)); + } + } gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (view), GTK_TREE_VIEW_GRID_LINES_HORIZONTAL); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (view)), GTK_SELECTION_MULTIPLE); /* this is a speed up, but no horizontal scrollbar :( */ diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c index fdc8b8d5..d8880244 100644 --- a/src/fe-gtk/userlistgui.c +++ b/src/fe-gtk/userlistgui.c @@ -52,6 +52,29 @@ enum static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token); +static void +userlist_column_width_notify_cb (GtkTreeViewColumn *column, GParamSpec *pspec, gpointer userdata) +{ + (void)pspec; + + int width = gtk_tree_view_column_get_width (column); + int *target = (int *)userdata; + + if (!target || width < 1 || *target == width) + return; + + *target = width; +} + +static void +userlist_apply_saved_column_width (GtkTreeViewColumn *column, int width) +{ + if (!column || width < 1) + return; + + gtk_tree_view_column_set_fixed_width (column, width); +} + static void userlist_update_min_width (session *sess) { @@ -71,7 +94,6 @@ userlist_update_min_width (session *sess) scrolled_window = gtk_widget_get_parent (sess->gui->user_tree); if (GTK_IS_SCROLLED_WINDOW (scrolled_window)) gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (scrolled_window), width); - gtk_widget_set_size_request (sess->gui->user_tree, width, -1); gtk_widget_set_size_request (sess->gui->user_box, width, -1); } @@ -712,6 +734,11 @@ userlist_add_columns (GtkTreeView * treeview) gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_expand (column, TRUE); gtk_tree_view_column_set_min_width (column, 1); + gtk_tree_view_column_set_resizable (column, TRUE); + userlist_apply_saved_column_width (column, prefs.hex_gui_ulist_nick_width); + g_signal_connect (G_OBJECT (column), "notify::width", + G_CALLBACK (userlist_column_width_notify_cb), + &prefs.hex_gui_ulist_nick_width); if (prefs.hex_gui_ulist_show_hosts) { @@ -728,6 +755,11 @@ userlist_add_columns (GtkTreeView * treeview) gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_expand (column, TRUE); gtk_tree_view_column_set_min_width (column, 1); + gtk_tree_view_column_set_resizable (column, TRUE); + userlist_apply_saved_column_width (column, prefs.hex_gui_ulist_host_width); + g_signal_connect (G_OBJECT (column), "notify::width", + G_CALLBACK (userlist_column_width_notify_cb), + &prefs.hex_gui_ulist_host_width); } } @@ -854,7 +886,7 @@ userlist_create (GtkWidget *box) treeview = gtk_tree_view_new (); gtk_widget_set_name (treeview, "zoitechat-userlist"); gtk_widget_set_can_focus (treeview, TRUE); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), TRUE); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)), GTK_SELECTION_MULTIPLE); From 7138a8f397682269ebd8e32973ac6031cd88238b Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Wed, 18 Mar 2026 09:06:10 -0600 Subject: [PATCH 12/13] Tighten userlist header/count spacing --- src/fe-gtk/maingui.c | 9 +++------ src/fe-gtk/userlistgui.c | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 8f9a9305..323babb3 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -3290,19 +3290,16 @@ mg_theme_window_destroy_cb (GtkWidget *widget, gpointer userdata) static void mg_create_userlist (session_gui *gui, GtkWidget *box) { - GtkWidget *frame, *ulist, *vbox; + GtkWidget *ulist, *vbox; vbox = mg_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 1); gtk_box_pack_start (GTK_BOX (box), vbox, TRUE, TRUE, 0); - frame = gtk_frame_new (NULL); - if (prefs.hex_gui_ulist_count) - gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, GUI_SPACING); - gui->namelistinfo = gtk_label_new (NULL); gtk_label_set_xalign (GTK_LABEL (gui->namelistinfo), 0.0f); gtk_widget_set_halign (gui->namelistinfo, GTK_ALIGN_START); - gtk_container_add (GTK_CONTAINER (frame), gui->namelistinfo); + if (prefs.hex_gui_ulist_count) + gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0); gui->user_tree = ulist = userlist_create (vbox); diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c index d8880244..dd67adcb 100644 --- a/src/fe-gtk/userlistgui.c +++ b/src/fe-gtk/userlistgui.c @@ -886,7 +886,7 @@ userlist_create (GtkWidget *box) treeview = gtk_tree_view_new (); gtk_widget_set_name (treeview, "zoitechat-userlist"); gtk_widget_set_can_focus (treeview, TRUE); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), TRUE); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)), GTK_SELECTION_MULTIPLE); From 7c24c1137d5e54493fc9da9ff767ec982de2fd57 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Wed, 18 Mar 2026 09:10:40 -0600 Subject: [PATCH 13/13] Split topic/mode rows, tighten mode bar spacing --- src/fe-gtk/maingui.c | 64 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 323babb3..14e02ced 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -307,6 +307,34 @@ mg_set_label_alignment_start (GtkWidget *widget) gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); } +static void +mg_apply_compact_mode_css (GtkWidget *widget) +{ + GtkStyleContext *context; + GtkCssProvider *provider; + + if (!widget) + return; + + context = gtk_widget_get_style_context (widget); + if (!context) + return; + + provider = g_object_get_data (G_OBJECT (widget), "mg-mode-css-provider"); + if (!provider) + { + provider = gtk_css_provider_new (); + g_object_set_data_full (G_OBJECT (widget), "mg-mode-css-provider", provider, g_object_unref); + } + + gtk_css_provider_load_from_data (provider, + ".zoitechat-mode-control { min-height: 11px; padding-top: 0; padding-bottom: 0; }" + ".zoitechat-mode-control label { padding-top: 0; padding-bottom: 0; }", + -1, NULL); + gtk_style_context_add_class (context, "zoitechat-mode-control"); + theme_css_apply_widget_provider (widget, GTK_STYLE_PROVIDER (provider)); +} + static GtkWidget * mg_box_new (GtkOrientation orientation, gboolean homogeneous, gint spacing) { @@ -2716,8 +2744,10 @@ mg_create_flagbutton (char *tip, GtkWidget *box, char *face) gtk_label_set_markup (GTK_LABEL(lbl), label_markup); btn = gtk_toggle_button_new (); - gtk_widget_set_size_request (btn, -1, 0); + gtk_widget_set_size_request (btn, -1, 11); gtk_widget_set_tooltip_text (btn, tip); + gtk_button_set_relief (GTK_BUTTON (btn), GTK_RELIEF_NONE); + mg_apply_compact_mode_css (btn); gtk_container_add (GTK_CONTAINER(btn), lbl); gtk_box_pack_start (GTK_BOX (box), btn, 0, 0, 0); @@ -2801,9 +2831,10 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) gui->key_entry = gtk_entry_new (); gtk_widget_set_name (gui->key_entry, "zoitechat-inputbox"); gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23); - gtk_widget_set_size_request (gui->key_entry, 115, -1); + gtk_widget_set_size_request (gui->key_entry, 115, 11); gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0); mg_apply_emoji_fallback_widget (gui->key_entry); + mg_apply_compact_mode_css (gui->key_entry); g_signal_connect (G_OBJECT (gui->key_entry), "activate", G_CALLBACK (mg_key_entry_cb), NULL); g_signal_connect (G_OBJECT (gui->key_entry), "key-press-event", @@ -2816,9 +2847,10 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) gui->limit_entry = gtk_entry_new (); gtk_widget_set_name (gui->limit_entry, "zoitechat-inputbox"); gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10); - gtk_widget_set_size_request (gui->limit_entry, 30, -1); + gtk_widget_set_size_request (gui->limit_entry, 30, 11); gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0); mg_apply_emoji_fallback_widget (gui->limit_entry); + mg_apply_compact_mode_css (gui->limit_entry); g_signal_connect (G_OBJECT (gui->limit_entry), "activate", G_CALLBACK (mg_limit_entry_cb), NULL); g_signal_connect (G_OBJECT (gui->limit_entry), "key-press-event", @@ -2904,11 +2936,14 @@ mg_create_dialogbuttons (GtkWidget *box) static void mg_create_topicbar (session *sess, GtkWidget *box) { - GtkWidget *hbox, *topic, *bbox; - session_gui *gui = sess->gui; + GtkWidget *vbox, *hbox, *mode_hbox, *topic, *bbox; + session_gui *gui = sess->gui; - gui->topic_bar = hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); - gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + gui->topic_bar = vbox = mg_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), vbox, 0, 0, 0); + + hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0); if (!gui->is_tab) sess->res->tab = NULL; @@ -2931,13 +2966,16 @@ mg_create_topicbar (session *sess, GtkWidget *box) g_signal_connect (G_OBJECT (topic), "leave-notify-event", G_CALLBACK (mg_topic_leave_cb), NULL); - gui->topicbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); - gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); - mg_create_chanmodebuttons (gui, bbox); + gui->dialogbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); + mg_create_dialogbuttons (bbox); - gui->dialogbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); - gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); - mg_create_dialogbuttons (bbox); + mode_hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), mode_hbox, 0, 0, 0); + + gui->topicbutton_box = bbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0); + gtk_box_pack_end (GTK_BOX (mode_hbox), bbox, 0, 0, 0); + mg_create_chanmodebuttons (gui, bbox); } /* check if a word is clickable */