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 (¤ttime)));
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 (¤ttime)));
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