Compare commits

..
Author SHA1 Message Date
deepend 1267655bbe fix tls unsupported issue on windows 2026-09-10 18:23:26 -06:00
deepend 5734b14e78 test first start experience 2026-09-10 07:06:25 -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
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
10 changed files with 2870 additions and 243 deletions
+2
View File
@@ -429,6 +429,8 @@ const struct prefs vars[] =
{"gui_lang", P_OFFINT (hex_gui_lang), TYPE_INT},
{"gui_mode_buttons", P_OFFINT (hex_gui_mode_buttons), TYPE_BOOL},
{"gui_mode_buttons_inline", P_OFFINT (hex_gui_mode_buttons_inline), TYPE_BOOL},
{"gui_onboarding_disable", P_OFFINT (hex_gui_onboarding_disable), TYPE_BOOL},
{"gui_onboarding_pending", P_OFFINT (hex_gui_onboarding_pending), TYPE_BOOL},
{"gui_pane_divider_position", P_OFFINT (hex_gui_pane_divider_position), TYPE_INT},
{"gui_pane_left_size", P_OFFINT (hex_gui_pane_left_size), TYPE_INT},
{"gui_pane_right_size", P_OFFINT (hex_gui_pane_right_size), TYPE_INT},
+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);
+16 -1
View File
@@ -191,6 +191,8 @@ struct defaultserver
gboolean ssl;
};
static gboolean servlist_needs_onboarding_flag;
static const struct defaultserver def[] =
{
{"2600net", 0},
@@ -1181,7 +1183,10 @@ servlist_load (void)
fp = zoitechat_fopen_file ("servlist.conf", "r", 0);
if (!fp)
{
servlist_needs_onboarding_flag = TRUE;
return FALSE;
}
while (fgets (buf, sizeof (buf) - 2, fp))
{
@@ -1245,6 +1250,7 @@ servlist_load (void)
net = servlist_net_add (buf + 2, NULL, FALSE);
}
fclose (fp);
servlist_needs_onboarding_flag = (network_list == NULL);
return TRUE;
}
@@ -1253,8 +1259,16 @@ void
servlist_init (void)
{
if (!network_list)
if (!servlist_load ())
{
if (!servlist_load () || !network_list)
servlist_load_defaults ();
}
}
gboolean
servlist_needs_onboarding (void)
{
return servlist_needs_onboarding_flag;
}
/* check if a charset is known by Iconv */
@@ -1395,6 +1409,7 @@ servlist_save (void)
}
fclose (fp);
servlist_needs_onboarding_flag = FALSE;
return TRUE;
}
+1
View File
@@ -93,6 +93,7 @@ extern GSList *network_list;
#define IRC_DEFAULT_CHARSET "UTF-8 (Unicode)"
void servlist_init (void);
gboolean servlist_needs_onboarding (void);
int servlist_save (void);
int servlist_cycle (server *serv);
void servlist_connect (session *sess, ircnet *net, gboolean join);
+2
View File
@@ -134,6 +134,8 @@ struct zoitechatprefs
unsigned int hex_gui_join_dialog;
unsigned int hex_gui_mode_buttons;
unsigned int hex_gui_mode_buttons_inline;
unsigned int hex_gui_onboarding_disable;
unsigned int hex_gui_onboarding_pending;
unsigned int hex_gui_quit_dialog;
/* unsigned int hex_gui_single; */
unsigned int hex_gui_slist_fav;
+1 -222
View File
@@ -925,217 +925,10 @@ mg_send_reply_or_text (session *sess, char *cmd)
mg_reply_update (sess);
}
typedef struct
{
gboolean bold;
gboolean italic;
gboolean underline;
gboolean strikethrough;
gboolean reverse;
gint foreground;
gint background;
gboolean pending_foreground;
gboolean pending_background;
} MgInputFormatState;
static void
mg_input_format_state_at_cursor (GtkEntry *entry, MgInputFormatState *state)
{
const char *text;
const char *cursor;
gsize limit;
gsize i;
gint cursor_pos;
memset (state, 0, sizeof (*state));
state->foreground = -1;
state->background = -1;
text = gtk_entry_get_text (entry);
cursor_pos = gtk_editable_get_position (GTK_EDITABLE (entry));
if (!text || cursor_pos < 0)
return;
cursor = g_utf8_offset_to_pointer (text, cursor_pos);
limit = (gsize) (cursor - text);
for (i = 0; i < limit;)
{
guchar ch = (guchar) text[i++];
switch (ch)
{
case ATTR_BOLD:
state->bold = !state->bold;
break;
case ATTR_ITALICS:
state->italic = !state->italic;
break;
case ATTR_UNDERLINE:
state->underline = !state->underline;
break;
case ATTR_STRIKETHROUGH:
state->strikethrough = !state->strikethrough;
break;
case ATTR_REVERSE:
state->reverse = !state->reverse;
break;
case ATTR_RESET:
case '\n':
state->bold = FALSE;
state->italic = FALSE;
state->underline = FALSE;
state->strikethrough = FALSE;
state->reverse = FALSE;
state->foreground = -1;
state->background = -1;
state->pending_foreground = FALSE;
state->pending_background = FALSE;
break;
case ATTR_COLOR:
{
gint value = 0;
gint digits = 0;
state->pending_foreground = FALSE;
state->pending_background = FALSE;
if (i >= limit)
{
state->pending_foreground = TRUE;
break;
}
if (!g_ascii_isdigit (text[i]))
{
state->foreground = -1;
state->background = -1;
break;
}
while (i < limit && digits < 2 && g_ascii_isdigit (text[i]))
{
value = (value * 10) + (text[i] - '0');
i++;
digits++;
}
state->foreground = value;
state->background = -1;
if (i < limit && text[i] == ',')
{
i++;
if (i >= limit)
{
state->pending_background = TRUE;
break;
}
value = 0;
digits = 0;
while (i < limit && digits < 2 && g_ascii_isdigit (text[i]))
{
value = (value * 10) + (text[i] - '0');
i++;
digits++;
}
if (digits > 0)
state->background = value;
}
}
break;
default:
break;
}
}
}
static void
mg_input_format_append (GString *summary, const char *text)
{
if (summary->len > 0)
g_string_append (summary, " · ");
g_string_append (summary, text);
}
static void
mg_input_format_update (GtkEntry *entry)
{
GtkWidget *format_box;
GtkWidget *format_label;
MgInputFormatState state;
GString *summary;
char color[48];
char *markup;
format_box = g_object_get_data (G_OBJECT (entry), "zoitechat-format-box");
format_label = g_object_get_data (G_OBJECT (entry), "zoitechat-format-label");
if (!format_box || !format_label)
return;
if (!prefs.hex_gui_input_attr)
{
gtk_widget_hide (format_box);
return;
}
mg_input_format_state_at_cursor (entry, &state);
summary = g_string_new (NULL);
if (state.bold)
mg_input_format_append (summary, "<b>Bold</b>");
if (state.italic)
mg_input_format_append (summary, "<i>Italic</i>");
if (state.underline)
mg_input_format_append (summary, "<u>Underline</u>");
if (state.strikethrough)
mg_input_format_append (summary, "<s>Strikethrough</s>");
if (state.reverse)
mg_input_format_append (summary, "Reverse");
if (state.foreground >= 0)
{
g_snprintf (color, sizeof (color), "Text color <b>%02d</b>", state.foreground);
mg_input_format_append (summary, color);
}
if (state.background >= 0)
{
g_snprintf (color, sizeof (color), "Background <b>%02d</b>", state.background);
mg_input_format_append (summary, color);
}
if (state.pending_foreground)
mg_input_format_append (summary, "Color: type foreground[,background]");
else if (state.pending_background)
mg_input_format_append (summary, "Background: type color");
if (summary->len == 0)
{
gtk_widget_hide (format_box);
g_string_free (summary, TRUE);
return;
}
markup = g_strdup_printf ("<span foreground='#7d8790'>Formatting · %s</span>", summary->str);
gtk_label_set_markup (GTK_LABEL (format_label), markup);
gtk_widget_show (format_label);
gtk_widget_show (format_box);
g_free (markup);
g_string_free (summary, TRUE);
}
static void
mg_inputbox_cursor_changed (GObject *object, GParamSpec *pspec, gpointer userdata)
{
(void) pspec;
(void) userdata;
mg_input_format_update (GTK_ENTRY (object));
}
static void
mg_inputbox_changed (GtkEditable *editable, session_gui *gui)
{
key_check_replace_on_change (editable, NULL);
mg_input_format_update (GTK_ENTRY (editable));
if (current_sess && current_sess->gui == gui)
mg_typing_update (current_sess, gtk_entry_get_text (GTK_ENTRY (editable)));
}
@@ -5006,7 +4799,7 @@ mg_create_search(session *sess, GtkWidget *box)
static void
mg_create_entry (session *sess, GtkWidget *box)
{
GtkWidget *hbox, *but, *entry, *format_box, *format_label;
GtkWidget *hbox, *but, *entry;
session_gui *gui = sess->gui;
const char *emoji_fallback_icon_names[] = {
"face-smile-symbolic",
@@ -5018,16 +4811,6 @@ mg_create_entry (session *sess, GtkWidget *box)
};
const char *emoji_fallback_icon_name;
format_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
gtk_widget_set_name (format_box, "zoitechat-formatbar");
gtk_widget_set_no_show_all (format_box, TRUE);
gtk_box_pack_start (GTK_BOX (box), format_box, 0, 0, 0);
format_label = gtk_label_new ("");
gtk_label_set_ellipsize (GTK_LABEL (format_label), PANGO_ELLIPSIZE_END);
gtk_box_pack_start (GTK_BOX (format_box), format_label, TRUE, TRUE, 8);
gtk_widget_show (format_label);
gtk_widget_hide (format_box);
gui->reply_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
gtk_widget_set_name (gui->reply_box, "zoitechat-replybar");
gtk_widget_set_no_show_all (gui->reply_box, TRUE);
@@ -5058,16 +4841,12 @@ mg_create_entry (session *sess, GtkWidget *box)
gui->input_box = entry = sexy_spell_entry_new ();
sexy_spell_entry_set_checked ((SexySpellEntry *)entry, prefs.hex_gui_input_spell);
sexy_spell_entry_set_parse_attributes ((SexySpellEntry *)entry, prefs.hex_gui_input_attr);
g_object_set_data (G_OBJECT (entry), "zoitechat-format-box", format_box);
g_object_set_data (G_OBJECT (entry), "zoitechat-format-label", format_label);
gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 0);
g_signal_connect (G_OBJECT (entry), "activate",
G_CALLBACK (mg_inputbox_cb), gui);
g_signal_connect (G_OBJECT (entry), "changed",
G_CALLBACK (mg_inputbox_changed), gui);
g_signal_connect (G_OBJECT (entry), "notify::cursor-position",
G_CALLBACK (mg_inputbox_cursor_changed), gui);
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
gtk_widget_set_name (entry, "zoitechat-inputbox");
+1
View File
@@ -91,6 +91,7 @@ zoitechat_gtk_ldflags = []
if host_machine.system() == 'windows'
zoitechat_gtk_sources += 'notifications/notification-windows.c'
zoitechat_gtk_deps += cc.find_library('dwmapi', required: true)
zoitechat_gtk_deps += cc.find_library('winhttp', required: true)
zoitechat_theme_deps += cc.find_library('dwmapi', required: true)
else
+2828 -18
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -645,6 +645,9 @@ static const char *const proxyuse[] =
static const setting network_settings[] =
{
{ST_HEADER, N_("Startup"), 0, 0, 0, 0},
{ST_TOGGLE, N_("Disable first-run assistant"), P_OFFINTNL(hex_gui_onboarding_disable), 0, 0, 0},
{ST_HEADER, N_("Your Address"), 0, 0, 0, 0},
{ST_ENTRY, N_("Bind to:"), P_OFFSETNL(hex_net_bind_host), 0, 0, sizeof prefs.hex_net_bind_host},
{ST_LABEL, N_("Only useful for computers with multiple addresses.")},