Compare commits

..
Author SHA1 Message Date
deepend b0a5c6f5e5 Improve GTK3 Performance 2026-09-09 16:45:28 -06:00
deepend bb3893d36c Fix channel ACTION routing after self-message handling 2026-09-03 09:27:07 -06:00
deepend-tildeclub fe57449f6c Merge pull request #374 from TehPeGaSuS/fix/weechat-relay-pm-split
Fix private messages splitting into two windows over WeeChat's IRC relay
2026-09-03 08:20:55 -07:00
ThePeGaSuS c725adaa09 Fix CTCP ACTION not rerouting self-sent /me to correct PM window
Mirrors the PRIVMSG fix: replayed /me messages sent by our own nick
via WeeChat relay backlog were always passed with fromme=FALSE,
so they never got rerouted to the actual PM query window.
2026-09-03 08:51:21 +02:00
deepend-tildeclub 7731bfa4e0 Merge pull request #377 from ZoiteChat/retain-searched-text-view
unblock navigation when in search
2026-09-02 09:46:13 -07:00
deepend e72546aaec fix accidental removal of mg_apply_emoji_fallback_widget (entry); 2026-09-02 10:43:09 -06:00
deepend d6d648f0f4 unblock navigation when in search 2026-09-02 10:41:21 -06:00
ThePeGaSuS 0ba523d071 Fix: private messages split into two windows over WeeChat's IRC relay
When you send a private message, ZoiteChat sometimes gets the same
message echoed back by the server so it can show it in the right
window. Whether it recognizes that echo depended on the server first
confirming a feature called "echo-message" during connection setup.

WeeChat's relay doesn't seem to confirm that feature the way other
servers (or ZNC) do, so ZoiteChat didn't recognize its own echoed
messages and treated them as brand new incoming messages from
yourself, opening a second, separate conversation window instead of
using the one you were already typing in.

