Rename UI_MANAGER to ENABLE_UI_MANAGER, put it into
[obnox/wireshark/wip.git] / gtk / proto_help.c
1 /* proto_help.c
2  * Routines for dynamic protocol help menus
3  *
4  * $Id$
5  *
6  * Edgar Gladkich <edgar.gladkich@incacon.de>
7  * Gerald Combs <gerald@wireshark.org>
8  *
9  * Wireshark - Network traffic analyzer
10  * By Gerald Combs <gerald@wireshark.org>
11  * Copyright 1998 Gerald Combs
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #if defined(GTK_DISABLE_DEPRECATED) && !defined(MAIN_MENU_USE_UIMANAGER)
32 # undef GTK_DISABLE_DEPRECATED
33 #endif
34
35 #include <string.h>
36
37 #include <gtk/gtk.h>
38 #include <gtk/webbrowser.h>
39
40 #include "../file.h"
41
42 #include <epan/prefs.h>
43 #include <epan/filesystem.h>
44 #include <epan/strutil.h>
45 #include <epan/proto.h>
46
47 #include "gtk/proto_help.h"
48
49 /*
50  * The protocol help files cannot be downloaded downloaded directly. You
51  * will need to go to http://www.inacon.com/download/, then download one
52  * of the packages, e.g. the source package and extract the protocol_help
53  * folder. To actually use the help you will also need to be able to
54  * access the URL beginning at the contents of the loclation line in the
55  * ph.ini file (e.g. location=http://www.inacon.de/ph/data/).
56  * To provide offline service download the contents of the given location
57  * to your computer, then change the location parameter in the ph.ini file
58  * to point to that new URL. This may be a file:/// URL.
59  */
60
61 /* Right now proto_help will not build with -DGTK_DISABLE_DEPRECATED due to
62    its use of GtkItemFactory.
63    See http://developer.gnome.org/gtk/2.24/GtkItemFactory.html: "As of GTK+ 2.4,
64    GtkItemFactory has been deprecated in favour of GtkUIManager."
65    This needs to be rewritten by the people who added this code or some other
66    volunteers. Otherwise this functionality will be lost once the non UI_MANAGER
67    stuff gets removed it isn't built by default any more.
68  */
69 #ifdef MAIN_MENU_USE_UIMANAGER
70 void proto_help_menu_modify(GtkTreeSelection *selection _U_, capture_file *cf _U_) {}
71 void proto_help_menu_init(GtkWidget *widget _U_) {}
72 void proto_help_init(void) {}
73 #else
74
75 #define PH_MENU_TOP "/Protocol Help"
76
77 #define PH_FILE_LOG "ph.log"
78 #define PH_INI_SUFFIX ".ini"
79 #define PH_PATH_SEARCH_STR "${PATH}"
80
81 /* .ini Section names */
82 #define PH_INI_GROUP_DATABASE "database"
83 #define PH_INI_GROUP_LOCATION_DATA "location data"
84 #define PH_INI_GROUP_MAP "map"
85
86 /* .ini [database] section keys */
87 #define PH_INI_DB_KEY_SOURCE "source"
88 #define PH_INI_DB_KEY_LOCATION_TEMPLATE "location"
89
90 /* .ini Path sections */
91 #define PH_INI_KEY_OVERVIEW "_OVERVIEW"
92
93 /* Where to look for .ini files */
94 #define PH_CONFFILE_SUBDIR "protocol_help"
95
96 #ifdef PH_DEBUG_LOG
97 const gchar *ph_log_path;
98 #endif
99
100 typedef struct proto_help_key_file_t {
101         GKeyFile *keyfile;
102         const gchar *source;
103         const gchar *loc_template;
104 } proto_help_key_file;
105
106 GPtrArray *g_ph_key_files = NULL;
107
108 GtkItemFactory *g_ph_menu_factory;
109
110 static void ph_menu_reset(void);
111 static void ph_menu_onclick(GtkWidget*, gpointer, guint);
112
113 static int ph_capture_get_protocol_id(GtkTreeSelection*, capture_file*);
114 static const gchar* ph_capture_get_protocol_name(GtkTreeSelection*, capture_file*);
115 static const gchar* ph_capture_get_protocol_abbrev(GtkTreeSelection*, capture_file*);
116 static gchar* ph_capture_get_description(capture_file*);
117
118 static proto_help_key_file *ph_ini_load_file(const gchar*);
119 static gchar* ph_ini_get_value(GKeyFile *, const gchar*, const gchar*, gchar *);
120 static gchar* ph_ini_get_path(GKeyFile *, const gchar*, const gchar*);
121 static gchar** ph_ini_get_keywords(GKeyFile *, const gchar*);
122
123 static guint ph_parse_string(const gchar*, const gchar*);
124
125 /* #define PH_DEBUG_LOG 1 */
126 #ifdef PH_DEBUG_LOG
127 static void ph_logging_handler(const gchar*, GLogLevelFlags, const gchar*, gpointer);
128 #endif
129
130 /** @file
131  * Protocol help routines. Adds web browser links to protocol menu items
132  * via configuration files.
133  */
134
135 /** Initialization
136  *
137  * @param void
138  * @return void
139  */
140 #define PH_CONF_DIRS 2
141 void proto_help_init(void)
142 {
143         gchar *search_dir[PH_CONF_DIRS];
144         const gchar *ini_name;
145         gchar *ini_path;
146         GDir *conf_dir;
147         int i;
148
149         search_dir[0] = g_strdup_printf("%s" G_DIR_SEPARATOR_S PH_CONFFILE_SUBDIR, get_datafile_dir());
150         /* XXX - Use profiles? */
151         search_dir[1] = get_persconffile_path(PH_CONFFILE_SUBDIR, FALSE, FALSE);
152
153 #ifdef PH_DEBUG_LOG
154         g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, ph_logging_handler, NULL);
155 #endif
156
157         if (g_ph_key_files)
158                 return;
159
160         g_ph_key_files = g_ptr_array_new();
161
162         /* Start loop */
163
164 #ifdef PH_DEBUG_LOG
165         ph_log_path = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", g_get_tmp_dir(), PH_FILE_LOG);
166 #endif
167
168         for (i = 0; i < PH_CONF_DIRS; i++) {
169                 g_log(NULL, G_LOG_LEVEL_INFO, "Looking for protocol help files in '%s'", search_dir[i]);
170                 conf_dir = g_dir_open(search_dir[i], 0, NULL);
171                 if (!conf_dir) {
172                         continue;
173                 }
174
175                 while ((ini_name = g_dir_read_name(conf_dir)) != NULL) {
176                         if (! g_str_has_suffix(ini_name, PH_INI_SUFFIX)) {
177                                 continue;
178                         }
179                         g_log(NULL, G_LOG_LEVEL_INFO, "-- Found '%s'", ini_name);
180                         ini_path = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", search_dir[i], ini_name);
181                         ph_ini_load_file(ini_path);
182                         g_free(ini_path);
183                 }
184                 g_dir_close(conf_dir);
185         }
186 }
187
188 /** Initialize the menu
189  *
190  * @param widget Context menu root
191  * @return void
192  */
193 void proto_help_menu_init(GtkWidget *widget)
194 {
195         g_ph_menu_factory = gtk_item_factory_from_widget(widget);
196         ph_menu_reset();
197 }
198
199 /** Clear the menu
200  *
201  * @param void
202  * @return void
203  */
204 static void ph_menu_reset(void)
205 {
206         GtkWidget *menu_item = NULL;
207         GList *menu_entries = NULL;
208         GList *menu_entry = NULL;
209
210         if(!g_ph_menu_factory) return;
211
212         menu_item = gtk_item_factory_get_widget(g_ph_menu_factory, PH_MENU_TOP);
213         menu_entries = gtk_container_get_children(GTK_CONTAINER(menu_item));
214
215         for(menu_entry = g_list_first(menu_entries); menu_entry != NULL; menu_entry = g_list_next(menu_entry))
216         {
217                 gtk_container_remove(GTK_CONTAINER(menu_item), menu_entry->data);
218         }
219
220         menu_item = gtk_item_factory_get_item(g_ph_menu_factory, PH_MENU_TOP);
221         gtk_widget_set_sensitive(menu_item, FALSE);
222 }
223
224 /* Callback to free URLs */
225 static void
226 url_destroy_cb(GtkWidget *w _U_, gpointer url) {
227         g_free(url);
228 }
229
230 /** Fill in the protocol help menu
231  *
232  * @param selection Currently-selected packet
233  * @param cf Capture file
234  * @return void
235  */
236 void proto_help_menu_modify(GtkTreeSelection *selection, capture_file *cf)
237 {
238         gchar *description;
239         const gchar *proto_abbrev, *proto_name;
240         gchar *value;
241         gchar **keys;
242         GHashTable *table;
243         guint i = 0, cur_kf;
244         GtkWidget *menu_item = NULL;
245         GtkItemFactoryEntry *menu_entry = NULL;
246         proto_help_key_file* phkf;
247         gchar *loc;
248         gboolean add_separator = FALSE;
249         gboolean found = FALSE;
250
251         if(!g_ph_menu_factory) return;
252         ph_menu_reset();
253
254         proto_abbrev = ph_capture_get_protocol_abbrev(selection, cf);
255         if(!proto_abbrev) return;
256
257         proto_name = ph_capture_get_protocol_name(selection, cf);
258         if(!proto_name) return;
259
260         description = ph_capture_get_description(cf);
261
262         for (cur_kf = 0; cur_kf < g_ph_key_files->len; cur_kf++) {
263                 phkf = (proto_help_key_file *) g_ptr_array_index(g_ph_key_files, cur_kf);
264                 g_assert(phkf);
265
266                 value = ph_ini_get_path(phkf->keyfile, proto_abbrev, PH_INI_KEY_OVERVIEW);
267
268                 if(!value)
269                 {
270                         g_log(NULL, G_LOG_LEVEL_DEBUG, "Overview page of the protocol '%s' is not defined", proto_abbrev);
271                         continue;
272                 }
273
274                 /*
275                  * XXX - We could save some memory here if we stored the location template
276                  * and search value as separate items. However, that makes freeing them
277                  * a bit messy.
278                  */
279                 loc = string_replace(phkf->loc_template, PH_PATH_SEARCH_STR, value);
280                 g_free(value);
281                 if (!loc || !strlen(loc)) continue;
282
283                 if (add_separator) {
284                         menu_entry = g_malloc0(sizeof(GtkItemFactoryEntry));
285                         menu_entry->path = g_strdup_printf("%s/<separator>", PH_MENU_TOP);
286                         menu_entry->item_type = "<Separator>";
287                         gtk_item_factory_create_item(g_ph_menu_factory, menu_entry, NULL, 2);
288                 }
289                 add_separator = TRUE;
290
291                 menu_entry = g_malloc0(sizeof(GtkItemFactoryEntry));
292                 menu_entry->path = g_strdup_printf("%s/%s %s Overview", PH_MENU_TOP, phkf->source, proto_name);
293                 menu_entry->callback = ph_menu_onclick;
294                 gtk_item_factory_create_item(g_ph_menu_factory, menu_entry, loc, 2);
295                 menu_item = gtk_item_factory_get_widget(g_ph_menu_factory, menu_entry->path);
296                 g_assert(menu_item);
297                 g_signal_connect(menu_item, "destroy", G_CALLBACK(url_destroy_cb), loc);
298
299                 found = TRUE;
300
301                 if(description)
302                 {
303                         keys = ph_ini_get_keywords(phkf->keyfile, proto_abbrev);
304                         table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
305
306                         for(i = 0; keys[i] != NULL; i++)
307                         {
308                                 if(!strcmp(keys[i], PH_INI_KEY_OVERVIEW)) continue; /* We already added the overview */
309                                 if(!ph_parse_string(description, keys[i])) continue; /* Bad format */
310                                 if(g_hash_table_lookup(table, g_ascii_strup(keys[i], -1)) != NULL) continue; /* Duplicate */
311
312                                 value = ph_ini_get_path(phkf->keyfile, proto_abbrev, keys[i]);
313                                 if(!value || !strlen(value)) continue;
314
315                                 loc = string_replace(phkf->loc_template, PH_PATH_SEARCH_STR, value);
316                                 g_free(value);
317                                 if (!loc || !strlen(loc)) continue;
318
319                                 g_hash_table_insert(table, g_ascii_strup(keys[i], -1), GINT_TO_POINTER(1));
320
321                                 menu_entry = g_malloc0(sizeof(GtkItemFactoryEntry));
322                                 menu_entry->path = g_strdup_printf("%s/%s", PH_MENU_TOP, keys[i]);
323                                 menu_entry->callback = ph_menu_onclick;
324                                 gtk_item_factory_create_item(g_ph_menu_factory, menu_entry, loc, 2);
325                                 menu_item = gtk_item_factory_get_widget(g_ph_menu_factory, menu_entry->path);
326                                 g_assert(menu_item);
327                                 g_signal_connect(menu_item, "destroy", G_CALLBACK(url_destroy_cb), loc);
328                         }
329
330                         g_hash_table_destroy(table);
331                 }
332         }
333
334         g_free(description);
335         menu_item = gtk_item_factory_get_item(g_ph_menu_factory, PH_MENU_TOP);
336         gtk_widget_set_sensitive(menu_item, found);
337 }
338
339 /**
340 * Function ph_menu_onclick
341 *
342 * @param GtkWidget *widget Description
343 * @param gpointer data Description
344 * @param guint action Description
345 * @return void
346 */
347 static void ph_menu_onclick(GtkWidget *widget _U_, gpointer data, guint action _U_)
348 {
349         gchar *loc = (gchar *) data;
350
351         g_log(NULL, G_LOG_LEVEL_DEBUG, "Sending '%s' to the browser.", loc);
352
353         if (! loc) {
354                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Protocol help ended up with a NULL URL.");
355                 return;
356         }
357
358         /* XXX - Should we do any RFC 3986 escaping first? */
359         if (! browser_open_url(loc)) {
360                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Couldn't get protocol help for %s", loc);
361         }
362 }
363
364 /** Get the field ID for a selected tree item.
365  *
366  * @param selection Tree selection
367  * @param cfile Capture file
368  * @return Field ID or 0
369  */
370 static int
371 ph_capture_get_protocol_id(GtkTreeSelection *selection, capture_file *cf)
372 {
373         GtkTreeModel *model = NULL;
374         GtkTreeIter iter;
375         GtkTreeIter parent;
376         field_info *finfo = NULL;
377         int proto_id = 0;
378
379         if(!cf->finfo_selected) return 0;
380         proto_id = cf->finfo_selected->hfinfo->id;
381
382         if(!proto_id)
383         {
384                 if(!gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), &model, &iter)) return 0;
385
386                 while(gtk_tree_model_iter_parent(model, &parent, &iter))
387                 {
388                         gtk_tree_model_get(model, &parent, 1, &finfo, -1);
389                         iter = parent;
390
391                         if(finfo->hfinfo->id > 0)
392                         {
393                                 proto_id = finfo->hfinfo->id;
394                                 break;
395                         }
396                 }
397         }
398
399         while(proto_id && !proto_registrar_is_protocol(proto_id))
400         {
401                 proto_id = proto_registrar_get_parent(proto_id);
402         }
403
404         return proto_id;
405 }
406
407 /** Get the protocol name for a selected tree item.
408  *
409  * @param selection Tree selection
410  * @param cfile Capture file
411  * @return Name for a protocol or NULL
412  */
413 static const gchar*
414 ph_capture_get_protocol_name(GtkTreeSelection *selection, capture_file *cf)
415 {
416         int proto_id = ph_capture_get_protocol_id(selection, cf);
417
418         return (!proto_id) ? NULL : proto_get_protocol_short_name(find_protocol_by_id(proto_id));
419 }
420
421 /** Get the abbreviated protocol name for a selected tree item.
422  *
423  * @param selection Tree selection
424  * @param cfile Capture file
425  * @return Abbreviated (lower-case) name for a protocol or NULL
426  */
427 static const gchar*
428 ph_capture_get_protocol_abbrev(GtkTreeSelection *selection, capture_file *cf)
429 {
430         int proto_id = ph_capture_get_protocol_id(selection, cf);
431
432         return (!proto_id) ? NULL : proto_registrar_get_abbrev(proto_id);
433 }
434
435 /** Return the selected item description
436  * @param cf capture file pointer
437  * @return Description of the selected item. MUST be freed with g_free().
438  */
439 static gchar* ph_capture_get_description(capture_file *cf)
440 {
441         gchar *buffer = NULL;
442
443         if(cf->finfo_selected->rep &&
444            strlen(cf->finfo_selected->rep->representation) > 0)
445         {
446                 buffer = g_strdup(cf->finfo_selected->rep->representation);
447         }
448         else
449         {
450                 buffer = g_malloc(ITEM_LABEL_LENGTH);
451                 proto_item_fill_label(cf->finfo_selected, buffer);
452         }
453
454         return buffer;
455 }
456
457 /** Load a protocol help key file and add it to the global array.
458  *
459  * @param filename Full path to the key file.
460  * @return Newly-created key file entry or NULL.
461  */
462 static proto_help_key_file *
463 ph_ini_load_file(const gchar *filename)
464 {
465         GKeyFile *kf;
466         GError *error = NULL;
467         proto_help_key_file *phkf = NULL;
468         gchar *old_template, *loc_template;
469         gchar **loc_data, *loc_repl, *loc_search;
470         gsize i, len;
471
472         if(!g_file_test(filename, G_FILE_TEST_EXISTS))
473         {
474                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Configuration file %s does not exists", filename);
475                 return NULL;
476         }
477
478         kf = g_key_file_new();
479
480         if(!g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, &error))
481         {
482                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Configuration file '%s' could not be loaded (%s)", filename, error->message);
483                 g_error_free(error);
484                 g_key_file_free(kf);
485
486                 return NULL;
487         }
488
489         loc_template = ph_ini_get_value(kf, PH_INI_GROUP_DATABASE, PH_INI_DB_KEY_LOCATION_TEMPLATE, NULL);
490         if (!loc_template) {
491                 g_key_file_free(kf);
492                 return NULL;
493         }
494
495         loc_data = g_key_file_get_keys(kf, PH_INI_GROUP_LOCATION_DATA, &len, NULL);
496         if (loc_data) {
497                 for (i = 0; i < len; i++) {
498                         loc_repl = ph_ini_get_value(kf, PH_INI_GROUP_LOCATION_DATA, loc_data[i], NULL);
499                         old_template = loc_template;
500                         loc_search = g_strdup_printf("${%s}", loc_data[i]);
501                         loc_template = string_replace(loc_template, loc_search, loc_repl);
502                         g_free(loc_repl);
503                         g_free(loc_search);
504                         g_free(old_template);
505                 }
506         }
507
508         /* Add ${PATH} to the end if it's not present */
509         if (!strstr(loc_template, PH_PATH_SEARCH_STR)) {
510                 old_template = loc_template;
511                 loc_template = g_strdup_printf("%s" PH_PATH_SEARCH_STR, old_template);
512                 g_free(old_template);
513         }
514
515         phkf = g_malloc(sizeof(proto_help_key_file));
516         phkf->keyfile = kf;
517         phkf->source = ph_ini_get_value(kf, PH_INI_GROUP_DATABASE, PH_INI_DB_KEY_SOURCE, "(Unknown source)");
518         phkf->loc_template = loc_template;
519
520         g_ptr_array_add(g_ph_key_files, phkf);
521
522         return phkf;
523 }
524
525 /** Fetch a value for a key from a key file with an optional default value.
526  *
527  * @param keyfile The key file to search
528  * @param group Key file group
529  * @param key Key file value to fetch
530  * @param alt Alternate string to return. May be NULL.
531  * @return const gchar* A newly-allocated key value, or a copy of alt if not found.
532  */
533 static gchar*
534 ph_ini_get_value(GKeyFile *keyfile, const gchar *group, const gchar *key, gchar *alt)
535 {
536         gchar *value = NULL;
537
538         if (keyfile) {
539                 value = g_key_file_get_string(keyfile, group, key, NULL);
540         }
541
542         if (!value) {
543                 value = g_strdup(alt);
544         }
545
546         return value;
547 }
548
549 /** Given a protocol name and a key, map the protocol name to a section in the
550  * keyfile, then look up the value for that key.
551  *
552  * @param keyfile The key file to search
553  * @param protocol Wireshark protocol name to map, e.g. "tcp".
554  * @param keyword The key to fetch from the mapped section.
555  * @return
556  */
557 static gchar*
558 ph_ini_get_path(GKeyFile *keyfile, const gchar *protocol, const gchar *keyword)
559 {
560         GError *error = NULL;
561         gchar *map;
562         gchar *value;
563
564         if(!keyfile || !protocol || !keyword) return NULL;
565
566         map = g_key_file_get_string(keyfile, PH_INI_GROUP_MAP, protocol, &error);
567
568         if(!map)
569         {
570                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Protocol '%s' is not defined (%s)", protocol, error->message);
571                 g_error_free(error);
572                 return NULL;
573         }
574
575         value = g_key_file_get_string(keyfile, map, keyword, NULL);
576         g_free(map);
577         return value;
578 }
579
580 /** Given a protocol name, map it to a section in the keyfile, then
581  * return the keys in that section.
582  *
583  * @param keyfile The key file to search
584  * @param protocol Wireshark protocol name to map, e.g. "tcp".
585  * @return An array of keys in the mapped section. Must be freed with g_strfreev().
586  */
587 static gchar**
588 ph_ini_get_keywords(GKeyFile *keyfile, const gchar *protocol)
589 {
590         GError *error = NULL;
591         gchar *map;
592         gchar **keys;
593         gsize length = 0;
594
595         if(!keyfile) return NULL;
596
597         map = g_key_file_get_string(keyfile, PH_INI_GROUP_MAP, protocol, &error);
598
599         if(!map)
600         {
601                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Protocol '%s' is not defined (%s)", protocol, error->message);
602                 g_error_free(error);
603                 return NULL;
604         }
605
606         error = NULL;
607         keys = g_key_file_get_keys(keyfile, map, &length, &error);
608         g_free(map);
609
610         if(!keys)
611         {
612                 g_log(NULL, G_LOG_LEVEL_DEBUG, "Display titles are not defined (%s)", protocol);
613                 g_error_free(error);
614         }
615
616         return keys;
617 }
618
619 /**
620 * Function ph_parse_string
621 *
622 * @param const gchar *description Description
623 * @param const gchar *value Description
624 * @return guint Description
625 */
626 static guint ph_parse_string(const gchar *description, const gchar *value)
627 {
628         GRegex *regex = NULL;
629         GMatchInfo *match_info = NULL;
630         gchar *pattern = NULL;
631         guint result = 0;
632
633         pattern = g_strdup_printf("(?<![0-9a-zA-Z_])%s(?![0-9a-zA-Z_])", value);
634         regex = g_regex_new(pattern, 0, 0, NULL);
635
636         g_regex_match(regex, description, 0, &match_info);
637         if(g_match_info_matches(match_info)) result = 1;
638
639         g_match_info_free(match_info);
640         g_regex_unref(regex);
641
642         return result;
643 }
644
645 #ifdef PH_DEBUG_LOG
646 /**
647  * Function ph_logging_handler
648  *
649  * @param const gchar *domain Description
650  * @param GLogLevelFlags level Description
651  * @param const gchar *message Description
652  * @param gpointer data Description
653  * @return void
654  */
655 static void ph_logging_handler(const gchar *domain _U_, GLogLevelFlags level, const gchar *message, gpointer data _U_)
656 {
657         gchar *log = NULL;
658         gchar *type = NULL;
659         FILE *file = NULL;
660         struct tm *timestamp = NULL;
661         time_t now;
662
663         time(&now);
664         timestamp = localtime(&now);
665
666         switch(level & G_LOG_LEVEL_MASK)
667         {
668                 case G_LOG_LEVEL_ERROR:
669                         type = "ERR";
670                         break;
671
672                 case G_LOG_LEVEL_DEBUG:
673                         type = "WARNING";
674                         break;
675
676                 case G_LOG_LEVEL_INFO:
677                         type = "INFO";
678                         break;
679
680                 default:
681                         type = "OTHER";
682         }
683
684         file = ws_fopen(ph_log_path, "a+");
685
686         if(file)
687         {
688                 log = g_strdup_printf("[%04u-%02u-%02u %02u:%02u:%02u %s] %s\n", timestamp->tm_year + 1900, timestamp->tm_mon + 1, timestamp->tm_mday, timestamp->tm_hour, timestamp->tm_min, timestamp->tm_sec, type, message);
689                 fputs(log, file);
690                 fclose(file);
691         }
692 }
693 #endif /* PH_DEBUG_LOG */
694 #endif /* MAIN_MENU_USE_UIMANAGER */