6 Commits

Author SHA1 Message Date
b0a5c6f5e5 Improve GTK3 Performance 2026-09-09 16:45:28 -06:00
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
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
6 changed files with 55 additions and 59 deletions

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;
}

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);

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 */

View File

@@ -5786,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);

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);
}

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;
}