1 /* X-Chat
2  * Copyright (C) 1998 Peter Zelezny.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include <time.h>
24 #include <sys/types.h>
25 #include <fcntl.h>
26 #include <sys/stat.h>
27 
28 #ifdef WIN32
29 #include <io.h>
30 #else
31 #include <unistd.h>
32 #include <sys/mman.h>
33 #endif
34 
35 #include "hexchat.h"
36 #include "cfgfiles.h"
37 #include "chanopt.h"
38 #include "plugin.h"
39 #include "fe.h"
40 #include "server.h"
41 #include "util.h"
42 #include "outbound.h"
43 #include "hexchatc.h"
44 #include "text.h"
45 #include "typedef.h"
46 #ifdef WIN32
47 #include <windows.h>
48 #endif
49 
50 #ifdef USE_LIBCANBERRA
51 #include <canberra.h>
52 #endif
53 
54 const gchar* unicode_fallback_string = "\357\277\275"; /* The Unicode replacement character 0xFFFD */
55 const gchar* arbitrary_encoding_fallback_string = "?";
56 
57 struct pevt_stage1
58 {
59 	int len;
60 	char *data;
61 	struct pevt_stage1 *next;
62 };
63 
64 #ifdef USE_LIBCANBERRA
65 static ca_context *ca_con;
66 #endif
67 
68 #define SCROLLBACK_MAX 32000
69 
70 static void mkdir_p (char *filename);
71 static char *log_create_filename (char *channame);
72 
73 static char *
scrollback_get_filename(session * sess)74 scrollback_get_filename (session *sess)
75 {
76 	char *net, *chan, *buf, *ret = NULL;
77 
78 	net = server_get_network (sess->server, FALSE);
79 	if (!net)
80 		return NULL;
81 
82 	net = log_create_filename (net);
83 	buf = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "scrollback" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s.txt", get_xdir (), net, "");
84 	mkdir_p (buf);
85 	g_free (buf);
86 
87 	chan = log_create_filename (sess->channel);
88 	if (chan[0])
89 		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);
90 	else
91 		buf = NULL;
92 	g_free (chan);
93 	g_free (net);
94 
95 	if (buf)
96 	{
97 		ret = g_filename_from_utf8 (buf, -1, NULL, NULL, NULL);
98 		g_free (buf);
99 	}
100 
101 	return ret;
102 }
103 
104 void
scrollback_close(session * sess)105 scrollback_close (session *sess)
106 {
107 	g_clear_object (&sess->scrollfile);
108 }
109 
110 /* shrink the file to roughly prefs.hex_text_max_lines */
111 
112 static void
scrollback_shrink(session * sess)113 scrollback_shrink (session *sess)
114 {
115 	char *buf, *p;
116 	gsize len;
117 	gint offset, lines = 0;
118 	const gint max_lines = MIN(prefs.hex_text_max_lines, SCROLLBACK_MAX);
119 
120 	if (!g_file_load_contents (sess->scrollfile, NULL, &buf, &len, NULL, NULL))
121 		return;
122 
123 	/* count all lines */
124 	p = buf;
125 	while (p != buf + len)
126 	{
127 		if (*p == '\n')
128 			lines++;
129 		p++;
130 	}
131 
132 	offset = lines - max_lines;
133 
134 	/* now just go back to where we want to start the file */
135 	p = buf;
136 	lines = 0;
137 	while (p != buf + len)
138 	{
139 		if (*p == '\n')
140 		{
141 			lines++;
142 			if (lines == offset)
143 			{
144 				p++;
145 				break;
146 			}
147 		}
148 		p++;
149 	}
150 
151 	if (g_file_replace_contents (sess->scrollfile, p, strlen(p), NULL, FALSE,
152 							G_FILE_CREATE_PRIVATE, NULL, NULL, NULL))
153 		sess->scrollwritten = lines;
154 
155 	g_free (buf);
156 }
157 
158 static void
scrollback_save(session * sess,char * text,time_t stamp)159 scrollback_save (session *sess, char *text, time_t stamp)
160 {
161 	GOutputStream *ostream;
162 	char *buf;
163 
164 	if (sess->type == SESS_SERVER && prefs.hex_gui_tab_server == 1)
165 		return;
166 
167 	if (sess->text_scrollback == SET_DEFAULT)
168 	{
169 		if (!prefs.hex_text_replay)
170 			return;
171 	}
172 	else
173 	{
174 		if (sess->text_scrollback != SET_ON)
175 			return;
176 	}
177 
178 	if (!sess->scrollfile)
179 	{
180 		if ((buf = scrollback_get_filename (sess)) == NULL)
181 			return;
182 
183 		sess->scrollfile = g_file_new_for_path (buf);
184 		g_free (buf);
185 	}
186 	else
187 	{
188 		/* Users can delete the folder after it's created... */
189 		GFile *parent = g_file_get_parent (sess->scrollfile);
190 		g_file_make_directory_with_parents (parent, NULL, NULL);
191 		g_object_unref (parent);
192 	}
193 
194 	ostream = G_OUTPUT_STREAM(g_file_append_to (sess->scrollfile, G_FILE_CREATE_PRIVATE, NULL, NULL));
195 	if (!ostream)
196 		return;
197 
198 	if (!stamp)
199 		stamp = time(0);
200 	if (sizeof (stamp) == 4)	/* gcc will optimize one of these out */
201 		buf = g_strdup_printf ("T %d ", (int) stamp);
202 	else
203 		buf = g_strdup_printf ("T %" G_GINT64_FORMAT " ", (gint64)stamp);
204 
205 	g_output_stream_write (ostream, buf, strlen (buf), NULL, NULL);
206 	g_output_stream_write (ostream, text, strlen (text), NULL, NULL);
207 	if (!g_str_has_suffix (text, "\n"))
208 		g_output_stream_write (ostream, "\n", 1, NULL, NULL);
209 
210 	g_free (buf);
211 	g_object_unref (ostream);
212 
213 	sess->scrollwritten++;
214 
215 	if ((sess->scrollwritten > prefs.hex_text_max_lines && prefs.hex_text_max_lines > 0) ||
216        sess->scrollwritten > SCROLLBACK_MAX)
217 		scrollback_shrink (sess);
218 }
219 
220 void
scrollback_load(session * sess)221 scrollback_load (session *sess)
222 {
223 	GInputStream *stream;
224 	GDataInputStream *istream;
225 	gchar *buf, *text;
226 	gint lines = 0;
227 	time_t stamp = 0;
228 
229 	if (sess->text_scrollback == SET_DEFAULT)
230 	{
231 		if (!prefs.hex_text_replay)
232 			return;
233 	}
234 	else
235 	{
236 		if (sess->text_scrollback != SET_ON)
237 			return;
238 	}
239 
240 	if (!sess->scrollfile)
241 	{
242 		if ((buf = scrollback_get_filename (sess)) == NULL)
243 			return;
244 
245 		sess->scrollfile = g_file_new_for_path (buf);
246 		g_free (buf);
247 	}
248 
249 	stream = G_INPUT_STREAM(g_file_read (sess->scrollfile, NULL, NULL));
250 	if (!stream)
251 		return;
252 
253 	istream = g_data_input_stream_new (stream);
254 	/*
255 	 * This is to avoid any issues moving between windows/unix
256 	 * but the docs mention an invalid \r without a following \n
257 	 * can lock up the program... (Our write() always adds \n)
258 	 */
259 	g_data_input_stream_set_newline_type (istream, G_DATA_STREAM_NEWLINE_TYPE_ANY);
260 	g_object_unref (stream);
261 
262 	while (1)
263 	{
264 		GError *err = NULL;
265 		gsize n_bytes;
266 
267 		buf = g_data_input_stream_read_line_utf8 (istream, &n_bytes, NULL, &err);
268 
269 		if (!err && buf)
270 		{
271 			/*
272 			 * Some scrollback lines have three blanks after the timestamp and a newline
273 			 * Some have only one blank and a newline
274 			 * Some don't even have a timestamp
275 			 * Some don't have any text at all
276 			 */
277 			if (buf[0] == 'T' && buf[1] == ' ')
278 			{
279 				if (sizeof (time_t) == 4)
280 					stamp = strtoul (buf + 2, NULL, 10);
281 				else
282 					stamp = g_ascii_strtoull (buf + 2, NULL, 10); /* in case time_t is 64 bits */
283 
284 				if (G_UNLIKELY(stamp == 0))
285 				{
286 					g_warning ("Invalid timestamp in scrollback file");
287 					continue;
288 				}
289 
290 				text = strchr (buf + 3, ' ');
291 				if (text && text[1])
292 				{
293 					if (prefs.hex_text_stripcolor_replay)
294 					{
295 						text = strip_color (text + 1, -1, STRIP_COLOR);
296 					}
297 
298 					fe_print_text (sess, text, stamp, TRUE);
299 
300 					if (prefs.hex_text_stripcolor_replay)
301 					{
302 						g_free (text);
303 					}
304 				}
305 				else
306 				{
307 					fe_print_text (sess, "  ", stamp, TRUE);
308 				}
309 			}
310 			else
311 			{
312 				if (strlen (buf))
313 					fe_print_text (sess, buf, 0, TRUE);
314 				else
315 					fe_print_text (sess, "  ", 0, TRUE);
316 			}
317 			lines++;
318 
319 			g_free (buf);
320 		}
321 		else if (err)
322 		{
323 			/* If its only an encoding error it may be specific to the line */
324 			if (g_error_matches (err, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
325 			{
326 				g_warning ("Invalid utf8 in scrollback file");
327 				g_clear_error (&err);
328 				continue;
329 			}
330 
331 			/* For general errors just give up */
332 			g_clear_error (&err);
333 			break;
334 		}
335 		else /* No new line */
336 		{
337 			break;
338 		}
339 	}
340 
341 	g_object_unref (istream);
342 
343 	sess->scrollwritten = lines;
344 
345 	if (lines)
346 	{
347 		text = ctime (&stamp);
348 		buf = g_strdup_printf ("\n*\t%s %s\n", _("Loaded log from"), text);
349 		fe_print_text (sess, buf, 0, TRUE);
350 		g_free (buf);
351 		/*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/
352 	}
353 }
354 
355 void
log_close(session * sess)356 log_close (session *sess)
357 {
358 	char obuf[512];
359 	time_t currenttime;
360 
361 	if (sess->logfd != -1)
362 	{
363 		currenttime = time (NULL);
364 		write (sess->logfd, obuf,
365 			 g_snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"),
366 						  ctime (&currenttime)));
367 		close (sess->logfd);
368 		sess->logfd = -1;
369 	}
370 }
371 
372 /*
373  * filename should be in utf8 encoding and will be
374  * converted to filesystem encoding automatically.
375  */
376 static void
mkdir_p(char * filename)377 mkdir_p (char *filename)
378 {
379 	char *dirname, *dirname_fs;
380 	GError *err = NULL;
381 
382 	dirname = g_path_get_dirname (filename);
383 	dirname_fs = g_filename_from_utf8 (dirname, -1, NULL, NULL, &err);
384 	if (!dirname_fs)
385 	{
386 		g_warning ("%s", err->message);
387 		g_error_free (err);
388 		g_free (dirname);
389 		return;
390 	}
391 
392 	g_mkdir_with_parents (dirname_fs, 0700);
393 
394 	g_free (dirname);
395 	g_free (dirname_fs);
396 }
397 
398 static char *
log_create_filename(char * channame)399 log_create_filename (char *channame)
400 {
401 	char *tmp, *ret;
402 	int mbl;
403 
404 	ret = tmp = g_strdup (channame);
405 	while (*tmp)
406 	{
407 		mbl = g_utf8_skip[((unsigned char *)tmp)[0]];
408 		if (mbl == 1)
409 		{
410 #ifndef WIN32
411 			*tmp = rfc_tolower (*tmp);
412 			if (*tmp == '/')
413 #else
414 			/* win32 can't handle filenames with \|/><:"*? characters */
415 			if (*tmp == '\\' || *tmp == '|' || *tmp == '/' ||
416 				 *tmp == '>'  || *tmp == '<' || *tmp == ':' ||
417 				 *tmp == '\"' || *tmp == '*' || *tmp == '?')
418 #endif
419 				*tmp = '_';
420 		}
421 		tmp += mbl;
422 	}
423 
424 	return ret;
425 }
426 
427 /* like strcpy, but % turns into %% */
428 
429 static char *
log_escape_strcpy(char * dest,char * src,char * end)430 log_escape_strcpy (char *dest, char *src, char *end)
431 {
432 	while (*src)
433 	{
434 		*dest = *src;
435 		if (dest + 1 == end)
436 			break;
437 		dest++;
438 		src++;
439 
440 		if (*src == '%')
441 		{
442 			if (dest + 1 == end)
443 				break;
444 			dest[0] = '%';
445 			dest++;
446 		}
447 	}
448 
449 	dest[0] = 0;
450 	return dest - 1;
451 }
452 
453 /* substitutes %c %n %s into buffer */
454 
455 static void
log_insert_vars(char * buf,int bufsize,char * fmt,char * c,char * n,char * s)456 log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s)
457 {
458 	char *end = buf + bufsize;
459 
460 	while (1)
461 	{
462 		switch (fmt[0])
463 		{
464 		case 0:
465 			buf[0] = 0;
466 			return;
467 
468 		case '%':
469 			fmt++;
470 			switch (fmt[0])
471 			{
472 			case 'c':
473 				buf = log_escape_strcpy (buf, c, end);
474 				break;
475 			case 'n':
476 				buf = log_escape_strcpy (buf, n, end);
477 				break;
478 			case 's':
479 				buf = log_escape_strcpy (buf, s, end);
480 				break;
481 			default:
482 				buf[0] = '%';
483 				buf++;
484 				buf[0] = fmt[0];
485 				break;
486 			}
487 			break;
488 
489 		default:
490 			buf[0] = fmt[0];
491 		}
492 		fmt++;
493 		buf++;
494 		/* doesn't fit? */
495 		if (buf == end)
496 		{
497 			buf[-1] = 0;
498 			return;
499 		}
500 	}
501 }
502 
503 static char *
log_create_pathname(char * servname,char * channame,char * netname)504 log_create_pathname (char *servname, char *channame, char *netname)
505 {
506 	char fname[384];
507 	char fnametime[384];
508 	time_t now;
509 
510 	if (!netname)
511 	{
512 		netname = g_strdup ("NETWORK");
513 	}
514 	else
515 	{
516 		netname = log_create_filename (netname);
517 	}
518 
519 	/* first, everything is in UTF-8 */
520 	if (!rfc_casecmp (channame, servname))
521 	{
522 		channame = g_strdup ("server");
523 	}
524 	else
525 	{
526 		channame = log_create_filename (channame);
527 	}
528 
529 	servname = log_create_filename (servname);
530 
531 	log_insert_vars (fname, sizeof (fname), prefs.hex_irc_logmask, channame, netname, servname);
532 	g_free (channame);
533 	g_free (netname);
534 	g_free (servname);
535 
536 	/* insert time/date */
537 	now = time (NULL);
538 	strftime_utf8 (fnametime, sizeof (fnametime), fname, now);
539 
540 	/* If one uses log mask variables, such as "%c/...", %c will be empty upon
541 	 * connecting since there's no channel name yet, so we have to make sure
542 	 * we won't try to write to the FS root. */
543 	if (g_path_is_absolute (prefs.hex_irc_logmask))
544 	{
545 		g_snprintf (fname, sizeof (fname), "%s", fnametime);
546 	}
547 	else	/* relative path */
548 	{
549 		g_snprintf (fname, sizeof (fname), "%s" G_DIR_SEPARATOR_S "logs" G_DIR_SEPARATOR_S "%s", get_xdir (), fnametime);
550 	}
551 
552 	/* create all the subdirectories */
553 	mkdir_p (fname);
554 
555 	return g_strdup (fname);
556 }
557 
558 static int
log_open_file(char * servname,char * channame,char * netname)559 log_open_file (char *servname, char *channame, char *netname)
560 {
561 	char buf[512];
562 	int fd;
563 	char *file;
564 	time_t currenttime;
565 
566 	file = log_create_pathname (servname, channame, netname);
567 	if (!file)
568 		return -1;
569 
570 	fd = g_open (file, O_CREAT | O_APPEND | O_WRONLY | OFLAGS, 0644);
571 	g_free (file);
572 
573 	if (fd == -1)
574 		return -1;
575 	currenttime = time (NULL);
576 	write (fd, buf,
577 			 g_snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"),
578 						  ctime (&currenttime)));
579 
580 	return fd;
581 }
582 
583 static void
log_open(session * sess)584 log_open (session *sess)
585 {
586 	static gboolean log_error = FALSE;
587 
588 	log_close (sess);
589 	sess->logfd = log_open_file (sess->server->servername, sess->channel,
590 										  server_get_network (sess->server, FALSE));
591 
592 	if (!log_error && sess->logfd == -1)
593 	{
594 		char *filename = log_create_pathname (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE));
595 		char *message = g_strdup_printf (_("* Can't open log file(s) for writing. Check the\npermissions on %s"), filename);
596 
597 		g_free (filename);
598 
599 		fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR);
600 
601 		g_free (message);
602 
603 		log_error = TRUE;
604 	}
605 }
606 
607 void
log_open_or_close(session * sess)608 log_open_or_close (session *sess)
609 {
610 	if (sess->text_logging == SET_DEFAULT)
611 	{
612 		if (prefs.hex_irc_logging)
613 			log_open (sess);
614 		else
615 			log_close (sess);
616 	}
617 	else
618 	{
619 		if (sess->text_logging)
620 			log_open (sess);
621 		else
622 			log_close (sess);
623 	}
624 }
625 
626 int
get_stamp_str(char * fmt,time_t tim,char ** ret)627 get_stamp_str (char *fmt, time_t tim, char **ret)
628 {
629 	char dest[128];
630 	gsize len_locale;
631 	gsize len_utf8;
632 
633 	/* strftime requires the format string to be in locale encoding. */
634 	fmt = g_locale_from_utf8 (fmt, -1, NULL, NULL, NULL);
635 
636 	len_locale = strftime_validated (dest, sizeof (dest), fmt, localtime (&tim));
637 
638 	g_free (fmt);
639 
640 	if (len_locale == 0)
641 	{
642 		return 0;
643 	}
644 
645 	*ret = g_locale_to_utf8 (dest, len_locale, NULL, &len_utf8, NULL);
646 	if (*ret == NULL)
647 	{
648 		return 0;
649 	}
650 
651 	return len_utf8;
652 }
653 
654 static void
log_write(session * sess,char * text,time_t ts)655 log_write (session *sess, char *text, time_t ts)
656 {
657 	char *temp;
658 	char *stamp;
659 	char *file;
660 	int len;
661 
662 	if (sess->text_logging == SET_DEFAULT)
663 	{
664 		if (!prefs.hex_irc_logging)
665 			return;
666 	}
667 	else
668 	{
669 		if (sess->text_logging != SET_ON)
670 			return;
671 	}
672 
673 	if (sess->logfd == -1)
674 	{
675 		log_open (sess);
676 	}
677 
678 	/* change to a different log file? */
679 	file = log_create_pathname (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE));
680 	if (file)
681 	{
682 		if (g_access (file, F_OK) != 0)
683 		{
684 			if (sess->logfd != -1)
685 			{
686 				close (sess->logfd);
687 			}
688 
689 			sess->logfd = log_open_file (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE));
690 		}
691 
692 		g_free (file);
693 	}
694 
695 	if (sess->logfd == -1)
696 	{
697 		return;
698 	}
699 
700 	if (prefs.hex_stamp_log)
701 	{
702 		if (!ts) ts = time(0);
703 		len = get_stamp_str (prefs.hex_stamp_log_format, ts, &stamp);
704 		if (len)
705 		{
706 			write (sess->logfd, stamp, len);
707 			g_free (stamp);
708 		}
709 	}
710 
711 	temp = strip_color (text, -1, STRIP_ALL);
712 	len = strlen (temp);
713 	write (sess->logfd, temp, len);
714 	/* lots of scripts/plugins print without a \n at the end */
715 	if (temp[len - 1] != '\n')
716 		write (sess->logfd, "\n", 1);	/* emulate what xtext would display */
717 	g_free (temp);
718 }
719 
720 /**
721  * Converts a given string using the given iconv converter. This is similar to g_convert_with_fallback, except that it is tolerant of sequences in
722  * the original input that are invalid even in from_encoding. g_convert_with_fallback fails for such text, whereas this function replaces such a
723  * sequence with the fallback string.
724  *
725  * If len is -1, strlen(text) is used to calculate the length. Do not pass -1 if text is supposed to contain \0 bytes, such as if from_encoding is a
726  * multi-byte encoding like UTF-16.
727  */
728 gchar *
text_convert_invalid(const gchar * text,gssize len,GIConv converter,const gchar * fallback,gsize * len_out)729 text_convert_invalid (const gchar* text, gssize len, GIConv converter, const gchar *fallback, gsize *len_out)
730 {
731 	gchar *result_part;
732 	gsize result_part_len;
733 	const gchar *end;
734 	gsize invalid_start_pos;
735 	GString *result;
736 	const gchar *current_start;
737 
738 	if (len == -1)
739 	{
740 		len = strlen (text);
741 	}
742 
743 	end = text + len;
744 
745 	/* Find the first position of an invalid sequence. */
746 	result_part = g_convert_with_iconv (text, len, converter, &invalid_start_pos, &result_part_len, NULL);
747 	g_iconv (converter, NULL, NULL, NULL, NULL);
748 
749 	if (result_part != NULL)
750 	{
751 		/* All text converted successfully on the first try. Return it. */
752 
753 		if (len_out != NULL)
754 		{
755 			*len_out = result_part_len;
756 		}
757 
758 		return result_part;
759 	}
760 
761 	/* One or more invalid sequences exist that need to be replaced with the fallback. */
762 
763 	result = g_string_sized_new (len);
764 	current_start = text;
765 
766 	for (;;)
767 	{
768 		g_assert (current_start + invalid_start_pos < end);
769 
770 		/* Convert everything before the position of the invalid sequence. It should be successful.
771 		 * But iconv may not convert everything till invalid_start_pos since the last few bytes may be part of a shift sequence.
772 		 * So get the new bytes_read and use it as the actual invalid_start_pos to handle this.
773 		 *
774 		 * See https://github.com/hexchat/hexchat/issues/1758
775 		 */
776 		result_part = g_convert_with_iconv (current_start, invalid_start_pos, converter, &invalid_start_pos, &result_part_len, NULL);
777 		g_iconv (converter, NULL, NULL, NULL, NULL);
778 
779 		g_assert (result_part != NULL);
780 		g_string_append_len (result, result_part, result_part_len);
781 		g_free (result_part);
782 
783 		/* Append the fallback */
784 		g_string_append (result, fallback);
785 
786 		/* Now try converting everything after the invalid sequence. */
787 		current_start += invalid_start_pos + 1;
788 
789 		result_part = g_convert_with_iconv (current_start, end - current_start, converter, &invalid_start_pos, &result_part_len, NULL);
790 		g_iconv (converter, NULL, NULL, NULL, NULL);
791 
792 		if (result_part != NULL)
793 		{
794 			/* The rest of the text converted successfully. Append it and return the whole converted text. */
795 
796 			g_string_append_len (result, result_part, result_part_len);
797 			g_free (result_part);
798 
799 			if (len_out != NULL)
800 			{
801 				*len_out = result->len;
802 			}
803 
804 			return g_string_free (result, FALSE);
805 		}
806 
807 		/* The rest of the text didn't convert successfully. invalid_start_pos has the position of the next invalid sequence. */
808 	}
809 }
810 
811 /**
812  * Replaces any invalid UTF-8 in the given text with the unicode replacement character.
813  */
814 gchar *
text_fixup_invalid_utf8(const gchar * text,gssize len,gsize * len_out)815 text_fixup_invalid_utf8 (const gchar* text, gssize len, gsize *len_out)
816 {
817 #if GLIB_CHECK_VERSION (2, 52, 0)
818 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
819 	gchar *result = g_utf8_make_valid (text, len);
820 G_GNUC_END_IGNORE_DEPRECATIONS
821 	if (len_out)
822 	{
823 		*len_out = strlen (result);
824 	}
825 	return result;
826 #else
827 	static GIConv utf8_fixup_converter = NULL;
828 	if (utf8_fixup_converter == NULL)
829 	{
830 		utf8_fixup_converter = g_iconv_open ("UTF-8", "UTF-8");
831 	}
832 
833 	return text_convert_invalid (text, len, utf8_fixup_converter, unicode_fallback_string, len_out);
834 #endif
835 }
836 
837 void
PrintTextTimeStamp(session * sess,char * text,time_t timestamp)838 PrintTextTimeStamp (session *sess, char *text, time_t timestamp)
839 {
840 	if (!sess)
841 	{
842 		if (!sess_list)
843 			return;
844 		sess = (session *) sess_list->data;
845 	}
846 
847 	/* make sure it's valid utf8 */
848 	if (text[0] == '\0')
849 	{
850 		text = g_strdup ("\n");
851 	}
852 	else
853 	{
854 		text = text_fixup_invalid_utf8 (text, -1, NULL);
855 	}
856 
857 	log_write (sess, text, timestamp);
858 	scrollback_save (sess, text, timestamp);
859 	fe_print_text (sess, text, timestamp, FALSE);
860 	g_free (text);
861 }
862 
863 void
PrintText(session * sess,char * text)864 PrintText (session *sess, char *text)
865 {
866 	PrintTextTimeStamp (sess, text, 0);
867 }
868 
869 void
PrintTextf(session * sess,const char * format,...)870 PrintTextf (session *sess, const char *format, ...)
871 {
872 	va_list args;
873 	char *buf;
874 
875 	va_start (args, format);
876 	buf = g_strdup_vprintf (format, args);
877 	va_end (args);
878 
879 	PrintText (sess, buf);
880 	g_free (buf);
881 }
882 
883 void
PrintTextTimeStampf(session * sess,time_t timestamp,const char * format,...)884 PrintTextTimeStampf (session *sess, time_t timestamp, const char *format, ...)
885 {
886 	va_list args;
887 	char *buf;
888 
889 	va_start (args, format);
890 	buf = g_strdup_vprintf (format, args);
891 	va_end (args);
892 
893 	PrintTextTimeStamp (sess, buf, timestamp);
894 	g_free (buf);
895 }
896 
897 /* Print Events stuff here --AGL */
898 
899 /* Consider the following a NOTES file:
900 
901    The main upshot of this is:
902    * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like
903    * The default text engine can be config'ed
904 
905    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:
906 
907    The normal %Cx (color) and %B (bold) etc work
908 
909    $x is replaced with the data in var x (e.g. $1 is often the nick)
910 
911    $axxx is replace with a single byte of value xxx (in base 10)
912 
913    AGL (990507)
914  */
915 
916 /* These lists are thus:
917    pntevts_text[] are the strings the user sees (WITH %x etc)
918    pntevts[] are the data strings with \000 etc
919  */
920 
921 /* To add a new event:
922 
923    Think up a name (like "Join")
924    Make up a pevt_name_help struct
925 	Add an entry to textevents.in
926 	Type: make textevents
927  */
928 
929 /* Internals:
930 
931    On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the
932    defaults are loaded. Any missing events are filled from defaults.
933    Each event is parsed by pevt_build_string and a binary output is produced
934    which looks like:
935 
936    (byte) value: 0 = {
937    (int) numbers of bytes
938    (char []) that number of byte to be memcpy'ed into the buffer
939    }
940    1 =
941    (byte) number of varable to insert
942    2 = end of buffer
943 
944    Each XP_TE_* signal is hard coded to call text_emit which calls
945    display_event which decodes the data
946 
947    This means that this system *should be faster* than g_snprintf because
948    it always 'knows' that format of the string (basically is preparses much
949    of the work)
950 
951    --AGL
952  */
953 
954 char *pntevts_text[NUM_XP];
955 char *pntevts[NUM_XP];
956 
957 #define pevt_generic_none_help NULL
958 
959 static char * const pevt_genmsg_help[] = {
960 	N_("Left message"),
961 	N_("Right message"),
962 };
963 
964 #if 0
965 static char * const pevt_identd_help[] = {
966 	N_("IP address"),
967 	N_("Username")
968 };
969 #endif
970 
971 static char * const pevt_join_help[] = {
972 	N_("The nick of the joining person"),
973 	N_("The channel being joined"),
974 	N_("The host of the person"),
975 	N_("The account of the person"),
976 };
977 
978 static char * const pevt_chanaction_help[] = {
979 	N_("Nickname"),
980 	N_("The action"),
981 	N_("Mode char"),
982 	N_("Identified text"),
983 };
984 
985 static char * const pevt_chanmsg_help[] = {
986 	N_("Nickname"),
987 	N_("The text"),
988 	N_("Mode char"),
989 	N_("Identified text"),
990 };
991 
992 static char * const pevt_privmsg_help[] = {
993 	N_("Nickname"),
994 	N_("The message"),
995 	N_("Identified text")
996 };
997 
998 static char * const pevt_capack_help[] = {
999 	N_("Server Name"),
1000 	N_("Acknowledged Capabilities")
1001 };
1002 
1003 static char * const pevt_capdel_help[] = {
1004 	N_("Server Name"),
1005 	N_("Removed Capabilities")
1006 };
1007 
1008 static char * const pevt_caplist_help[] = {
1009 	N_("Server Name"),
1010 	N_("Server Capabilities")
1011 };
1012 
1013 static char * const pevt_capreq_help[] = {
1014 	N_("Requested Capabilities")
1015 };
1016 
1017 static char * const pevt_changenick_help[] = {
1018 	N_("Old nickname"),
1019 	N_("New nickname"),
1020 };
1021 
1022 static char * const pevt_newtopic_help[] = {
1023 	N_("Nick of person who changed the topic"),
1024 	N_("Topic"),
1025 	N_("Channel"),
1026 };
1027 
1028 static char * const pevt_topic_help[] = {
1029 	N_("Channel"),
1030 	N_("Topic"),
1031 };
1032 
1033 static char * const pevt_kick_help[] = {
1034 	N_("The nickname of the kicker"),
1035 	N_("The person being kicked"),
1036 	N_("The channel"),
1037 	N_("The reason"),
1038 };
1039 
1040 static char * const pevt_part_help[] = {
1041 	N_("The nick of the person leaving"),
1042 	N_("The host of the person"),
1043 	N_("The channel"),
1044 };
1045 
1046 static char * const pevt_chandate_help[] = {
1047 	N_("The channel"),
1048 	N_("The time"),
1049 };
1050 
1051 static char * const pevt_topicdate_help[] = {
1052 	N_("The channel"),
1053 	N_("The creator"),
1054 	N_("The time"),
1055 };
1056 
1057 static char * const pevt_quit_help[] = {
1058 	N_("Nick"),
1059 	N_("Reason"),
1060 	N_("Host"),
1061 };
1062 
1063 static char * const pevt_pingrep_help[] = {
1064 	N_("Who it's from"),
1065 	N_("The time in x.x format (see below)"),
1066 };
1067 
1068 static char * const pevt_notice_help[] = {
1069 	N_("Who it's from"),
1070 	N_("The message"),
1071 };
1072 
1073 static char * const pevt_channotice_help[] = {
1074 	N_("Who it's from"),
1075 	N_("The Channel it's going to"),
1076 	N_("The message"),
1077 };
1078 
1079 static char * const pevt_uchangenick_help[] = {
1080 	N_("Old nickname"),
1081 	N_("New nickname"),
1082 };
1083 
1084 static char * const pevt_ukick_help[] = {
1085 	N_("The person being kicked"),
1086 	N_("The channel"),
1087 	N_("The nickname of the kicker"),
1088 	N_("The reason"),
1089 };
1090 
1091 static char * const pevt_partreason_help[] = {
1092 	N_("The nick of the person leaving"),
1093 	N_("The host of the person"),
1094 	N_("The channel"),
1095 	N_("The reason"),
1096 };
1097 
1098 static char * const pevt_ctcpsnd_help[] = {
1099 	N_("The sound"),
1100 	N_("The nick of the person"),
1101 	N_("The channel"),
1102 };
1103 
1104 static char * const pevt_ctcpgen_help[] = {
1105 	N_("The CTCP event"),
1106 	N_("The nick of the person"),
1107 };
1108 
1109 static char * const pevt_ctcpgenc_help[] = {
1110 	N_("The CTCP event"),
1111 	N_("The nick of the person"),
1112 	N_("The Channel it's going to"),
1113 };
1114 
1115 static char * const pevt_chansetkey_help[] = {
1116 	N_("The nick of the person who set the key"),
1117 	N_("The key"),
1118 };
1119 
1120 static char * const pevt_chansetlimit_help[] = {
1121 	N_("The nick of the person who set the limit"),
1122 	N_("The limit"),
1123 };
1124 
1125 static char * const pevt_chanop_help[] = {
1126 	N_("The nick of the person who did the op'ing"),
1127 	N_("The nick of the person who has been op'ed"),
1128 };
1129 
1130 static char * const pevt_chanhop_help[] = {
1131 	N_("The nick of the person who has been halfop'ed"),
1132 	N_("The nick of the person who did the halfop'ing"),
1133 };
1134 
1135 static char * const pevt_chanvoice_help[] = {
1136 	N_("The nick of the person who did the voice'ing"),
1137 	N_("The nick of the person who has been voice'ed"),
1138 };
1139 
1140 static char * const pevt_chanban_help[] = {
1141 	N_("The nick of the person who did the banning"),
1142 	N_("The ban mask"),
1143 };
1144 
1145 static char * const pevt_chanquiet_help[] = {
1146 	N_("The nick of the person who did the quieting"),
1147 	N_("The quiet mask"),
1148 };
1149 
1150 static char * const pevt_chanrmkey_help[] = {
1151 	N_("The nick who removed the key"),
1152 };
1153 
1154 static char * const pevt_chanrmlimit_help[] = {
1155 	N_("The nick who removed the limit"),
1156 };
1157 
1158 static char * const pevt_chandeop_help[] = {
1159 	N_("The nick of the person who did the deop'ing"),
1160 	N_("The nick of the person who has been deop'ed"),
1161 };
1162 static char * const pevt_chandehop_help[] = {
1163 	N_("The nick of the person who did the dehalfop'ing"),
1164 	N_("The nick of the person who has been dehalfop'ed"),
1165 };
1166 
1167 static char * const pevt_chandevoice_help[] = {
1168 	N_("The nick of the person who did the devoice'ing"),
1169 	N_("The nick of the person who has been devoice'ed"),
1170 };
1171 
1172 static char * const pevt_chanunban_help[] = {
1173 	N_("The nick of the person who did the unban'ing"),
1174 	N_("The ban mask"),
1175 };
1176 
1177 static char * const pevt_chanunquiet_help[] = {
1178 	N_("The nick of the person who did the unquiet'ing"),
1179 	N_("The quiet mask"),
1180 };
1181 
1182 static char * const pevt_chanexempt_help[] = {
1183 	N_("The nick of the person who did the exempt"),
1184 	N_("The exempt mask"),
1185 };
1186 
1187 static char * const pevt_chanrmexempt_help[] = {
1188 	N_("The nick of the person removed the exempt"),
1189 	N_("The exempt mask"),
1190 };
1191 
1192 static char * const pevt_chaninvite_help[] = {
1193 	N_("The nick of the person who did the invite"),
1194 	N_("The invite mask"),
1195 };
1196 
1197 static char * const pevt_chanrminvite_help[] = {
1198 	N_("The nick of the person removed the invite"),
1199 	N_("The invite mask"),
1200 };
1201 
1202 static char * const pevt_chanmodegen_help[] = {
1203 	N_("The nick of the person setting the mode"),
1204 	N_("The mode's sign (+/-)"),
1205 	N_("The mode letter"),
1206 	N_("The channel it's being set on"),
1207 };
1208 
1209 static char * const pevt_whois1_help[] = {
1210 	N_("Nickname"),
1211 	N_("Username"),
1212 	N_("Host"),
1213 	N_("Full name"),
1214 };
1215 
1216 static char * const pevt_whois2_help[] = {
1217 	N_("Nickname"),
1218 	N_("Channel Membership/\"is an IRC operator\""),
1219 };
1220 
1221 static char * const pevt_whois3_help[] = {
1222 	N_("Nickname"),
1223 	N_("Server Information"),
1224 };
1225 
1226 static char * const pevt_whois4_help[] = {
1227 	N_("Nickname"),
1228 	N_("Idle time"),
1229 };
1230 
1231 static char * const pevt_whois4t_help[] = {
1232 	N_("Nickname"),
1233 	N_("Idle time"),
1234 	N_("Signon time"),
1235 };
1236 
1237 static char * const pevt_whois5_help[] = {
1238 	N_("Nickname"),
1239 	N_("Away reason"),
1240 };
1241 
1242 static char * const pevt_whois6_help[] = {
1243 	N_("Nickname"),
1244 };
1245 
1246 static char * const pevt_whoisid_help[] = {
1247 	N_("Nickname"),
1248 	N_("Message"),
1249 	"Numeric"
1250 };
1251 
1252 static char * const pevt_whoisauth_help[] = {
1253 	N_("Nickname"),
1254 	N_("Message"),
1255 	N_("Account"),
1256 };
1257 
1258 static char * const pevt_whoisrealhost_help[] = {
1259 	N_("Nickname"),
1260 	N_("Real user@host"),
1261 	N_("Real IP"),
1262 	N_("Message"),
1263 };
1264 
1265 static char * const pevt_generic_channel_help[] = {
1266 	N_("Channel Name"),
1267 };
1268 
1269 static char * const pevt_saslauth_help[] = {
1270 	N_("Username"),
1271 	N_("Mechanism")
1272 };
1273 
1274 static char * const pevt_saslresponse_help[] = {
1275 	N_("Server Name"),
1276 	N_("Raw Numeric or Identifier"),
1277 	N_("Username"),
1278 	N_("Message")
1279 };
1280 
1281 static char * const pevt_servertext_help[] = {
1282 	N_("Text"),
1283 	N_("Server Name"),
1284 	N_("Raw Numeric or Identifier")
1285 };
1286 
1287 static char * const pevt_sslmessage_help[] = {
1288 	N_("Text"),
1289 	N_("Server Name")
1290 };
1291 
1292 static char * const pevt_invited_help[] = {
1293 	N_("Channel Name"),
1294 	N_("Nick of person who invited you"),
1295 	N_("Server Name"),
1296 };
1297 
1298 static char * const pevt_invitedother_help[] = {
1299 	N_("Channel Name"),
1300 	N_("Nick of person who sent the invite"),
1301 	N_("Nick of person who was invited"),
1302 	N_("Server Name"),
1303 };
1304 
1305 static char * const pevt_usersonchan_help[] = {
1306 	N_("Channel Name"),
1307 	N_("Users"),
1308 };
1309 
1310 static char * const pevt_nickclash_help[] = {
1311 	N_("Nickname in use"),
1312 	N_("Nick being tried"),
1313 };
1314 
1315 static char * const pevt_connfail_help[] = {
1316 	N_("Error"),
1317 };
1318 
1319 static char * const pevt_connect_help[] = {
1320 	N_("Host"),
1321 	N_("IP"),
1322 	N_("Port"),
1323 };
1324 
1325 static char * const pevt_sconnect_help[] = {
1326 	"PID"
1327 };
1328 
1329 static char * const pevt_generic_nick_help[] = {
1330 	N_("Nickname"),
1331 	N_("Server Name"),
1332 	N_("Network")
1333 };
1334 
1335 static char * const pevt_chanmodes_help[] = {
1336 	N_("Channel Name"),
1337 	N_("Modes string"),
1338 };
1339 
1340 static char * const pevt_chanurl_help[] = {
1341 	N_("Channel Name"),
1342 	N_("URL"),
1343 };
1344 
1345 static char * const pevt_rawmodes_help[] = {
1346 	N_("Nickname"),
1347 	N_("Modes string"),
1348 };
1349 
1350 static char * const pevt_kill_help[] = {
1351 	N_("Nickname"),
1352 	N_("Reason"),
1353 };
1354 
1355 static char * const pevt_dccchaterr_help[] = {
1356 	N_("Nickname"),
1357 	N_("IP address"),
1358 	N_("Port"),
1359 	N_("Error"),
1360 };
1361 
1362 static char * const pevt_dccstall_help[] = {
1363 	N_("DCC Type"),
1364 	N_("Filename"),
1365 	N_("Nickname"),
1366 };
1367 
1368 static char * const pevt_generic_file_help[] = {
1369 	N_("Filename"),
1370 	N_("Error"),
1371 };
1372 
1373 static char * const pevt_dccrecverr_help[] = {
1374 	N_("Filename"),
1375 	N_("Destination filename"),
1376 	N_("Nickname"),
1377 	N_("Error"),
1378 };
1379 
1380 static char * const pevt_dccrecvcomp_help[] = {
1381 	N_("Filename"),
1382 	N_("Destination filename"),
1383 	N_("Nickname"),
1384 	N_("CPS"),
1385 };
1386 
1387 static char * const pevt_dccconfail_help[] = {
1388 	N_("DCC Type"),
1389 	N_("Nickname"),
1390 	N_("Error"),
1391 };
1392 
1393 static char * const pevt_dccchatcon_help[] = {
1394 	N_("Nickname"),
1395 	N_("IP address"),
1396 };
1397 
1398 static char * const pevt_dcccon_help[] = {
1399 	N_("Nickname"),
1400 	N_("IP address"),
1401 	N_("Filename"),
1402 };
1403 
1404 static char * const pevt_dccsendfail_help[] = {
1405 	N_("Filename"),
1406 	N_("Nickname"),
1407 	N_("Error"),
1408 };
1409 
1410 static char * const pevt_dccsendcomp_help[] = {
1411 	N_("Filename"),
1412 	N_("Nickname"),
1413 	N_("CPS"),
1414 };
1415 
1416 static char * const pevt_dccoffer_help[] = {
1417 	N_("Filename"),
1418 	N_("Nickname"),
1419 	N_("Pathname"),
1420 };
1421 
1422 static char * const pevt_dccfileabort_help[] = {
1423 	N_("Nickname"),
1424 	N_("Filename")
1425 };
1426 
1427 static char * const pevt_dccchatabort_help[] = {
1428 	N_("Nickname"),
1429 };
1430 
1431 static char * const pevt_dccresumeoffer_help[] = {
1432 	N_("Nickname"),
1433 	N_("Filename"),
1434 	N_("Position"),
1435 };
1436 
1437 static char * const pevt_dccsendoffer_help[] = {
1438 	N_("Nickname"),
1439 	N_("Filename"),
1440 	N_("Size"),
1441 	N_("IP address"),
1442 };
1443 
1444 static char * const pevt_dccgenericoffer_help[] = {
1445 	N_("DCC String"),
1446 	N_("Nickname"),
1447 };
1448 
1449 static char * const pevt_notifyaway_help[] = {
1450 	N_("Nickname"),
1451 	N_("Away Reason"),
1452 };
1453 
1454 static char * const pevt_notifynumber_help[] = {
1455 	N_("Number of notify items"),
1456 };
1457 
1458 static char * const pevt_serverlookup_help[] = {
1459 	N_("Server Name"),
1460 };
1461 
1462 static char * const pevt_servererror_help[] = {
1463 	N_("Text"),
1464 };
1465 
1466 static char * const pevt_foundip_help[] = {
1467 	N_("IP"),
1468 };
1469 
1470 static char * const pevt_dccrename_help[] = {
1471 	N_("Old Filename"),
1472 	N_("New Filename"),
1473 };
1474 
1475 static char * const pevt_ctcpsend_help[] = {
1476 	N_("Receiver"),
1477 	N_("Message"),
1478 };
1479 
1480 static char * const pevt_ignoreaddremove_help[] = {
1481 	N_("Hostmask"),
1482 };
1483 
1484 static char * const pevt_resolvinguser_help[] = {
1485 	N_("Nickname"),
1486 	N_("Hostname"),
1487 };
1488 
1489 static char * const pevt_malformed_help[] = {
1490 	N_("Nickname"),
1491 	N_("The Packet"),
1492 };
1493 
1494 static char * const pevt_pingtimeout_help[] = {
1495 	N_("Seconds"),
1496 };
1497 
1498 static char * const pevt_uinvite_help[] = {
1499 	N_("Nick of person who have been invited"),
1500 	N_("Channel Name"),
1501 	N_("Server Name"),
1502 };
1503 
1504 static char * const pevt_banlist_help[] = {
1505 	N_("Channel"),
1506 	N_("Banmask"),
1507 	N_("Who set the ban"),
1508 	N_("Ban time"),
1509 };
1510 
1511 static char * const pevt_discon_help[] = {
1512 	N_("Error"),
1513 };
1514 
1515 static char * const pevt_stdrpl_help[] = {
1516 	N_("Error Code"),
1517 	N_("Error Message"),
1518 };
1519 
1520 static char * const pevt_stdrplcmd_help[] = {
1521 	N_("Command"),
1522 	N_("Error Code"),
1523 	N_("Error Message"),
1524 };
1525 
1526 #include "textevents.h"
1527 
1528 static void
pevent_load_defaults(void)1529 pevent_load_defaults (void)
1530 {
1531 	int i;
1532 
1533 	for (i = 0; i < NUM_XP; i++)
1534 	{
1535 		g_free (pntevts_text[i]);
1536 
1537 		/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1538 		if (te[i].num_args & 128)
1539 			pntevts_text[i] = g_strdup (te[i].def);
1540 		else
1541 			pntevts_text[i] = g_strdup (_(te[i].def));
1542 	}
1543 }
1544 
1545 void
pevent_make_pntevts(void)1546 pevent_make_pntevts (void)
1547 {
1548 	int i, m;
1549 
1550 	for (i = 0; i < NUM_XP; i++)
1551 	{
1552 		g_free (pntevts[i]);
1553 		if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
1554 		{
1555 			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1556 			const gboolean translate = !(te[i].num_args & 128);
1557 
1558 			g_warning ("Error parsing event %s\nLoading default.", te[i].name);
1559 			g_free (pntevts_text[i]);
1560 
1561 			if (translate)
1562 				pntevts_text[i] = g_strdup (_(te[i].def));
1563 			else
1564 				pntevts_text[i] = g_strdup (te[i].def);
1565 
1566 			if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0 && !translate)
1567 			{
1568 				g_error ("HexChat CRITICAL *** default event text failed to build!");
1569 			}
1570 			else
1571 			{
1572 				g_warning ("Error parsing translated event %s\nLoading untranslated.", te[i].name);
1573 				g_free (pntevts_text[i]);
1574 
1575 				pntevts_text[i] = g_strdup (te[i].def);
1576 
1577 				if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
1578 				{
1579 					g_error ("HexChat CRITICAL *** default event text failed to build!");
1580 				}
1581 			}
1582 		}
1583 	}
1584 }
1585 
1586 /* Loading happens at 2 levels:
1587    1) File is read into blocks
1588    2) Pe block is parsed and loaded
1589 
1590    --AGL */
1591 
1592 /* Better hope you pass good args.. --AGL */
1593 
1594 static void
pevent_trigger_load(int * i_penum,char ** i_text,char ** i_snd)1595 pevent_trigger_load (int *i_penum, char **i_text, char **i_snd)
1596 {
1597 	int penum = *i_penum;
1598 	char *text = *i_text, *snd = *i_snd;
1599 
1600 	if (penum != -1 && text != NULL)
1601 	{
1602 		g_free (pntevts_text[penum]);
1603 		pntevts_text[penum] = g_strdup (text);
1604 	}
1605 
1606 	g_free (text);
1607 	g_free (snd);
1608 	*i_text = NULL;
1609 	*i_snd = NULL;
1610 	*i_penum = 0;
1611 }
1612 
1613 static int
pevent_find(char * name,int * i_i)1614 pevent_find (char *name, int *i_i)
1615 {
1616 	int i = *i_i, j;
1617 
1618 	j = i + 1;
1619 	while (1)
1620 	{
1621 		if (j == NUM_XP)
1622 			j = 0;
1623 		if (strcmp (te[j].name, name) == 0)
1624 		{
1625 			*i_i = j;
1626 			return j;
1627 		}
1628 		if (j == i)
1629 			return -1;
1630 		j++;
1631 	}
1632 }
1633 
1634 int
pevent_load(char * filename)1635 pevent_load (char *filename)
1636 {
1637 	/* AGL, I've changed this file and pevent_save, could you please take a look at
1638 	 *      the changes and possibly modify them to suit you
1639 	 *      //David H
1640 	 */
1641 	char *buf, *ibuf;
1642 	int fd, i = 0, pnt = 0;
1643 	struct stat st;
1644 	char *text = NULL, *snd = NULL;
1645 	int penum = 0;
1646 	char *ofs;
1647 
1648 	if (filename == NULL)
1649 		fd = hexchat_open_file ("pevents.conf", O_RDONLY, 0, 0);
1650 	else
1651 		fd = hexchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);
1652 
1653 	if (fd == -1)
1654 		return 1;
1655 	if (fstat (fd, &st) != 0)
1656 	{
1657 		close (fd);
1658 		return 1;
1659 	}
1660 	ibuf = g_malloc (st.st_size);
1661 	read (fd, ibuf, st.st_size);
1662 	close (fd);
1663 
1664 	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
1665 	{
1666 		if (buf[0] == '#')
1667 			continue;
1668 		if (strlen (buf) == 0)
1669 			continue;
1670 
1671 		ofs = strchr (buf, '=');
1672 		if (!ofs)
1673 			continue;
1674 		*ofs = 0;
1675 		ofs++;
1676 
1677 		if (strcmp (buf, "event_name") == 0)
1678 		{
1679 			if (penum >= 0)
1680 				pevent_trigger_load (&penum, &text, &snd);
1681 			penum = pevent_find (ofs, &i);
1682 			continue;
1683 		} else if (strcmp (buf, "event_text") == 0)
1684 		{
1685 			g_free (text);
1686 			text = g_strdup (ofs);
1687 			continue;
1688 		}
1689 
1690 		continue;
1691 	}
1692 
1693 	pevent_trigger_load (&penum, &text, &snd);
1694 	g_free (ibuf);
1695 	return 0;
1696 }
1697 
1698 static void
pevent_check_all_loaded(void)1699 pevent_check_all_loaded (void)
1700 {
1701 	int i;
1702 
1703 	for (i = 0; i < NUM_XP; i++)
1704 	{
1705 		if (pntevts_text[i] == NULL)
1706 		{
1707 			/*printf ("%s\n", te[i].name);
1708 			g_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]);
1709 			   gtkutil_simpledialog(out); */
1710 			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1711 			if (te[i].num_args & 128)
1712 				pntevts_text[i] = g_strdup (te[i].def);
1713 			else
1714 				pntevts_text[i] = g_strdup (_(te[i].def));
1715 		}
1716 	}
1717 }
1718 
1719 void
load_text_events()1720 load_text_events ()
1721 {
1722 	memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP));
1723 	memset (&pntevts, 0, sizeof (char *) * (NUM_XP));
1724 
1725 	if (pevent_load (NULL))
1726 		pevent_load_defaults ();
1727 	pevent_check_all_loaded ();
1728 	pevent_make_pntevts ();
1729 }
1730 
1731 /*
1732 	CL: format_event now handles filtering of arguments:
1733 	1) if prefs.hex_text_stripcolor_msg is set, filter all style control codes from arguments
1734 	2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself
1735 */
1736 #define ARG_FLAG(argn) (1 << (argn))
1737 
1738 void
format_event(session * sess,int index,char ** args,char * o,gsize sizeofo,unsigned int stripcolor_args)1739 format_event (session *sess, int index, char **args, char *o, gsize sizeofo, unsigned int stripcolor_args)
1740 {
1741 	int len, ii, numargs;
1742 	gsize oi;
1743 	char *i, *ar, d, a, done_all = FALSE;
1744 
1745 	i = pntevts[index];
1746 	numargs = te[index].num_args & 0x7f;
1747 
1748 	oi = ii = len = d = a = 0;
1749 	o[0] = 0;
1750 
1751 	if (i == NULL)
1752 		return;
1753 
1754 	while (done_all == FALSE)
1755 	{
1756 		d = i[ii++];
1757 		switch (d)
1758 		{
1759 		case 0:
1760 			memcpy (&len, &(i[ii]), sizeof (int));
1761 			ii += sizeof (int);
1762 			if (oi + len > sizeofo)
1763 			{
1764 				printf ("Overflow in display_event (%s)\n", i);
1765 				o[0] = 0;
1766 				return;
1767 			}
1768 			memcpy (&(o[oi]), &(i[ii]), len);
1769 			oi += len;
1770 			ii += len;
1771 			break;
1772 		case 1:
1773 			a = i[ii++];
1774 			if (a > numargs)
1775 			{
1776 				fprintf (stderr,
1777 							"HexChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
1778 							a, numargs, i);
1779 				break;
1780 			}
1781 			ar = args[(int) a + 1];
1782 			if (ar == NULL)
1783 			{
1784 				printf ("arg[%d] is NULL in print event\n", a + 1);
1785 			} else
1786 			{
1787 				if (strlen (ar) > sizeofo - oi - 4)
1788 					ar[sizeofo - oi - 4] = 0;	/* Avoid buffer overflow */
1789 				if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL);
1790 				else len = strip_hidden_attribute (ar, &o[oi]);
1791 				oi += len;
1792 			}
1793 			break;
1794 		case 2:
1795 			o[oi++] = '\n';
1796 			o[oi++] = 0;
1797 			done_all = TRUE;
1798 			continue;
1799 		case 3:
1800 			if (prefs.hex_text_indent)
1801 				o[oi++] = '\t';
1802 			else
1803 				o[oi++] = ' ';
1804 			break;
1805 		}
1806 	}
1807 	o[oi] = 0;
1808 	if (*o == '\n')
1809 		o[0] = 0;
1810 }
1811 
1812 static void
display_event(session * sess,int event,char ** args,unsigned int stripcolor_args,time_t timestamp)1813 display_event (session *sess, int event, char **args,
1814 					unsigned int stripcolor_args, time_t timestamp)
1815 {
1816 	char o[4096];
1817 	format_event (sess, event, args, o, sizeof (o), stripcolor_args);
1818 	if (o[0])
1819 		PrintTextTimeStamp (sess, o, timestamp);
1820 }
1821 
1822 int
pevt_build_string(const char * input,char ** output,int * max_arg)1823 pevt_build_string (const char *input, char **output, int *max_arg)
1824 {
1825 	struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next;
1826 	int clen;
1827 	char o[4096], d, *obuf, *i;
1828 	int oi, ii, max = -1, len, x;
1829 
1830 	len = strlen (input);
1831 	i = g_malloc (len + 1);
1832 	memcpy (i, input, len + 1);
1833 	check_special_chars (i, TRUE);
1834 
1835 	len = strlen (i);
1836 
1837 	clen = oi = ii = 0;
1838 
1839 	for (;;)
1840 	{
1841 		if (ii == len)
1842 			break;
1843 		d = i[ii++];
1844 		if (d != '$')
1845 		{
1846 			o[oi++] = d;
1847 			continue;
1848 		}
1849 		if (i[ii] == '$')
1850 		{
1851 			o[oi++] = '$';
1852 			continue;
1853 		}
1854 		if (oi > 0)
1855 		{
1856 			s = g_new (struct pevt_stage1, 1);
1857 			if (base == NULL)
1858 				base = s;
1859 			if (last != NULL)
1860 				last->next = s;
1861 			last = s;
1862 			s->next = NULL;
1863 			s->data = g_malloc (oi + sizeof (int) + 1);
1864 			s->len = oi + sizeof (int) + 1;
1865 			clen += oi + sizeof (int) + 1;
1866 			s->data[0] = 0;
1867 			memcpy (&(s->data[1]), &oi, sizeof (int));
1868 			memcpy (&(s->data[1 + sizeof (int)]), o, oi);
1869 			oi = 0;
1870 		}
1871 		if (ii == len)
1872 		{
1873 			fe_message ("String ends with a $", FE_MSG_WARN);
1874 			goto err;
1875 		}
1876 		d = i[ii++];
1877 		if (d == 'a')
1878 		{
1879 			/* Hex value */
1880 			if (ii == len)
1881 				goto a_len_error;
1882 			d = i[ii++];
1883 			d -= '0';
1884 			x = d * 100;
1885 			if (ii == len)
1886 				goto a_len_error;
1887 			d = i[ii++];
1888 			d -= '0';
1889 			x += d * 10;
1890 			if (ii == len)
1891 				goto a_len_error;
1892 			d = i[ii++];
1893 			d -= '0';
1894 			x += d;
1895 			if (x > 255)
1896 				goto a_range_error;
1897 			o[oi++] = x;
1898 			continue;
1899 
1900 		a_len_error:
1901 			fe_message ("String ends in $a", FE_MSG_WARN);
1902 			goto err;
1903 		a_range_error:
1904 			fe_message ("$a value is greater than 255", FE_MSG_WARN);
1905 			goto err;
1906 		}
1907 		if (d == 't')
1908 		{
1909 			/* Tab - if tabnicks is set then write '\t' else ' ' */
1910 			s = g_new (struct pevt_stage1, 1);
1911 			if (base == NULL)
1912 				base = s;
1913 			if (last != NULL)
1914 				last->next = s;
1915 			last = s;
1916 			s->next = NULL;
1917 			s->data = g_malloc (1);
1918 			s->len = 1;
1919 			clen += 1;
1920 			s->data[0] = 3;
1921 
1922 			continue;
1923 		}
1924 		if (d < '1' || d > '9')
1925 		{
1926 			g_snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d);
1927 			fe_message (o, FE_MSG_WARN);
1928 			goto err;
1929 		}
1930 		d -= '0';
1931 		if (max < d)
1932 			max = d;
1933 		s = g_new (struct pevt_stage1, 1);
1934 		if (base == NULL)
1935 			base = s;
1936 		if (last != NULL)
1937 			last->next = s;
1938 		last = s;
1939 		s->next = NULL;
1940 		s->data = g_malloc (2);
1941 		s->len = 2;
1942 		clen += 2;
1943 		s->data[0] = 1;
1944 		s->data[1] = d - 1;
1945 	}
1946 	if (oi > 0)
1947 	{
1948 		s = g_new (struct pevt_stage1, 1);
1949 		if (base == NULL)
1950 			base = s;
1951 		if (last != NULL)
1952 			last->next = s;
1953 		last = s;
1954 		s->next = NULL;
1955 		s->data = g_malloc (oi + sizeof (int) + 1);
1956 		s->len = oi + sizeof (int) + 1;
1957 		clen += oi + sizeof (int) + 1;
1958 		s->data[0] = 0;
1959 		memcpy (&(s->data[1]), &oi, sizeof (int));
1960 		memcpy (&(s->data[1 + sizeof (int)]), o, oi);
1961 		oi = 0;
1962 	}
1963 	s = g_new (struct pevt_stage1, 1);
1964 	if (base == NULL)
1965 		base = s;
1966 	if (last != NULL)
1967 		last->next = s;
1968 	s->next = NULL;
1969 	s->data = g_malloc (1);
1970 	s->len = 1;
1971 	clen += 1;
1972 	s->data[0] = 2;
1973 
1974 	oi = 0;
1975 	s = base;
1976 	obuf = g_malloc (clen);
1977 
1978 	while (s)
1979 	{
1980 		next = s->next;
1981 		memcpy (&obuf[oi], s->data, s->len);
1982 		oi += s->len;
1983 		g_free (s->data);
1984 		g_free (s);
1985 		s = next;
1986 	}
1987 
1988 	g_free (i);
1989 
1990 	if (max_arg)
1991 		*max_arg = max;
1992 	if (output)
1993 		*output = obuf;
1994 	else
1995 		g_free (obuf);
1996 
1997 	return 0;
1998 
1999 err:
2000 	while (s)
2001 	{
2002 		next = s->next;
2003 		g_free (s->data);
2004 		g_free (s);
2005 		s = next;
2006 	}
2007 
2008 	g_free(i);
2009 
2010 	return 1;
2011 }
2012 
2013 
2014 /* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */
2015 /* also light/dark gray (14/15) */
2016 /* 5,7,8 are all shades of yellow which happen to look damn near the same */
2017 
2018 static char rcolors[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 };
2019 
2020 int
text_color_of(char * name)2021 text_color_of (char *name)
2022 {
2023 	int i = 0, sum = 0;
2024 
2025 	while (name[i])
2026 		sum += name[i++];
2027 	sum %= sizeof (rcolors) / sizeof (char);
2028 	return rcolors[sum];
2029 }
2030 
2031 
2032 /* called by EMIT_SIGNAL macro */
2033 
2034 void
text_emit(int index,session * sess,char * a,char * b,char * c,char * d,time_t timestamp)2035 text_emit (int index, session *sess, char *a, char *b, char *c, char *d,
2036 			  time_t timestamp)
2037 {
2038 	char *word[PDIWORDS];
2039 	int i;
2040 	tab_state_flags current_state = sess->tab_state;
2041 	tab_state_flags plugin_state = sess->last_tab_state;
2042 	unsigned int stripcolor_args = (chanopt_is_set (prefs.hex_text_stripcolor_msg, sess->text_strip) ? 0xFFFFFFFF : 0);
2043 	char tbuf[NICKLEN + 4];
2044 
2045 	if (a != NULL && prefs.hex_text_color_nicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG))
2046 	{
2047 		g_snprintf (tbuf, sizeof (tbuf), "\003%d%s", text_color_of (a), a);
2048 		a = tbuf;
2049 		stripcolor_args &= ~ARG_FLAG(1);	/* don't strip color from this argument */
2050 	}
2051 
2052 	word[0] = te[index].name;
2053 	word[1] = (a ? a : "\000");
2054 	word[2] = (b ? b : "\000");
2055 	word[3] = (c ? c : "\000");
2056 	word[4] = (d ? d : "\000");
2057 	for (i = 5; i < PDIWORDS; i++)
2058 		word[i] = "\000";
2059 
2060 	/* We want to ignore the tab state if the plugin emits new events
2061 	 * and restore it if it doesn't eat the current one */
2062 	sess->tab_state = plugin_state;
2063 	if (plugin_emit_print (sess, word, timestamp))
2064 		return;
2065 
2066 	/* The plugin may have changed the state which we should respect.
2067 	 * If the state is NEW_DATA we don't actually know if that was on
2068 	 * purpose though as print() sets it, so for now we ignore that. FIXME */
2069 	if (sess->tab_state == plugin_state || sess->tab_state == TAB_STATE_NEW_DATA)
2070 		sess->tab_state = current_state;
2071 
2072 	/* If a plugin's callback executes "/close", 'sess' may be invalid */
2073 	if (!is_session (sess))
2074 		return;
2075 
2076 	switch (index)
2077 	{
2078 	case XP_TE_JOIN:
2079 	case XP_TE_PART:
2080 	case XP_TE_PARTREASON:
2081 	case XP_TE_QUIT:
2082 		/* implement ConfMode / Hide Join and Part Messages */
2083 		if (chanopt_is_set (prefs.hex_irc_conf_mode, sess->text_hidejoinpart))
2084 			return;
2085 		break;
2086 
2087 	/* ===Private message=== */
2088 	case XP_TE_PRIVMSG:
2089 	case XP_TE_DPRIVMSG:
2090 	case XP_TE_PRIVACTION:
2091 	case XP_TE_DPRIVACTION:
2092 		if (chanopt_is_set (prefs.hex_input_beep_priv, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2093 			sound_beep (sess);
2094 		if (chanopt_is_set (prefs.hex_input_flash_priv, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2095 			fe_flash_window (sess);
2096 		/* why is this one different? because of plugin-tray.c's hooks! ugly */
2097 		if (sess->alert_tray == SET_ON)
2098 			fe_tray_set_icon (FE_ICON_MESSAGE);
2099 		break;
2100 
2101 	/* ===Highlighted message=== */
2102 	case XP_TE_HCHANACTION:
2103 	case XP_TE_HCHANMSG:
2104 		if (chanopt_is_set (prefs.hex_input_beep_hilight, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2105 			sound_beep (sess);
2106 		if (chanopt_is_set (prefs.hex_input_flash_hilight, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2107 			fe_flash_window (sess);
2108 		if (sess->alert_tray == SET_ON)
2109 			fe_tray_set_icon (FE_ICON_MESSAGE);
2110 		break;
2111 
2112 	/* ===Channel message=== */
2113 	case XP_TE_CHANACTION:
2114 	case XP_TE_CHANMSG:
2115 		if (chanopt_is_set (prefs.hex_input_beep_chans, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2116 			sound_beep (sess);
2117 		if (chanopt_is_set (prefs.hex_input_flash_chans, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
2118 			fe_flash_window (sess);
2119 		if (sess->alert_tray == SET_ON)
2120 			fe_tray_set_icon (FE_ICON_MESSAGE);
2121 		break;
2122 
2123 	/* ===Nick change message=== */
2124 	case XP_TE_CHANGENICK:
2125 		if (prefs.hex_irc_hide_nickchange)
2126 			return;
2127 		break;
2128 	}
2129 
2130 	if (!prefs.hex_away_omit_alerts || !sess->server->is_away)
2131 		sound_play_event (index);
2132 	display_event (sess, index, word, stripcolor_args, timestamp);
2133 }
2134 
2135 char *
text_find_format_string(char * name)2136 text_find_format_string (char *name)
2137 {
2138 	int i = 0;
2139 
2140 	i = pevent_find (name, &i);
2141 	if (i >= 0)
2142 		return pntevts_text[i];
2143 
2144 	return NULL;
2145 }
2146 
2147 int
text_emit_by_name(char * name,session * sess,time_t timestamp,char * a,char * b,char * c,char * d)2148 text_emit_by_name (char *name, session *sess, time_t timestamp,
2149 				   char *a, char *b, char *c, char *d)
2150 {
2151 	int i = 0;
2152 
2153 	i = pevent_find (name, &i);
2154 	if (i >= 0)
2155 	{
2156 		text_emit (i, sess, a, b, c, d, timestamp);
2157 		return 1;
2158 	}
2159 
2160 	return 0;
2161 }
2162 
2163 void
pevent_save(char * fn)2164 pevent_save (char *fn)
2165 {
2166 	int fd, i;
2167 	char buf[1024];
2168 
2169 	if (!fn)
2170 		fd = hexchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY,
2171 									 0x180, XOF_DOMODE);
2172 	else
2173 		fd = hexchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180,
2174 									 XOF_FULLPATH | XOF_DOMODE);
2175 	if (fd == -1)
2176 	{
2177 		/*
2178 		   fe_message ("Error opening config file\n", FALSE);
2179 		   If we get here when X-Chat is closing the fe-message causes a nice & hard crash
2180 		   so we have to use perror which doesn't rely on GTK
2181 		 */
2182 
2183 		perror ("Error opening config file\n");
2184 		return;
2185 	}
2186 
2187 	for (i = 0; i < NUM_XP; i++)
2188 	{
2189 		write (fd, buf, g_snprintf (buf, sizeof (buf),
2190 										  "event_name=%s\n", te[i].name));
2191 		write (fd, buf, g_snprintf (buf, sizeof (buf),
2192 										  "event_text=%s\n\n", pntevts_text[i]));
2193 	}
2194 
2195 	close (fd);
2196 }
2197 
2198 /* =========================== */
2199 /* ========== SOUND ========== */
2200 /* =========================== */
2201 
2202 char *sound_files[NUM_XP];
2203 
2204 void
sound_beep(session * sess)2205 sound_beep (session *sess)
2206 {
2207 	if (!prefs.hex_gui_focus_omitalerts || fe_gui_info (sess, 0) != 1)
2208 	{
2209 		if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0])
2210 			/* user defined beep _file_ */
2211 			sound_play_event (XP_TE_BEEP);
2212 		else
2213 			/* system beep */
2214 			fe_beep (sess);
2215 	}
2216 }
2217 
2218 void
sound_play(const char * file,gboolean quiet)2219 sound_play (const char *file, gboolean quiet)
2220 {
2221 	char *buf;
2222 	char *wavfile;
2223 #ifndef WIN32
2224 	char *cmd;
2225 #endif
2226 
2227 	/* the pevents GUI editor triggers this after removing a soundfile */
2228 	if (!file[0])
2229 	{
2230 		return;
2231 	}
2232 
2233 	/* check for fullpath */
2234 	if (g_path_is_absolute (file))
2235 	{
2236 		wavfile = g_strdup (file);
2237 	}
2238 	else
2239 	{
2240 		wavfile = g_build_filename (get_xdir (), HEXCHAT_SOUND_DIR, file, NULL);
2241 	}
2242 
2243 	if (g_access (wavfile, R_OK) == 0)
2244 	{
2245 #ifdef WIN32
2246 		gunichar2 *wavfile_utf16 = g_utf8_to_utf16 (wavfile, -1, NULL, NULL, NULL);
2247 
2248 		if (wavfile_utf16 != NULL)
2249 		{
2250 			PlaySoundW (wavfile_utf16, NULL, SND_NODEFAULT | SND_FILENAME | SND_ASYNC);
2251 
2252 			g_free (wavfile_utf16);
2253 		}
2254 #else
2255 #ifdef USE_LIBCANBERRA
2256 		if (ca_con == NULL)
2257 		{
2258 			ca_context_create (&ca_con);
2259 			ca_context_change_props (ca_con,
2260 											CA_PROP_APPLICATION_ID, "hexchat",
2261 											CA_PROP_APPLICATION_NAME, "HexChat",
2262 											CA_PROP_APPLICATION_ICON_NAME, "hexchat", NULL);
2263 		}
2264 
2265 		if (ca_context_play (ca_con, 0, CA_PROP_MEDIA_FILENAME, wavfile, NULL) != 0)
2266 #endif
2267 		{
2268 			cmd = g_find_program_in_path ("play");
2269 
2270 			if (cmd)
2271 			{
2272 				buf = g_strdup_printf ("%s \"%s\"", cmd, wavfile);
2273 				hexchat_exec (buf);
2274 				g_free (buf);
2275 				g_free (cmd);
2276 			}
2277 		}
2278 #endif
2279 	}
2280 	else
2281 	{
2282 		if (!quiet)
2283 		{
2284 			buf = g_strdup_printf (_("Cannot read sound file:\n%s"), wavfile);
2285 			fe_message (buf, FE_MSG_ERROR);
2286 			g_free (buf);
2287 		}
2288 	}
2289 
2290 	g_free (wavfile);
2291 }
2292 
2293 void
sound_play_event(int i)2294 sound_play_event (int i)
2295 {
2296 	if (sound_files[i])
2297 	{
2298 		sound_play (sound_files[i], FALSE);
2299 	}
2300 }
2301 
2302 static void
sound_load_event(char * evt,char * file)2303 sound_load_event (char *evt, char *file)
2304 {
2305 	int i = 0;
2306 
2307 	if (file[0] && pevent_find (evt, &i) != -1)
2308 	{
2309 		g_free (sound_files[i]);
2310 		sound_files[i] = g_strdup (file);
2311 	}
2312 }
2313 
2314 void
sound_load()2315 sound_load ()
2316 {
2317 	int fd;
2318 	char buf[512];
2319 	char evt[128];
2320 
2321 	memset (&sound_files, 0, sizeof (char *) * (NUM_XP));
2322 
2323 	fd = hexchat_open_file ("sound.conf", O_RDONLY, 0, 0);
2324 	if (fd == -1)
2325 		return;
2326 
2327 	evt[0] = 0;
2328 	while (waitline (fd, buf, sizeof buf, FALSE) != -1)
2329 	{
2330 		if (strncmp (buf, "event=", 6) == 0)
2331 		{
2332 			safe_strcpy (evt, buf + 6, sizeof (evt));
2333 		}
2334 		else if (strncmp (buf, "sound=", 6) == 0)
2335 		{
2336 			if (evt[0] != 0)
2337 			{
2338 				sound_load_event (evt, buf + 6);
2339 				evt[0] = 0;
2340 			}
2341 		}
2342 	}
2343 
2344 	close (fd);
2345 }
2346 
2347 void
sound_save()2348 sound_save ()
2349 {
2350 	int fd, i;
2351 	char buf[512];
2352 
2353 	fd = hexchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180,
2354 								 XOF_DOMODE);
2355 	if (fd == -1)
2356 		return;
2357 
2358 	for (i = 0; i < NUM_XP; i++)
2359 	{
2360 		if (sound_files[i] && sound_files[i][0])
2361 		{
2362 			write (fd, buf, g_snprintf (buf, sizeof (buf),
2363 											  "event=%s\n", te[i].name));
2364 			write (fd, buf, g_snprintf (buf, sizeof (buf),
2365 											  "sound=%s\n\n", sound_files[i]));
2366 		}
2367 	}
2368 
2369 	close (fd);
2370 }
2371