hexchat/src/common/text.c

2356 lines
46 KiB
C
Raw Normal View History

2011-02-24 06:14:30 +03:00
/* X-Chat
* Copyright (C) 1998 Peter Zelezny.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
2012-12-23 23:36:54 +04:00
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
2011-02-24 06:14:30 +03:00
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
2012-07-21 16:26:19 +04:00
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#include <sys/mman.h>
#endif
2012-10-24 23:33:02 +04:00
#include "hexchat.h"
2011-02-24 06:14:30 +03:00
#include "cfgfiles.h"
#include "chanopt.h"
#include "plugin.h"
#include "fe.h"
#include "server.h"
#include "util.h"
#include "outbound.h"
2012-10-24 23:33:02 +04:00
#include "hexchatc.h"
2011-02-24 06:14:30 +03:00
#include "text.h"
#include "typedef.h"
2011-02-24 06:14:30 +03:00
#ifdef WIN32
#include <windows.h>
#endif
2013-04-28 03:02:27 +04:00
#ifdef USE_LIBCANBERRA
#include <canberra.h>
#endif
2011-02-24 06:14:30 +03:00
struct pevt_stage1
{
int len;
char *data;
struct pevt_stage1 *next;
};
2012-11-03 21:24:25 +04:00
static void mkdir_p (char *filename);
2011-02-24 06:14:30 +03:00
static char *log_create_filename (char *channame);
static char *
2012-11-03 21:24:25 +04:00
scrollback_get_filename (session *sess)
2011-02-24 06:14:30 +03:00
{
2012-11-03 21:24:25 +04:00
char *net, *chan, *buf;
2011-02-24 06:14:30 +03:00
net = server_get_network (sess->server, FALSE);
if (!net)
return NULL;
2012-11-03 21:24:25 +04:00
buf = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "scrollback" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s.txt", get_xdir (), net, "");
2011-02-24 06:14:30 +03:00
mkdir_p (buf);
2012-11-03 21:24:25 +04:00
g_free (buf);
2011-02-24 06:14:30 +03:00
chan = log_create_filename (sess->channel);
2012-11-03 21:24:25 +04:00
buf = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "scrollback" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s.txt", get_xdir (), net, chan);
2011-02-24 06:14:30 +03:00
free (chan);
return buf;
}
#if 0
static void
scrollback_unlock (session *sess)
{
char buf[1024];
if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL)
return;
strcat (buf, ".lock");
unlink (buf);
}
static gboolean
scrollback_lock (session *sess)
{
char buf[1024];
int fh;
if (scrollback_get_filename (sess, buf, sizeof (buf) - 6) == NULL)
return FALSE;
strcat (buf, ".lock");
if (access (buf, F_OK) == 0)
return FALSE; /* can't get lock */
fh = open (buf, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644);
if (fh == -1)
return FALSE;
return TRUE;
}
#endif
void
scrollback_close (session *sess)
{
if (sess->scrollfd != -1)
{
close (sess->scrollfd);
sess->scrollfd = -1;
}
}
2012-10-22 17:55:43 +04:00
/* shrink the file to roughly prefs.hex_text_max_lines */
2011-02-24 06:14:30 +03:00
static void
scrollback_shrink (session *sess)
{
2012-11-03 21:24:25 +04:00
char *file;
2011-02-24 06:14:30 +03:00
char *buf;
int fh;
int lines;
int line;
gsize len;
2011-02-24 06:14:30 +03:00
char *p;
scrollback_close (sess);
sess->scrollwritten = 0;
lines = 0;
2012-11-03 21:24:25 +04:00
if ((file = scrollback_get_filename (sess)) == NULL)
{
g_free (file);
2011-02-24 06:14:30 +03:00
return;
2012-11-03 21:24:25 +04:00
}
2011-02-24 06:14:30 +03:00
2012-11-03 21:24:25 +04:00
if (!g_file_get_contents (file, &buf, &len, NULL))
{
g_free (file);
2011-02-24 06:14:30 +03:00
return;
2012-11-03 21:24:25 +04:00
}
2011-02-24 06:14:30 +03:00
/* count all lines */
p = buf;
while (p != buf + len)
{
if (*p == '\n')
lines++;
p++;
}
2012-11-03 21:24:25 +04:00
fh = g_open (file, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0644);
g_free (file);
2011-02-24 06:14:30 +03:00
if (fh == -1)
{
free (buf);
return;
}
line = 0;
p = buf;
while (p != buf + len)
{
if (*p == '\n')
{
line++;
2012-10-22 17:55:43 +04:00
if (line >= lines - prefs.hex_text_max_lines &&
2011-02-24 06:14:30 +03:00
p + 1 != buf + len)
{
p++;
write (fh, p, len - (p - buf));
break;
}
}
p++;
}
close (fh);
free (buf);
}
static void
scrollback_save (session *sess, char *text)
{
2012-11-03 21:24:25 +04:00
char *buf;
2011-02-24 06:14:30 +03:00
time_t stamp;
int len;
if (sess->type == SESS_SERVER)
return;
if (sess->text_scrollback == SET_DEFAULT)
{
2012-10-22 17:55:43 +04:00
if (!prefs.hex_text_replay)
2011-02-24 06:14:30 +03:00
return;
}
else
{
if (sess->text_scrollback != SET_ON)
return;
}
if (sess->scrollfd == -1)
{
2012-11-03 21:24:25 +04:00
if ((buf = scrollback_get_filename (sess)) == NULL)
2011-02-24 06:14:30 +03:00
return;
2012-11-03 21:24:25 +04:00
sess->scrollfd = g_open (buf, O_CREAT | O_APPEND | O_WRONLY, 0644);
g_free (buf);
2011-02-24 06:14:30 +03:00
if (sess->scrollfd == -1)
return;
}
stamp = time (0);
if (sizeof (stamp) == 4) /* gcc will optimize one of these out */
buf = g_strdup_printf ("T %d ", (int) stamp);
2011-02-24 06:14:30 +03:00
else
2012-11-03 21:24:25 +04:00
buf = g_strdup_printf ("T %" G_GINT64_FORMAT " ", (gint64)stamp);
write (sess->scrollfd, buf, strlen (buf));
g_free (buf);
2011-02-24 06:14:30 +03:00
len = strlen (text);
write (sess->scrollfd, text, len);
if (len && text[len - 1] != '\n')
write (sess->scrollfd, "\n", 1);
sess->scrollwritten++;
2012-10-22 17:55:43 +04:00
if ((sess->scrollwritten * 2 > prefs.hex_text_max_lines && prefs.hex_text_max_lines > 0) ||
2011-02-24 06:14:30 +03:00
sess->scrollwritten > 32000)
scrollback_shrink (sess);
}
void
scrollback_load (session *sess)
{
2012-11-03 21:24:25 +04:00
char *buf;
2011-02-24 06:14:30 +03:00
char *text;
time_t stamp;
int lines;
2012-11-03 21:24:25 +04:00
GIOChannel *io;
GError *file_error = NULL;
GError *io_err = NULL;
2011-02-24 06:14:30 +03:00
if (sess->text_scrollback == SET_DEFAULT)
{
2012-10-22 17:55:43 +04:00
if (!prefs.hex_text_replay)
2011-02-24 06:14:30 +03:00
return;
}
else
{
if (sess->text_scrollback != SET_ON)
return;
}
2012-11-03 21:24:25 +04:00
if ((buf = scrollback_get_filename (sess)) == NULL)
2011-02-24 06:14:30 +03:00
return;
io = g_io_channel_new_file (buf, "r", &file_error);
2012-11-03 21:24:25 +04:00
g_free (buf);
if (!io)
2011-02-24 06:14:30 +03:00
return;
lines = 0;
2012-11-03 21:24:25 +04:00
while (1)
{
gsize n_bytes;
2012-11-03 21:24:25 +04:00
GIOStatus io_status;
2012-11-03 21:24:25 +04:00
io_status = g_io_channel_read_line (io, &buf, &n_bytes, NULL, &io_err);
if (io_status == G_IO_STATUS_NORMAL)
{
2012-11-03 21:24:25 +04:00
char *buf_tmp;
2012-11-03 21:24:25 +04:00
n_bytes--;
buf_tmp = buf;
buf = g_strndup (buf_tmp, n_bytes);
g_free (buf_tmp);
2012-11-03 21:24:25 +04:00
if (buf[0] == 'T')
2011-02-24 06:14:30 +03:00
{
2012-11-03 21:24:25 +04:00
if (sizeof (time_t) == 4)
stamp = strtoul (buf + 2, NULL, 10);
else
stamp = strtoull (buf + 2, NULL, 10); /* in case time_t is 64 bits */
text = strchr (buf + 3, ' ');
if (text)
{
2012-11-03 21:24:25 +04:00
if (prefs.hex_text_stripcolor_replay)
{
text = strip_color (text + 1, -1, STRIP_COLOR);
}
2012-11-03 21:24:25 +04:00
fe_print_text (sess, text, stamp);
2012-10-22 17:55:43 +04:00
if (prefs.hex_text_stripcolor_replay)
{
g_free (text);
}
2012-07-19 12:34:48 +04:00
}
2012-11-03 21:24:25 +04:00
lines++;
2011-02-24 06:14:30 +03:00
}
2012-11-03 21:24:25 +04:00
g_free (buf);
2011-02-24 06:14:30 +03:00
}
2012-11-03 21:24:25 +04:00
else
break;
2011-02-24 06:14:30 +03:00
}
2012-11-03 21:24:25 +04:00
g_io_channel_unref (io);
2011-02-24 06:14:30 +03:00
sess->scrollwritten = lines;
if (lines)
{
text = ctime (&stamp);
text[24] = 0; /* get rid of the \n */
2012-11-03 21:24:25 +04:00
buf = g_strdup_printf ("\n*\t%s %s\n\n", _("Loaded log from"), text);
2011-02-24 06:14:30 +03:00
fe_print_text (sess, buf, 0);
2012-11-03 21:24:25 +04:00
g_free (buf);
2011-02-24 06:14:30 +03:00
/*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/
}
}
void
log_close (session *sess)
{
char obuf[512];
time_t currenttime;
if (sess->logfd != -1)
{
currenttime = time (NULL);
write (sess->logfd, obuf,
snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"),
ctime (&currenttime)));
close (sess->logfd);
sess->logfd = -1;
}
}
static void
2012-11-03 21:24:25 +04:00
mkdir_p (char *filename)
2011-02-24 06:14:30 +03:00
{
2012-11-03 21:24:25 +04:00
char *dirname;
dirname = g_path_get_dirname (filename);
2011-02-24 06:14:30 +03:00
2012-11-03 21:24:25 +04:00
g_mkdir_with_parents (dirname, 0700);
2011-02-24 06:14:30 +03:00
2012-11-03 21:24:25 +04:00
g_free (dirname);
2011-02-24 06:14:30 +03:00
}
static char *
log_create_filename (char *channame)
{
char *tmp, *ret;
int mbl;
ret = tmp = strdup (channame);
while (*tmp)
{
mbl = g_utf8_skip[((unsigned char *)tmp)[0]];
if (mbl == 1)
{
#ifndef WIN32
*tmp = rfc_tolower (*tmp);
if (*tmp == '/')
#else
/* win32 can't handle filenames with \|/><:"*? characters */
if (*tmp == '\\' || *tmp == '|' || *tmp == '/' ||
*tmp == '>' || *tmp == '<' || *tmp == ':' ||
*tmp == '\"' || *tmp == '*' || *tmp == '?')
#endif
*tmp = '_';
}
tmp += mbl;
}
return ret;
}
/* like strcpy, but % turns into %% */
static char *
log_escape_strcpy (char *dest, char *src, char *end)
{
while (*src)
{
*dest = *src;
if (dest + 1 == end)
break;
dest++;
src++;
if (*src == '%')
{
if (dest + 1 == end)
break;
dest[0] = '%';
dest++;
}
}
dest[0] = 0;
return dest - 1;
}
/* substitutes %c %n %s into buffer */
static void
log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s)
{
char *end = buf + bufsize;
while (1)
{
switch (fmt[0])
{
case 0:
buf[0] = 0;
return;
case '%':
fmt++;
switch (fmt[0])
{
case 'c':
buf = log_escape_strcpy (buf, c, end);
break;
case 'n':
buf = log_escape_strcpy (buf, n, end);
break;
case 's':
buf = log_escape_strcpy (buf, s, end);
break;
default:
buf[0] = '%';
buf++;
buf[0] = fmt[0];
break;
}
break;
default:
buf[0] = fmt[0];
}
fmt++;
buf++;
/* doesn't fit? */
if (buf == end)
{
buf[-1] = 0;
return;
}
}
}
2012-10-04 01:18:10 +04:00
static int
logmask_is_fullpath ()
{
/* Check if final path/filename is absolute or relative.
* If one uses log mask variables, such as "%c/...", %c will be empty upon
* connecting since there's no channel name yet, so we have to make sure
* we won't try to write to the FS root. On Windows we can be sure it's
* full path if the 2nd character is a colon since Windows doesn't allow
* colons in filenames.
*/
#ifdef WIN32
/* Treat it as full path if it
* - starts with '\' which denotes the root directory of the current drive letter
* - starts with a drive letter and followed by ':'
*/
2012-10-22 16:50:36 +04:00
if (prefs.hex_irc_logmask[0] == '\\' || (((prefs.hex_irc_logmask[0] >= 'A' && prefs.hex_irc_logmask[0] <= 'Z') || (prefs.hex_irc_logmask[0] >= 'a' && prefs.hex_irc_logmask[0] <= 'z')) && prefs.hex_irc_logmask[1] == ':'))
2012-10-04 01:18:10 +04:00
#else
2012-10-22 16:50:36 +04:00
if (prefs.hex_irc_logmask[0] == '/')
2012-10-04 01:18:10 +04:00
#endif
{
return 1;
}
else
{
return 0;
}
}
2011-02-24 06:14:30 +03:00
static char *
log_create_pathname (char *servname, char *channame, char *netname)
{
char fname[384];
char fnametime[384];
struct tm *tm;
time_t now;
if (!netname)
2012-10-04 01:18:10 +04:00
{
2011-02-24 06:14:30 +03:00
netname = "NETWORK";
2012-10-04 01:18:10 +04:00
}
2011-02-24 06:14:30 +03:00
/* first, everything is in UTF-8 */
if (!rfc_casecmp (channame, servname))
2012-10-04 01:18:10 +04:00
{
2011-02-24 06:14:30 +03:00
channame = strdup ("server");
2012-10-04 01:18:10 +04:00
}
2011-02-24 06:14:30 +03:00
else
2012-10-04 01:18:10 +04:00
{
2011-02-24 06:14:30 +03:00
channame = log_create_filename (channame);
2012-10-04 01:18:10 +04:00
}
2012-10-22 16:50:36 +04:00
log_insert_vars (fname, sizeof (fname), prefs.hex_irc_logmask, channame, netname, servname);
2011-02-24 06:14:30 +03:00
free (channame);
/* insert time/date */
now = time (NULL);
tm = localtime (&now);
strftime (fnametime, sizeof (fnametime), fname, tm);
2012-10-04 01:18:10 +04:00
/* create final path/filename */
if (logmask_is_fullpath ())
{
2011-02-24 06:14:30 +03:00
snprintf (fname, sizeof (fname), "%s", fnametime);
}
else /* relative path */
{
2012-11-03 21:24:25 +04:00
snprintf (fname, sizeof (fname), "%s" G_DIR_SEPARATOR_S "logs" G_DIR_SEPARATOR_S "%s", get_xdir (), fnametime);
}
2011-02-24 06:14:30 +03:00
/* create all the subdirectories */
2012-11-03 21:24:25 +04:00
mkdir_p (fname);
2011-02-24 06:14:30 +03:00
2012-11-03 21:24:25 +04:00
return g_strdup(fname);
2011-02-24 06:14:30 +03:00
}
static int
log_open_file (char *servname, char *channame, char *netname)
{
char buf[512];
int fd;
char *file;
time_t currenttime;
file = log_create_pathname (servname, channame, netname);
if (!file)
return -1;
#ifdef WIN32
2012-11-03 21:24:25 +04:00
fd = g_open (file, O_CREAT | O_APPEND | O_WRONLY, S_IREAD|S_IWRITE);
2011-02-24 06:14:30 +03:00
#else
2012-11-03 21:24:25 +04:00
fd = g_open (file, O_CREAT | O_APPEND | O_WRONLY, 0644);
2011-02-24 06:14:30 +03:00
#endif
g_free (file);
if (fd == -1)
return -1;
currenttime = time (NULL);
write (fd, buf,
snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"),
ctime (&currenttime)));
return fd;
}
static void
log_open (session *sess)
{
static gboolean log_error = FALSE;
log_close (sess);
sess->logfd = log_open_file (sess->server->servername, sess->channel,
server_get_network (sess->server, FALSE));
if (!log_error && sess->logfd == -1)
{
2012-11-03 21:24:25 +04:00
char *message;
2011-02-24 06:14:30 +03:00
2012-11-03 21:24:25 +04:00
message = g_strdup_printf (_("* Can't open log file(s) for writing. Check the\npermissions on %s"),
2012-10-04 01:18:10 +04:00
log_create_pathname (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE)));
fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR);
2012-11-03 21:24:25 +04:00
g_free (message);
2011-02-24 06:14:30 +03:00
log_error = TRUE;
}
}
void
log_open_or_close (session *sess)
{
if (sess->text_logging == SET_DEFAULT)
{
2012-10-22 16:50:36 +04:00
if (prefs.hex_irc_logging)
2011-02-24 06:14:30 +03:00
log_open (sess);
else
log_close (sess);
}
else
{
if (sess->text_logging)
log_open (sess);
else
log_close (sess);
}
}
int
get_stamp_str (char *fmt, time_t tim, char **ret)
{
char *loc = NULL;
char dest[128];
gsize len;
/* strftime wants the format string in LOCALE! */
if (!prefs.utf8_locale)
{
const gchar *charset;
g_get_charset (&charset);
loc = g_convert_with_fallback (fmt, -1, charset, "UTF-8", "?", 0, 0, 0);
if (loc)
fmt = loc;
}
len = strftime (dest, sizeof (dest), fmt, localtime (&tim));
2012-02-16 20:21:15 +04:00
#ifdef WIN32
if (!len)
{
/* use failsafe format until a correct one is specified */
len = strftime (dest, sizeof (dest), "[%H:%M:%S]", localtime (&tim));
2012-02-16 20:21:15 +04:00
}
#endif
2011-02-24 06:14:30 +03:00
if (len)
{
if (prefs.utf8_locale)
*ret = g_strdup (dest);
else
*ret = g_locale_to_utf8 (dest, len, 0, &len, 0);
}
if (loc)
g_free (loc);
return len;
}
static void
log_write (session *sess, char *text)
{
char *temp;
char *stamp;
char *file;
int len;
if (sess->text_logging == SET_DEFAULT)
{
2012-10-22 16:50:36 +04:00
if (!prefs.hex_irc_logging)
2011-02-24 06:14:30 +03:00
return;
}
else
{
if (sess->text_logging != SET_ON)
return;
}
if (sess->logfd == -1)
log_open (sess);
/* change to a different log file? */
file = log_create_pathname (sess->server->servername, sess->channel,
server_get_network (sess->server, FALSE));
if (file)
{
if (g_access (file, F_OK) != 0)
2011-02-24 06:14:30 +03:00
{
close (sess->logfd);
sess->logfd = log_open_file (sess->server->servername, sess->channel,
server_get_network (sess->server, FALSE));
}
g_free (file);
}
2012-10-22 17:55:43 +04:00
if (prefs.hex_stamp_log)
2011-02-24 06:14:30 +03:00
{
2012-10-22 17:55:43 +04:00
len = get_stamp_str (prefs.hex_stamp_log_format, time (0), &stamp);
2011-02-24 06:14:30 +03:00
if (len)
{
write (sess->logfd, stamp, len);
g_free (stamp);
}
}
temp = strip_color (text, -1, STRIP_ALL);
len = strlen (temp);
write (sess->logfd, temp, len);
/* lots of scripts/plugins print without a \n at the end */
if (temp[len - 1] != '\n')
write (sess->logfd, "\n", 1); /* emulate what xtext would display */
g_free (temp);
}
/* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8 */
/* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */
/* 2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */
/* thing useful in this range and it helps us receive from mIRC */
/* 3. The five undefined chars in CP1252 80-9f are replaced with */
/* ISO-8859-15 control codes. */
/* 4. Handles 0xa4 as a Euro symbol ala ISO-8859-15. */
/* 5. Uses ISO-8859-1 (which matches CP1252) for everything else. */
/* 6. This routine measured 3x faster than g_convert :) */
static unsigned char *
iso_8859_1_to_utf8 (unsigned char *text, int len, gsize *bytes_written)
{
unsigned int idx;
unsigned char *res, *output;
static const unsigned short lowtable[] = /* 74 byte table for 80-a4 */
{
/* compressed utf-8 table: if the first byte's 0x20 bit is set, it
indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */
0x82ac, /* 80 Euro. CP1252 from here on... */
0xe281, /* 81 NA */
0x809a, /* 82 */
0xe692, /* 83 */
0x809e, /* 84 */
0x80a6, /* 85 */
0x80a0, /* 86 */
0x80a1, /* 87 */
0xeb86, /* 88 */
0x80b0, /* 89 */
0xe5a0, /* 8a */
0x80b9, /* 8b */
0xe592, /* 8c */
0xe28d, /* 8d NA */
0xe5bd, /* 8e */
0xe28f, /* 8f NA */
0xe290, /* 90 NA */
0x8098, /* 91 */
0x8099, /* 92 */
0x809c, /* 93 */
0x809d, /* 94 */
0x80a2, /* 95 */
0x8093, /* 96 */
0x8094, /* 97 */
0xeb9c, /* 98 */
0x84a2, /* 99 */
0xe5a1, /* 9a */
0x80ba, /* 9b */
0xe593, /* 9c */
0xe29d, /* 9d NA */
0xe5be, /* 9e */
0xe5b8, /* 9f */
0xe2a0, /* a0 */
0xe2a1, /* a1 */
0xe2a2, /* a2 */
0xe2a3, /* a3 */
0x82ac /* a4 ISO-8859-15 Euro. */
};
if (len == -1)
len = strlen (text);
/* worst case scenario: every byte turns into 3 bytes */
res = output = g_malloc ((len * 3) + 1);
if (!output)
return NULL;
while (len)
{
if (G_LIKELY (*text < 0x80))
{
*output = *text; /* ascii maps directly */
}
else if (*text <= 0xa4) /* 80-a4 use a lookup table */
{
idx = *text - 0x80;
if (lowtable[idx] & 0x2000)
{
*output++ = (lowtable[idx] >> 8) & 0xdf; /* 2 byte utf-8 */
*output = lowtable[idx] & 0xff;
}
else
{
*output++ = 0xe2; /* 3 byte utf-8 */
*output++ = (lowtable[idx] >> 8) & 0xff;
*output = lowtable[idx] & 0xff;
}
}
else if (*text < 0xc0)
{
*output++ = 0xc2;
*output = *text;
}
else
{
*output++ = 0xc3;
*output = *text - 0x40;
}
output++;
text++;
len--;
}
*output = 0; /* terminate */
*bytes_written = output - res;
return res;
}
char *
text_validate (char **text, int *len)
{
char *utf;
gsize utf_len;
/* valid utf8? */
if (g_utf8_validate (*text, *len, 0))
return NULL;
#ifdef WIN32
if (GetACP () == 1252) /* our routine is better than iconv's 1252 */
#else
if (prefs.utf8_locale)
#endif
/* fallback to iso-8859-1 */
utf = iso_8859_1_to_utf8 (*text, *len, &utf_len);
else
{
/* fallback to locale */
utf = g_locale_to_utf8 (*text, *len, 0, &utf_len, NULL);
if (!utf)
utf = iso_8859_1_to_utf8 (*text, *len, &utf_len);
}
if (!utf)
{
*text = g_strdup ("%INVALID%");
*len = 9;
} else
{
*text = utf;
*len = utf_len;
}
return utf;
}
void
PrintText (session *sess, char *text)
{
char *conv;
if (!sess)
{
if (!sess_list)
return;
sess = (session *) sess_list->data;
}
/* make sure it's valid utf8 */
if (text[0] == 0)
{
text = "\n";
conv = NULL;
} else
{
int len = -1;
conv = text_validate ((char **)&text, &len);
}
log_write (sess, text);
scrollback_save (sess, text);
fe_print_text (sess, text, 0);
if (conv)
g_free (conv);
}
void
PrintTextf (session *sess, char *format, ...)
{
va_list args;
char *buf;
va_start (args, format);
buf = g_strdup_vprintf (format, args);
va_end (args);
PrintText (sess, buf);
g_free (buf);
}
/* Print Events stuff here --AGL */
/* Consider the following a NOTES file:
The main upshot of this is:
* Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like
* The default text engine can be config'ed
By default it should appear *exactly* the same (I'm working hard not to change the default style) but if you go into Settings->Edit Event Texts you can change the text's. The format is thus:
The normal %Cx (color) and %B (bold) etc work
$x is replaced with the data in var x (e.g. $1 is often the nick)
$axxx is replace with a single byte of value xxx (in base 10)
AGL (990507)
*/
/* These lists are thus:
pntevts_text[] are the strings the user sees (WITH %x etc)
pntevts[] are the data strings with \000 etc
*/
/* To add a new event:
Think up a name (like "Join")
Make up a pevt_name_help struct
Add an entry to textevents.in
Type: make textevents
*/
/* Internals:
On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the
defaults are loaded. Any missing events are filled from defaults.
Each event is parsed by pevt_build_string and a binary output is produced
which looks like:
(byte) value: 0 = {
(int) numbers of bytes
(char []) that number of byte to be memcpy'ed into the buffer
}
1 =
(byte) number of varable to insert
2 = end of buffer
Each XP_TE_* signal is hard coded to call text_emit which calls
display_event which decodes the data
This means that this system *should be faster* than snprintf because
it always 'knows' that format of the string (basically is preparses much
of the work)
--AGL
*/
char *pntevts_text[NUM_XP];
char *pntevts[NUM_XP];
#define pevt_generic_none_help NULL
static char * const pevt_genmsg_help[] = {
N_("Left message"),
N_("Right message"),
};
#if 0
2012-11-03 22:04:47 +04:00
static char * const pevt_identd_help[] = {
N_("IP address"),
N_("Username")
};
#endif
2012-11-03 22:04:47 +04:00
2011-02-24 06:14:30 +03:00
static char * const pevt_join_help[] = {
N_("The nick of the joining person"),
N_("The channel being joined"),
N_("The host of the person"),
};
static char * const pevt_chanaction_help[] = {
N_("Nickname"),
N_("The action"),
N_("Mode char"),
N_("Identified text"),
};
static char * const pevt_chanmsg_help[] = {
N_("Nickname"),
N_("The text"),
N_("Mode char"),
N_("Identified text"),
};
static char * const pevt_privmsg_help[] = {
N_("Nickname"),
N_("The message"),
N_("Identified text")
};
2012-11-03 10:46:51 +04:00
static char * const pevt_capack_help[] = {
N_("Server Name"),
N_("Acknowledged Capabilities")
2012-11-03 10:46:51 +04:00
};
static char * const pevt_caplist_help[] = {
N_("Server Name"),
N_("Server Capabilities")
};
static char * const pevt_capreq_help[] = {
N_("Requested Capabilities")
2012-11-03 10:46:51 +04:00
};
2011-02-24 06:14:30 +03:00
static char * const pevt_changenick_help[] = {
N_("Old nickname"),
N_("New nickname"),
};
static char * const pevt_newtopic_help[] = {
N_("Nick of person who changed the topic"),
N_("Topic"),
N_("Channel"),
};
static char * const pevt_topic_help[] = {
N_("Channel"),
N_("Topic"),
};
static char * const pevt_kick_help[] = {
N_("The nickname of the kicker"),
N_("The person being kicked"),
N_("The channel"),
N_("The reason"),
};
static char * const pevt_part_help[] = {
N_("The nick of the person leaving"),
N_("The host of the person"),
N_("The channel"),
};
static char * const pevt_chandate_help[] = {
N_("The channel"),
N_("The time"),
};
static char * const pevt_topicdate_help[] = {
N_("The channel"),
N_("The creator"),
N_("The time"),
};
static char * const pevt_quit_help[] = {
N_("Nick"),
N_("Reason"),
N_("Host"),
};
static char * const pevt_pingrep_help[] = {
N_("Who it's from"),
N_("The time in x.x format (see below)"),
};
static char * const pevt_notice_help[] = {
N_("Who it's from"),
N_("The message"),
};
static char * const pevt_channotice_help[] = {
N_("Who it's from"),
N_("The Channel it's going to"),
N_("The message"),
};
static char * const pevt_uchangenick_help[] = {
N_("Old nickname"),
N_("New nickname"),
};
static char * const pevt_ukick_help[] = {
N_("The person being kicked"),
N_("The channel"),
N_("The nickname of the kicker"),
N_("The reason"),
};
static char * const pevt_partreason_help[] = {
N_("The nick of the person leaving"),
N_("The host of the person"),
N_("The channel"),
N_("The reason"),
};
static char * const pevt_ctcpsnd_help[] = {
N_("The sound"),
N_("The nick of the person"),
N_("The channel"),
};
static char * const pevt_ctcpgen_help[] = {
N_("The CTCP event"),
N_("The nick of the person"),
};
static char * const pevt_ctcpgenc_help[] = {
N_("The CTCP event"),
N_("The nick of the person"),
N_("The Channel it's going to"),
};
static char * const pevt_chansetkey_help[] = {
N_("The nick of the person who set the key"),
N_("The key"),
};
static char * const pevt_chansetlimit_help[] = {
N_("The nick of the person who set the limit"),
N_("The limit"),
};
static char * const pevt_chanop_help[] = {
N_("The nick of the person who did the op'ing"),
N_("The nick of the person who has been op'ed"),
};
static char * const pevt_chanhop_help[] = {
N_("The nick of the person who has been halfop'ed"),
N_("The nick of the person who did the halfop'ing"),
};
static char * const pevt_chanvoice_help[] = {
N_("The nick of the person who did the voice'ing"),
N_("The nick of the person who has been voice'ed"),
};
static char * const pevt_chanban_help[] = {
N_("The nick of the person who did the banning"),
N_("The ban mask"),
};
2013-04-08 01:49:30 +04:00
static char * const pevt_chanquiet_help[] = {
N_("The nick of the person who did the quieting"),
N_("The quiet mask"),
};
2011-02-24 06:14:30 +03:00
static char * const pevt_chanrmkey_help[] = {
N_("The nick who removed the key"),
};
static char * const pevt_chanrmlimit_help[] = {
N_("The nick who removed the limit"),
};
static char * const pevt_chandeop_help[] = {
N_("The nick of the person of did the deop'ing"),
N_("The nick of the person who has been deop'ed"),
};
static char * const pevt_chandehop_help[] = {
N_("The nick of the person of did the dehalfop'ing"),
N_("The nick of the person who has been dehalfop'ed"),
};
static char * const pevt_chandevoice_help[] = {
N_("The nick of the person of did the devoice'ing"),
N_("The nick of the person who has been devoice'ed"),
};
static char * const pevt_chanunban_help[] = {
N_("The nick of the person of did the unban'ing"),
N_("The ban mask"),
};
2013-04-08 01:49:30 +04:00
static char * const pevt_chanunquiet_help[] = {
N_("The nick of the person of did the unquiet'ing"),
N_("The quiet mask"),
};
2011-02-24 06:14:30 +03:00
static char * const pevt_chanexempt_help[] = {
N_("The nick of the person who did the exempt"),
N_("The exempt mask"),
};
static char * const pevt_chanrmexempt_help[] = {
N_("The nick of the person removed the exempt"),
N_("The exempt mask"),
};
static char * const pevt_chaninvite_help[] = {
N_("The nick of the person who did the invite"),
N_("The invite mask"),
};
static char * const pevt_chanrminvite_help[] = {
N_("The nick of the person removed the invite"),
N_("The invite mask"),
};
static char * const pevt_chanmodegen_help[] = {
N_("The nick of the person setting the mode"),
N_("The mode's sign (+/-)"),
N_("The mode letter"),
N_("The channel it's being set on"),
};
static char * const pevt_whois1_help[] = {
N_("Nickname"),
N_("Username"),
N_("Host"),
N_("Full name"),
};
static char * const pevt_whois2_help[] = {
N_("Nickname"),
N_("Channel Membership/\"is an IRC operator\""),
};
static char * const pevt_whois3_help[] = {
N_("Nickname"),
N_("Server Information"),
};
static char * const pevt_whois4_help[] = {
N_("Nickname"),
N_("Idle time"),
};
static char * const pevt_whois4t_help[] = {
N_("Nickname"),
N_("Idle time"),
N_("Signon time"),
};
static char * const pevt_whois5_help[] = {
N_("Nickname"),
N_("Away reason"),
};
static char * const pevt_whois6_help[] = {
N_("Nickname"),
};
static char * const pevt_whoisid_help[] = {
N_("Nickname"),
N_("Message"),
"Numeric"
};
static char * const pevt_whoisauth_help[] = {
N_("Nickname"),
N_("Message"),
N_("Account"),
};
static char * const pevt_whoisrealhost_help[] = {
N_("Nickname"),
N_("Real user@host"),
N_("Real IP"),
N_("Message"),
};
static char * const pevt_generic_channel_help[] = {
N_("Channel Name"),
};
static char * const pevt_saslauth_help[] = {
N_("Username")
};
static char * const pevt_saslresponse_help[] = {
N_("Server Name"),
N_("Raw Numeric or Identifier"),
N_("Username"),
N_("Message")
};
2011-02-24 06:14:30 +03:00
static char * const pevt_servertext_help[] = {
N_("Text"),
N_("Server Name"),
N_("Raw Numeric or Identifier")
};
static char * const pevt_sslmessage_help[] = {
N_("Text"),
N_("Server Name")
};
static char * const pevt_invited_help[] = {
N_("Channel Name"),
N_("Nick of person who invited you"),
N_("Server Name"),
};
static char * const pevt_usersonchan_help[] = {
N_("Channel Name"),
N_("Users"),
};
static char * const pevt_nickclash_help[] = {
N_("Nickname in use"),
N_("Nick being tried"),
};
static char * const pevt_connfail_help[] = {
N_("Error"),
};
static char * const pevt_connect_help[] = {
N_("Host"),
N_("IP"),
N_("Port"),
};
static char * const pevt_sconnect_help[] = {
"PID"
};
static char * const pevt_generic_nick_help[] = {
N_("Nickname"),
N_("Server Name"),
N_("Network")
};
static char * const pevt_chanmodes_help[] = {
N_("Channel Name"),
N_("Modes string"),
};
static char * const pevt_rawmodes_help[] = {
N_("Nickname"),
N_("Modes string"),
};
static char * const pevt_kill_help[] = {
N_("Nickname"),
N_("Reason"),
};
static char * const pevt_dccchaterr_help[] = {
N_("Nickname"),
N_("IP address"),
N_("Port"),
N_("Error"),
};
static char * const pevt_dccstall_help[] = {
N_("DCC Type"),
N_("Filename"),
N_("Nickname"),
};
static char * const pevt_generic_file_help[] = {
N_("Filename"),
N_("Error"),
};
static char * const pevt_dccrecverr_help[] = {
N_("Filename"),
N_("Destination filename"),
N_("Nickname"),
N_("Error"),
};
static char * const pevt_dccrecvcomp_help[] = {
N_("Filename"),
N_("Destination filename"),
N_("Nickname"),
N_("CPS"),
};
static char * const pevt_dccconfail_help[] = {
N_("DCC Type"),
N_("Nickname"),
N_("Error"),
};
static char * const pevt_dccchatcon_help[] = {
N_("Nickname"),
N_("IP address"),
};
static char * const pevt_dcccon_help[] = {
N_("Nickname"),
N_("IP address"),
N_("Filename"),
};
static char * const pevt_dccsendfail_help[] = {
N_("Filename"),
N_("Nickname"),
N_("Error"),
};
static char * const pevt_dccsendcomp_help[] = {
N_("Filename"),
N_("Nickname"),
N_("CPS"),
};
static char * const pevt_dccoffer_help[] = {
N_("Filename"),
N_("Nickname"),
N_("Pathname"),
};
static char * const pevt_dccfileabort_help[] = {
N_("Nickname"),
N_("Filename")
};
static char * const pevt_dccchatabort_help[] = {
N_("Nickname"),
};
static char * const pevt_dccresumeoffer_help[] = {
N_("Nickname"),
N_("Filename"),
N_("Position"),
};
static char * const pevt_dccsendoffer_help[] = {
N_("Nickname"),
N_("Filename"),
N_("Size"),
N_("IP address"),
};
static char * const pevt_dccgenericoffer_help[] = {
N_("DCC String"),
N_("Nickname"),
};
2013-04-08 04:41:29 +04:00
static char * const pevt_notifyaway_help[] = {
N_("Nickname"),
N_("Away Reason"),
};
2011-02-24 06:14:30 +03:00
static char * const pevt_notifynumber_help[] = {
N_("Number of notify items"),
};
static char * const pevt_serverlookup_help[] = {
N_("Server Name"),
};
static char * const pevt_servererror_help[] = {
N_("Text"),
};
static char * const pevt_foundip_help[] = {
N_("IP"),
};
static char * const pevt_dccrename_help[] = {
N_("Old Filename"),
N_("New Filename"),
};
static char * const pevt_ctcpsend_help[] = {
N_("Receiver"),
N_("Message"),
};
static char * const pevt_ignoreaddremove_help[] = {
N_("Hostmask"),
};
static char * const pevt_resolvinguser_help[] = {
N_("Nickname"),
N_("Hostname"),
};
static char * const pevt_malformed_help[] = {
N_("Nickname"),
N_("The Packet"),
};
static char * const pevt_pingtimeout_help[] = {
N_("Seconds"),
};
static char * const pevt_uinvite_help[] = {
N_("Nick of person who have been invited"),
N_("Channel Name"),
N_("Server Name"),
};
static char * const pevt_banlist_help[] = {
N_("Channel"),
N_("Banmask"),
N_("Who set the ban"),
N_("Ban time"),
};
static char * const pevt_discon_help[] = {
N_("Error"),
};
#include "textevents.h"
static void
pevent_load_defaults ()
{
int i;
for (i = 0; i < NUM_XP; i++)
{
if (pntevts_text[i])
free (pntevts_text[i]);
/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
if (te[i].num_args & 128)
pntevts_text[i] = strdup (te[i].def);
else
pntevts_text[i] = strdup (_(te[i].def));
}
}
void
pevent_make_pntevts ()
{
int i, m;
char out[1024];
for (i = 0; i < NUM_XP; i++)
{
if (pntevts[i] != NULL)
free (pntevts[i]);
if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
{
snprintf (out, sizeof (out),
_("Error parsing event %s.\nLoading default."), te[i].name);
fe_message (out, FE_MSG_WARN);
free (pntevts_text[i]);
/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
if (te[i].num_args & 128)
pntevts_text[i] = strdup (te[i].def);
else
pntevts_text[i] = strdup (_(te[i].def));
if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
{
fprintf (stderr,
2012-11-11 16:51:50 +04:00
"HexChat CRITICAL *** default event text failed to build!\n");
2011-02-24 06:14:30 +03:00
abort ();
}
}
}
}
/* Loading happens at 2 levels:
1) File is read into blocks
2) Pe block is parsed and loaded
--AGL */
/* Better hope you pass good args.. --AGL */
static void
pevent_trigger_load (int *i_penum, char **i_text, char **i_snd)
{
int penum = *i_penum, len;
char *text = *i_text, *snd = *i_snd;
if (penum != -1 && text != NULL)
{
len = strlen (text) + 1;
if (pntevts_text[penum])
free (pntevts_text[penum]);
pntevts_text[penum] = malloc (len);
memcpy (pntevts_text[penum], text, len);
}
if (text)
free (text);
if (snd)
free (snd);
*i_text = NULL;
*i_snd = NULL;
*i_penum = 0;
}
static int
pevent_find (char *name, int *i_i)
{
int i = *i_i, j;
j = i + 1;
while (1)
{
if (j == NUM_XP)
j = 0;
if (strcmp (te[j].name, name) == 0)
{
*i_i = j;
return j;
}
if (j == i)
return -1;
j++;
}
}
int
pevent_load (char *filename)
{
/* AGL, I've changed this file and pevent_save, could you please take a look at
* the changes and possibly modify them to suit you
* //David H
*/
char *buf, *ibuf;
int fd, i = 0, pnt = 0;
struct stat st;
char *text = NULL, *snd = NULL;
int penum = 0;
char *ofs;
if (filename == NULL)
2012-10-30 14:35:39 +04:00
fd = hexchat_open_file ("pevents.conf", O_RDONLY, 0, 0);
2011-02-24 06:14:30 +03:00
else
2012-10-30 14:35:39 +04:00
fd = hexchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);
2011-02-24 06:14:30 +03:00
if (fd == -1)
return 1;
if (fstat (fd, &st) != 0)
return 1;
ibuf = malloc (st.st_size);
read (fd, ibuf, st.st_size);
close (fd);
while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
{
if (buf[0] == '#')
continue;
if (strlen (buf) == 0)
continue;
ofs = strchr (buf, '=');
if (!ofs)
continue;
*ofs = 0;
ofs++;
/*if (*ofs == 0)
continue;*/
if (strcmp (buf, "event_name") == 0)
{
if (penum >= 0)
pevent_trigger_load (&penum, &text, &snd);
penum = pevent_find (ofs, &i);
continue;
} else if (strcmp (buf, "event_text") == 0)
{
if (text)
free (text);
#if 0
/* This allows updating of old strings. We don't use new defaults
if the user has customized the strings (.e.g a text theme).
Hash of the old default is enough to identify and replace it.
This only works in English. */
switch (g_str_hash (ofs))
{
case 0x526743a4:
/* %C08,02 Hostmask PRIV NOTI CHAN CTCP INVI UNIG %O */
text = strdup (te[XP_TE_IGNOREHEADER].def);
break;
case 0xe91bc9c2:
/* %C08,02 %O */
text = strdup (te[XP_TE_IGNOREFOOTER].def);
break;
case 0x1fbfdf22:
/* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */
text = strdup (te[XP_TE_DCCFILEERR].def);
break;
default:
text = strdup (ofs);
}
#else
text = strdup (ofs);
#endif
continue;
}/* else if (strcmp (buf, "event_sound") == 0)
{
if (snd)
free (snd);
snd = strdup (ofs);
continue;
}*/
continue;
}
pevent_trigger_load (&penum, &text, &snd);
free (ibuf);
return 0;
}
static void
pevent_check_all_loaded ()
{
int i;
for (i = 0; i < NUM_XP; i++)
{
if (pntevts_text[i] == NULL)
{
/*printf ("%s\n", te[i].name);
2012-10-30 14:35:39 +04:00
snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of HexChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]);
2011-02-24 06:14:30 +03:00
gtkutil_simpledialog(out); */
/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
if (te[i].num_args & 128)
pntevts_text[i] = strdup (te[i].def);
else
pntevts_text[i] = strdup (_(te[i].def));
}
}
}
void
load_text_events ()
{
memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP));
memset (&pntevts, 0, sizeof (char *) * (NUM_XP));
if (pevent_load (NULL))
pevent_load_defaults ();
pevent_check_all_loaded ();
pevent_make_pntevts ();
}
/*
CL: format_event now handles filtering of arguments:
2012-10-22 17:55:43 +04:00
1) if prefs.hex_text_stripcolor_msg is set, filter all style control codes from arguments
2011-02-24 06:14:30 +03:00
2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself
*/
#define ARG_FLAG(argn) (1 << (argn))
void
format_event (session *sess, int index, char **args, char *o, int sizeofo, unsigned int stripcolor_args)
{
int len, oi, ii, numargs;
char *i, *ar, d, a, done_all = FALSE;
i = pntevts[index];
numargs = te[index].num_args & 0x7f;
oi = ii = len = d = a = 0;
o[0] = 0;
if (i == NULL)
return;
while (done_all == FALSE)
{
d = i[ii++];
switch (d)
{
case 0:
memcpy (&len, &(i[ii]), sizeof (int));
ii += sizeof (int);
if (oi + len > sizeofo)
{
printf ("Overflow in display_event (%s)\n", i);
o[0] = 0;
return;
}
memcpy (&(o[oi]), &(i[ii]), len);
oi += len;
ii += len;
break;
case 1:
a = i[ii++];
if (a > numargs)
{
fprintf (stderr,
2012-11-11 16:51:50 +04:00
"HexChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
2011-02-24 06:14:30 +03:00
a, numargs, i);
break;
}
ar = args[(int) a + 1];
if (ar == NULL)
{
printf ("arg[%d] is NULL in print event\n", a + 1);
} else
{
if (strlen (ar) > sizeofo - oi - 4)
ar[sizeofo - oi - 4] = 0; /* Avoid buffer overflow */
2011-02-24 06:14:30 +03:00
if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL);
else len = strip_hidden_attribute (ar, &o[oi]);
oi += len;
}
break;
case 2:
o[oi++] = '\n';
o[oi++] = 0;
done_all = TRUE;
continue;
case 3:
/* if (sess->type == SESS_DIALOG)
{
if (prefs.dialog_indent_nicks)
o[oi++] = '\t';
else
o[oi++] = ' ';
} else
{*/
2012-10-22 17:55:43 +04:00
if (prefs.hex_text_indent)
2011-02-24 06:14:30 +03:00
o[oi++] = '\t';
else
o[oi++] = ' ';
/*}*/
break;
}
}
o[oi] = 0;
if (*o == '\n')
o[0] = 0;
}
static void
display_event (session *sess, int event, char **args, unsigned int stripcolor_args)
{
char o[4096];
format_event (sess, event, args, o, sizeof (o), stripcolor_args);
if (o[0])
PrintText (sess, o);
}
int
pevt_build_string (const char *input, char **output, int *max_arg)
{
struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next;
int clen;
char o[4096], d, *obuf, *i;
int oi, ii, max = -1, len, x;
len = strlen (input);
i = malloc (len + 1);
memcpy (i, input, len + 1);
check_special_chars (i, TRUE);
len = strlen (i);
clen = oi = ii = 0;
for (;;)
{
if (ii == len)
break;
d = i[ii++];
if (d != '$')
{
o[oi++] = d;
continue;
}
if (i[ii] == '$')
{
o[oi++] = '$';
continue;
}
if (oi > 0)
{
s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
if (base == NULL)
base = s;
if (last != NULL)
last->next = s;
last = s;
s->next = NULL;
s->data = malloc (oi + sizeof (int) + 1);
s->len = oi + sizeof (int) + 1;
clen += oi + sizeof (int) + 1;
s->data[0] = 0;
memcpy (&(s->data[1]), &oi, sizeof (int));
memcpy (&(s->data[1 + sizeof (int)]), o, oi);
oi = 0;
}
if (ii == len)
{
fe_message ("String ends with a $", FE_MSG_WARN);
return 1;
}
d = i[ii++];
if (d == 'a')
{ /* Hex value */
x = 0;
if (ii == len)
goto a_len_error;
d = i[ii++];
d -= '0';
x = d * 100;
if (ii == len)
goto a_len_error;
d = i[ii++];
d -= '0';
x += d * 10;
if (ii == len)
goto a_len_error;
d = i[ii++];
d -= '0';
x += d;
if (x > 255)
goto a_range_error;
o[oi++] = x;
continue;
a_len_error:
fe_message ("String ends in $a", FE_MSG_WARN);
return 1;
a_range_error:
fe_message ("$a value is greater than 255", FE_MSG_WARN);
return 1;
}
if (d == 't')
{
/* Tab - if tabnicks is set then write '\t' else ' ' */
s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
if (base == NULL)
base = s;
if (last != NULL)
last->next = s;
last = s;
s->next = NULL;
s->data = malloc (1);
s->len = 1;
clen += 1;
s->data[0] = 3;
continue;
}
if (d < '1' || d > '9')
{
snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d);
fe_message (o, FE_MSG_WARN);
return 1;
}
d -= '0';
if (max < d)
max = d;
s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
if (base == NULL)
base = s;
if (last != NULL)
last->next = s;
last = s;
s->next = NULL;
s->data = malloc (2);
s->len = 2;
clen += 2;
s->data[0] = 1;
s->data[1] = d - 1;
}
if (oi > 0)
{
s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
if (base == NULL)
base = s;
if (last != NULL)
last->next = s;
last = s;
s->next = NULL;
s->data = malloc (oi + sizeof (int) + 1);
s->len = oi + sizeof (int) + 1;
clen += oi + sizeof (int) + 1;
s->data[0] = 0;
memcpy (&(s->data[1]), &oi, sizeof (int));
memcpy (&(s->data[1 + sizeof (int)]), o, oi);
oi = 0;
}
s = (struct pevt_stage1 *) malloc (sizeof (struct pevt_stage1));
if (base == NULL)
base = s;
if (last != NULL)
last->next = s;
last = s;
s->next = NULL;
s->data = malloc (1);
s->len = 1;
clen += 1;
s->data[0] = 2;
oi = 0;
s = base;
obuf = malloc (clen);
while (s)
{
next = s->next;
memcpy (&obuf[oi], s->data, s->len);
oi += s->len;
free (s->data);
free (s);
s = next;
}
free (i);
if (max_arg)
*max_arg = max;
if (output)
*output = obuf;
return 0;
}
/* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */
/* also light/dark gray (14/15) */
2013-04-28 02:56:11 +04:00
/* 5,7,8 are all shades of yellow which happen to look damn near the same */
2011-02-24 06:14:30 +03:00
static char rcolors[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 };
int
text_color_of (char *name)
2011-02-24 06:14:30 +03:00
{
int i = 0, sum = 0;
while (name[i])
sum += name[i++];
sum %= sizeof (rcolors) / sizeof (char);
return rcolors[sum];
}
/* called by EMIT_SIGNAL macro */
void
text_emit (int index, session *sess, char *a, char *b, char *c, char *d)
{
char *word[PDIWORDS];
int i;
2012-10-22 17:55:43 +04:00
unsigned int stripcolor_args = (prefs.hex_text_stripcolor_msg ? 0xFFFFFFFF : 0);
2011-02-24 06:14:30 +03:00
char tbuf[NICKLEN + 4];
2012-10-22 17:55:43 +04:00
if (prefs.hex_text_color_nicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG))
2011-02-24 06:14:30 +03:00
{
snprintf (tbuf, sizeof (tbuf), "\003%d%s", text_color_of (a), a);
2011-02-24 06:14:30 +03:00
a = tbuf;
stripcolor_args &= ~ARG_FLAG(1); /* don't strip color from this argument */
}
word[0] = te[index].name;
word[1] = (a ? a : "\000");
word[2] = (b ? b : "\000");
word[3] = (c ? c : "\000");
word[4] = (d ? d : "\000");
for (i = 5; i < PDIWORDS; i++)
word[i] = "\000";
if (plugin_emit_print (sess, word))
return;
/* If a plugin's callback executes "/close", 'sess' may be invalid */
if (!is_session (sess))
return;
switch (index)
{
case XP_TE_JOIN:
case XP_TE_PART:
case XP_TE_PARTREASON:
case XP_TE_QUIT:
/* implement ConfMode / Hide Join and Part Messages */
2012-10-22 16:50:36 +04:00
if (chanopt_is_set (prefs.hex_irc_conf_mode, sess->text_hidejoinpart))
2011-02-24 06:14:30 +03:00
return;
break;
/* ===Private message=== */
case XP_TE_PRIVMSG:
case XP_TE_DPRIVMSG:
case XP_TE_PRIVACTION:
case XP_TE_DPRIVACTION:
if (chanopt_is_set (prefs.hex_input_beep_priv, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2011-02-24 06:14:30 +03:00
sound_beep (sess);
if (chanopt_is_set (prefs.hex_input_flash_priv, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2011-02-24 06:14:30 +03:00
fe_flash_window (sess);
/* why is this one different? because of plugin-tray.c's hooks! ugly */
if (sess->alert_tray == SET_ON)
fe_tray_set_icon (FE_ICON_MESSAGE);
break;
/* ===Highlighted message=== */
case XP_TE_HCHANACTION:
case XP_TE_HCHANMSG:
if (chanopt_is_set (prefs.hex_input_beep_hilight, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2011-02-24 06:14:30 +03:00
sound_beep (sess);
if (chanopt_is_set (prefs.hex_input_flash_hilight, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2011-02-24 06:14:30 +03:00
fe_flash_window (sess);
if (sess->alert_tray == SET_ON)
fe_tray_set_icon (FE_ICON_MESSAGE);
break;
/* ===Channel message=== */
case XP_TE_CHANACTION:
case XP_TE_CHANMSG:
if (chanopt_is_set (prefs.hex_input_beep_chans, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2011-02-24 06:14:30 +03:00
sound_beep (sess);
if (chanopt_is_set (prefs.hex_input_flash_chans, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2011-02-24 06:14:30 +03:00
fe_flash_window (sess);
if (sess->alert_tray == SET_ON)
fe_tray_set_icon (FE_ICON_MESSAGE);
break;
}
sound_play_event (index);
display_event (sess, index, word, stripcolor_args);
}
char *
text_find_format_string (char *name)
{
int i = 0;
i = pevent_find (name, &i);
if (i >= 0)
return pntevts_text[i];
return NULL;
}
int
text_emit_by_name (char *name, session *sess, char *a, char *b, char *c, char *d)
{
int i = 0;
i = pevent_find (name, &i);
if (i >= 0)
{
text_emit (i, sess, a, b, c, d);
return 1;
}
return 0;
}
void
pevent_save (char *fn)
{
int fd, i;
char buf[1024];
if (!fn)
2012-10-30 14:35:39 +04:00
fd = hexchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY,
2011-02-24 06:14:30 +03:00
0x180, XOF_DOMODE);
else
2012-10-30 14:35:39 +04:00
fd = hexchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180,
2011-02-24 06:14:30 +03:00
XOF_FULLPATH | XOF_DOMODE);
if (fd == -1)
{
/*
fe_message ("Error opening config file\n", FALSE);
If we get here when X-Chat is closing the fe-message causes a nice & hard crash
so we have to use perror which doesn't rely on GTK
*/
perror ("Error opening config file\n");
return;
}
for (i = 0; i < NUM_XP; i++)
{
write (fd, buf, snprintf (buf, sizeof (buf),
"event_name=%s\n", te[i].name));
write (fd, buf, snprintf (buf, sizeof (buf),
"event_text=%s\n\n", pntevts_text[i]));
}
close (fd);
}
/* =========================== */
/* ========== SOUND ========== */
/* =========================== */
char *sound_files[NUM_XP];
void
sound_beep (session *sess)
{
2013-04-23 08:45:37 +04:00
if (!prefs.hex_gui_focus_omitalerts || !fe_gui_info (sess, 0) == 1)
{
if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0])
/* user defined beep _file_ */
sound_play_event (XP_TE_BEEP);
else
/* system beep */
fe_beep (sess);
}
2011-02-24 06:14:30 +03:00
}
void
2013-04-28 01:00:18 +04:00
sound_play (const char *file, gboolean quiet)
2011-02-24 06:14:30 +03:00
{
2012-11-03 21:24:25 +04:00
char *buf;
char *wavfile;
2013-04-28 01:28:24 +04:00
#ifndef WIN32
2011-02-24 06:14:30 +03:00
char *cmd;
2013-04-28 03:02:27 +04:00
#ifdef USE_LIBCANBERRA
ca_context *con;
#endif
2013-04-28 01:28:24 +04:00
#endif
2011-02-24 06:14:30 +03:00
2013-04-28 03:02:27 +04:00
2011-02-24 06:14:30 +03:00
/* the pevents GUI editor triggers this after removing a soundfile */
if (!file[0])
2013-04-27 23:07:05 +04:00
{
2011-02-24 06:14:30 +03:00
return;
2013-04-27 23:07:05 +04:00
}
2011-02-24 06:14:30 +03:00
#ifdef WIN32
2013-04-28 01:00:18 +04:00
/* check for fullpath */
if (file[0] == '\\' || (((file[0] >= 'A' && file[0] <= 'Z') || (file[0] >= 'a' && file[0] <= 'z')) && file[1] == ':'))
2012-11-03 21:24:25 +04:00
#else
2013-04-28 01:00:18 +04:00
if (file[0] == '/')
2012-11-03 21:24:25 +04:00
#endif
2011-02-24 06:14:30 +03:00
{
2012-11-03 21:24:25 +04:00
wavfile = g_strdup (file);
}
else
2011-02-24 06:14:30 +03:00
{
2013-04-14 12:47:37 +04:00
wavfile = g_build_filename (get_xdir (), "sounds", file, NULL);
2011-02-24 06:14:30 +03:00
}
2012-11-03 21:24:25 +04:00
if (g_access (wavfile, R_OK) == 0)
2011-02-24 06:14:30 +03:00
{
2013-04-27 23:07:05 +04:00
#ifdef WIN32
PlaySound (wavfile, NULL, SND_NODEFAULT|SND_FILENAME|SND_ASYNC);
#else
2013-04-28 03:02:27 +04:00
#ifdef USE_LIBCANBERRA
ca_context_create (&con);
/* TODO: Volume setting? */
if (ca_context_play (con, 0,
CA_PROP_MEDIA_FILENAME, wavfile, NULL) == 0)
{
g_free (wavfile);
return;
}
#endif
cmd = g_find_program_in_path ("play");
2011-02-24 06:14:30 +03:00
2013-04-27 23:07:05 +04:00
if (cmd)
2011-02-24 06:14:30 +03:00
{
2013-04-27 23:07:05 +04:00
buf = g_strdup_printf ("%s \"%s\"", cmd, wavfile);
hexchat_exec (buf);
g_free (buf);
g_free (cmd);
2012-11-03 21:24:25 +04:00
}
2011-02-24 06:14:30 +03:00
#endif
2012-11-03 21:24:25 +04:00
}
else
2011-02-24 06:14:30 +03:00
{
if (!quiet)
{
2012-11-03 21:24:25 +04:00
buf = g_strdup_printf (_("Cannot read sound file:\n%s"), wavfile);
2011-02-24 06:14:30 +03:00
fe_message (buf, FE_MSG_ERROR);
2012-11-03 21:24:25 +04:00
g_free (buf);
2011-02-24 06:14:30 +03:00
}
}
2012-11-03 21:24:25 +04:00
g_free (wavfile);
2011-02-24 06:14:30 +03:00
}
void
sound_play_event (int i)
{
if (sound_files[i])
2013-04-28 01:00:18 +04:00
{
sound_play (sound_files[i], FALSE);
}
2011-02-24 06:14:30 +03:00
}
static void
sound_load_event (char *evt, char *file)
{
int i = 0;
if (file[0] && pevent_find (evt, &i) != -1)
{
if (sound_files[i])
free (sound_files[i]);
sound_files[i] = strdup (file);
}
}
void
sound_load ()
{
int fd;
char buf[512];
char evt[128];
memset (&sound_files, 0, sizeof (char *) * (NUM_XP));
2012-10-30 14:35:39 +04:00
fd = hexchat_open_file ("sound.conf", O_RDONLY, 0, 0);
2011-02-24 06:14:30 +03:00
if (fd == -1)
return;
evt[0] = 0;
while (waitline (fd, buf, sizeof buf, FALSE) != -1)
{
if (strncmp (buf, "event=", 6) == 0)
{
safe_strcpy (evt, buf + 6, sizeof (evt));
}
else if (strncmp (buf, "sound=", 6) == 0)
{
if (evt[0] != 0)
{
sound_load_event (evt, buf + 6);
evt[0] = 0;
}
}
}
close (fd);
}
void
sound_save ()
{
int fd, i;
char buf[512];
2012-10-30 14:35:39 +04:00
fd = hexchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180,
2011-02-24 06:14:30 +03:00
XOF_DOMODE);
if (fd == -1)
return;
for (i = 0; i < NUM_XP; i++)
{
if (sound_files[i] && sound_files[i][0])
{
write (fd, buf, snprintf (buf, sizeof (buf),
"event=%s\n", te[i].name));
write (fd, buf, snprintf (buf, sizeof (buf),
"sound=%s\n\n", sound_files[i]));
}
}
close (fd);
}