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)
381 if (dir
!= start
&& *dir
== '/')
384 mkdir (start
, S_IRUSR
| S_IWUSR
| S_IXUSR
);
392 log_create_filename (char *channame
)
397 ret
= tmp
= strdup (channame
);
400 mbl
= g_utf8_skip
[((unsigned char *)tmp
)[0]];
403 *tmp
= rfc_tolower (*tmp
);
413 /* like strcpy, but % turns into %% */
416 log_escape_strcpy (char *dest
, char *src
, char *end
)
439 /* substitutes %c %n %s into buffer */
442 log_insert_vars (char *buf
, int bufsize
, char *fmt
, char *c
, char *n
, char *s
)
444 char *end
= buf
+ bufsize
;
459 buf
= log_escape_strcpy (buf
, c
, end
);
462 buf
= log_escape_strcpy (buf
, n
, end
);
465 buf
= log_escape_strcpy (buf
, s
, end
);
490 log_create_pathname (char *servname
, char *channame
, char *netname
)
501 /* first, everything is in UTF-8 */
502 if (!rfc_casecmp (channame
, servname
))
503 channame
= strdup ("server");
505 channame
= log_create_filename (channame
);
506 log_insert_vars (fname
, sizeof (fname
), prefs
.logmask
, channame
, netname
, servname
);
509 /* insert time/date */
511 tm
= localtime (&now
);
512 strftime (fnametime
, sizeof (fnametime
), fname
, tm
);
514 /* create final path/filename */
515 if (fnametime
[0] == '/') /* is it fullpath already? */
516 snprintf (fname
, sizeof (fname
), "%s", fnametime
);
518 snprintf (fname
, sizeof (fname
), "%s/xchatlogs/%s", get_xdir_utf8 (), fnametime
);
520 /* now we need it in FileSystem encoding */
521 fs
= xchat_filename_from_utf8 (fname
, -1, 0, 0, 0);
523 /* create all the subdirectories */
531 log_open_file (char *servname
, char *channame
, char *netname
)
538 file
= log_create_pathname (servname
, channame
, netname
);
542 fd
= open (file
, O_CREAT
| O_APPEND
| O_WRONLY
, 0644);
547 currenttime
= time (NULL
);
549 snprintf (buf
, sizeof (buf
), _("**** BEGIN LOGGING AT %s\n"),
550 ctime (¤ttime
)));
556 log_open (session
*sess
)
558 static gboolean log_error
= FALSE
;
561 sess
->logfd
= log_open_file (sess
->server
->servername
, sess
->channel
,
562 server_get_network (sess
->server
, FALSE
));
564 if (!log_error
&& sess
->logfd
== -1)
567 snprintf (message
, sizeof (message
),
568 _("* Can't open log file(s) for writing. Check the\n" \
569 " permissions on %s/xchatlogs"), get_xdir_utf8 ());
570 fe_message (message
, FE_MSG_WAIT
| FE_MSG_ERROR
);
577 log_open_or_close (session
*sess
)
579 if (sess
->text_logging
== SET_DEFAULT
)
588 if (sess
->text_logging
)
596 get_stamp_str (char *fmt
, time_t tim
, char **ret
)
602 /* strftime wants the format string in LOCALE! */
603 if (!prefs
.utf8_locale
)
605 const gchar
*charset
;
607 g_get_charset (&charset
);
608 loc
= g_convert_with_fallback (fmt
, -1, charset
, "UTF-8", "?", 0, 0, 0);
613 len
= strftime (dest
, sizeof (dest
), fmt
, localtime (&tim
));
616 if (prefs
.utf8_locale
)
617 *ret
= g_strdup (dest
);
619 *ret
= g_locale_to_utf8 (dest
, len
, 0, &len
, 0);
629 log_write (session
*sess
, char *text
)
636 if (sess
->text_logging
== SET_DEFAULT
)
643 if (sess
->text_logging
!= SET_ON
)
647 if (sess
->logfd
== -1)
650 /* change to a different log file? */
651 file
= log_create_pathname (sess
->server
->servername
, sess
->channel
,
652 server_get_network (sess
->server
, FALSE
));
655 if (access (file
, F_OK
) != 0)
658 sess
->logfd
= log_open_file (sess
->server
->servername
, sess
->channel
,
659 server_get_network (sess
->server
, FALSE
));
664 if (prefs
.timestamp_logs
)
666 len
= get_stamp_str (prefs
.timestamp_log_format
, time (0), &stamp
);
669 write (sess
->logfd
, stamp
, len
);
673 temp
= strip_color (text
, -1, STRIP_ALL
);
675 write (sess
->logfd
, temp
, len
);
676 /* lots of scripts/plugins print without a \n at the end */
677 if (temp
[len
- 1] != '\n')
678 write (sess
->logfd
, "\n", 1); /* emulate what xtext would display */
682 /* converts a CP1252/ISO-8859-1(5) hybrid to UTF-8 */
683 /* Features: 1. It never fails, all 00-FF chars are converted to valid UTF-8 */
684 /* 2. Uses CP1252 in the range 80-9f because ISO doesn't have any- */
685 /* thing useful in this range and it helps us receive from mIRC */
686 /* 3. The five undefined chars in CP1252 80-9f are replaced with */
687 /* ISO-8859-15 control codes. */
688 /* 4. Handles 0xa4 as a Euro symbol ala ISO-8859-15. */
689 /* 5. Uses ISO-8859-1 (which matches CP1252) for everything else. */
690 /* 6. This routine measured 3x faster than g_convert :) */
692 static unsigned char *
693 iso_8859_1_to_utf8 (unsigned char *text
, int len
, gsize
*bytes_written
)
696 unsigned char *res
, *output
;
697 static const unsigned short lowtable
[] = /* 74 byte table for 80-a4 */
699 /* compressed utf-8 table: if the first byte's 0x20 bit is set, it
700 indicates a 2-byte utf-8 sequence, otherwise prepend a 0xe2. */
701 0x82ac, /* 80 Euro. CP1252 from here on... */
737 0x82ac /* a4 ISO-8859-15 Euro. */
743 /* worst case scenario: every byte turns into 3 bytes */
744 res
= output
= g_malloc ((len
* 3) + 1);
750 if (G_LIKELY (*text
< 0x80))
752 *output
= *text
; /* ascii maps directly */
754 else if (*text
<= 0xa4) /* 80-a4 use a lookup table */
757 if (lowtable
[idx
] & 0x2000)
759 *output
++ = (lowtable
[idx
] >> 8) & 0xdf; /* 2 byte utf-8 */
760 *output
= lowtable
[idx
] & 0xff;
764 *output
++ = 0xe2; /* 3 byte utf-8 */
765 *output
++ = (lowtable
[idx
] >> 8) & 0xff;
766 *output
= lowtable
[idx
] & 0xff;
769 else if (*text
< 0xc0)
777 *output
= *text
- 0x40;
783 *output
= 0; /* terminate */
784 *bytes_written
= output
- res
;
790 text_validate (char **text
, int *len
)
796 if (g_utf8_validate (*text
, *len
, 0))
799 if (prefs
.utf8_locale
)
800 /* fallback to iso-8859-1 */
801 utf
= iso_8859_1_to_utf8 (*text
, *len
, &utf_len
);
804 /* fallback to locale */
805 utf
= g_locale_to_utf8 (*text
, *len
, 0, &utf_len
, NULL
);
807 utf
= iso_8859_1_to_utf8 (*text
, *len
, &utf_len
);
812 *text
= g_strdup ("%INVALID%");
824 PrintText (session
*sess
, char *text
)
832 sess
= (session
*) sess_list
->data
;
835 /* make sure it's valid utf8 */
843 conv
= text_validate ((char **)&text
, &len
);
846 log_write (sess
, text
);
847 scrollback_save (sess
, text
);
848 fe_print_text (sess
, text
, 0);
855 PrintTextf (session
*sess
, char *format
, ...)
860 va_start (args
, format
);
861 buf
= g_strdup_vprintf (format
, args
);
864 PrintText (sess
, buf
);
868 /* Print Events stuff here --AGL */
870 /* Consider the following a NOTES file:
872 The main upshot of this is:
873 * Plugins and Perl scripts (when I get round to signaling perl.c) can intercept text events and do what they like
874 * The default text engine can be config'ed
876 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:
878 The normal %Cx (color) and %B (bold) etc work
880 $x is replaced with the data in var x (e.g. $1 is often the nick)
882 $axxx is replace with a single byte of value xxx (in base 10)
887 /* These lists are thus:
888 pntevts_text[] are the strings the user sees (WITH %x etc)
889 pntevts[] are the data strings with \000 etc
892 /* To add a new event:
894 Think up a name (like "Join")
895 Make up a pevt_name_help struct
896 Add an entry to textevents.in
897 Type: make textevents
902 On startup ~/.xchat/printevents.conf is loaded if it doesn't exist the
903 defaults are loaded. Any missing events are filled from defaults.
904 Each event is parsed by pevt_build_string and a binary output is produced
908 (int) numbers of bytes
909 (char []) that number of byte to be memcpy'ed into the buffer
912 (byte) number of varable to insert
915 Each XP_TE_* signal is hard coded to call text_emit which calls
916 display_event which decodes the data
918 This means that this system *should be faster* than snprintf because
919 it always 'knows' that format of the string (basically is preparses much
925 char *pntevts_text
[NUM_XP
];
926 char *pntevts
[NUM_XP
];
928 #define pevt_generic_none_help NULL
930 static char * const pevt_genmsg_help
[] = {
935 static char * const pevt_join_help
[] = {
936 N_("The nick of the joining person"),
937 N_("The channel being joined"),
938 N_("The host of the person"),
941 static char * const pevt_chanaction_help
[] = {
945 N_("Identified text"),
948 static char * const pevt_chanmsg_help
[] = {
952 N_("Identified text"),
955 static char * const pevt_privmsg_help
[] = {
958 N_("Identified text")
961 static char * const pevt_capack_help
[] = {
963 N_("Acknowledged Capabilities")
966 static char * const pevt_caplist_help
[] = {
968 N_("Server Capabilities")
971 static char * const pevt_capreq_help
[] = {
972 N_("Requested Capabilities")
975 static char * const pevt_changenick_help
[] = {
980 static char * const pevt_newtopic_help
[] = {
981 N_("Nick of person who changed the topic"),
986 static char * const pevt_topic_help
[] = {
991 static char * const pevt_kick_help
[] = {
992 N_("The nickname of the kicker"),
993 N_("The person being kicked"),
998 static char * const pevt_part_help
[] = {
999 N_("The nick of the person leaving"),
1000 N_("The host of the person"),
1004 static char * const pevt_chandate_help
[] = {
1009 static char * const pevt_topicdate_help
[] = {
1015 static char * const pevt_quit_help
[] = {
1021 static char * const pevt_pingrep_help
[] = {
1022 N_("Who it's from"),
1023 N_("The time in x.x format (see below)"),
1026 static char * const pevt_notice_help
[] = {
1027 N_("Who it's from"),
1031 static char * const pevt_channotice_help
[] = {
1032 N_("Who it's from"),
1033 N_("The Channel it's going to"),
1037 static char * const pevt_uchangenick_help
[] = {
1042 static char * const pevt_ukick_help
[] = {
1043 N_("The person being kicked"),
1045 N_("The nickname of the kicker"),
1049 static char * const pevt_partreason_help
[] = {
1050 N_("The nick of the person leaving"),
1051 N_("The host of the person"),
1056 static char * const pevt_ctcpsnd_help
[] = {
1058 N_("The nick of the person"),
1062 static char * const pevt_ctcpgen_help
[] = {
1063 N_("The CTCP event"),
1064 N_("The nick of the person"),
1067 static char * const pevt_ctcpgenc_help
[] = {
1068 N_("The CTCP event"),
1069 N_("The nick of the person"),
1070 N_("The Channel it's going to"),
1073 static char * const pevt_chansetkey_help
[] = {
1074 N_("The nick of the person who set the key"),
1078 static char * const pevt_chansetlimit_help
[] = {
1079 N_("The nick of the person who set the limit"),
1083 static char * const pevt_chanop_help
[] = {
1084 N_("The nick of the person who did the op'ing"),
1085 N_("The nick of the person who has been op'ed"),
1088 static char * const pevt_chanhop_help
[] = {
1089 N_("The nick of the person who has been halfop'ed"),
1090 N_("The nick of the person who did the halfop'ing"),
1093 static char * const pevt_chanvoice_help
[] = {
1094 N_("The nick of the person who did the voice'ing"),
1095 N_("The nick of the person who has been voice'ed"),
1098 static char * const pevt_chanban_help
[] = {
1099 N_("The nick of the person who did the banning"),
1103 static char * const pevt_chanrmkey_help
[] = {
1104 N_("The nick who removed the key"),
1107 static char * const pevt_chanrmlimit_help
[] = {
1108 N_("The nick who removed the limit"),
1111 static char * const pevt_chandeop_help
[] = {
1112 N_("The nick of the person of did the deop'ing"),
1113 N_("The nick of the person who has been deop'ed"),
1115 static char * const pevt_chandehop_help
[] = {
1116 N_("The nick of the person of did the dehalfop'ing"),
1117 N_("The nick of the person who has been dehalfop'ed"),
1120 static char * const pevt_chandevoice_help
[] = {
1121 N_("The nick of the person of did the devoice'ing"),
1122 N_("The nick of the person who has been devoice'ed"),
1125 static char * const pevt_chanunban_help
[] = {
1126 N_("The nick of the person of did the unban'ing"),
1130 static char * const pevt_chanexempt_help
[] = {
1131 N_("The nick of the person who did the exempt"),
1132 N_("The exempt mask"),
1135 static char * const pevt_chanrmexempt_help
[] = {
1136 N_("The nick of the person removed the exempt"),
1137 N_("The exempt mask"),
1140 static char * const pevt_chaninvite_help
[] = {
1141 N_("The nick of the person who did the invite"),
1142 N_("The invite mask"),
1145 static char * const pevt_chanrminvite_help
[] = {
1146 N_("The nick of the person removed the invite"),
1147 N_("The invite mask"),
1150 static char * const pevt_chanmodegen_help
[] = {
1151 N_("The nick of the person setting the mode"),
1152 N_("The mode's sign (+/-)"),
1153 N_("The mode letter"),
1154 N_("The channel it's being set on"),
1157 static char * const pevt_whois1_help
[] = {
1164 static char * const pevt_whois2_help
[] = {
1166 N_("Channel Membership/\"is an IRC operator\""),
1169 static char * const pevt_whois3_help
[] = {
1171 N_("Server Information"),
1174 static char * const pevt_whois4_help
[] = {
1179 static char * const pevt_whois4t_help
[] = {
1185 static char * const pevt_whois5_help
[] = {
1190 static char * const pevt_whois6_help
[] = {
1194 static char * const pevt_whoisid_help
[] = {
1200 static char * const pevt_whoisauth_help
[] = {
1206 static char * const pevt_whoisrealhost_help
[] = {
1208 N_("Real user@host"),
1213 static char * const pevt_generic_channel_help
[] = {
1217 static char * const pevt_saslauth_help
[] = {
1221 static char * const pevt_saslresponse_help
[] = {
1223 N_("Raw Numeric or Identifier"),
1228 static char * const pevt_servertext_help
[] = {
1231 N_("Raw Numeric or Identifier")
1234 static char * const pevt_sslmessage_help
[] = {
1239 static char * const pevt_invited_help
[] = {
1241 N_("Nick of person who invited you"),
1245 static char * const pevt_usersonchan_help
[] = {
1250 static char * const pevt_nickclash_help
[] = {
1251 N_("Nickname in use"),
1252 N_("Nick being tried"),
1255 static char * const pevt_connfail_help
[] = {
1259 static char * const pevt_connect_help
[] = {
1265 static char * const pevt_sconnect_help
[] = {
1269 static char * const pevt_generic_nick_help
[] = {
1275 static char * const pevt_chanmodes_help
[] = {
1280 static char * const pevt_rawmodes_help
[] = {
1285 static char * const pevt_kill_help
[] = {
1290 static char * const pevt_dccchaterr_help
[] = {
1297 static char * const pevt_dccstall_help
[] = {
1303 static char * const pevt_generic_file_help
[] = {
1308 static char * const pevt_dccrecverr_help
[] = {
1310 N_("Destination filename"),
1315 static char * const pevt_dccrecvcomp_help
[] = {
1317 N_("Destination filename"),
1322 static char * const pevt_dccconfail_help
[] = {
1328 static char * const pevt_dccchatcon_help
[] = {
1333 static char * const pevt_dcccon_help
[] = {
1339 static char * const pevt_dccsendfail_help
[] = {
1345 static char * const pevt_dccsendcomp_help
[] = {
1351 static char * const pevt_dccoffer_help
[] = {
1357 static char * const pevt_dccfileabort_help
[] = {
1362 static char * const pevt_dccchatabort_help
[] = {
1366 static char * const pevt_dccresumeoffer_help
[] = {
1372 static char * const pevt_dccsendoffer_help
[] = {
1379 static char * const pevt_dccgenericoffer_help
[] = {
1384 static char * const pevt_notifynumber_help
[] = {
1385 N_("Number of notify items"),
1388 static char * const pevt_serverlookup_help
[] = {
1392 static char * const pevt_servererror_help
[] = {
1396 static char * const pevt_foundip_help
[] = {
1400 static char * const pevt_dccrename_help
[] = {
1405 static char * const pevt_ctcpsend_help
[] = {
1410 static char * const pevt_ignoreaddremove_help
[] = {
1414 static char * const pevt_resolvinguser_help
[] = {
1419 static char * const pevt_malformed_help
[] = {
1424 static char * const pevt_pingtimeout_help
[] = {
1428 static char * const pevt_uinvite_help
[] = {
1429 N_("Nick of person who have been invited"),
1434 static char * const pevt_banlist_help
[] = {
1437 N_("Who set the ban"),
1441 static char * const pevt_discon_help
[] = {
1445 #include "textevents.h"
1448 pevent_load_defaults ()
1452 for (i
= 0; i
< NUM_XP
; i
++)
1454 if (pntevts_text
[i
])
1455 free (pntevts_text
[i
]);
1457 /* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1458 if (te
[i
].num_args
& 128)
1459 pntevts_text
[i
] = strdup (te
[i
].def
);
1461 pntevts_text
[i
] = strdup (_(te
[i
].def
));
1466 pevent_make_pntevts ()
1471 for (i
= 0; i
< NUM_XP
; i
++)
1473 if (pntevts
[i
] != NULL
)
1475 if (pevt_build_string (pntevts_text
[i
], &(pntevts
[i
]), &m
) != 0)
1477 snprintf (out
, sizeof (out
),
1478 _("Error parsing event %s.\nLoading default."), te
[i
].name
);
1479 fe_message (out
, FE_MSG_WARN
);
1480 free (pntevts_text
[i
]);
1481 /* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1482 if (te
[i
].num_args
& 128)
1483 pntevts_text
[i
] = strdup (te
[i
].def
);
1485 pntevts_text
[i
] = strdup (_(te
[i
].def
));
1486 if (pevt_build_string (pntevts_text
[i
], &(pntevts
[i
]), &m
) != 0)
1489 "XChat CRITICAL *** default event text failed to build!\n");
1496 /* Loading happens at 2 levels:
1497 1) File is read into blocks
1498 2) Pe block is parsed and loaded
1502 /* Better hope you pass good args.. --AGL */
1505 pevent_trigger_load (int *i_penum
, char **i_text
, char **i_snd
)
1507 int penum
= *i_penum
, len
;
1508 char *text
= *i_text
, *snd
= *i_snd
;
1510 if (penum
!= -1 && text
!= NULL
)
1512 len
= strlen (text
) + 1;
1513 if (pntevts_text
[penum
])
1514 free (pntevts_text
[penum
]);
1515 pntevts_text
[penum
] = malloc (len
);
1516 memcpy (pntevts_text
[penum
], text
, len
);
1529 pevent_find (char *name
, int *i_i
)
1538 if (strcmp (te
[j
].name
, name
) == 0)
1550 pevent_load (char *filename
)
1552 /* AGL, I've changed this file and pevent_save, could you please take a look at
1553 * the changes and possibly modify them to suit you
1557 int fd
, i
= 0, pnt
= 0;
1559 char *text
= NULL
, *snd
= NULL
;
1563 if (filename
== NULL
)
1564 fd
= xchat_open_file ("pevents.conf", O_RDONLY
, 0, 0);
1566 fd
= xchat_open_file (filename
, O_RDONLY
, 0, XOF_FULLPATH
);
1570 if (fstat (fd
, &st
) != 0)
1572 ibuf
= malloc (st
.st_size
);
1573 read (fd
, ibuf
, st
.st_size
);
1576 while (buf_get_line (ibuf
, &buf
, &pnt
, st
.st_size
))
1580 if (strlen (buf
) == 0)
1583 ofs
= strchr (buf
, '=');
1591 if (strcmp (buf
, "event_name") == 0)
1594 pevent_trigger_load (&penum
, &text
, &snd
);
1595 penum
= pevent_find (ofs
, &i
);
1597 } else if (strcmp (buf
, "event_text") == 0)
1603 /* This allows updating of old strings. We don't use new defaults
1604 if the user has customized the strings (.e.g a text theme).
1605 Hash of the old default is enough to identify and replace it.
1606 This only works in English. */
1608 switch (g_str_hash (ofs
))
1611 /* %C08,02 Hostmask PRIV NOTI CHAN CTCP INVI UNIG %O */
1612 text
= strdup (te
[XP_TE_IGNOREHEADER
].def
);
1617 text
= strdup (te
[XP_TE_IGNOREFOOTER
].def
);
1621 /* -%C10-%C11-%O$tDCC RECV: Cannot open $1 for writing - aborting. */
1622 text
= strdup (te
[XP_TE_DCCFILEERR
].def
);
1626 text
= strdup (ofs
);
1629 text
= strdup (ofs
);
1633 }/* else if (strcmp (buf, "event_sound") == 0)
1644 pevent_trigger_load (&penum
, &text
, &snd
);
1650 pevent_check_all_loaded ()
1654 for (i
= 0; i
< NUM_XP
; i
++)
1656 if (pntevts_text
[i
] == NULL
)
1658 /*printf ("%s\n", te[i].name);
1659 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]);
1660 gtkutil_simpledialog(out); */
1661 /* make-te.c sets this 128 flag (DON'T call gettext() flag) */
1662 if (te
[i
].num_args
& 128)
1663 pntevts_text
[i
] = strdup (te
[i
].def
);
1665 pntevts_text
[i
] = strdup (_(te
[i
].def
));
1673 memset (&pntevts_text
, 0, sizeof (char *) * (NUM_XP
));
1674 memset (&pntevts
, 0, sizeof (char *) * (NUM_XP
));
1676 if (pevent_load (NULL
))
1677 pevent_load_defaults ();
1678 pevent_check_all_loaded ();
1679 pevent_make_pntevts ();
1683 CL: format_event now handles filtering of arguments:
1684 1) if prefs.stripcolor is set, filter all style control codes from arguments
1685 2) always strip \010 (ATTR_HIDDEN) from arguments: it is only for use in the format string itself
1687 #define ARG_FLAG(argn) (1 << (argn))
1690 format_event (session
*sess
, int index
, char **args
, char *o
, int sizeofo
, unsigned int stripcolor_args
)
1692 int len
, oi
, ii
, numargs
;
1693 char *i
, *ar
, d
, a
, done_all
= FALSE
;
1696 numargs
= te
[index
].num_args
& 0x7f;
1698 oi
= ii
= len
= d
= a
= 0;
1704 while (done_all
== FALSE
)
1710 memcpy (&len
, &(i
[ii
]), sizeof (int));
1712 if (oi
+ len
> sizeofo
)
1714 printf ("Overflow in display_event (%s)\n", i
);
1718 memcpy (&(o
[oi
]), &(i
[ii
]), len
);
1727 "XChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
1731 ar
= args
[(int) a
+ 1];
1734 printf ("arg[%d] is NULL in print event\n", a
+ 1);
1737 if (stripcolor_args
& ARG_FLAG(a
+ 1)) len
= strip_color2 (ar
, -1, &o
[oi
], STRIP_ALL
);
1738 else len
= strip_hidden_attribute (ar
, &o
[oi
]);
1748 /* if (sess->type == SESS_DIALOG)
1750 if (prefs.dialog_indent_nicks)
1756 if (prefs
.indent_nicks
)
1770 display_event (session
*sess
, int event
, char **args
, unsigned int stripcolor_args
)
1773 format_event (sess
, event
, args
, o
, sizeof (o
), stripcolor_args
);
1775 PrintText (sess
, o
);
1779 pevt_build_string (const char *input
, char **output
, int *max_arg
)
1781 struct pevt_stage1
*s
= NULL
, *base
= NULL
, *last
= NULL
, *next
;
1783 char o
[4096], d
, *obuf
, *i
;
1784 int oi
, ii
, max
= -1, len
, x
;
1786 len
= strlen (input
);
1787 i
= malloc (len
+ 1);
1788 memcpy (i
, input
, len
+ 1);
1789 check_special_chars (i
, TRUE
);
1812 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1819 s
->data
= malloc (oi
+ sizeof (int) + 1);
1820 s
->len
= oi
+ sizeof (int) + 1;
1821 clen
+= oi
+ sizeof (int) + 1;
1823 memcpy (&(s
->data
[1]), &oi
, sizeof (int));
1824 memcpy (&(s
->data
[1 + sizeof (int)]), o
, oi
);
1829 fe_message ("String ends with a $", FE_MSG_WARN
);
1857 fe_message ("String ends in $a", FE_MSG_WARN
);
1860 fe_message ("$a value is greater than 255", FE_MSG_WARN
);
1865 /* Tab - if tabnicks is set then write '\t' else ' ' */
1866 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1873 s
->data
= malloc (1);
1880 if (d
< '1' || d
> '9')
1882 snprintf (o
, sizeof (o
), "Error, invalid argument $%c\n", d
);
1883 fe_message (o
, FE_MSG_WARN
);
1889 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1896 s
->data
= malloc (2);
1904 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1911 s
->data
= malloc (oi
+ sizeof (int) + 1);
1912 s
->len
= oi
+ sizeof (int) + 1;
1913 clen
+= oi
+ sizeof (int) + 1;
1915 memcpy (&(s
->data
[1]), &oi
, sizeof (int));
1916 memcpy (&(s
->data
[1 + sizeof (int)]), o
, oi
);
1919 s
= (struct pevt_stage1
*) malloc (sizeof (struct pevt_stage1
));
1926 s
->data
= malloc (1);
1933 obuf
= malloc (clen
);
1937 memcpy (&obuf
[oi
], s
->data
, s
->len
);
1955 /* black n white(0/1) are bad colors for nicks, and we'll use color 2 for us */
1956 /* also light/dark gray (14/15) */
1957 /* 5,7,8 are all shades of yellow which happen to look dman near the same */
1959 static char rcolors
[] = { 19, 20, 22, 24, 25, 26, 27, 28, 29 };
1962 color_of (char *name
)
1968 sum
%= sizeof (rcolors
) / sizeof (char);
1969 return rcolors
[sum
];
1973 /* called by EMIT_SIGNAL macro */
1976 text_emit (int index
, session
*sess
, char *a
, char *b
, char *c
, char *d
, char* file
, int lineno
)
1978 char *word
[PDIWORDS
];
1980 unsigned int stripcolor_args
= (prefs
.stripcolor
? 0xFFFFFFFF : 0);
1981 char tbuf
[NICKLEN
+ 4];
1983 if(getenv("XCHATDEBUG"))
1984 dprintf(2, "%s:%d %s %s %s %s\n", file
, lineno
, a
? a
: "", b
? b
: "", c
? c
: "", d
? d
: "");
1986 if (prefs
.colorednicks
&& (index
== XP_TE_CHANACTION
|| index
== XP_TE_CHANMSG
))
1988 snprintf (tbuf
, sizeof (tbuf
), "\003%d%s", color_of (a
), a
);
1990 stripcolor_args
&= ~ARG_FLAG(1); /* don't strip color from this argument */
1993 word
[0] = te
[index
].name
;
1994 word
[1] = (a
? a
: "\000");
1995 word
[2] = (b
? b
: "\000");
1996 word
[3] = (c
? c
: "\000");
1997 word
[4] = (d
? d
: "\000");
1998 for (i
= 5; i
< PDIWORDS
; i
++)
2001 if (plugin_emit_print (sess
, word
))
2004 /* If a plugin's callback executes "/close", 'sess' may be invalid */
2005 if (!is_session (sess
))
2012 case XP_TE_PARTREASON
:
2014 /* implement ConfMode / Hide Join and Part Messages */
2015 if (chanopt_is_set (prefs
.confmode
, sess
->text_hidejoinpart
))
2019 /* ===Private message=== */
2021 case XP_TE_DPRIVMSG
:
2022 case XP_TE_PRIVACTION
:
2023 case XP_TE_DPRIVACTION
:
2024 if (chanopt_is_set_a (prefs
.input_beep_priv
, sess
->alert_beep
))
2026 if (chanopt_is_set_a (prefs
.input_flash_priv
, sess
->alert_taskbar
))
2027 fe_flash_window (sess
);
2028 /* why is this one different? because of plugin-tray.c's hooks! ugly */
2029 if (sess
->alert_tray
== SET_ON
)
2030 fe_tray_set_icon (FE_ICON_MESSAGE
);
2033 /* ===Highlighted message=== */
2034 case XP_TE_HCHANACTION
:
2035 case XP_TE_HCHANMSG
:
2036 if (chanopt_is_set_a (prefs
.input_beep_hilight
, sess
->alert_beep
))
2038 if (chanopt_is_set_a (prefs
.input_flash_hilight
, sess
->alert_taskbar
))
2039 fe_flash_window (sess
);
2040 if (sess
->alert_tray
== SET_ON
)
2041 fe_tray_set_icon (FE_ICON_MESSAGE
);
2044 /* ===Channel message=== */
2045 case XP_TE_CHANACTION
:
2047 if (chanopt_is_set_a (prefs
.input_beep_chans
, sess
->alert_beep
))
2049 if (chanopt_is_set_a (prefs
.input_flash_chans
, sess
->alert_taskbar
))
2050 fe_flash_window (sess
);
2051 if (sess
->alert_tray
== SET_ON
)
2052 fe_tray_set_icon (FE_ICON_MESSAGE
);
2055 /* do not spam log */
2056 if(!sess
->total
) return;
2059 sound_play_event (index
);
2060 display_event (sess
, index
, word
, stripcolor_args
);
2064 text_find_format_string (char *name
)
2068 i
= pevent_find (name
, &i
);
2070 return pntevts_text
[i
];
2076 text_emit_by_name (char *name
, session
*sess
, char *a
, char *b
, char *c
, char *d
)
2080 i
= pevent_find (name
, &i
);
2083 text_emit (i
, sess
, a
, b
, c
, d
, "", 0);
2091 pevent_save (char *fn
)
2097 fd
= xchat_open_file ("pevents.conf", O_CREAT
| O_TRUNC
| O_WRONLY
,
2100 fd
= xchat_open_file (fn
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0x180,
2101 XOF_FULLPATH
| XOF_DOMODE
);
2105 fe_message ("Error opening config file\n", FALSE);
2106 If we get here when X-Chat is closing the fe-message causes a nice & hard crash
2107 so we have to use perror which doesn't rely on GTK
2110 perror ("Error opening config file\n");
2114 for (i
= 0; i
< NUM_XP
; i
++)
2116 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2117 "event_name=%s\n", te
[i
].name
));
2118 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2119 "event_text=%s\n\n", pntevts_text
[i
]));
2125 /* =========================== */
2126 /* ========== SOUND ========== */
2127 /* =========================== */
2129 char *sound_files
[NUM_XP
];
2132 sound_beep (session
*sess
)
2134 if (sound_files
[XP_TE_BEEP
] && sound_files
[XP_TE_BEEP
][0])
2135 /* user defined beep _file_ */
2136 sound_play_event (XP_TE_BEEP
);
2143 sound_find_command (void)
2145 /* some sensible unix players. You're bound to have one of them */
2146 static const char * const progs
[] = {"aplay", "esdplay", "soxplay", "artsplay", NULL
};
2150 if (prefs
.soundcmd
[0])
2151 return g_strdup (prefs
.soundcmd
);
2155 cmd
= g_find_program_in_path (progs
[i
]);
2165 sound_play (const char *file
, gboolean quiet
)
2172 /* the pevents GUI editor triggers this after removing a soundfile */
2178 snprintf (wavfile
, sizeof (wavfile
), "%s/%s", prefs
.sounddir
, file
);
2181 strncpy (wavfile
, file
, sizeof (wavfile
));
2183 wavfile
[sizeof (wavfile
) - 1] = 0; /* ensure termination */
2185 file_fs
= xchat_filename_from_utf8 (wavfile
, -1, 0, 0, 0);
2189 if (access (file_fs
, R_OK
) == 0)
2191 cmd
= sound_find_command ();
2195 if (strchr (file_fs
, ' '))
2196 snprintf (buf
, sizeof (buf
), "%s \"%s\"", cmd
, file_fs
);
2198 snprintf (buf
, sizeof (buf
), "%s %s", cmd
, file_fs
);
2199 buf
[sizeof (buf
) - 1] = '\0';
2210 snprintf (buf
, sizeof (buf
), _("Cannot read sound file:\n%s"), wavfile
);
2211 fe_message (buf
, FE_MSG_ERROR
);
2219 sound_play_event (int i
)
2222 sound_play (sound_files
[i
], FALSE
);
2226 sound_load_event (char *evt
, char *file
)
2230 if (file
[0] && pevent_find (evt
, &i
) != -1)
2233 free (sound_files
[i
]);
2234 sound_files
[i
] = strdup (file
);
2245 memset (&sound_files
, 0, sizeof (char *) * (NUM_XP
));
2247 fd
= xchat_open_file ("sound.conf", O_RDONLY
, 0, 0);
2252 while (waitline (fd
, buf
, sizeof buf
, FALSE
) != -1)
2254 if (strncmp (buf
, "event=", 6) == 0)
2256 safe_strcpy (evt
, buf
+ 6, sizeof (evt
));
2258 else if (strncmp (buf
, "sound=", 6) == 0)
2262 sound_load_event (evt
, buf
+ 6);
2277 fd
= xchat_open_file ("sound.conf", O_CREAT
| O_TRUNC
| O_WRONLY
, 0x180,
2282 for (i
= 0; i
< NUM_XP
; i
++)
2284 if (sound_files
[i
] && sound_files
[i
][0])
2286 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2287 "event=%s\n", te
[i
].name
));
2288 write (fd
, buf
, snprintf (buf
, sizeof (buf
),
2289 "sound=%s\n\n", sound_files
[i
]));