2 linphone, gtk-glade interface.
3 Copyright (C) 2008 Simon MORLAT (simon.morlat@linphone.org)
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include <sys/types.h>
26 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
28 extern gchar *linphone_logfile;
30 static GtkWidget *log_window=NULL;
31 static GStaticMutex log_mutex=G_STATIC_MUTEX_INIT;
32 static GList *log_queue=NULL;
33 static const char *dateformat="%Y%m%d-%H:%M:%S";
35 #define LOG_MAX_CHARS 1000000 /*1 mega bytes of traces*/
37 typedef struct _LinphoneGtkLog{
44 * Module to log to a file
47 /* Marker to insert as a line at the start of every log file */
48 #define LOGFILE_MARKER_START "----<start>----"
49 /* Marker to insert as a line at the end of every log file */
50 #define LOGFILE_MARKER_STOP "----<end>----"
51 /* Number of files to keep in history, log file rotation will be
53 #define LOGFILE_ROTATION 4
54 /* Pointer to opened log file */
55 static FILE *_logfile = NULL;
58 /* Called on exit, print out the marker, close the file and avoid to
60 void linphone_gtk_log_uninit()
62 if (_logfile != NULL) {
63 fprintf(_logfile, "%s\n", LOGFILE_MARKER_STOP);
69 /* Called when we start logging, find a good place for the log files,
70 perform rotation, insert the start marker and return the pointer to
71 the file that should be used for logging, or NULL on errors or if
73 static FILE *linphone_gtk_log_init()
75 static char _logdir[1024];
76 static char _logfname[1024];
77 static gboolean _log_init = FALSE;
78 const char *dst_fname=NULL;
81 if (linphone_gtk_get_core()!=NULL){
82 dst_fname = linphone_gtk_get_ui_config("logfile",NULL);
83 dateformat=linphone_gtk_get_ui_config("logfile_date_format",dateformat);
85 /* For anything to happen, we need a logfile configuration variable,
86 this is our trigger */
88 /* arrange for _logdir to contain a
89 directory that has been created and _logfname to contain the
90 path to a file to which we will log */
92 const char *appdata=getenv("LOCALAPPDATA");
94 snprintf(_logdir, sizeof(_logdir),"%s\\Linphone", appdata);
99 #define PATH_SEPARATOR '\\'
101 const char *home=getenv("HOME");
103 snprintf(_logdir, sizeof(_logdir),"%s/.linphone", home);
104 mkdir(_logdir,S_IRUSR | S_IWUSR | S_IRGRP);
108 #define PATH_SEPARATOR '/'
110 if (_logdir[0] != '\0') {
111 /* We have a directory, fix the path to the log file in it and
112 open the file so that we will be appending to it. */
113 snprintf(_logfname, sizeof(_logfname), "%s%c%s",_logdir, PATH_SEPARATOR, dst_fname);
115 }else if (linphone_logfile!=NULL){
116 snprintf(_logfname,sizeof(_logfname),"%s",linphone_logfile);
119 if (_logfname[0]!='\0'){
120 /* If the constant LOGFILE_ROTATION is greater than zero, then
121 we kick away a simple rotation that will ensure that there
122 are never more than LOGFILE_ROTATION+1 old copies of the
123 log file on the disk. The oldest file is always rotated
124 "away" as expected. Rotated files have the same name as
125 the main log file, though with a number 0..LOGFILE_ROTATION
126 at the end, where the greater the number is, the older the
128 if (ortp_file_exist(_logfname)==0 && LOGFILE_ROTATION > 0) {
130 char old_fname[1024];
131 char new_fname[1024];
133 /* Rotate away existing files. We make sure to remove the
134 old files otherwise rename() would not work properly. We
135 have to loop in reverse here. */
136 for (i=LOGFILE_ROTATION-1;i>=0;i--) {
137 snprintf(old_fname, sizeof(old_fname), "%s%c%s.%d",
138 _logdir, PATH_SEPARATOR, dst_fname, i);
139 snprintf(new_fname, sizeof(new_fname), "%s%c%s.%d",
140 _logdir, PATH_SEPARATOR, dst_fname, i+1);
141 if (ortp_file_exist(old_fname)==0) {
142 if (ortp_file_exist(new_fname)==0)
144 rename(old_fname, new_fname);
147 /* Move current log file as the first of the rotation. Make
148 sure to remove the old .0 also, since otherwise rename()
149 would not work as expected. */
150 snprintf(new_fname, sizeof(new_fname), "%s%c%s.%d",
151 _logdir, PATH_SEPARATOR, dst_fname, 0);
152 if (ortp_file_exist(new_fname)==0)
154 rename(_logfname, new_fname);
156 /* Start a new log file and mark that we have now initialised */
157 _logfile = fopen(_logfname, "w");
158 fprintf(_logfile, "%s\n", LOGFILE_MARKER_START);
165 static void linphone_gtk_log_file(OrtpLogLevel lev, const char *msg)
170 outlog = linphone_gtk_log_init();
171 if (outlog != NULL) {
172 /* We have an opened file and we have initialised properly, it's
173 time to write all these log messages. We convert the log level
174 from oRTP into something readable and timestamp each log
175 message. The format of the time stamp can be controlled by
176 logfile_date_format in the GtkUi section of the config file,
177 but it defaults to something compact, but yet readable. */
178 const char *lname="undef";
181 /* Convert level constant to text */
202 /* Get current time and format it properly */
204 strftime(date, sizeof(date), dateformat, localtime(&now));
205 /* Now print out the message to the logfile. We don't flush,
206 maybe we should do to ensure that we have all the messages in
207 case of a crash (which is one of the main reasons we have a
208 log facility in the first place). */
209 fprintf(outlog, "[%s] [%s] %s\n", date, lname, msg);
214 void linphone_gtk_log_hide(){
216 gtk_widget_hide(log_window);
219 void linphone_gtk_create_log_window(void){
221 log_window=linphone_gtk_create_window("log");
222 b=gtk_text_view_get_buffer(GTK_TEXT_VIEW(linphone_gtk_get_widget(log_window,"textview")));
223 gtk_text_buffer_create_tag(b,"red","foreground","red",NULL);
224 gtk_text_buffer_create_tag(b,"orange","foreground","orange",NULL);
225 /*prevent the log window from being destroyed*/
226 g_signal_connect (G_OBJECT (log_window), "delete-event",
227 G_CALLBACK (gtk_widget_hide_on_delete), log_window);
231 void linphone_gtk_destroy_log_window(void){
232 GtkWidget *w=log_window;
233 g_static_mutex_lock(&log_mutex);
235 gtk_widget_destroy(w);
236 g_static_mutex_unlock(&log_mutex);
239 void linphone_gtk_log_show(void){
240 gtk_widget_show(log_window);
241 gtk_window_present(GTK_WINDOW(log_window));
244 static void linphone_gtk_display_log(GtkTextView *v, OrtpLogLevel lev, const char *msg){
245 GtkTextIter iter,begin;
248 const char *lname="undef";
250 b=gtk_text_view_get_buffer(v);
268 g_error("Bad level !");
271 gtk_text_buffer_get_end_iter(b,&iter);
272 off=gtk_text_iter_get_offset(&iter);
273 gtk_text_buffer_insert(b,&iter,lname,-1);
274 gtk_text_buffer_get_end_iter(b,&iter);
275 gtk_text_buffer_insert(b,&iter,": ",-1);
276 gtk_text_buffer_get_end_iter(b,&iter);
277 gtk_text_buffer_insert(b,&iter,msg,-1);
278 gtk_text_buffer_get_end_iter(b,&iter);
279 gtk_text_buffer_insert(b,&iter,"\n",-1);
280 gtk_text_buffer_get_end_iter(b,&iter);
281 gtk_text_buffer_get_iter_at_offset(b,&begin,off);
282 if (lev==ORTP_ERROR || lev==ORTP_FATAL) gtk_text_buffer_apply_tag_by_name(b,"red",&begin,&iter);
283 else if (lev==ORTP_WARNING) gtk_text_buffer_apply_tag_by_name(b,"orange",&begin,&iter);
285 while(gtk_text_buffer_get_char_count(b)>LOG_MAX_CHARS){
286 GtkTextIter iter_line_after;
287 gtk_text_buffer_get_start_iter(b,&iter);
288 iter_line_after=iter;
289 if (gtk_text_iter_forward_line(&iter_line_after)){
290 gtk_text_buffer_delete(b,&iter,&iter_line_after);
296 static void stick_to_end(GtkTextView *v){
299 b=gtk_text_view_get_buffer(v);
300 gtk_text_buffer_get_end_iter(b,&iter);
301 gtk_text_view_scroll_to_iter(v,&iter,0,FALSE,1.0,0);
304 void linphone_gtk_log_scroll_to_end(GtkToggleButton *button){
305 if (gtk_toggle_button_get_active(button)){
306 GtkTextView *v=GTK_TEXT_VIEW(linphone_gtk_get_widget(log_window,"textview"));
312 * called from Gtk main loop.
314 gboolean linphone_gtk_check_logs(){
317 if (log_window) v=GTK_TEXT_VIEW(linphone_gtk_get_widget(log_window,"textview"));
318 g_static_mutex_lock(&log_mutex);
319 for(elem=log_queue;elem!=NULL;elem=elem->next){
320 LinphoneGtkLog *lgl=(LinphoneGtkLog*)elem->data;
321 if (v) linphone_gtk_display_log(v,lgl->lev,lgl->msg);
325 if (log_queue) g_list_free(log_queue);
327 g_static_mutex_unlock(&log_mutex);
329 linphone_gtk_log_scroll_to_end(GTK_TOGGLE_BUTTON(linphone_gtk_get_widget(log_window,"scroll_to_end")));
335 * Called from any linphone thread.
337 void linphone_gtk_log_push(OrtpLogLevel lev, const char *fmt, va_list args){
338 gchar *msg=g_strdup_vprintf(fmt,args);
339 LinphoneGtkLog *lgl=g_new(LinphoneGtkLog,1);
342 g_static_mutex_lock(&log_mutex);
343 log_queue=g_list_append(log_queue,lgl);
344 linphone_gtk_log_file(lev, msg);
345 g_static_mutex_unlock(&log_mutex);
348 void linphone_gtk_log_clear(void){
350 GtkTextIter end,begin;
353 v=GTK_TEXT_VIEW(linphone_gtk_get_widget(log_window,"textview"));
354 b=gtk_text_view_get_buffer(v);
355 gtk_text_buffer_get_start_iter(b,&begin);
356 gtk_text_buffer_get_end_iter(b,&end);
357 gtk_text_buffer_delete(b,&begin,&end);