mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-09-22 15:47:10 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe9a2ee202 | ||
|
|
5e27dc7e9f | ||
|
|
69d083e3c0 | ||
|
|
2b95f9724e | ||
|
|
4066a07892 | ||
|
|
443ab58f65 | ||
|
|
e0116acbe4 | ||
|
|
91e9bf0207 | ||
|
|
aff0335f7e | ||
|
|
4d8baaa19b | ||
|
|
65452d80f1 | ||
|
|
df4d432151 | ||
|
|
1390f2c783 | ||
|
|
be99d0e900 | ||
|
|
97fcf1d5fc | ||
|
|
c9b14d7c45 | ||
|
|
3d0dda0347 | ||
|
|
8d69b35d67 | ||
|
|
7ba142a9f2 |
@@ -0,0 +1,132 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a problem with ZoiteChat
|
||||
title: "[Bug]: "
|
||||
labels: bug
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
```
|
||||
+------------------------------------------------------------+
|
||||
| ZoiteChat Bug Report |
|
||||
| Please fill this out so the bug goblin has enough crumbs. |
|
||||
+------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
What went wrong?
|
||||
|
||||
```text
|
||||
Describe the bug clearly and briefly.
|
||||
```
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
```text
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
```
|
||||
|
||||
## Expected Result
|
||||
|
||||
```text
|
||||
What should have happened?
|
||||
```
|
||||
|
||||
## Actual Result
|
||||
|
||||
```text
|
||||
What happened instead?
|
||||
```
|
||||
|
||||
## ZoiteChat Version
|
||||
|
||||
```text
|
||||
ZoiteChat version:
|
||||
Build/source: GitHub release / Fedora package / source build / Windows installer / other
|
||||
Commit hash, if built from source:
|
||||
```
|
||||
|
||||
## System Information
|
||||
|
||||
```text
|
||||
OS:
|
||||
OS version:
|
||||
Desktop environment:
|
||||
Display server: Wayland / X11 / Windows / other
|
||||
CPU architecture: x86_64 / aarch64 / other
|
||||
```
|
||||
|
||||
## IRC / Network Details
|
||||
|
||||
```text
|
||||
IRC network/server:
|
||||
Port:
|
||||
TLS/SSL enabled: yes / no
|
||||
SASL enabled: yes / no
|
||||
Using bouncer/ZNC: yes / no
|
||||
Proxy/VPN involved: yes / no
|
||||
IRCv3 capabilities involved, if known:
|
||||
```
|
||||
|
||||
## Plugins, Scripts, and Config
|
||||
|
||||
```text
|
||||
Plugins enabled:
|
||||
Scripts loaded:
|
||||
Theme/custom CSS:
|
||||
Custom config changes:
|
||||
Does the bug still happen with a clean config? yes / no / untested
|
||||
```
|
||||
|
||||
## Logs / Terminal Output
|
||||
|
||||
Paste relevant output below.
|
||||
|
||||
```text
|
||||
```
|
||||
|
||||
## Raw IRC Lines, If Relevant
|
||||
|
||||
Remove passwords, tokens, IPs, private channels, private messages, certs, and account details before posting.
|
||||
|
||||
```text
|
||||
```
|
||||
|
||||
## Crash Details, If Relevant
|
||||
|
||||
```text
|
||||
Did ZoiteChat crash? yes / no
|
||||
Crash reporter output:
|
||||
Backtrace:
|
||||
Core dump available: yes / no
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
Attach screenshots or screen recordings if the issue is visual.
|
||||
|
||||
## Regression Check
|
||||
|
||||
```text
|
||||
Did this work in an older version? yes / no / unknown
|
||||
Last known working version:
|
||||
First broken version:
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] I searched existing issues first.
|
||||
* [ ] I tested with the latest ZoiteChat version available to me.
|
||||
* [ ] I included steps to reproduce the issue.
|
||||
* [ ] I removed private information from logs and raw IRC lines.
|
||||
* [ ] I included system and network details where relevant.
|
||||
|
||||
## Extra Notes
|
||||
|
||||
```text
|
||||
Anything else that might help.
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,6 +1,17 @@
|
||||
ZoiteChat ChangeLog
|
||||
=================
|
||||
|
||||
2.18.3 (2026-06-29)
|
||||
-------------------
|
||||
|
||||
- Added IRCv3 support for message tags, echo-message, typing notifications, and replies.
|
||||
- Restored the accept-invalid-cert TLS bypass behavior.
|
||||
- Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.
|
||||
- Snapshot Python hooks during plugin unload for safer plugin shutdown.
|
||||
- Fixed Windows GtkStatusIcon tray menu popup behavior.
|
||||
- Fixed self-echoed private messages routing to the target tab.
|
||||
- Fixed the lag ping timeout window.
|
||||
|
||||
2.18.2 (2026-06-20)
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -29,6 +29,19 @@
|
||||
<id>zoitechat.desktop</id>
|
||||
</provides>
|
||||
<releases>
|
||||
<release date="2026-06-29" version="2.18.3">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Added IRCv3 support for message tags, echo-message, typing notifications, and replies.</li>
|
||||
<li>Restored the accept-invalid-cert TLS bypass behavior.</li>
|
||||
<li>Replaced legacy IPv4 APIs and added support for OpenSSL 4 APIs.</li>
|
||||
<li>Snapshot Python hooks during plugin unload for safer plugin shutdown.</li>
|
||||
<li>Fixed Windows GtkStatusIcon tray menu popup behavior.</li>
|
||||
<li>Fixed self-echoed private messages routing to the target tab.</li>
|
||||
<li>Fixed the lag ping timeout window.</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release date="2026-06-20" version="2.18.2">
|
||||
<description>
|
||||
<p>Security and connections:</p>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
project('zoitechat', 'c',
|
||||
version: '2.18.2',
|
||||
version: '2.18.3',
|
||||
meson_version: '>= 0.55.0',
|
||||
default_options: [
|
||||
'c_std=c17',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: zoitechat
|
||||
Version: 2.18.2
|
||||
Version: 2.18.3
|
||||
Release: %autorelease
|
||||
Summary: HexChat-based IRC client
|
||||
License: GPL-2.0-or-later WITH cryptsetup-OpenSSL-exception
|
||||
|
||||
@@ -19,7 +19,7 @@ else:
|
||||
if not hasattr(sys, 'argv'):
|
||||
sys.argv = ['<zoitechat>']
|
||||
|
||||
VERSION = b'2.18.2'
|
||||
VERSION = b'2.18.3'
|
||||
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
||||
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
||||
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
||||
@@ -173,7 +173,7 @@ class Plugin:
|
||||
|
||||
def __del__(self):
|
||||
log('unloading', self.filename)
|
||||
for hook in self.hooks:
|
||||
for hook in list(self.hooks):
|
||||
if hook.is_unload is True:
|
||||
try:
|
||||
hook.callback(hook.userdata)
|
||||
|
||||
@@ -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,
|
||||
|
||||
+168
-1
@@ -46,6 +46,155 @@
|
||||
#include "servlist.h"
|
||||
#include "sts.h"
|
||||
#include "text.h"
|
||||
|
||||
void reply_state_clear (session *sess);
|
||||
|
||||
gboolean
|
||||
reply_msgid_valid (const char *msgid)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (!msgid || !*msgid || *msgid == ':')
|
||||
return FALSE;
|
||||
|
||||
for (p = msgid; *p; p++)
|
||||
{
|
||||
if (*p == ' ' || *p == '\r' || *p == '\n')
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
reply_item_free (reply_item *item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
g_free (item->msgid);
|
||||
g_free (item->nick);
|
||||
g_free (item->text);
|
||||
g_free (item);
|
||||
}
|
||||
|
||||
void
|
||||
reply_cache_free (session *sess)
|
||||
{
|
||||
if (!sess)
|
||||
return;
|
||||
g_slist_free_full (sess->reply_items, (GDestroyNotify) reply_item_free);
|
||||
sess->reply_items = NULL;
|
||||
reply_state_clear (sess);
|
||||
}
|
||||
|
||||
reply_item *
|
||||
reply_cache_find (session *sess, const char *msgid)
|
||||
{
|
||||
GSList *list;
|
||||
|
||||
if (!sess || !reply_msgid_valid (msgid))
|
||||
return NULL;
|
||||
|
||||
for (list = sess->reply_items; list; list = list->next)
|
||||
{
|
||||
reply_item *item = list->data;
|
||||
if (!strcmp (item->msgid, msgid))
|
||||
return item;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
reply_item *
|
||||
reply_cache_latest_from (session *sess, const char *nick)
|
||||
{
|
||||
GSList *list;
|
||||
|
||||
if (!sess || !nick || !*nick)
|
||||
return NULL;
|
||||
|
||||
for (list = sess->reply_items; list; list = list->next)
|
||||
{
|
||||
reply_item *item = list->data;
|
||||
if (!sess->server->p_cmp (item->nick, nick))
|
||||
return item;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
reply_state_clear (session *sess)
|
||||
{
|
||||
if (!sess)
|
||||
return;
|
||||
g_clear_pointer (&sess->reply_msgid, g_free);
|
||||
g_clear_pointer (&sess->reply_target, g_free);
|
||||
g_clear_pointer (&sess->reply_nick, g_free);
|
||||
g_clear_pointer (&sess->reply_text, g_free);
|
||||
}
|
||||
|
||||
void
|
||||
reply_state_set (session *sess, const char *msgid, const char *target, const char *nick, const char *text)
|
||||
{
|
||||
if (!sess || !reply_msgid_valid (msgid))
|
||||
return;
|
||||
reply_state_clear (sess);
|
||||
sess->reply_msgid = g_strdup (msgid);
|
||||
sess->reply_target = g_strdup (target && *target ? target : sess->channel);
|
||||
sess->reply_nick = g_strdup (nick && *nick ? nick : _("message"));
|
||||
sess->reply_text = g_strdup (text && *text ? text : _("Original message unavailable"));
|
||||
}
|
||||
|
||||
static void
|
||||
reply_cache_add (session *sess, const char *msgid, const char *nick, const char *text, time_t timestamp)
|
||||
{
|
||||
reply_item *item;
|
||||
|
||||
if (!sess || !reply_msgid_valid (msgid) || !nick || !text)
|
||||
return;
|
||||
|
||||
item = reply_cache_find (sess, msgid);
|
||||
if (item)
|
||||
{
|
||||
g_free (item->nick);
|
||||
g_free (item->text);
|
||||
item->nick = g_strdup (nick);
|
||||
item->text = g_strdup (text);
|
||||
item->timestamp = timestamp;
|
||||
return;
|
||||
}
|
||||
|
||||
item = g_new0 (reply_item, 1);
|
||||
item->msgid = g_strdup (msgid);
|
||||
item->nick = g_strdup (nick);
|
||||
item->text = g_strdup (text);
|
||||
item->timestamp = timestamp;
|
||||
sess->reply_items = g_slist_prepend (sess->reply_items, item);
|
||||
|
||||
if (g_slist_length (sess->reply_items) > 200)
|
||||
{
|
||||
GSList *last = g_slist_last (sess->reply_items);
|
||||
item = last->data;
|
||||
sess->reply_items = g_slist_delete_link (sess->reply_items, last);
|
||||
reply_item_free (item);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
reply_context_print (session *sess, const message_tags_data *tags_data)
|
||||
{
|
||||
reply_item *item;
|
||||
|
||||
if (!sess || !tags_data || !tags_data->reply)
|
||||
return;
|
||||
|
||||
item = reply_cache_find (sess, tags_data->reply);
|
||||
if (item)
|
||||
PrintTextTimeStampf (sess, tags_data->timestamp, "\00314│ ↪ %s: %.180s\017\n", item->nick, item->text);
|
||||
else
|
||||
PrintTextTimeStamp (sess, "\00314│ ↪ Original message unavailable\017\n", tags_data->timestamp);
|
||||
}
|
||||
#include "ctcp.h"
|
||||
#include "zoitechatc.h"
|
||||
#include "chanopt.h"
|
||||
@@ -450,6 +599,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,12 +670,17 @@ 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)
|
||||
sess->server->p_set_back (sess->server);
|
||||
reply_context_print (sess, tags_data);
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL,
|
||||
0, tags_data->timestamp);
|
||||
reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -532,6 +689,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
||||
if (is_hilight (from, text, sess, serv))
|
||||
hilight = TRUE;
|
||||
|
||||
reply_context_print (sess, tags_data);
|
||||
|
||||
if (sess->type == SESS_DIALOG)
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
|
||||
tags_data->timestamp);
|
||||
@@ -541,6 +700,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
||||
else
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext,
|
||||
0, tags_data->timestamp);
|
||||
|
||||
reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1483,7 +1644,7 @@ inbound_foundip (session *sess, char *ip, const message_tags_data *tags_data)
|
||||
{
|
||||
sess->server->dcc_ip = addr.s_addr;
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_FOUNDIP, sess->server->server_session,
|
||||
inet_ntoa (addr),
|
||||
net_ip (ntohl (addr.s_addr)),
|
||||
NULL, NULL, NULL, 0, tags_data->timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1742,6 +1903,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 +2038,8 @@ static const char * const supported_caps[] = {
|
||||
"account-tag",
|
||||
"extended-monitor",
|
||||
"standard-replies",
|
||||
"message-tags",
|
||||
"echo-message",
|
||||
|
||||
/* ZNC */
|
||||
"znc.in/server-time-iso",
|
||||
|
||||
@@ -78,6 +78,13 @@ void inbound_login_end (session *sess, char *text,
|
||||
void inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
|
||||
char *text, char fromme, int id,
|
||||
const message_tags_data *tags_data);
|
||||
gboolean reply_msgid_valid (const char *msgid);
|
||||
void reply_cache_free (session *sess);
|
||||
reply_item *reply_cache_find (session *sess, const char *msgid);
|
||||
reply_item *reply_cache_latest_from (session *sess, const char *nick);
|
||||
void reply_state_set (session *sess, const char *msgid, const char *target,
|
||||
const char *nick, const char *text);
|
||||
void reply_state_clear (session *sess);
|
||||
void clear_channel (session *sess);
|
||||
void set_topic (session *sess, char *topic, char *stripped_topic);
|
||||
void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
|
||||
|
||||
@@ -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)
|
||||
|
||||
+17
-1
@@ -90,10 +90,26 @@ net_set_socket_options (int sok)
|
||||
char *
|
||||
net_ip (uint32_t addr)
|
||||
{
|
||||
static char buf[INET_ADDRSTRLEN];
|
||||
struct in_addr ia;
|
||||
|
||||
ia.s_addr = htonl (addr);
|
||||
return inet_ntoa (ia);
|
||||
if (!inet_ntop (AF_INET, &ia, buf, sizeof (buf)))
|
||||
buf[0] = 0;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
int
|
||||
net_parse_ipv4 (const char *hostname, uint32_t *addr)
|
||||
{
|
||||
struct in_addr ia;
|
||||
|
||||
if (inet_pton (AF_INET, hostname, &ia) != 1)
|
||||
return FALSE;
|
||||
|
||||
*addr = ia.s_addr;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
@@ -39,6 +39,7 @@ int net_connect (netstore *ns, int sok4, int sok6, int *sok_return);
|
||||
char *net_resolve (netstore *ns, char *hostname, int port, char **real_host);
|
||||
void net_bind (netstore *tobindto, int sok4, int sok6);
|
||||
char *net_ip (uint32_t addr);
|
||||
int net_parse_ipv4 (const char *hostname, uint32_t *addr);
|
||||
int net_lookup_ipv4 (const char *hostname, uint32_t *addr);
|
||||
void net_sockets (int *sok4, int *sok6);
|
||||
|
||||
|
||||
+144
-9
@@ -468,7 +468,7 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop)
|
||||
type = prefs.hex_irc_ban_type;
|
||||
|
||||
buf[0] = 0;
|
||||
if (inet_addr (fullhost) != (guint32) -1) /* "fullhost" is really a IP number */
|
||||
if (net_parse_ipv4 (fullhost, &(guint32){0})) /* "fullhost" is really a IP number */
|
||||
{
|
||||
lastdot = strrchr (fullhost, '.');
|
||||
if (!lastdot)
|
||||
@@ -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,137 @@ 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 = sess->channel;
|
||||
char *text = word_eol[3];
|
||||
char *escaped;
|
||||
char *tags;
|
||||
|
||||
if (!reply_msgid_valid (msgid) || !*target || !*text)
|
||||
return FALSE;
|
||||
|
||||
if (*text == ':')
|
||||
text++;
|
||||
|
||||
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;
|
||||
no_tags.reply = msgid;
|
||||
|
||||
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 +3006,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;
|
||||
|
||||
@@ -4139,9 +4270,11 @@ const struct commands xc_cmds[] = {
|
||||
#endif
|
||||
{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to ZoiteChat, as if it was received from the IRC server")},
|
||||
{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
|
||||
{"REPLY", cmd_reply, 0, 0, 1, N_("REPLY <msgid> <message>, sends a reply-tagged message to the current channel or dialog")},
|
||||
{"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 +4807,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 +4818,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
|
||||
|
||||
+199
-16
@@ -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);
|
||||
@@ -1009,6 +1021,40 @@ process_numeric (session * sess, int n,
|
||||
|
||||
/* handle named messages that starts with a ':' */
|
||||
|
||||
|
||||
static void
|
||||
emit_standard_reply (session *sess, const char *type, char *command, char *code, char *text, const message_tags_data *tags_data)
|
||||
{
|
||||
int is_global = g_strcmp0 (command, "*") == 0;
|
||||
|
||||
if (!text)
|
||||
text = "";
|
||||
if (*text == ':')
|
||||
text++;
|
||||
|
||||
if (!strcmp (type, "FAIL"))
|
||||
{
|
||||
if (is_global)
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||
else
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||
}
|
||||
else if (!strcmp (type, "WARN"))
|
||||
{
|
||||
if (is_global)
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||
else
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||
}
|
||||
else if (!strcmp (type, "NOTE"))
|
||||
{
|
||||
if (is_global)
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
||||
else
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
||||
const message_tags_data *tags_data)
|
||||
@@ -1322,7 +1368,20 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
||||
{
|
||||
if (ignore_check (word[1], IG_PRIV))
|
||||
return;
|
||||
inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data);
|
||||
if (serv->have_echo_message && !serv->p_cmp (nick, serv->nick))
|
||||
{
|
||||
session *target_sess = find_dialog (serv, to);
|
||||
|
||||
if (!target_sess)
|
||||
target_sess = find_channel (serv, to);
|
||||
if (target_sess)
|
||||
inbound_chanmsg (serv, target_sess, target_sess->channel, nick, text, TRUE, tags_data->identified, tags_data);
|
||||
else if (serv->front_session)
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_MSGSEND, serv->front_session, to, text, NULL, NULL, 0, tags_data->timestamp);
|
||||
} else
|
||||
{
|
||||
inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1335,6 +1394,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 == ':')
|
||||
@@ -1431,6 +1513,11 @@ process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol
|
||||
inbound_sasl_authenticate (sess->server, word_eol[2]);
|
||||
return;
|
||||
}
|
||||
if (!strncmp (buf, "FAIL ", 5) || !strncmp (buf, "WARN ", 5) || !strncmp (buf, "NOTE ", 5))
|
||||
{
|
||||
emit_standard_reply (sess, word_eol[1], word_eol[2], word_eol[3], word_eol[4], tags_data);
|
||||
return;
|
||||
}
|
||||
|
||||
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, buf, sess->server->servername,
|
||||
rawname, NULL, 0, tags_data->timestamp);
|
||||
@@ -1533,39 +1620,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 +1845,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 +1877,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+14
-3
@@ -540,6 +540,9 @@ ssl_cb_verify (int ok, X509_STORE_CTX * ctx)
|
||||
g_snprintf (buf, sizeof (buf), "* Verify E: %s (%d)",
|
||||
X509_verify_cert_error_string (err), err);
|
||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, g_sess, buf, NULL, NULL, NULL, 0);
|
||||
|
||||
if (g_sess && g_sess->server->accept_invalid_cert)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ok;
|
||||
@@ -663,11 +666,15 @@ ssl_do_connect (server * serv)
|
||||
g_snprintf (buf, sizeof (buf), "* Verify E: Failed to validate hostname (%d)",
|
||||
hostname_err);
|
||||
EMIT_SIGNAL (XP_TE_SSLMESSAGE, serv->server_session, buf, NULL, NULL, NULL, 0);
|
||||
goto conn_fail;
|
||||
if (!serv->accept_invalid_cert)
|
||||
goto conn_fail;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (serv->accept_invalid_cert)
|
||||
break;
|
||||
|
||||
g_snprintf (buf, sizeof (buf), "%s.? (%d)",
|
||||
X509_verify_cert_error_string (verify_error),
|
||||
verify_error);
|
||||
@@ -925,7 +932,7 @@ server_read_child (GIOChannel *source, GIOCondition condition, server *serv)
|
||||
break;
|
||||
case '5': /* prefs ip discovered */
|
||||
waitline2 (source, tbuf, sizeof tbuf);
|
||||
prefs.local_ip = inet_addr (tbuf);
|
||||
net_parse_ipv4 (tbuf, &prefs.local_ip);
|
||||
break;
|
||||
case '7': /* prefs.hex_net_bind_host resolve failed */
|
||||
sprintf (outbuf,
|
||||
@@ -1099,7 +1106,7 @@ traverse_socks (int print_fd, int sok, char *serverAddr, int port)
|
||||
sc.version = 4;
|
||||
sc.type = 1;
|
||||
sc.port = htons (port);
|
||||
sc.address = inet_addr (serverAddr);
|
||||
net_parse_ipv4 (serverAddr, &sc.address);
|
||||
g_strlcpy (sc.username, prefs.hex_irc_user_name, sizeof (sc.username));
|
||||
|
||||
send (sok, (char *) &sc, 8 + strlen (sc.username) + 1, 0);
|
||||
@@ -1814,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);
|
||||
@@ -1848,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;
|
||||
@@ -1978,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);
|
||||
|
||||
+25
-12
@@ -154,7 +154,7 @@ int
|
||||
_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
|
||||
{
|
||||
X509 *peer_cert;
|
||||
X509_PUBKEY *key;
|
||||
const X509_PUBKEY *key;
|
||||
X509_ALGOR *algor = NULL;
|
||||
EVP_PKEY *peer_pkey;
|
||||
char notBefore[64];
|
||||
@@ -350,7 +350,7 @@ _SSL_close (SSL * ssl)
|
||||
{
|
||||
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
||||
SSL_free (ssl);
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#if !defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#ifdef HAVE_ERR_REMOVE_THREAD_STATE
|
||||
ERR_remove_thread_state (NULL);
|
||||
#else
|
||||
@@ -507,9 +507,12 @@ _SSL_check_subject_altname (X509 *cert, const char *host)
|
||||
static int
|
||||
_SSL_check_common_name (X509 *cert, const char *host)
|
||||
{
|
||||
X509_NAME *name;
|
||||
char *common_name = NULL;
|
||||
const X509_NAME *name;
|
||||
const X509_NAME_ENTRY *entry;
|
||||
const ASN1_STRING *common_name_asn1;
|
||||
const unsigned char *common_name;
|
||||
int common_name_len;
|
||||
int common_name_index;
|
||||
int rv = -1;
|
||||
GInetAddress *addr;
|
||||
|
||||
@@ -517,16 +520,27 @@ _SSL_check_common_name (X509 *cert, const char *host)
|
||||
if (name == NULL)
|
||||
return -1;
|
||||
|
||||
common_name_len = X509_NAME_get_text_by_NID (name, NID_commonName, NULL, 0);
|
||||
if (common_name_len < 0)
|
||||
common_name_index = X509_NAME_get_index_by_NID (name, NID_commonName, -1);
|
||||
if (common_name_index < 0)
|
||||
return -1;
|
||||
|
||||
common_name = g_malloc0 (common_name_len + 1);
|
||||
entry = X509_NAME_get_entry (name, common_name_index);
|
||||
if (entry == NULL)
|
||||
return -1;
|
||||
|
||||
X509_NAME_get_text_by_NID (name, NID_commonName, common_name, common_name_len + 1);
|
||||
common_name_asn1 = X509_NAME_ENTRY_get_data (entry);
|
||||
if (common_name_asn1 == NULL)
|
||||
return -1;
|
||||
|
||||
#ifdef HAVE_ASN1_STRING_GET0_DATA
|
||||
common_name = ASN1_STRING_get0_data (common_name_asn1);
|
||||
#else
|
||||
common_name = ASN1_STRING_data (common_name_asn1);
|
||||
#endif
|
||||
common_name_len = ASN1_STRING_length (common_name_asn1);
|
||||
|
||||
/* NUL bytes in CN? */
|
||||
if (common_name_len != (int)strlen(common_name))
|
||||
if (common_name_len != (int)strlen((const char *)common_name))
|
||||
{
|
||||
g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
|
||||
rv = -2;
|
||||
@@ -539,18 +553,17 @@ _SSL_check_common_name (X509 *cert, const char *host)
|
||||
* We don't want to attempt wildcard matching against IP
|
||||
* addresses, so perform a simple comparison here.
|
||||
*/
|
||||
if (g_strcmp0 (common_name, host) == 0)
|
||||
if (g_strcmp0 ((const char *)common_name, host) == 0)
|
||||
rv = 0;
|
||||
else
|
||||
rv = -1;
|
||||
|
||||
g_object_unref (addr);
|
||||
}
|
||||
else if (_SSL_match_hostname (common_name, host) == 0)
|
||||
else if (_SSL_match_hostname ((const char *)common_name, host) == 0)
|
||||
rv = 0;
|
||||
|
||||
out:
|
||||
g_free(common_name);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-1
@@ -380,6 +380,7 @@ lag_check (void)
|
||||
time_t now = time (0);
|
||||
time_t lag;
|
||||
time_t ping_age;
|
||||
unsigned long ping_timeout;
|
||||
|
||||
tim = make_ping_time ();
|
||||
|
||||
@@ -389,8 +390,11 @@ lag_check (void)
|
||||
if (serv->connected && serv->end_of_motd)
|
||||
{
|
||||
lag = now - serv->ping_recv;
|
||||
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0 && lag > prefs.hex_net_ping_timeout && lag > 0)
|
||||
ping_timeout = (unsigned long) prefs.hex_net_ping_timeout * 1000;
|
||||
if (serv->lag_sent && prefs.hex_net_ping_timeout != 0
|
||||
&& tim - serv->lag_sent > ping_timeout)
|
||||
{
|
||||
lag = (tim - serv->lag_sent) / 1000;
|
||||
sprintf (tbuf, "%" G_GINT64_FORMAT, (gint64) lag);
|
||||
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
|
||||
NULL, NULL, 0);
|
||||
@@ -793,7 +797,13 @@ 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);
|
||||
reply_cache_free (killsess);
|
||||
g_free (killsess->topic);
|
||||
g_free (killsess->current_modes);
|
||||
|
||||
|
||||
@@ -388,6 +388,14 @@ typedef enum {
|
||||
TAB_STATE_NEW_HILIGHT = (1 << 2),
|
||||
} tab_state_flags;
|
||||
|
||||
typedef struct reply_item
|
||||
{
|
||||
char *msgid;
|
||||
char *nick;
|
||||
char *text;
|
||||
time_t timestamp;
|
||||
} reply_item;
|
||||
|
||||
typedef struct session
|
||||
{
|
||||
/* Per-Channel Alerts */
|
||||
@@ -429,8 +437,17 @@ typedef struct session
|
||||
char *quitreason;
|
||||
char *topic;
|
||||
char *current_modes; /* free() me */
|
||||
GSList *reply_items;
|
||||
char *reply_msgid;
|
||||
char *reply_target;
|
||||
char *reply_nick;
|
||||
char *reply_text;
|
||||
|
||||
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 +511,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 +562,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 +625,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 */
|
||||
|
||||
@@ -163,6 +163,8 @@ typedef struct session_gui
|
||||
*op_xpm, /* icon to the left of nickname */
|
||||
*namelistinfo, /* label above userlist */
|
||||
*input_box,
|
||||
*reply_box,
|
||||
*reply_label,
|
||||
*flag_wid[NUM_FLAG_WIDS], /* channelmode buttons */
|
||||
*limit_entry, /* +l */
|
||||
*key_entry; /* +k */
|
||||
|
||||
+181
-2
@@ -173,6 +173,7 @@ enum
|
||||
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
||||
|
||||
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
||||
static void mg_reply_show_child (GtkWidget *widget, gpointer data);
|
||||
|
||||
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
||||
|
||||
@@ -731,6 +732,164 @@ 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_reply_show_child (GtkWidget *widget, gpointer data)
|
||||
{
|
||||
gtk_widget_show (widget);
|
||||
}
|
||||
|
||||
void
|
||||
mg_reply_update (session *sess)
|
||||
{
|
||||
char *nick;
|
||||
char *text;
|
||||
char *markup;
|
||||
|
||||
if (!sess || !sess->gui || !sess->gui->reply_box || !sess->gui->reply_label)
|
||||
return;
|
||||
|
||||
if (!sess->reply_msgid)
|
||||
{
|
||||
gtk_widget_hide (sess->gui->reply_box);
|
||||
return;
|
||||
}
|
||||
|
||||
nick = g_markup_escape_text (sess->reply_nick ? sess->reply_nick : _("message"), -1);
|
||||
text = g_markup_escape_text (sess->reply_text ? sess->reply_text : _("Original message unavailable"), -1);
|
||||
markup = g_strdup_printf ("<span foreground='#7d8790'>↪ Replying to <b>%s</b> · %.160s</span>", nick, text);
|
||||
gtk_label_set_markup (GTK_LABEL (sess->gui->reply_label), markup);
|
||||
gtk_container_foreach (GTK_CONTAINER (sess->gui->reply_box), mg_reply_show_child, NULL);
|
||||
gtk_widget_show (sess->gui->reply_box);
|
||||
g_free (markup);
|
||||
g_free (text);
|
||||
g_free (nick);
|
||||
}
|
||||
|
||||
static void
|
||||
mg_reply_cancel_cb (GtkWidget *wid, session *sess)
|
||||
{
|
||||
reply_state_clear (sess);
|
||||
mg_reply_update (sess);
|
||||
}
|
||||
|
||||
static void
|
||||
mg_send_reply_or_text (session *sess, char *cmd)
|
||||
{
|
||||
char *reply_cmd;
|
||||
|
||||
if (!sess->reply_msgid || cmd[0] == prefs.hex_input_command_char[0])
|
||||
{
|
||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sess->server->connected || !mg_client_tag_allowed (sess->server, "reply"))
|
||||
{
|
||||
PrintText (sess, _("Replies are not supported on this server. Sending normally.\n"));
|
||||
reply_state_clear (sess);
|
||||
mg_reply_update (sess);
|
||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
reply_cmd = g_strdup_printf ("%cREPLY %s %s", prefs.hex_input_command_char[0], sess->reply_msgid, cmd);
|
||||
handle_multiline (sess, reply_cmd, TRUE, FALSE);
|
||||
g_free (reply_cmd);
|
||||
reply_state_clear (sess);
|
||||
mg_reply_update (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)
|
||||
{
|
||||
@@ -772,7 +931,7 @@ mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
|
||||
}
|
||||
|
||||
if (sess)
|
||||
handle_multiline (sess, cmd, TRUE, FALSE);
|
||||
mg_send_reply_or_text (sess, cmd);
|
||||
|
||||
g_free (cmd);
|
||||
}
|
||||
@@ -4541,6 +4700,20 @@ mg_create_entry (session *sess, GtkWidget *box)
|
||||
};
|
||||
const char *emoji_fallback_icon_name;
|
||||
|
||||
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);
|
||||
gtk_box_pack_start (GTK_BOX (box), gui->reply_box, 0, 0, 0);
|
||||
gui->reply_label = gtk_label_new ("");
|
||||
gtk_label_set_ellipsize (GTK_LABEL (gui->reply_label), PANGO_ELLIPSIZE_END);
|
||||
gtk_box_pack_start (GTK_BOX (gui->reply_box), gui->reply_label, TRUE, TRUE, 8);
|
||||
but = gtk_button_new_with_label ("×");
|
||||
gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE);
|
||||
gtk_widget_set_can_focus (but, FALSE);
|
||||
gtk_box_pack_start (GTK_BOX (gui->reply_box), but, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (mg_reply_cancel_cb), sess);
|
||||
gtk_widget_hide (gui->reply_box);
|
||||
|
||||
hbox = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0);
|
||||
|
||||
@@ -4562,7 +4735,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 +5367,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)
|
||||
{
|
||||
|
||||
@@ -48,6 +48,7 @@ void mg_dnd_drop_file (session *sess, char *target, char *uri);
|
||||
void mg_change_layout (int type);
|
||||
void mg_update_meters (session_gui *);
|
||||
void mg_inputbox_cb (GtkWidget *igad, session_gui *gui);
|
||||
void mg_reply_update (session *sess);
|
||||
void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata);
|
||||
GtkWidget *mg_submenu (GtkWidget *menu, char *text);
|
||||
/* DND */
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "../common/zoitechatc.h"
|
||||
#include "../common/cfgfiles.h"
|
||||
#include "../common/outbound.h"
|
||||
#include "../common/inbound.h"
|
||||
#include "../common/ignore.h"
|
||||
#include "../common/fe.h"
|
||||
#include "../common/server.h"
|
||||
@@ -784,6 +785,25 @@ fe_userlist_update (session *sess, struct User *user)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
menu_reply_to_latest_cb (GtkWidget *wid, gpointer data)
|
||||
{
|
||||
reply_item *item;
|
||||
|
||||
item = reply_cache_latest_from (current_sess, str_copy);
|
||||
if (!item)
|
||||
{
|
||||
PrintText (current_sess, _("No recent message to reply to.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
reply_state_set (current_sess, item->msgid, current_sess->channel, item->nick, item->text);
|
||||
mg_reply_update (current_sess);
|
||||
if (current_sess->gui && current_sess->gui->input_box)
|
||||
gtk_widget_grab_focus (current_sess->gui->input_box);
|
||||
}
|
||||
|
||||
void
|
||||
menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||
{
|
||||
@@ -827,6 +847,12 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel)
|
||||
else
|
||||
menu_create (menu, popup_list, str_copy, FALSE);
|
||||
|
||||
if (num_sel <= 1)
|
||||
{
|
||||
menu_quick_item_with_callback (menu_reply_to_latest_cb, _("Reply"), menu, 0);
|
||||
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0);
|
||||
}
|
||||
|
||||
if (num_sel == 0) /* xtext click */
|
||||
menu_add_plugin_items (menu, "\x5$NICK", str_copy);
|
||||
else /* userlist treeview click */
|
||||
|
||||
@@ -48,6 +48,9 @@ typedef struct _GtkStatusIcon GtkStatusIcon;
|
||||
|
||||
#ifndef WIN32
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <windows.h>
|
||||
#include <gdk/gdkwin32.h>
|
||||
#endif
|
||||
|
||||
typedef enum /* current icon status */
|
||||
@@ -1092,6 +1095,119 @@ tray_menu_settings (GtkWidget * wid, gpointer none)
|
||||
setup_open ();
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
#define TRAY_WIN32_HIDE 1
|
||||
#define TRAY_WIN32_AWAY 2
|
||||
#define TRAY_WIN32_BACK 3
|
||||
#define TRAY_WIN32_PREFS 4
|
||||
#define TRAY_WIN32_QUIT 5
|
||||
|
||||
static WCHAR *
|
||||
tray_win32_menu_label (const char *label)
|
||||
{
|
||||
char *plain;
|
||||
char *src;
|
||||
char *dst;
|
||||
WCHAR *wide;
|
||||
|
||||
plain = g_strdup (label ? label : "");
|
||||
for (src = plain, dst = plain; *src; src++)
|
||||
{
|
||||
if (*src == '_')
|
||||
continue;
|
||||
*dst++ = *src;
|
||||
}
|
||||
*dst = 0;
|
||||
wide = g_utf8_to_utf16 (plain, -1, NULL, NULL, NULL);
|
||||
g_free (plain);
|
||||
|
||||
return wide;
|
||||
}
|
||||
|
||||
static void
|
||||
tray_win32_append_item (HMENU menu, UINT id, const char *label, gboolean enabled)
|
||||
{
|
||||
WCHAR *wide;
|
||||
|
||||
wide = tray_win32_menu_label (label);
|
||||
AppendMenuW (menu, MF_STRING | (enabled ? MF_ENABLED : MF_GRAYED), id, wide);
|
||||
g_free (wide);
|
||||
}
|
||||
|
||||
static HWND
|
||||
tray_win32_get_hwnd (void)
|
||||
{
|
||||
GtkWindow *win;
|
||||
GdkWindow *gdk_win;
|
||||
|
||||
win = GTK_WINDOW (zoitechat_get_info (ph, "gtkwin_ptr"));
|
||||
if (!win)
|
||||
return GetActiveWindow ();
|
||||
|
||||
gdk_win = gtk_widget_get_window (GTK_WIDGET (win));
|
||||
if (!gdk_win)
|
||||
return GetActiveWindow ();
|
||||
|
||||
return gdk_win32_window_get_handle (gdk_win);
|
||||
}
|
||||
|
||||
static void
|
||||
tray_win32_menu_cb (void)
|
||||
{
|
||||
HMENU menu;
|
||||
POINT point;
|
||||
UINT command;
|
||||
HWND hwnd;
|
||||
int away_status;
|
||||
|
||||
zoitechat_set_context (ph, zoitechat_find_context (ph, NULL, NULL));
|
||||
|
||||
menu = CreatePopupMenu ();
|
||||
if (!menu)
|
||||
return;
|
||||
|
||||
away_status = tray_find_away_status ();
|
||||
tray_win32_append_item (menu, TRAY_WIN32_HIDE,
|
||||
tray_get_window_status () == WS_HIDDEN ? _("_Restore Window") : _("_Hide Window"), TRUE);
|
||||
AppendMenuW (menu, MF_SEPARATOR, 0, NULL);
|
||||
tray_win32_append_item (menu, TRAY_WIN32_AWAY, _("_Away"), away_status != 1);
|
||||
tray_win32_append_item (menu, TRAY_WIN32_BACK, _("_Back"), away_status != 2);
|
||||
AppendMenuW (menu, MF_SEPARATOR, 0, NULL);
|
||||
tray_win32_append_item (menu, TRAY_WIN32_PREFS, _("_Preferences"), TRUE);
|
||||
AppendMenuW (menu, MF_SEPARATOR, 0, NULL);
|
||||
tray_win32_append_item (menu, TRAY_WIN32_QUIT, _("_Quit"), TRUE);
|
||||
|
||||
GetCursorPos (&point);
|
||||
hwnd = tray_win32_get_hwnd ();
|
||||
if (hwnd)
|
||||
SetForegroundWindow (hwnd);
|
||||
command = TrackPopupMenu (menu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
|
||||
point.x, point.y, 0, hwnd, NULL);
|
||||
DestroyMenu (menu);
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case TRAY_WIN32_HIDE:
|
||||
tray_toggle_visibility (FALSE);
|
||||
break;
|
||||
case TRAY_WIN32_AWAY:
|
||||
tray_foreach_server (NULL, "away");
|
||||
break;
|
||||
case TRAY_WIN32_BACK:
|
||||
tray_foreach_server (NULL, "back");
|
||||
break;
|
||||
case TRAY_WIN32_PREFS:
|
||||
tray_menu_settings (NULL, NULL);
|
||||
break;
|
||||
case TRAY_WIN32_QUIT:
|
||||
tray_menu_quit_cb (NULL, NULL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
tray_menu_populate (GtkWidget *menu)
|
||||
{
|
||||
@@ -1219,6 +1335,15 @@ tray_window_visibility_cb (GtkWidget *widget, gpointer userdata)
|
||||
static void
|
||||
tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
|
||||
{
|
||||
#ifdef WIN32
|
||||
(void)widget;
|
||||
(void)button;
|
||||
(void)time;
|
||||
(void)userdata;
|
||||
|
||||
tray_win32_menu_cb ();
|
||||
return;
|
||||
#else
|
||||
static GtkWidget *menu;
|
||||
|
||||
(void)button;
|
||||
@@ -1258,6 +1383,7 @@ tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata)
|
||||
if (event)
|
||||
gdk_event_free (event);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
+121
-5
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.18.2
|
||||
2.18.3
|
||||
|
||||
Reference in New Issue
Block a user