1 Commits

Author SHA1 Message Date
763f4de80b IRCv3 +message-tags, +echo-message, +typing, +reply 2026-06-23 15:49:48 -06:00
14 changed files with 552 additions and 29 deletions

View File

@@ -78,6 +78,7 @@ int fe_input_add (int sok, int flags, void *func, void *data);
void fe_input_remove (int tag);
void fe_idle_add (void *func, void *data);
void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
void fe_set_typing (struct session *sess, const char *nick, const char *state);
typedef enum
{
FE_COLOR_NONE = 0,

View File

@@ -450,6 +450,9 @@ inbound_action (session *sess, char *chan, char *from, char *ip, char *text,
fromme = TRUE;
}
if (!fromme)
fe_set_typing (sess, from, "done");
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
if (!fromme && !privaction)
@@ -518,6 +521,9 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
fromme = TRUE;
}
if (!fromme)
fe_set_typing (sess, from, "done");
if (fromme)
{
if (prefs.hex_away_auto_unmark && serv->is_away && !tags_data->timestamp)
@@ -1742,6 +1748,10 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
serv->have_awaynotify = enable;
else if (!strcmp (extension, "account-tag"))
serv->have_account_tag = enable;
else if (!strcmp (extension, "message-tags"))
serv->have_message_tags = enable;
else if (!strcmp (extension, "echo-message"))
serv->have_echo_message = enable;
else if (!strcmp (extension, "sasl"))
{
serv->have_sasl = enable;
@@ -1873,6 +1883,8 @@ static const char * const supported_caps[] = {
"account-tag",
"extended-monitor",
"standard-replies",
"message-tags",
"echo-message",
/* ZNC */
"znc.in/server-time-iso",

View File

@@ -907,6 +907,10 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data)
{
if (g_strcmp0 (tokvalue, "ascii") == 0)
serv->p_cmp = (void *)g_ascii_strcasecmp;
} else if (g_strcmp0 (tokname, "CLIENTTAGDENY") == 0)
{
g_free (serv->clienttagdeny);
serv->clienttagdeny = tokadding ? g_strdup (tokvalue) : NULL;
} else if (g_strcmp0 (tokname, "CHARSET") == 0)
{
if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)

View File

@@ -2744,8 +2744,8 @@ cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[])
while ((split_text = split_up_text (sess, act + offset, cmd_length, split_text)))
{
sess->server->p_action (sess->server, sess->channel, split_text);
/* print it to screen */
inbound_action (sess, sess->channel, sess->server->nick, "",
if (!sess->server->have_echo_message)
inbound_action (sess, sess->channel, sess->server->nick, "",
split_text, TRUE, FALSE,
&no_tags);
@@ -2756,8 +2756,8 @@ cmd_me (struct session *sess, char *tbuf, char *word[], char *word_eol[])
}
sess->server->p_action (sess->server, sess->channel, act + offset);
/* print it to screen */
inbound_action (sess, sess->channel, sess->server->nick, "",
if (!sess->server->have_echo_message)
inbound_action (sess, sess->channel, sess->server->nick, "",
act + offset, TRUE, FALSE, &no_tags);
} else
{
@@ -2821,6 +2821,133 @@ cmd_mop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
return TRUE;
}
static gboolean
client_tag_allowed (server *serv, const char *tag)
{
char **deny;
int i;
if (!serv->have_message_tags)
return FALSE;
if (!serv->clienttagdeny || !*serv->clienttagdeny)
return TRUE;
deny = g_strsplit (serv->clienttagdeny, ",", 0);
for (i = 0; deny[i]; i++)
{
if (!strcmp (deny[i], "*") || !strcmp (deny[i], tag) || (deny[i][0] == '+' && !strcmp (deny[i] + 1, tag)))
{
g_strfreev (deny);
return FALSE;
}
}
g_strfreev (deny);
return TRUE;
}
static char *
client_tag_escape (const char *text)
{
GString *out;
const char *p;
out = g_string_sized_new (strlen (text));
for (p = text; *p; p++)
{
switch (*p)
{
case ';':
g_string_append (out, "\\:");
break;
case ' ':
g_string_append (out, "\\s");
break;
case '\\':
g_string_append (out, "\\\\");
break;
case '\r':
g_string_append (out, "\\r");
break;
case '\n':
g_string_append (out, "\\n");
break;
default:
g_string_append_c (out, *p);
break;
}
}
return g_string_free (out, FALSE);
}
static int
cmd_reply (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{
char *msgid = word[2];
char *target = word[3];
char *text = word_eol[4];
char *escaped;
char *tags;
if (!*msgid || !*target || !*text)
return FALSE;
if (!sess->server->connected || !client_tag_allowed (sess->server, "reply"))
{
notc_msg (sess);
return TRUE;
}
escaped = client_tag_escape (msgid);
tags = g_strdup_printf ("+reply=%s", escaped);
sess->server->p_message_tagged (sess->server, tags, target, text);
if (!sess->server->have_echo_message)
{
session *target_sess = find_dialog (sess->server, target);
message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT;
if (!target_sess)
target_sess = find_channel (sess->server, target);
if (target_sess)
inbound_chanmsg (target_sess->server, target_sess, target_sess->channel, target_sess->server->nick, text, TRUE, FALSE, &no_tags);
}
g_free (tags);
g_free (escaped);
return TRUE;
}
static int
cmd_typing (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{
char *state = word[2];
char *target = word[3];
char tags[32];
if (!*state)
state = "active";
if (!*target)
target = sess->channel;
if (!*target || (strcmp (state, "active") && strcmp (state, "paused") && strcmp (state, "done")))
return FALSE;
if (!sess->server->connected || !client_tag_allowed (sess->server, "typing"))
{
notc_msg (sess);
return TRUE;
}
g_snprintf (tags, sizeof (tags), "+typing=%s", state);
sess->server->p_tagmsg (sess->server, tags, target);
return TRUE;
}
static int
cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{
@@ -2875,7 +3002,7 @@ cmd_msg (struct session *sess, char *tbuf, char *word[], char *word_eol[])
newsess = find_dialog (sess->server, nick);
if (!newsess)
newsess = find_channel (sess->server, nick);
if (newsess)
if (newsess && !sess->server->have_echo_message)
{
message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT;
@@ -4138,10 +4265,12 @@ const struct commands xc_cmds[] = {
N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")},
#endif
{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to ZoiteChat, as if it was received from the IRC server")},
{"REPLY", cmd_reply, 0, 0, 1, N_("REPLY <msgid> <target> <message>, sends a reply-tagged message")},
{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
{"SAY", cmd_say, 0, 0, 1,
N_("SAY <text>, sends the text to the object in the current window")},
{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
{"TYPING", cmd_typing, 0, 0, 1, N_("TYPING [active|paused|done] [target], sends a typing notification")},
#ifdef USE_OPENSSL
{"SERVCHAN", cmd_servchan, 0, 0, 1,
N_("SERVCHAN [-noproxy] [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")},
@@ -4674,8 +4803,9 @@ handle_say (session *sess, char *text, int check_spch)
while ((split_text = split_up_text (sess, text + offset, cmd_length, split_text)))
{
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
split_text, TRUE, FALSE, &no_tags);
if (!sess->server->have_echo_message)
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
split_text, TRUE, FALSE, &no_tags);
sess->server->p_message (sess->server, sess->channel, split_text);
if (*split_text)
@@ -4684,7 +4814,8 @@ handle_say (session *sess, char *text, int check_spch)
g_free (split_text);
}
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
if (!sess->server->have_echo_message)
inbound_chanmsg (sess->server, sess, sess->channel, sess->server->nick,
text + offset, TRUE, FALSE, &no_tags);
sess->server->p_message (sess->server, sess->channel, text + offset);
} else

View File

@@ -362,6 +362,18 @@ irc_message (server *serv, char *channel, char *text)
tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
}
static void
irc_message_tagged (server *serv, char *tags, char *channel, char *text)
{
tcp_sendf (serv, "@%s PRIVMSG %s :%s\r\n", tags, channel, text);
}
static void
irc_tagmsg (server *serv, char *tags, char *target)
{
tcp_sendf (serv, "@%s TAGMSG %s\r\n", tags, target);
}
static void
irc_action (server *serv, char *channel, char *act)
{
@@ -425,7 +437,7 @@ static int
irc_raw (server *serv, char *raw)
{
int len;
char tbuf[4096];
char tbuf[8704];
if (*raw)
{
len = strlen (raw);
@@ -1335,6 +1347,29 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
tags_data);
return;
case WORDL('T','A','G','M'):
if (tags_data->typing)
{
session *typing_sess = NULL;
char *to = word[3];
if (is_channel (serv, to))
typing_sess = find_channel (serv, to);
else if (!serv->p_cmp (to, serv->nick))
typing_sess = find_dialog (serv, nick);
if (!typing_sess)
typing_sess = sess;
if (!serv->p_cmp (nick, serv->nick))
return;
if (!strcmp (tags_data->typing, "done"))
fe_set_typing (typing_sess, nick, "done");
else if (!strcmp (tags_data->typing, "paused"))
fe_set_typing (typing_sess, nick, "paused");
else if (!strcmp (tags_data->typing, "active"))
fe_set_typing (typing_sess, nick, "active");
}
return;
case WORDL('W','A','L','L'):
text = word_eol[3];
if (*text == ':')
@@ -1533,39 +1568,130 @@ handle_message_tag_time (const char *time, message_tags_data *tags_data)
*
* See http://ircv3.atheme.org/specification/message-tags-3.2
*/
static char *
message_tag_unescape (const char *value)
{
GString *out;
const char *p;
if (!*value)
return NULL;
out = g_string_sized_new (strlen (value));
for (p = value; *p; p++)
{
if (*p != '\\')
{
g_string_append_c (out, *p);
continue;
}
p++;
if (!*p)
break;
switch (*p)
{
case ':':
g_string_append_c (out, ';');
break;
case 's':
g_string_append_c (out, ' ');
break;
case '\\':
g_string_append_c (out, '\\');
break;
case 'r':
g_string_append_c (out, '\r');
break;
case 'n':
g_string_append_c (out, '\n');
break;
default:
g_string_append_c (out, *p);
break;
}
}
value = out->str;
if (!g_utf8_validate (value, -1, NULL))
{
g_string_free (out, TRUE);
return NULL;
}
return g_string_free (out, FALSE);
}
static void
handle_message_tags (server *serv, const char *tags_str,
message_tags_data *tags_data)
{
char **tags;
char *time = NULL;
int i;
/* FIXME We might want to avoid the allocation overhead here since
* this might be called for every message from the server.
*/
tags = g_strsplit (tags_str, ";", 0);
for (i=0; tags[i]; i++)
for (i = 0; tags[i]; i++)
{
char *key = tags[i];
char *value = strchr (tags[i], '=');
char *raw_value = strchr (tags[i], '=');
char *value = NULL;
if (!value)
if (!*key)
continue;
*value = '\0';
value++;
if (raw_value)
{
*raw_value = '\0';
raw_value++;
value = message_tag_unescape (raw_value);
}
if (serv->have_account_tag && !strcmp (key, "account"))
tags_data->account = g_strdup (value);
if (serv->have_idmsg && strcmp (key, "solanum.chat/identified"))
{
g_free (tags_data->account);
tags_data->account = value;
value = NULL;
}
else if (serv->have_idmsg && !strcmp (key, "solanum.chat/identified"))
{
tags_data->identified = TRUE;
}
else if (serv->have_server_time && !strcmp (key, "time"))
{
g_free (time);
time = value;
value = NULL;
}
else if (!strcmp (key, "msgid"))
{
g_free (tags_data->msgid);
tags_data->msgid = value;
value = NULL;
}
else if (!strcmp (key, "+reply"))
{
g_free (tags_data->reply);
tags_data->reply = value;
value = NULL;
}
else if (!strcmp (key, "+typing"))
{
g_free (tags_data->typing);
tags_data->typing = value;
value = NULL;
}
if (serv->have_server_time && !strcmp (key, "time"))
handle_message_tag_time (value, tags_data);
g_free (value);
}
if (time)
handle_message_tag_time (time, tags_data);
g_free (time);
g_strfreev (tags);
}
@@ -1667,6 +1793,9 @@ void
message_tags_data_free (message_tags_data *tags_data)
{
g_clear_pointer (&tags_data->account, g_free);
g_clear_pointer (&tags_data->msgid, g_free);
g_clear_pointer (&tags_data->reply, g_free);
g_clear_pointer (&tags_data->typing, g_free);
}
void
@@ -1696,6 +1825,8 @@ proto_fill_her_up (server *serv)
serv->p_set_back = irc_set_back;
serv->p_set_away = irc_set_away;
serv->p_message = irc_message;
serv->p_message_tagged = irc_message_tagged;
serv->p_tagmsg = irc_tagmsg;
serv->p_action = irc_action;
serv->p_notice = irc_notice;
serv->p_topic = irc_topic;

View File

@@ -28,6 +28,9 @@
NULL, /* account name */ \
FALSE, /* identified to nick */ \
(time_t)0, /* timestamp */ \
NULL, \
NULL, \
NULL, \
}
#define STRIP_COLON(word, word_eol, idx) (word)[(idx)][0] == ':' ? (word_eol)[(idx)]+1 : (word)[(idx)]
@@ -41,6 +44,9 @@ typedef struct
char *account;
gboolean identified;
time_t timestamp;
char *msgid;
char *reply;
char *typing;
} message_tags_data;
void message_tags_data_free (message_tags_data *tags_data);

View File

@@ -1821,6 +1821,7 @@ void
server_set_defaults (server *serv)
{
g_free (serv->chantypes);
g_clear_pointer (&serv->clienttagdeny, g_free);
g_free (serv->chanmodes);
g_free (serv->nick_prefixes);
g_free (serv->nick_modes);
@@ -1855,6 +1856,8 @@ server_set_defaults (server *serv)
serv->have_extjoin = FALSE;
serv->have_account_tag = FALSE;
serv->have_server_time = FALSE;
serv->have_message_tags = FALSE;
serv->have_echo_message = FALSE;
serv->have_sasl = FALSE;
serv->have_except = FALSE;
serv->have_invite = FALSE;
@@ -1985,6 +1988,7 @@ server_free (server *serv)
g_free (serv->nick_prefixes);
g_free (serv->chanmodes);
g_free (serv->chantypes);
g_free (serv->clienttagdeny);
g_free (serv->bad_nick_prefixes);
g_free (serv->last_away_reason);
g_free (serv->encoding);

View File

@@ -31,6 +31,7 @@ struct User
char *servername;
char *account;
time_t lasttalk;
time_t typing_time;
unsigned int access; /* axs bit field */
char prefix[2]; /* @ + % */
unsigned int op:1;
@@ -39,6 +40,7 @@ struct User
unsigned int me:1;
unsigned int away:1;
unsigned int selected:1;
unsigned int typing:2;
};
#define USERACCESS_SIZE 12

View File

@@ -793,6 +793,11 @@ session_free (session *killsess)
send_quit_or_part (killsess);
if (killsess->typing_timeout_tag)
fe_timeout_remove (killsess->typing_timeout_tag);
if (killsess->typing_animation_tag)
fe_timeout_remove (killsess->typing_animation_tag);
history_free (&killsess->history);
g_free (killsess->topic);
g_free (killsess->current_modes);

View File

@@ -431,6 +431,10 @@ typedef struct session
char *current_modes; /* free() me */
int mode_timeout_tag;
int typing_timeout_tag;
int typing_status;
int typing_animation_tag;
int typing_animation_frame;
struct session *lastlog_sess;
struct nbexec *running_exec;
@@ -494,6 +498,8 @@ typedef struct server
void (*p_set_back)(struct server *);
void (*p_set_away)(struct server *, char *reason);
void (*p_message)(struct server *, char *channel, char *text);
void (*p_message_tagged)(struct server *, char *tags, char *channel, char *text);
void (*p_tagmsg)(struct server *, char *tags, char *target);
void (*p_action)(struct server *, char *channel, char *act);
void (*p_notice)(struct server *, char *channel, char *text);
void (*p_topic)(struct server *, char *channel, char *topic);
@@ -543,6 +549,7 @@ typedef struct server
int loginmethod; /* see login_types[] */
char *chantypes; /* for 005 numeric - free me */
char *clienttagdeny;
char *chanmodes; /* for 005 numeric - free me */
char *nick_prefixes; /* e.g. "*@%+" */
char *nick_modes; /* e.g. "aohv" */
@@ -605,6 +612,8 @@ typedef struct server
unsigned int have_extjoin:1; /* cap extended-join */
unsigned int have_account_tag:1; /* cap account-tag */
unsigned int have_server_time:1; /* cap server-time */
unsigned int have_message_tags:1;
unsigned int have_echo_message:1;
unsigned int have_sasl:1; /* SASL capability */
unsigned int have_except:1; /* ban exemptions +e */
unsigned int have_invite:1; /* invite exemptions +I */

View File

@@ -731,6 +731,96 @@ mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
return FALSE;
}
static gboolean
mg_client_tag_allowed (server *serv, const char *tag)
{
char **deny;
int i;
if (!serv->have_message_tags)
return FALSE;
if (!serv->clienttagdeny || !*serv->clienttagdeny)
return TRUE;
deny = g_strsplit (serv->clienttagdeny, ",", 0);
for (i = 0; deny[i]; i++)
{
if (!strcmp (deny[i], "*") || !strcmp (deny[i], tag) || (deny[i][0] == '+' && !strcmp (deny[i] + 1, tag)))
{
g_strfreev (deny);
return FALSE;
}
}
g_strfreev (deny);
return TRUE;
}
static void
mg_send_typing (session *sess, const char *state)
{
char tags[32];
if (!sess || !sess->server->connected || !mg_client_tag_allowed (sess->server, "typing") || !sess->channel[0])
return;
if (sess->type != SESS_CHANNEL && sess->type != SESS_DIALOG)
return;
g_snprintf (tags, sizeof (tags), "+typing=%s", state);
sess->server->p_tagmsg (sess->server, tags, sess->channel);
}
static int
mg_typing_pause_cb (session *sess)
{
sess->typing_timeout_tag = 0;
if (sess->typing_status == 1)
{
mg_send_typing (sess, "paused");
sess->typing_status = 2;
}
return 0;
}
static void
mg_typing_update (session *sess, const char *text)
{
if (!sess)
return;
if (sess->typing_timeout_tag)
{
fe_timeout_remove (sess->typing_timeout_tag);
sess->typing_timeout_tag = 0;
}
if (!text || !*text || text[0] == prefs.hex_input_command_char[0])
{
if (sess->typing_status)
mg_send_typing (sess, "done");
sess->typing_status = 0;
return;
}
if (sess->typing_status != 1)
{
mg_send_typing (sess, "active");
sess->typing_status = 1;
}
sess->typing_timeout_tag = fe_timeout_add_seconds (6, mg_typing_pause_cb, sess);
}
static void
mg_inputbox_changed (GtkEditable *editable, session_gui *gui)
{
key_check_replace_on_change (editable, NULL);
if (current_sess && current_sess->gui == gui)
mg_typing_update (current_sess, gtk_entry_get_text (GTK_ENTRY (editable)));
}
void
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
{
@@ -4562,7 +4652,7 @@ mg_create_entry (session *sess, GtkWidget *box)
g_signal_connect (G_OBJECT (entry), "activate",
G_CALLBACK (mg_inputbox_cb), gui);
g_signal_connect (G_OBJECT (entry), "changed",
G_CALLBACK (key_check_replace_on_change), NULL);
G_CALLBACK (mg_inputbox_changed), gui);
gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
gtk_widget_set_name (entry, "zoitechat-inputbox");
@@ -5194,6 +5284,12 @@ fe_set_channel (session *sess)
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
}
void
fe_set_typing (session *sess, const char *nick, const char *state)
{
fe_userlist_set_typing (sess, nick, state);
}
void
mg_changui_new (session *sess, restore_gui *res, int tab, int focus)
{

View File

@@ -19,6 +19,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "fe-gtk.h"
@@ -53,6 +54,36 @@ enum
static void userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken token, gboolean has_token);
static const char *
userlist_typing_suffix (session *sess, struct User *user)
{
static const char *active[] = { " [✎]", " [✎.]", " [✎..]" };
if (!user || !user->typing)
return "";
if (user->typing == 2)
return " [✎…]";
return active[sess->typing_animation_frame % G_N_ELEMENTS (active)];
}
static char *
userlist_nick_markup (session *sess, struct User *user)
{
char *nick = g_markup_escape_text (user->nick, -1);
const char *typing = userlist_typing_suffix (sess, user);
if (*typing)
{
char *marked = g_strdup_printf ("%s%s", nick, typing);
g_free (nick);
return marked;
}
return nick;
}
static const char *
userlist_prefix_color (char prefix)
{
@@ -463,6 +494,87 @@ fe_userlist_remove (session *sess, struct User *user)
return sel;
}
static gboolean
userlist_typing_tick (session *sess)
{
GtkTreeModel *model;
GtkTreeIter iter;
gboolean valid;
gboolean keep = FALSE;
time_t now = time (NULL);
if (!sess || !sess->res || !sess->res->user_model)
return FALSE;
sess->typing_animation_frame++;
model = GTK_TREE_MODEL (sess->res->user_model);
valid = gtk_tree_model_get_iter_first (model, &iter);
while (valid)
{
struct User *user = NULL;
gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
if (user && user->typing)
{
char *nick;
if ((user->typing == 1 && now - user->typing_time >= 6) || (user->typing == 2 && now - user->typing_time >= 30))
user->typing = 0;
nick = userlist_nick_markup (sess, user);
gtk_list_store_set (sess->res->user_model, &iter, COL_NICK, nick, -1);
g_free (nick);
if (user->typing)
keep = TRUE;
}
valid = gtk_tree_model_iter_next (model, &iter);
}
if (!keep)
{
sess->typing_animation_tag = 0;
return FALSE;
}
return TRUE;
}
void
fe_userlist_set_typing (session *sess, const char *nick, const char *state)
{
struct User *user;
GtkTreeIter *iter;
int sel;
if (!sess || !nick || !sess->res || !sess->res->user_model)
return;
user = userlist_find (sess, nick);
if (!user)
return;
if (!strcmp (state, "active"))
user->typing = 1;
else if (!strcmp (state, "paused"))
user->typing = 2;
else
user->typing = 0;
user->typing_time = time (NULL);
iter = find_row (sess, GTK_TREE_VIEW (sess->gui->user_tree), GTK_TREE_MODEL (sess->res->user_model), user, &sel);
if (iter)
{
char *nick = userlist_nick_markup (sess, user);
gtk_list_store_set (sess->res->user_model, iter, COL_NICK, nick, -1);
g_free (nick);
}
if (user->typing && !sess->typing_animation_tag)
sess->typing_animation_tag = fe_timeout_add (350, userlist_typing_tick, sess);
}
void
fe_userlist_rehash (session *sess, struct User *user)
{
@@ -493,9 +605,14 @@ fe_userlist_rehash (session *sess, struct User *user)
}
}
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
{
char *nick = userlist_nick_markup (sess, user);
gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
COL_NICK, nick,
COL_HOST, user->hostname,
-1);
g_free (nick);
}
userlist_store_color (GTK_LIST_STORE (sess->res->user_model), iter, nick_token, have_nick_token);
}
@@ -506,7 +623,6 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
GdkPixbuf *pix = get_user_icon (sess->server, newuser);
GtkTreeIter iter;
char *nick;
char *nick_escaped;
char *prefix = NULL;
char *prefix_escaped;
char prefix_text[2];
@@ -530,8 +646,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
}
}
nick_escaped = g_markup_escape_text (newuser->nick, -1);
nick = nick_escaped;
nick = userlist_nick_markup (sess, newuser);
if (!prefs.hex_gui_ulist_icons)
{
if (newuser->prefix[0] != '\0' && newuser->prefix[0] != ' ')
@@ -559,7 +674,7 @@ fe_userlist_insert (session *sess, struct User *newuser, gboolean sel)
userlist_store_color (GTK_LIST_STORE (model), &iter, nick_token, have_nick_token);
g_free (prefix);
g_free (nick_escaped);
g_free (nick);
userlist_row_map_set (sess, model, newuser, &iter);
@@ -757,6 +872,7 @@ userlist_add_columns (GtkTreeView * treeview)
gtk_tree_view_column_pack_start (column, renderer, TRUE);
gtk_tree_view_column_add_attribute (column, renderer, "markup", COL_NICK);
gtk_tree_view_column_add_attribute (column, renderer, THEME_GTK_FOREGROUND_PROPERTY, COL_GDKCOLOR);
column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 1);
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_expand (column, TRUE);

View File

@@ -26,6 +26,7 @@ GtkWidget *userlist_create (GtkWidget *box);
GtkListStore *userlist_create_model (session *sess);
void userlist_show (session *sess);
void userlist_select (session *sess, char *name);
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
char **userlist_selection_list (GtkWidget *widget, int *num_ret);
GdkPixbuf *get_user_icon (server *serv, struct User *user);

View File

@@ -647,6 +647,11 @@ void
fe_set_topic (struct session *sess, char *topic, char *stripped_topic)
{
}
void
fe_set_typing (struct session *sess, const char *nick, const char *state)
{
}
void
fe_cleanup (void)
{