This makes ZoiteChat recognize its own message by checking who sent
it, instead of only trusting that feature confirmation. That way,
sent and received messages always end up in the same conversation
window, regardless of how well the server/relay announces its
capabilities.
2026-08-28 20:48:12 +02:00
9 changed files with 73 additions and 61 deletions
+1
View File
@@ -591,6 +591,7 @@ const struct prefs vars[] =
{"text_replay", P_OFFINT (hex_text_replay), TYPE_BOOL},
{"text_search_case_match", P_OFFINT (hex_text_search_case_match), TYPE_BOOL},
{"text_search_highlight_all", P_OFFINT (hex_text_search_highlight_all), TYPE_BOOL},
{"text_search_keep_position", P_OFFINT (hex_text_search_keep_position), TYPE_BOOL},
{"text_search_follow", P_OFFINT (hex_text_search_follow), TYPE_BOOL},
{"text_search_regexp", P_OFFINT (hex_text_search_regexp), TYPE_BOOL},
{"text_show_marker", P_OFFINT (hex_text_show_marker), TYPE_BOOL},
+15 -1
View File
@@ -126,7 +126,21 @@ ctcp_handle (session *sess, char *to, char *nick, char *ip,
if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
goto generic;
inbound_action (sess, to, nick, ip, msg + 7, FALSE, tags_data->identified, tags_data);
{
gboolean private_fromme = !serv->p_cmp (nick, serv->nick) && !is_channel (serv, to);
if (private_fromme)
{
session *target = find_dialog (serv, to);
if (target)
sess = target;
else if (serv->front_session)
sess = serv->front_session;
}
inbound_action (sess, to, nick, ip, msg + 7, private_fromme, tags_data->identified, tags_data);
}
return;
}
+1 -1
View File
@@ -1368,7 +1368,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
{
if (ignore_check (word[1], IG_PRIV))
return;
if (serv->have_echo_message && !serv->p_cmp (nick, serv->nick))
if (!serv->p_cmp (nick, serv->nick))
{
session *target_sess = find_dialog (serv, to);
+1
View File
@@ -217,6 +217,7 @@ struct zoitechatprefs
unsigned int hex_text_replay;
unsigned int hex_text_search_case_match;
unsigned int hex_text_search_highlight_all;
unsigned int hex_text_search_keep_position;
unsigned int hex_text_search_follow;
unsigned int hex_text_search_regexp;
unsigned int hex_text_show_marker;
+1 -1
View File
@@ -114,7 +114,7 @@ typedef struct restore_gui
/* information stored when this tab isn't front-most */
GtkListStore *user_model; /* for filling the GtkTreeView */
GHashTable *user_row_refs;
GHashTable *user_row_iters; /* User * -> persistent GtkTreeIter * */
void *buffer; /* xtext_Buffer */
char *input_text; /* input text buffer (while not-front tab) */
char *topic_text; /* topic GtkEntry buffer */
+17 -4
View File
@@ -4688,7 +4688,11 @@ mg_search_toggle(session *sess)
{
gtk_widget_hide(sess->gui->shbox);
gtk_widget_grab_focus(sess->gui->input_box);
if (prefs.hex_text_search_keep_position)
g_signal_handler_block (sess->gui->shentry, sess->gui->search_changed_signal);
gtk_entry_set_text(GTK_ENTRY(sess->gui->shentry), "");
if (prefs.hex_text_search_keep_position)
g_signal_handler_unblock (sess->gui->shentry, sess->gui->search_changed_signal);
}
else
{
@@ -4702,10 +4706,19 @@ mg_search_toggle(session *sess)
}
static gboolean
search_handle_esc (GtkWidget *win, GdkEventKey *key, session *sess)
search_handle_keypress (GtkWidget *win, GdkEventKey *key, session *sess)
{
if (key->keyval == GDK_KEY_Escape)
{
mg_search_toggle(sess);
return TRUE;
}
if (((key->keyval == GDK_KEY_Page_Up || key->keyval == GDK_KEY_Page_Down) &&
!(key->state & gtk_accelerator_get_default_mod_mask ())) ||
((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_Down) &&
(key->state & gtk_accelerator_get_default_mod_mask ()) == GDK_SHIFT_MASK))
return key_handle_key_press (sess->gui->input_box, key, sess);
return FALSE;
}
@@ -4735,7 +4748,7 @@ mg_create_search(session *sess, GtkWidget *box)
mg_apply_emoji_fallback_widget (entry);
mg_apply_entry_scroll_artifact_fix (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_keypress), 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."));
@@ -5773,9 +5786,9 @@ void
fe_session_callback (session *sess)
{
gtk_xtext_buffer_free (sess->res->buffer);
if (sess->res->user_row_iters)
g_hash_table_destroy (sess->res->user_row_iters);
g_object_unref (G_OBJECT (sess->res->user_model));
if (sess->res->user_row_refs)
g_hash_table_destroy (sess->res->user_row_refs);
if (sess->res->banlist && sess->res->banlist->window)
mg_close_gen (NULL, sess->res->banlist->window);
+1
View File
@@ -171,6 +171,7 @@ static const setting appearance_settings[] =
{ST_TOGGLE, N_("Colored nick names"), P_OFFINTNL(hex_text_color_nicks), N_("Give each person on IRC a different color"),0,0},
{ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(hex_text_indent), N_("Make nick names right-justified"),0,0},
{ST_TOGGLE, N_ ("Show marker line"), P_OFFINTNL (hex_text_show_marker), N_ ("Insert a red line after the last read text."), 0, 0},
{ST_TOGGLE, N_("Keep search position when closing"), P_OFFINTNL(hex_text_search_keep_position), 0, 0, 0},
{ST_HEADER, N_("Timestamps"),0,0,0},
{ST_TOGGLE, N_("Enable timestamps"), P_OFFINTNL(hex_stamp_text),0,0,1},
+20 -47
View File
@@ -218,75 +218,49 @@ scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model)
static GHashTable *
userlist_row_map_ensure (session *sess)
{
if (!sess->res->user_row_refs)
sess->res->user_row_refs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gtk_tree_row_reference_free);
if (!sess->res->user_row_iters)
sess->res->user_row_iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gtk_tree_iter_free);
return sess->res->user_row_refs;
return sess->res->user_row_iters;
}
static void
userlist_row_map_remove (session *sess, struct User *user)
{
if (!sess->res->user_row_refs)
if (!sess->res->user_row_iters)
return;
g_hash_table_remove (sess->res->user_row_refs, user);
g_hash_table_remove (sess->res->user_row_iters, user);
}
static void
userlist_row_map_set (session *sess, GtkTreeModel *model, struct User *user, GtkTreeIter *iter)
{
GtkTreePath *path;
GtkTreeRowReference *ref;
path = gtk_tree_model_get_path (model, iter);
if (!path)
/* The shared tree view can still show another session's model while a
* tab switch is pending. Never cache an iterator from that model. */
if (model != GTK_TREE_MODEL (sess->res->user_model))
return;
ref = gtk_tree_row_reference_new (model, path);
gtk_tree_path_free (path);
if (!ref)
return;
g_hash_table_replace (userlist_row_map_ensure (sess), user, ref);
/* GtkListStore guarantees persistent iterators until their row is
* removed, including across sorting. Unlike row references, these do
* not require every cached position to be updated on each insertion. */
g_hash_table_replace (userlist_row_map_ensure (sess), user, gtk_tree_iter_copy (iter));
}
static gboolean
userlist_row_map_get_iter (session *sess, GtkTreeModel *model, struct User *user, GtkTreeIter *iter)
{
GtkTreeRowReference *ref;
GtkTreePath *path;
struct User *row_user;
GtkTreeIter *cached;
if (!sess->res->user_row_refs)
if (model != GTK_TREE_MODEL (sess->res->user_model) || !sess->res->user_row_iters)
return FALSE;
ref = g_hash_table_lookup (sess->res->user_row_refs, user);
if (!ref)
cached = g_hash_table_lookup (sess->res->user_row_iters, user);
if (!cached)
return FALSE;
path = gtk_tree_row_reference_get_path (ref);
if (!path)
{
g_hash_table_remove (sess->res->user_row_refs, user);
return FALSE;
}
if (!gtk_tree_model_get_iter (model, iter, path))
{
gtk_tree_path_free (path);
g_hash_table_remove (sess->res->user_row_refs, user);
return FALSE;
}
gtk_tree_path_free (path);
gtk_tree_model_get (model, iter, COL_USER, &row_user, -1);
if (row_user != user)
{
g_hash_table_remove (sess->res->user_row_refs, user);
return FALSE;
}
/* Removal and clear invalidate the cache before deleting model rows. */
*iter = *cached;
return TRUE;
}
@@ -587,7 +561,6 @@ fe_userlist_rehash (session *sess, struct User *user)
GTK_TREE_MODEL(sess->res->user_model), user, &sel);
if (!iter)
return;
userlist_row_map_set (sess, GTK_TREE_MODEL (sess->res->user_model), user, iter);
if (prefs.hex_away_track && user->away)
{
@@ -698,8 +671,8 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
void
fe_userlist_clear (session *sess)
{
if (sess->res->user_row_refs)
g_hash_table_remove_all (sess->res->user_row_refs);
if (sess->res->user_row_iters)
g_hash_table_remove_all (sess->res->user_row_iters);
gtk_list_store_clear (sess->res->user_model);
}
+16 -7
View File
@@ -3130,6 +3130,7 @@ gtk_xtext_class_init (GtkXTextClass * class)
typedef struct chunk_s {
GSList *slp;
gboolean collect_metadata;
int off1, len1, emph;
offlen_t meta;
} chunk_t;
@@ -3142,12 +3143,19 @@ xtext_do_chunk(chunk_t *c)
if (c->len1 == 0)
return;
/* Copying, searching and saving only need the stripped text. */
if (!c->collect_metadata)
{
c->len1 = 0;
return;
}
meta = g_new (offlen_t, 1);
meta->off = c->off1;
meta->len = c->len1;
meta->emph = c->emph;
meta->width = 0;
c->slp = g_slist_append (c->slp, meta);
c->slp = g_slist_prepend (c->slp, meta);
c->len1 = 0;
}
@@ -3170,6 +3178,7 @@ gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf,
new_str = outbuf;
c.slp = NULL;
c.collect_metadata = slpp != NULL;
c.off1 = 0;
c.len1 = 0;
c.emph = 0;
@@ -3240,9 +3249,7 @@ bad_utf8: /* Normal ending sequence, and give up if bad utf8 */
*newlen = i;
if (slpp)
*slpp = c.slp;
else
g_slist_free_full (c.slp, g_free);
*slpp = g_slist_reverse (c.slp);
return new_str;
}
@@ -4341,7 +4348,7 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent)
if (win_width >= ent->indent + ent->str_width)
{
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (ent->str_len));
ent->sublines = g_slist_prepend (ent->sublines, GINT_TO_POINTER (ent->str_len));
ent->subline_count = 1;
return ent->subline_count;
}
@@ -4352,13 +4359,15 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent)
do
{
len = find_next_wrap (buf->xtext, ent, str, win_width, indent);
ent->sublines = g_slist_append (ent->sublines, GINT_TO_POINTER (str + len - ent->str));
ent->sublines = g_slist_prepend (ent->sublines, GINT_TO_POINTER (str + len - ent->str));
ent->subline_count++;
indent = buf->indent;
str += len;
}
while (str < ent->str + ent->str_len);
ent->subline_count = g_slist_length (ent->sublines);
/* Preserve display order without walking the growing list per wrap. */
ent->sublines = g_slist_reverse (ent->sublines);
return ent->subline_count;
}