2 * Copyright (C) 1998 Peter Zelezny.
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
25 #include <sys/types.h>
46 struct pevt_stage1
*next
;
50 static void mkdir_p (char *dir
);
51 static char *log_create_filename (char *channame
);
55 scrollback_get_filename (session
*sess
, char *buf
, int max
)
59 net
= server_get_network (sess
->server
, FALSE
);
63 snprintf (buf
, max
, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net
, "");
66 chan
= log_create_filename (sess
->channel
);
67 snprintf (buf
, max
, "%s/scrollback/%s/%s.txt", get_xdir_fs (), net
, chan
);
76 scrollback_unlock (session
*sess
)
80 if (scrollback_get_filename (sess
, buf
, sizeof (buf
) - 6) == NULL
)
83 strcat (buf
, ".lock");
88 scrollback_lock (session
*sess
)
93 if (scrollback_get_filename (sess
, buf
, sizeof (buf
) - 6) == NULL
)
96 strcat (buf
, ".lock");
98 if (access (buf
, F_OK
) == 0)
99 return FALSE
; /* can't get lock */
101 fh
= open (buf
, O_CREAT
| O_TRUNC
| O_APPEND
| O_WRONLY
, 0644);
111 scrollback_close (session
*sess
)
113 if (sess
->scrollfd
!= -1)
115 close (sess
->scrollfd
);
121 file_to_buffer (char *file
, int *len
)
127 fh
= open (file
, O_RDONLY
| OFLAGS
);
133 buf
= malloc (st
.st_size
);
140 if (read (fh
, buf
, st
.st_size
) != st
.st_size
)
152 /* shrink the file to roughly prefs.max_lines */
155 scrollback_shrink (session
*sess
)
165 scrollback_close (sess
);
166 sess
->scrollwritten
= 0;
169 if (scrollback_get_filename (sess
, file
, sizeof (file
)) == NULL
)
172 buf
= file_to_buffer (file
, &len
);
176 /* count all lines */
178 while (p
!= buf
+ len
)
185 fh
= open (file
, O_CREAT
| O_TRUNC
| O_APPEND
| O_WRONLY
, 0644);
194 while (p
!= buf
+ len
)
199 if (line
>= lines
- prefs
.max_lines
&&
203 write (fh
, p
, len
- (p
- buf
));
215 scrollback_save (session
*sess
, char *text
)
221 if (sess
->type
== SESS_SERVER
)
224 if (sess
->text_scrollback
== SET_DEFAULT
)
226 if (!prefs
.text_replay
)
231 if (sess
->text_scrollback
!= SET_ON
)
235 if (sess
->scrollfd
== -1)
237 if (scrollback_get_filename (sess
, buf
, sizeof (buf
)) == NULL
)
240 sess
->scrollfd
= open (buf
, O_CREAT
| O_APPEND
| O_WRONLY
, 0644);
241 if (sess
->scrollfd
== -1)
246 if (sizeof (stamp
) == 4) /* gcc will optimize one of these out */
247 write (sess
->scrollfd
, buf
, snprintf (buf
, sizeof (buf
), "T %d ", (int)stamp
));
249 write (sess
->scrollfd
, buf
, snprintf (buf
, sizeof (buf
), "T %"G_GINT64_FORMAT
" ", (gint64
)stamp
));
252 write (sess
->scrollfd
, text
, len
);
253 if (len
&& text
[len
- 1] != '\n')
254 write (sess
->scrollfd
, "\n", 1);
256 sess
->scrollwritten
++;
258 if ((sess
->scrollwritten
* 2 > prefs
.max_lines
&& prefs
.max_lines
> 0) ||
259 sess
->scrollwritten
> 32000)
260 scrollback_shrink (sess
);
264 scrollback_load (session
*sess
)
273 const char *begin
, *eol
;
275 if (sess
->text_scrollback
== SET_DEFAULT
)
277 if (!prefs
.text_replay
)
282 if (sess
->text_scrollback
!= SET_ON
)
286 if (scrollback_get_filename (sess
, buf
, sizeof (buf
)) == NULL
)
289 fh
= open (buf
, O_RDONLY
| OFLAGS
);
293 if (fstat (fh
, &statbuf
) < 0)
296 map
= mmap (NULL
, statbuf
.st_size
, PROT_READ
, MAP_PRIVATE
, fh
, 0);
297 if (map
== MAP_FAILED
)
300 end_map
= map
+ statbuf
.st_size
;
304 while (begin
< end_map
)
308 eol
= memchr (begin
, '\n', end_map
- begin
);
313 n_bytes
= MIN (eol
- begin
, sizeof (buf
) - 1);
315 strncpy (buf
, begin
, n_bytes
);
321 if (sizeof (time_t) == 4)
322 stamp
= strtoul (buf
+ 2, NULL
, 10);
324 stamp
= strtoull (buf
+ 2, NULL
, 10); /* just incase time_t is 64 bits */
325 text
= strchr (buf
+ 3, ' ');
328 text
= strip_color (text
+ 1, -1, STRIP_COLOR
);
329 fe_print_text (sess
, text
, stamp
);
338 sess
->scrollwritten
= lines
;
342 text
= ctime (&stamp
);
343 text
[24] = 0; /* get rid of the \n */
344 snprintf (buf
, sizeof (buf
), "\n*\t%s %s\n\n", _("Loaded log from"), text
);
345 fe_print_text (sess
, buf
, 0);
346 /*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/
349 munmap (map
, statbuf
.st_size
);
354 log_close (session
*sess
)
359 if (sess
->logfd
!= -1)
361 currenttime
= time (NULL
);
362 write (sess
->logfd
, obuf
,
363 snprintf (obuf
, sizeof (obuf
) - 1, _("**** ENDING LOGGING AT %s\n"),
364 ctime (¤ttime
)));
371 mkdir_p (char *dir
) /* like "mkdir -p" from a shell, FS encoding */
375 /* the whole thing already exists? */
376 if (access (dir
, F_OK
) == 0)
382 if (dir
!= start
&& (*dir
== '/' || *dir
== '\\'))
384 if (dir
!= start
&& *dir
== '/')
391 mkdir (start
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
400 log_create_filename (char *channame
)
405 ret
= tmp
= strdup (channame
);
408 mbl
= g_utf8_skip
[((unsigned char *)tmp
)[0]];
412 *tmp
= rfc_tolower (*tmp
);
415 /* win32 can't handle filenames with \|/><:"*? characters */
416 if (*tmp
== '\\' || *tmp
== '|' || *tmp
== '/' ||
417 *tmp
== '>' || *tmp
== '<' || *tmp
== ':' ||
418 *tmp
== '\"' || *tmp
== '*' || *tmp
== '?')
428 /* like strcpy, but % turns into %% */
431 log_escape_strcpy (char *dest
, char *src
, char *end
)
454 /* substitutes %c %n %s into buffer */
457 log_insert_vars (char *buf
, int bufsize
, char *fmt
, char *c
, char *n
, char *s
)
459 char *end
= buf
+ bufsize
;
474 buf
= log_escape_strcpy (buf
, c
, end
);
477 buf
= log_escape_strcpy (buf
, n
, end
);
480 buf
= log_escape_strcpy (buf
, s
, end
);
505 log_create_pathname (char *servname
, char *channame
, char *netname
)
516 /* first, everything is in UTF-8 */
517 if (!rfc_casecmp (channame
, servname
))
518 channame
= strdup ("server");
520 channame
= log_create_filename (channame
);
521 log_insert_vars (fname
, sizeof (fname
), prefs
.logmask
, channame
, netname
, servname
);
524 /* insert time/date */
526 tm
= localtime (&now
);
527 strftime (fnametime
, sizeof (fnametime
), fname
, tm
);
529 /* create final path/filename */
531 if (fnametime
[0] == '/' || (fnametime
[0] >= 'A' && fnametime
[1] == ':'))
533 if (fnametime
[0] == '/') /* is it fullpath already? */
535 snprintf (fname
, sizeof (fname
), "%s", fnametime
);
537 snprintf (fname
, sizeof (fname
), "%s/xchatlogs/%s", get_xdir_utf8 (), fnametime
);
539 /* now we need it in FileSystem encoding */
540 fs
= xchat_filename_from_utf8 (fname
, -1, 0, 0, 0);
542 /* create all the subdirectories */
550 log_open_file (char *servname
, char *channame
, char *netname
)
557 file
= log_create_pathname (servname
, channame
, netname
);
562 fd
= open (file
, O_CREAT
| O_APPEND
| O_WRONLY
, S_IREAD
|S_IWRITE
);
564 fd
= open (file
, O_CREAT
| O_APPEND
| O_WRONLY
, 0644);
570 currenttime
= time (NULL
);
572 snprintf (buf
, sizeof (buf
), _("**** BEGIN LOGGING AT %s\n"),
573 ctime (¤ttime
)));
579 log_open (session
*sess
)
581 static gboolean log_error
= FALSE
;
584 sess
->logfd
= log_open_file (sess
->server
->servername
, sess
->channel
,
585 server_get_network (sess
->server
, FALSE
));
587 if (!log_error
&& sess
->logfd
== -1)
590 snprintf (message
, sizeof (message
),
591 _("* Can't open log file(s) for writing. Check the\n" \
592 " permissions on %s/xchatlogs"), get_xdir_utf8 ());
593 fe_message (message
, FE_MSG_WAIT
| FE_MSG_ERROR
);
600 log_open_or_close (session
*sess
)
602 if (sess
->text_logging
== SET_DEFAULT
)
611 if (sess
->text_logging
)
619 get_stamp_str (char *fmt
, time_t tim
, char **ret
)
625 /* strftime wants the format string in LOCALE! */
626 if (!prefs
.utf8_locale
)
628 const gchar
*charset
;
630 g_get_charset (&charset
);
631 loc
= g_convert_with_fallback (fmt
, -1, charset
, "UTF-8", "?", 0, 0, 0);
636 len
= strftime (dest
, sizeof (dest
), fmt
, localtime (&tim
));
639 if (prefs
.utf8_locale
)
640 *ret
= g_strdup (dest
);
642 *ret
= g_locale_to_utf8 (dest
, len
, 0, &len
, 0);
652 log_write (session
*sess
, char *text
)
659 if (sess
->text_logging
== SET_DEFAULT
)
666 if (sess
->text_logging
!= SET_ON
)
670 if (sess
->logfd
== -1)
673 /* change to a different log file? */
674 file
= log_create_pathname (sess
->server
->servername
, sess
->channel
,
675 server_get_network (sess
->server
, FALSE
));
678 if (access (file
, F_OK
) != 0)
681 sess
->logfd
= log_open_file (sess
->server
->servername
, sess
->channel
,
682 server_get_network (sess
->server
, FALSE
));
687 if (prefs
.timestamp_logs
)
689 len
= get_stamp_str (prefs
.timestamp_log_format
, time (0), &stamp
);
692 write (sess
->logfd
, stamp
, len
);
696 temp
= strip_color (text
, -1, STRIP_ALL
);
698 write (sess
->logfd
, temp
, len
);
699 /* lots of scripts/plugins print without a \n at the end */
700 if (temp
[len
- 1] != '\n')
701 write (sess
->logfd
, "\n", 1); /* emulate what xtext would display */
705 /* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8 */
706 /* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */
707 /* 2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */
708 /* thing useful in this range and it helps us receive from mIRC */
709 /* 3. The five undefined chars in CP1252 80-9f are replaced with */
710 /* ISO-8859-15 control codes. */
711 /* 4. Handles 0xa4 as a Euro symbol ala ISO-8859-15. */
712 /* 5. Uses ISO-8859-1 (which matches CP1252) for everything else. */
713 /* 6. This routine measured 3x faster than g_convert :) */
715 static unsigned char *
716 iso_8859_1_to_utf8 (unsigned char *text
, int len
, gsize
*bytes_written
)
719 unsigned char *res
, *output
;
720 static const unsigned short lowtable
[] = /* 74 byte table for 80-a4 */
722 /* compressed utf-8 table: if the first byte's 0x20 bit is set, it
723 indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */
724 0x82ac, /* 80 Euro. CP1252 from here on... */
760 0x82ac /* a4 ISO-8859-15 Euro. */
766 /* worst case scenario: every byte turns into 3 bytes */
767 res
= output
= g_malloc ((len
* 3) + 1);
773 if (G_LIKELY (*text
< 0x80))
775 *output
= *text
; /* ascii maps directly */
777 else if (*text
<= 0xa4) /* 80-a4 use a lookup table */
780 if (lowtable
[idx
] & 0x2000)
782 *output
++ = (lowtable
[idx
] >> 8) & 0xdf; /* 2 byte utf-8 */
783 *output
= lowtable
[idx
] & 0xff;
787 *output
++ = 0xe2; /* 3 byte utf-8 */
788 *output
++ = (lowtable
[idx
] >> 8) & 0xff;
789 *output
= lowtable
[idx
] & 0xff;
792 else if (*text
< 0xc0)
800 *output
= *text
- 0x40;
806 *output
= 0; /* terminate */
807 *bytes_written
= output
- res
;
813 text_validate (char **text
, int *len
)
819 if (g_utf8_validate (*text
, *len
, 0))
823 if (GetACP () == 1252) /* our routine is better than iconv's 1252 */
825 if (prefs
.utf8_locale
)
827 /* fallback to iso-8859-1 */
828 utf
= iso_8859_1_to_utf8 (*text
, *len
, &utf_len
);
831 /* fallback to locale */
832 utf
= g_locale_to_utf8 (*text
, *len
, 0, &utf_len
, NULL
);
834 utf
= iso_8859_1_to_utf8 (*text
, *len
, &utf_len
);
839 *text
= g_strdup ("%INVALID%");
851 PrintText (session
*sess
, char *text
)
859 sess
= (session
*) sess_list
->data
;
862 /* make sure it's valid utf8 */
870 conv
= text_validate ((char **)&text
, &len
);
873 log_write (sess
, text
);
874 scrollback_save (sess
, text
);
875 fe_print_text (sess
, text
, 0);
882 PrintTextf (session
*sess
, char *format
, ...)
887 va_start (args
, format
);
888 buf
= g_strdup_vprintf (format
, args
);
891 PrintText (sess
, buf
);
895 /* Print Events stuff here --AGL */
897 /* Consider the following a NOTES file:
899 The main upshot of this is:
900 * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like
901 * The default text engine can be config'ed
903 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:
905 The normal %Cx (color) and %B (bold) etc work
907 $x is replaced with the data in var x (e.g. $1 is often the nick)
909 $axxx is replace with a single byte of value xxx (in base 10)
914 /* These lists are thus:
915 pntevts_text[] are the strings the user sees (WITH %x etc)
916 pntevts[] are the data strings with \000 etc
919 /* To add a new event:
921 Think up a name (like "Join")
922 Make up a pevt_name_help struct
923 Add an entry to textevents.in
924 Type: make textevents
929 On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the
930 defaults are loaded. Any missing events are filled from defaults.
931 Each event is parsed by pevt_build_string and a binary output is produced
935 (int) numbers of bytes
936 (char []) that number of byte to be memcpy'ed into the buffer
939 (byte) number of varable to insert
942 Each XP_TE_* signal is hard coded to call text_emit which calls
943 display_event which decodes the data
945 This means that this system *should be faster* than snprintf because
946 it always 'knows' that format of the string (basically is preparses much
952 char *pntevts_text
[NUM_XP
];
953 char *pntevts
[NUM_XP
];
955 #define pevt_generic_none_help NULL
957 static char * const pevt_genmsg_help
[] = {
962 static char * const pevt_join_help
[] = {
963 N_("The nick of the joining person"),
964 N_("The channel being joined"),
965 N_("The host of the person"),
968 static char * const pevt_chanaction_help
[] = {
972 N_("Identified text"),
975 static char * const pevt_chanmsg_help
[] = {
979 N_("Identified text"),
982 static char * const pevt_privmsg_help
[] = {
985 N_("Identified text")
988 static char * const pevt_changenick_help
[] = {
993 static char * const pevt_newtopic_help
[] = {
994 N_("Nick of person who changed the topic"),
999 static char * const pevt_topic_help
[] = {
1004 static char * const pevt_kick_help
[] = {
1005 N_("The nickname of the kicker"),
1006 N_("The person being kicked"),
1011 static char * const pevt_part_help
[] = {
1012 N_("The nick of the person leaving"),
1013 N_("The host of the person"),
1017 static char * const pevt_chandate_help
[] = {
1022 static char * const pevt_topicdate_help
[] = {
1028 static char * const pevt_quit_help
[] = {
1034 static char * const pevt_pingrep_help
[] = {
1035 N_("Who it's from"),
1036 N_("The time in x.x format (see below)"),
1039 static char * const pevt_notice_help
[] = {
1040 N_("Who it's from"),
1044 static char * const pevt_channotice_help
[] = {
1045 N_("Who it's from"),
1046 N_("The Channel it's going to"),
1050 static char * const pevt_uchangenick_help
[] = {
1055 static char * const pevt_ukick_help
[] = {
1056 N_("The person being kicked"),
1058 N_("The nickname of the kicker"),
1062 static char * const pevt_partreason_help
[] = {
1063 N_("The nick of the person leaving"),
1064 N_("The host of the person"),
1069 static char * const pevt_ctcpsnd_help
[] = {
1071 N_("The nick of the person"),
1075 static char * const pevt_ctcpgen_help
[] = {
1076 N_("The CTCP event"),
1077 N_("The nick of the person"),
1080 static char * const pevt_ctcpgenc_help
[] = {
1081 N_("The CTCP event"),
1082 N_("The nick of the person"),
1083 N_("The Channel it's going to"),
1086 static char * const pevt_chansetkey_help
[] = {
1087 N_("The nick of the person who set the key"),
1091 static char * const pevt_chansetlimit_help
[] = {
1092 N_("The nick of the person who set the limit"),
1096 static char * const pevt_chanop_help
[] = {
1097 N_("The nick of the person who did the op'ing"),
1098 N_("The nick of the person who has been op'ed"),
1101 static char * const pevt_chanhop_help
[] = {
1102 N_("The nick of the person who has been halfop'ed"),
1103 N_("The nick of the person who did the halfop'ing"),
1106 static char * const pevt_chanvoice_help
[] = {
1107 N_("The nick of the person who did the voice'ing"),
1108 N_("The nick of the person who has been voice'ed"),
1111 static char * const pevt_chanban_help
[] = {
1112 N_("The nick of the person who did the banning"),
1116 static char * const pevt_chanrmkey_help
[] = {
1117 N_("The nick who removed the key"),
1120 static char * const pevt_chanrmlimit_help
[] = {
1121 N_("The nick who removed the limit"),
1124 static char * const pevt_chandeop_help
[] = {
1125 N_("The nick of the person of did the deop'ing"),
1126 N_("The nick of the person who has been deop'ed"),
1128 static char * const pevt_chandehop_help
[] = {
1129 N_("The nick of the person of did the dehalfop'ing"),
1130 N_("The nick of the person who has been dehalfop'ed"),
1133 static char * const pevt_chandevoice_help
[] = {
1134 N_("The nick of the person of did the devoice'ing"),
1135 N_("The nick of the person who has been devoice'ed"),
1138 static char * const pevt_chanunban_help
[] = {
1139 N_("The nick of the person of did the unban'ing"),
1143 static char * const pevt_chanexempt_help
[] = {
1144 N_("The nick of the person who did the exempt"),
1145 N_("The exempt mask"),
1148 static char * const pevt_chanrmexempt_help
[] = {
1149 N_("The nick of the person removed the exempt"),
1150 N_("The exempt mask"),
1153 static char * const pevt_chaninvite_help
[] = {
1154 N_("The nick of the person who did the invite"),
1155 N_("The invite mask"),
1158 static char * const pevt_chanrminvite_help
[] = {
1159 N_("The nick of the person removed the invite"),
1160 N_("The invite mask"),
1163 static char * const pevt_chanmodegen_help
[] = {
1164 N_("The nick of the person setting the mode"),
1165 N_("The mode's sign (+/-)"),
1166 N_("The mode letter"),
1167 N_("The channel it's being set on"),
1170 static char * const pevt_whois1_help
[] = {
1177 static char * const pevt_whois2_help
[] = {
1179 N_("Channel Membership/\"is an IRC operator\""),
1182 static char * const pevt_whois3_help
[] = {
1184 N_("Server Information"),
1187 static char * const pevt_whois4_help
[] = {
1192 static char * const pevt_whois4t_help
[] = {
1198 static char * const pevt_whois5_help
[] = {
1203 static char * const pevt_whois6_help
[] = {
1207 static char * const pevt_whoisid_help
[] = {
1213 static char * const pevt_whoisauth_help
[] = {
1219 static char * const pevt_whoisrealhost_help
[] = {
1221 N_("Real user@host"),
1226 static char * const pevt_generic_channel_help
[] = {
1230 static char * const pevt_servertext_help
[] = {
1233 N_("Raw Numeric or Identifier")
1236 static char * const pevt_sslmessage_help
[] = {
1241 static char * const pevt_invited_help
[] = {
1243 N_("Nick of person who invited you"),
1247 static char * const pevt_usersonchan_help
[] = {
1252 static char * const pevt_nickclash_help
[] = {
1253 N_("Nickname in use"),
1254 N_("Nick being tried"),
1257 static char * const pevt_connfail_help
[] = {
1261 static char * const pevt_connect_help
[] = {
1267 static char * const pevt_sconnect_help
[] = {
1271 static char * const pevt_generic_nick_help
[] = {
1277 static char * const pevt_chanmodes_help
[] = {
1282 static char * const pevt_rawmodes_help
[] = {
1287 static char * const pevt_kill_help
[] = {
1292 static char * const pevt_dccchaterr_help
[] = {
1299 static char * const pevt_dccstall_help
[] = {
1305 static char * const pevt_generic_file_help
[] = {
1310 static char * const pevt_dccrecverr_help
[] = {
1312 N_("Destination filename"),
1317 static char * const pevt_dccrecvcomp_help
[] = {
1319 N_("Destination filename"),
1324 static char * const pevt_dccconfail_help
[] = {
1330 static char * const pevt_dccchatcon_help
[] = {
1335 static char * const pevt_dcccon_help
[] = {
1341 static char * const pevt_dccsendfail_help
[] = {
1347 static char * const pevt_dccsendcomp_help
[] = {
1353 static char * const pevt_dccoffer_help
[] = {
1359 static char * const pevt_dccfileabort_help
[] = {
1364 static char * const pevt_dccchatabort_help
[] = {
1368 static char * const pevt_dccresumeoffer_help
[] = {
1374 static char * const pevt_dccsendoffer_help
[] = {
1381 static char * const pevt_dccgenericoffer_help
[] = {
1386 static char * const pevt_notifynumber_help
[] = {
1387 N_("Number of notify items"),
1390 static char * const pevt_serverlookup_help
[] = {
1394 static char * const pevt_servererror_help
[] = {
1398 static char * const pevt_foundip_help
[] = {
1402 static char * const pevt_dccrename_help
[] = {
1407 static char * const pevt_ctcpsend_help
[] = {
1412 static char * const pevt_ignoreaddremove_help
[] = {
1416 static char * const pevt_resolvinguser_help
[] = {
1421 static char * const pevt_malformed_help
[] = {
1426 static char * const pevt_pingtimeout_help
[] = {
1430 static char * const pevt_uinvite_help
[] = {
1431 N_("Nick of person who have been invited"),
1436 static char * const pevt_banlist_help
[] = {
1439 N_("Who set the ban"),
1443 static char * const pevt_discon_help
[] = {
1447 #include "textevents.h"
1450 pevent_load_defaults ()
1454 for (i
= 0; i
< NUM_XP
; i
++)
1456 if (pntevts_text
[i
])
1457 free (pntevts_text
[i
]);
1459 /* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1460 if (te
[i
].num_args
& 128)
1461 pntevts_text
[i
] = strdup (te
[i
].def
);
1463 pntevts_text
[i
] = strdup (_(te
[i
].def
));
1468 pevent_make_pntevts ()
1473 for (i
= 0; i
< NUM_XP
; i
++)
1475 if (pntevts
[i
] != NULL
)
1477 if (pevt_build_string (pntevts_text
[i
], &(pntevts
[i
]), &m
) != 0)
1479 snprintf (out
, sizeof (out
),
1480 _("Error parsing event %s.\nLoading default."), te
[i
].name
);
1481 fe_message (out
, FE_MSG_WARN
);
1482 free (pntevts_text
[i
]);
1483 /* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1484 if (te
[i
].num_args
& 128)
1485 pntevts_text
[i
] = strdup (te
[i
].def
);
1487 pntevts_text
[i
] = strdup (_(te
[i
].def
));
1488 if (pevt_build_string (pntevts_text
[i
], &(pntevts
[i
]), &m
) != 0)
1491 "XChat CRITICAL *** default event text failed to build!\n");
1498 /* Loading happens at 2 levels:
1499 1) File is read into blocks
1500 2) Pe block is parsed and loaded
1504 /* Better hope you pass good args.. --AGL */
1507 pevent_trigger_load (int *i_penum
, char **i_text
, char **i_snd
)
1509 int penum
= *i_penum
, len
;
1510 char *text
= *i_text
, *snd
= *i_snd
;
1512 if (penum
!= -1 && text
!= NULL
)
1514 len
= strlen (text
) + 1;
1515 if (pntevts_text
[penum
])
1516 free (pntevts_text
[penum
]);
1517 pntevts_text
[penum
] = malloc (len
);
1518 memcpy (pntevts_text
[penum
], text
, len
);
1531 pevent_find (char *name
, int *i_i
)
1540 if (strcmp (te
[j
].name
, name
) == 0)
1552 pevent_load (char *filename
)
1554 /* AGL, I've changed this file and pevent_save, could you please take a look at
1555 * the changes and possibly modify them to suit you
1559 int fd
, i
= 0, pnt
= 0;
1561 char *text
= NULL
, *snd
= NULL
;
1565 if (filename
== NULL
)
1566 fd
= xchat_open_file ("pevents.conf", O_RDONLY
, 0, 0);
1568 fd
= xchat_open_file (filename
, O_RDONLY
, 0, XOF_FULLPATH
);
1572 if (fstat (fd
, &st
) != 0)
1574 ibuf
= malloc (st
.st_size
);
1575 read (fd
, ibuf
, st
.st_size
);
1578 while (buf_get_line (ibuf
, &buf
, &pnt
, st
.st_size
))
1582 if (strlen (buf
) == 0)
1585 ofs
= strchr (buf
, '=');
1593 if (strcmp (buf
, "event_name") == 0)
1596 pevent_trigger_load (&penum
, &text
, &snd
);
1597 penum
= pevent_find (ofs
, &i
);
1599 } else if (strcmp (buf
, "event_text") == 0)
1605 /* This allows updating of old strings. We don't use new defaults
1606 if the user has customized the strings (.e.g a text theme).
1607 Hash of the old default is enough to identify and replace it.
1608 This only works in English. */
1610 switch (g_str_hash (ofs
))
1613 /* %C08,02 Hostmask PRIV NOTI CHAN CTCP INVI UNIG %O */
1614 text
= strdup (te
[XP_TE_IGNOREHEADER
].def
);
1619 text
= strdup (te
[XP_TE_IGNOREFOOTER
].def
);
1623 /* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */
1624 text
= strdup (te
[XP_TE_DCCFILEERR
].def
);
1628 text
= strdup (ofs
);
1631 text
= strdup (ofs
);
1635 }/* else if (strcmp (buf, "event_sound") == 0)
1646 pevent_trigger_load (&penum
, &text
, &snd
);
1652 pevent_check_all_loaded ()
1656 for (i
= 0; i
< NUM_XP
; i
++)
1658 if (pntevts_text
[i
] == NULL
)
1660 /*printf ("%s\n", te[i].name);
1661 snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of XChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]);
1662 gtkutil_simpledialog(out); */
1663 /* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1664 if (te
[i
].num_args
& 128)
1665 pntevts_text
[i
] = strdup (te
[i
].def
);
1667 pntevts_text
[i
] = strdup (_(te
[i
].def
));
1675 memset (&pntevts_text
, 0, sizeof (char *) * (NUM_XP
));
1676 memset (&pntevts
, 0, sizeof (char *) * (NUM_XP
));
1678 if (pevent_load (NULL
))
1679 pevent_load_defaults ();
1680 pevent_check_all_loaded ();
1681 pevent_make_pntevts ();
1685 CL: format_event now handles filtering of arguments:
1686 1) if prefs.stripcolor is set, filter all style control codes from arguments
1687 2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself
1689 #define ARG_FLAG(argn) (1 << (argn))
1692 format_event (session
*sess
, int index
, char **args
, char *o
, int sizeofo
, unsigned int stripcolor_args
)
1694 int len
, oi
, ii
, numargs
;
1695 char *i
, *ar
, d
, a
, done_all
= FALSE
;
1698 numargs
= te
[index
].num_args
& 0x7f;
1700 oi
= ii
= len
= d
= a
= 0;
1706 while (done_all
== FALSE
)
1712 memcpy (&len
, &(i
[ii
]), sizeof (int));
1714 if (oi
+ len
> sizeofo
)
1716 printf ("Overflow in display_event (%s)\n", i
);
1720 memcpy (&(o
[oi
]), &(i
[ii
]), len
);
1729 "XChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
1733 ar
= args
[(int) a
+ 1];
1736 printf ("arg[%d] is NULL in print event\n", a
+ 1);
1739 if (stripcolor_args
& ARG_FLAG(a
+ 1)) len
= strip_color2 (ar
, -1, &o
[oi
], STRIP_ALL
);
1740 else len
= strip_hidden_attribute (ar
, &o
[oi
]);
1750 /* if (sess->type == SESS_DIALOG)
1752 if (prefs.dialog_indent_nicks)
1758 if (prefs
.indent_nicks
)
1772 display_event (session
*sess
, int event
, char **args
, unsigned int stripcolor_args
)
1775 format_event (sess
, event
, args
, o
, sizeof (o
), stripcolor_args
);
1777 PrintText (sess
, o
);
1781 pevt_build_string (const char *input
, char **output
, int *max_arg
)
1783 struct pevt_stage1
*s
= NULL
, *base
= NULL
, *last
= NULL
, *next
;
1785 char o
[4096], d
, *obuf
, *i
;
1786 int oi
, ii
, max
= -1, len
, x
;
1788 len
= strlen (input
);
1789 i
= malloc (len
+ 1);
1790 memcpy (i
, input
, len
+ 1);
1791 check_special_chars (i
, TRUE
);
1814 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1821 s
->data
= malloc (oi
+ sizeof (int) + 1);
1822 s
->len
= oi
+ sizeof (int) + 1;
1823 clen
+= oi
+ sizeof (int) + 1;
1825 memcpy (&(s
->data
[1]), &oi
, sizeof (int));
1826 memcpy (&(s
->data
[1 + sizeof (int)]), o
, oi
);
1831 fe_message ("String ends with a $", FE_MSG_WARN
);
1859 fe_message ("String ends in $a", FE_MSG_WARN
);
1862 fe_message ("$a value is greater than 255", FE_MSG_WARN
);
1867 /* Tab - if tabnicks is set then write '\t' else ' ' */
1868 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1875 s
->data
= malloc (1);
1882 if (d
< '1' || d
> '9')
1884 snprintf (o
, sizeof (o
), "Error, invalid argument $%c\n", d
);
1885 fe_message (o
, FE_MSG_WARN
);
1891 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1898 s
->data
= malloc (2);
1906 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1913 s
->data
= malloc (oi
+ sizeof (int) + 1);
1914 s
->len
= oi
+ sizeof (int) + 1;
1915 clen
+= oi
+ sizeof (int) + 1;
1917 memcpy (&(s
->data
[1]), &oi
, sizeof (int));
1918 memcpy (&(s
->data
[1 + sizeof (int)]), o
, oi
);
1921 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1928 s
->data
= malloc (1);
1935 obuf
= malloc (clen
);
1939 memcpy (&obuf
[oi
], s
->data
, s
->len
);
1957 /* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */
1958 /* also light/dark gray (14/15) */
1959 /* 5,7,8 are all shades of yellow which happen to look dman near the same */
1961 static char rcolors
[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 };
1964 color_of (char *name
)
1970 sum
%= sizeof (rcolors
) / sizeof (char);
1971 return rcolors
[sum
];
1975 /* called by EMIT_SIGNAL macro */
1978 text_emit (int index
, session
*sess
, char *a
, char *b
, char *c
, char *d
, char* file
, int lineno
)
1980 char *word
[PDIWORDS
];
1982 unsigned int stripcolor_args
= (prefs
.stripcolor
? 0xFFFFFFFF : 0);
1983 char tbuf
[NICKLEN
+ 4];
1985 if(getenv("XCHATDEBUG"))
1986 dprintf(2, "%s:%d %s %s %s %s\n", file
, lineno
, a
? a
: "", b
? b
: "", c
? c
: "", d
? d
: "");
1988 if (prefs
.colorednicks
&& (index
== XP_TE_CHANACTION
|| index
== XP_TE_CHANMSG
))
1990 snprintf (tbuf
, sizeof (tbuf
), "\003%d%s", color_of (a
), a
);
1992 stripcolor_args
&= ~ARG_FLAG(1); /* don't strip color from this argument */
1995 word
[0] = te
[index
].name
;
1996 word
[1] = (a
? a
: "\000");
1997 word
[2] = (b
? b
: "\000");
1998 word
[3] = (c
? c
: "\000");
1999 word
[4] = (d
? d
: "\000");
2000 for (i
= 5; i
< PDIWORDS
; i
++)
2003 if (plugin_emit_print (sess
, word
))
2006 /* If a plugin's callback executes "/close", 'sess' may be invalid */
2007 if (!is_session (sess
))
2014 case XP_TE_PARTREASON
:
2016 /* implement ConfMode / Hide Join and Part Messages */
2017 if (chanopt_is_set (prefs
.confmode
, sess
->text_hidejoinpart
))
2021 /* ===Private message=== */
2023 case XP_TE_DPRIVMSG
:
2024 case XP_TE_PRIVACTION
:
2025 case XP_TE_DPRIVACTION
:
2026 if (chanopt_is_set_a (prefs
.input_beep_priv
, sess
->alert_beep
))
2028 if (chanopt_is_set_a (prefs
.input_flash_priv
, sess
->alert_taskbar
))
2029 fe_flash_window (sess
);
2030 /* why is this one different? because of plugin-tray.c's hooks! ugly */
2031 if (sess
->alert_tray
== SET_ON
)
2032 fe_tray_set_icon (FE_ICON_MESSAGE
);
2035 /* ===Highlighted message=== */
2036 case XP_TE_HCHANACTION
:
2037 case XP_TE_HCHANMSG
:
2038 if (chanopt_is_set_a (prefs
.input_beep_hilight
, sess
->alert_beep
))
2040 if (chanopt_is_set_a (prefs
.input_flash_hilight
, sess
->alert_taskbar
))
2041 fe_flash_window (sess
);
2042 if (sess
->alert_tray
== SET_ON
)
2043 fe_tray_set_icon (FE_ICON_MESSAGE
);
2046 /* ===Channel message=== */
2047 case XP_TE_CHANACTION
:
2049 if (chanopt_is_set_a (prefs
.input_beep_chans
, sess
->alert_beep
))
2051 if (chanopt_is_set_a (prefs
.input_flash_chans
, sess
->alert_taskbar
))
2052 fe_flash_window (sess
);
2053 if (sess
->alert_tray
== SET_ON
)
2054 fe_tray_set_icon (FE_ICON_MESSAGE
);
2058 sound_play_event (index
);
2059 display_event (sess
, index
, word
, stripcolor_args
);
2063 text_find_format_string (char *name
)
2067 i
= pevent_find (name
, &i
);
2069 return pntevts_text
[i
];
2075 text_emit_by_name (char *name
, session
*sess
, char *a
, char *b
, char *c
, char *d
)
2079 i
= pevent_find (name
, &i
);
2082 text_emit (i
, sess
, a
, b
, c
, d
, "", 0);
2090 pevent_save (char *fn
)
2096 fd
= xchat_open_file ("pevents.conf", O_CREAT
| O_TRUNC
| O_WRONLY
,
2099 fd
= xchat_open_file (fn
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0x180,
2100 XOF_FULLPATH
| XOF_DOMODE
);
2104 fe_message ("Error opening config file\n", FALSE);
2105 If we get here when X-Chat is closing the fe-message causes a nice & hard crash
2106 so we have to use perror which doesn't rely on GTK
2109 perror ("Error opening config file\n");
2113 for (i
= 0; i
< NUM_XP
; i
++)
2115 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2116 "event_name=%s\n", te
[i
].name
));
2117 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2118 "event_text=%s\n\n", pntevts_text
[i
]));
2124 /* =========================== */
2125 /* ========== SOUND ========== */
2126 /* =========================== */
2128 char *sound_files
[NUM_XP
];
2131 sound_beep (session
*sess
)
2133 if (sound_files
[XP_TE_BEEP
] && sound_files
[XP_TE_BEEP
][0])
2134 /* user defined beep _file_ */
2135 sound_play_event (XP_TE_BEEP
);
2142 sound_find_command (void)
2144 /* some sensible unix players. You're bound to have one of them */
2145 static const char * const progs
[] = {"aplay", "esdplay", "soxplay", "artsplay", NULL
};
2149 if (prefs
.soundcmd
[0])
2150 return g_strdup (prefs
.soundcmd
);
2154 cmd
= g_find_program_in_path (progs
[i
]);
2164 sound_play (const char *file
, gboolean quiet
)
2171 /* the pevents GUI editor triggers this after removing a soundfile */
2176 /* check for fullpath, windows style */
2177 if (strlen (file
) > 3 &&
2178 file
[1] == ':' && (file
[2] == '\\' || file
[2] == '/') )
2180 strncpy (wavfile
, file
, sizeof (wavfile
));
2185 snprintf (wavfile
, sizeof (wavfile
), "%s/%s", prefs
.sounddir
, file
);
2188 strncpy (wavfile
, file
, sizeof (wavfile
));
2190 wavfile
[sizeof (wavfile
) - 1] = 0; /* ensure termination */
2192 file_fs
= xchat_filename_from_utf8 (wavfile
, -1, 0, 0, 0);
2196 if (access (file_fs
, R_OK
) == 0)
2198 cmd
= sound_find_command ();
2201 if (cmd
== NULL
|| strcmp (cmd
, "esdplay") == 0)
2203 PlaySound (file_fs
, NULL
, SND_NODEFAULT
|SND_FILENAME
|SND_ASYNC
);
2209 if (strchr (file_fs
, ' '))
2210 snprintf (buf
, sizeof (buf
), "%s \"%s\"", cmd
, file_fs
);
2212 snprintf (buf
, sizeof (buf
), "%s %s", cmd
, file_fs
);
2213 buf
[sizeof (buf
) - 1] = '\0';
2225 snprintf (buf
, sizeof (buf
), _("Cannot read sound file:\n%s"), wavfile
);
2226 fe_message (buf
, FE_MSG_ERROR
);
2234 sound_play_event (int i
)
2237 sound_play (sound_files
[i
], FALSE
);
2241 sound_load_event (char *evt
, char *file
)
2245 if (file
[0] && pevent_find (evt
, &i
) != -1)
2248 free (sound_files
[i
]);
2249 sound_files
[i
] = strdup (file
);
2260 memset (&sound_files
, 0, sizeof (char *) * (NUM_XP
));
2262 fd
= xchat_open_file ("sound.conf", O_RDONLY
, 0, 0);
2267 while (waitline (fd
, buf
, sizeof buf
, FALSE
) != -1)
2269 if (strncmp (buf
, "event=", 6) == 0)
2271 safe_strcpy (evt
, buf
+ 6, sizeof (evt
));
2273 else if (strncmp (buf
, "sound=", 6) == 0)
2277 sound_load_event (evt
, buf
+ 6);
2292 fd
= xchat_open_file ("sound.conf", O_CREAT
| O_TRUNC
| O_WRONLY
, 0x180,
2297 for (i
= 0; i
< NUM_XP
; i
++)
2299 if (sound_files
[i
] && sound_files
[i
][0])
2301 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2302 "event=%s\n", te
[i
].name
));
2303 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2304 "sound=%s\n\n", sound_files
[i
]));