Move the common parts of iface_lists.[ch] from ui/gtk/ to ui/. Leave the
[metze/wireshark/wip.git] / ui / gtk / webbrowser.c
1 /* The GIMP -- an image manipulation program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * Web Browser Plug-in
5  * Copyright (C) 2003  Henrik Brix Andersen <brix@gimp.org>
6  *
7  * $Id$
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 /* Wireshark - this file is copied from "The GIMP" V2.0.2
25  * You will find the original file in the gimp distribution zip under:
26  * \plug-ins\common\webbrowser.c
27  *
28  * It was modified to suit the Wireshark environment (#if 0)!
29  *
30  * For the UNIX+X11 launcher, see this blog post:
31  *
32  * http://blogs.gnome.org/timj/2006/11/24/24112006-how-to-start-a-web-browser/
33  *
34  * for a discussion of how Beast launches a browser, a link that shows
35  * the rather complicated code it uses, and some information on why it
36  * goes through all that pain.  See also Kevin Krammer's comment, which
37  * notes that the problem might be that the GNOME, KDE, and XFCE
38  * launcher programs always cause the window to be opened in the background,
39  * regardless of whether an instance of the app is running or not (the
40  * app gets launched - in the background - if it's not already running,
41  * and is told to open a new window/tab if it's already running), while
42  * launchers such as sensible-browser, which xdg-open falls back to,
43  * launch the app in the foreground if it's not already running, leading
44  * to the "first window is in the foreground, subsequent windows are in
45  * the background" behavior in non-GNOME/KDE/XFCE environments.
46  *
47  * Perhaps the right strategy is to:
48  *
49  *      Check whether we're in a GNOME/KDE/XFCE session and, if
50  *      we are, try xdg-open, as it works around, among other things,
51  *      some kfmclient bugs, and run it synchronously (that will fail
52  *      if we detect a GNOME/KDE/XFCE session but the launcher is
53  *      missing, but so it goes).  If we don't have xdg-open, try
54  *      the appropriate launcher for the environment, but ignore
55  *      the return code from kfmclient, as it might be bogus (that's
56  *      the bug xdg-open works around).
57  *
58  *      Otherwise, try the "broken/unpredictable browser launchers",
59  *      but run them in the background and leave them running, and
60  *      ignore the exit code, and then try x-www-browser, and then
61  *      try directly launching a user-specified browser.  (Beast tries
62  *      a bunch of browsers, with the user not being allowed to
63  *      specify which one they want.)
64  *
65  * On the other hand, see bug 2699, in which xdg-open is itself buggy.
66  */
67
68 #ifdef HAVE_CONFIG_H
69 #  include <config.h>
70 #endif
71 #include <string.h> /* strlen, strstr */
72
73 #include <gtk/gtk.h>
74
75 #include <epan/filesystem.h>
76 #include <epan/prefs.h>
77
78 #include "ui/simple_dialog.h"
79
80 #include "ui/gtk/webbrowser.h"
81
82 #if defined(G_OS_WIN32)
83 /* Win32 - use Windows shell services to start a browser */
84 #include <windows.h>
85 /* We're using Unicode */
86 #include <tchar.h>
87 #include <wsutil/unicode-utils.h>
88 /* if WIN32_LEAN_AND_MEAN is defined, shellapi.h is needed too */
89 #include <shellapi.h>
90 #elif defined (HAVE_OS_X_FRAMEWORKS)
91 /* Mac OS X - use Launch Services to start a browser */
92 #include <CoreFoundation/CoreFoundation.h>
93 #include <ApplicationServices/ApplicationServices.h>
94 #elif defined(HAVE_XDG_OPEN)
95 /* UNIX+X11 desktop with Portland Group stuff - use xdg-open to start a browser */
96 #else
97 /* Everything else - launch the browser ourselves */
98 #define MUST_LAUNCH_BROWSER_OURSELVES
99 #endif
100
101 #ifdef MUST_LAUNCH_BROWSER_OURSELVES
102 static gchar*   strreplace       (const gchar      *string,
103                                   const gchar      *delimiter,
104                                   const gchar      *replacement);
105 #endif
106
107 gboolean
108 browser_needs_pref(void)
109 {
110 #ifdef MUST_LAUNCH_BROWSER_OURSELVES
111     return TRUE;
112 #else
113     return FALSE;
114 #endif
115 }
116
117
118 gboolean
119 browser_open_url (const gchar *url)
120 {
121 #if defined(G_OS_WIN32)
122
123   return ((gint) ShellExecute (HWND_DESKTOP, _T("open"), utf_8to16(url), NULL, NULL, SW_SHOWNORMAL) > 32);
124
125 #elif defined(HAVE_OS_X_FRAMEWORKS)
126
127   CFStringRef url_CFString;
128   CFURLRef url_CFURL;
129   OSStatus status;
130
131   /*
132    * XXX - if URLs passed to "browser_open_url()" contain non-ASCII
133    * characters, we'd have to choose an appropriate value from the
134    * CFStringEncodings enum.
135    */
136   url_CFString = CFStringCreateWithCString(NULL, url, kCFStringEncodingASCII);
137   if (url_CFString == NULL)
138     return (FALSE);
139   url_CFURL = CFURLCreateWithString(NULL, url_CFString, NULL);
140   CFRelease(url_CFString);
141   if (url_CFURL == NULL) {
142     /*
143      * XXX - this could mean that the url_CFString wasn't a valid URL,
144      * or that memory allocation failed.  We can't determine which,
145      * except perhaps by providing our own allocator and somehow
146      * flagging allocation failures.
147      */
148     return (FALSE);
149   }
150   /*
151    * XXX - this is a Launch Services result code, and we should probably
152    * display a dialog box if it's not 0, describing what the error was.
153    * Then again, we should probably do the same for the ShellExecute call,
154    * unless that call itself happens to pop up a dialog box for all errors.
155    */
156   status = LSOpenCFURLRef(url_CFURL, NULL);
157   CFRelease(url_CFURL);
158   return (status == 0);
159
160 #elif defined(HAVE_XDG_OPEN)
161
162   GError   *error = NULL;
163   gchar    *argv[3];
164   gboolean  retval;
165
166   g_return_val_if_fail (url != NULL, FALSE);
167
168   argv[0] = "xdg-open";
169   argv[1] = (char *)url;        /* Grr - g_spawn_async() shouldn't modify this */
170   argv[2] = NULL;
171
172   /*
173    * XXX - use g_spawn_on_screen() so the browser window shows up on
174    * the same screen?
175    */
176   retval = g_spawn_async (NULL, argv, NULL,
177                           G_SPAWN_SEARCH_PATH,
178                           NULL, NULL,
179                           NULL, &error);
180
181   if (! retval)
182     {
183       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
184           "%sCould not execute xdg-open: %s\n\n\"%s\"",
185           simple_dialog_primary_start(), simple_dialog_primary_end(),
186           error->message);
187       g_error_free (error);
188     }
189
190   return retval;
191
192 #elif defined(MUST_LAUNCH_BROWSER_OURSELVES)
193
194   GError    *error = NULL;
195   gchar     *browser;
196   gchar     *argument;
197   gchar     *cmd;
198   gchar    **argv;
199   gboolean   retval;
200
201   g_return_val_if_fail (url != NULL, FALSE);
202
203   /*  browser = gimp_gimprc_query ("web-browser");*/
204   browser = g_strdup(prefs.gui_webbrowser);
205
206   if (browser == NULL || ! strlen (browser))
207     {
208       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
209           "Web browser not specified.\n"
210           "Please correct the web browser setting in the Preferences dialog.");
211       g_free (browser);
212       return FALSE;
213     }
214
215   /* quote the url since it might contains special chars */
216   argument = g_shell_quote (url);
217
218   /* replace %s with URL */
219   if (strstr (browser, "%s"))
220     cmd = strreplace (browser, "%s", argument);
221   else
222     cmd = g_strconcat (browser, " ", argument, NULL);
223
224   g_free (argument);
225
226   /* parse the cmd line */
227   if (! g_shell_parse_argv (cmd, NULL, &argv, &error))
228     {
229       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
230           "%sCould not parse web browser command: \"%s\"%s\n\n\"%s\"\n\n%s",
231           simple_dialog_primary_start(), browser, simple_dialog_primary_end(),
232           error->message,
233           "Please correct the web browser setting in the Preferences dialog.");
234       g_error_free (error);
235       return FALSE;
236     }
237
238   /*
239    * XXX - use g_spawn_on_screen() so the browser window shows up on
240    * the same screen?
241    */
242   retval = g_spawn_async (NULL, argv, NULL,
243                           G_SPAWN_SEARCH_PATH,
244                           NULL, NULL,
245                           NULL, &error);
246
247   if (! retval)
248     {
249       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
250           "%sCould not execute web browser: \"%s\"%s\n\n\"%s\"\n\n%s",
251           simple_dialog_primary_start(), browser, simple_dialog_primary_end(),
252           error->message,
253           "Please correct the web browser setting in the Preferences dialog.");
254       g_error_free (error);
255     }
256
257   g_free (browser);
258   g_free (cmd);
259   g_strfreev (argv);
260
261   return retval;
262 #endif
263 }
264
265 /** Convert local absolute path to uri.
266  *
267  * @param filename to (absolute pathed) filename to convert
268  * @return a newly allocated uri, you must g_free it later
269  */
270 gchar *
271 filename2uri(const gchar *filename)
272 {
273     int i = 0;
274     gchar *file_tmp;
275     GString *filestr;
276
277
278     filestr = g_string_sized_new(200);
279
280     /* this escaping is somewhat slow but should working fine */
281     for(i=0; filename[i]; i++) {
282         switch(filename[i]) {
283         case(' '):
284             g_string_append(filestr, "%20");
285             break;
286         case('%'):
287             g_string_append(filestr, "%%");
288             break;
289         case('\\'):
290             g_string_append_c(filestr, '/');
291             break;
292             /* XXX - which other chars need to be escaped? */
293         default:
294             g_string_append_c(filestr, filename[i]);
295         }
296     }
297
298
299     /* prepend URI header "file:" appropriate for the system */
300 #ifdef G_OS_WIN32
301     /* XXX - how do we handle UNC names (e.g. //servername/sharename/dir1/dir2/capture-file.cap) */
302     g_string_prepend(filestr, "file:///");
303 #else
304     g_string_prepend(filestr, "file://");
305 #endif
306
307     file_tmp = filestr->str;
308
309     g_string_free(filestr, FALSE /* don't free segment data */);
310
311     return file_tmp;
312 }
313
314 gboolean
315 filemanager_open_directory (const gchar *path)
316 {
317 #if defined(G_OS_WIN32)
318   /* ShellExecute(...,"explore",...) needs path to be explicitly a directory;
319      Otherwise 'explore' will fail if a file exists with a basename matching
320      the provided directory path.
321      (eg: wireshak-gtk2.exe exists in the same directory as  a wireshark-gtk2
322           directory entry).
323   */
324   gint   ret;
325   gchar *xpath;
326   xpath = g_strconcat(path,
327                       g_str_has_suffix(path, "\\") ? "" : "\\",
328                       NULL);
329   ret = (gint) ShellExecute (HWND_DESKTOP, _T("explore"), utf_8to16(xpath), NULL, NULL, SW_SHOWNORMAL);
330   g_free(xpath);
331   return (ret > 32);
332
333 #elif defined(HAVE_OS_X_FRAMEWORKS)
334
335   CFStringRef path_CFString;
336   CFURLRef path_CFURL;
337   OSStatus status;
338
339   path_CFString = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
340   if (path_CFString == NULL)
341     return (FALSE);
342   path_CFURL = CFURLCreateWithFileSystemPath(NULL, path_CFString,
343                                              kCFURLPOSIXPathStyle, true);
344   CFRelease(path_CFString);
345   if (path_CFURL == NULL) {
346     /*
347      * XXX - does this always mean that that memory allocation failed?
348      */
349     return (FALSE);
350   }
351   /*
352    * XXX - this is a Launch Services result code, and we should probably
353    * display a dialog box if it's not 0, describing what the error was.
354    * Then again, we should probably do the same for the ShellExecute call,
355    * unless that call itself happens to pop up a dialog box for all errors.
356    */
357   status = LSOpenCFURLRef(path_CFURL, NULL);
358   CFRelease(path_CFURL);
359   return (status == 0);
360
361 #elif defined(HAVE_XDG_OPEN)
362
363   GError   *error = NULL;
364   gchar    *argv[3];
365   gboolean  retval;
366
367   g_return_val_if_fail (path != NULL, FALSE);
368
369   argv[0] = "xdg-open";
370   argv[1] = (char *)path;       /* Grr - g_spawn_async() shouldn't modify this */
371   argv[2] = NULL;
372
373   /*
374    * XXX - use g_spawn_on_screen() so the file managaer window shows up on
375    * the same screen?
376    */
377   retval = g_spawn_async (NULL, argv, NULL,
378                           G_SPAWN_SEARCH_PATH,
379                           NULL, NULL,
380                           NULL, &error);
381
382   if (! retval)
383     {
384       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
385           "%sCould not execute xdg-open: %s\n\n\"%s\"",
386           simple_dialog_primary_start(), simple_dialog_primary_end(),
387           error->message);
388       g_error_free (error);
389     }
390
391   return retval;
392
393 #elif defined(MUST_LAUNCH_BROWSER_OURSELVES)
394
395   GError    *error = NULL;
396   gchar     *browser;
397   gchar     *argument;
398   gchar     *cmd;
399   gchar    **argv;
400   gboolean   retval;
401
402   g_return_val_if_fail (path != NULL, FALSE);
403
404   /*  browser = gimp_gimprc_query ("web-browser");*/
405   browser = g_strdup(prefs.gui_webbrowser);
406
407   if (browser == NULL || ! strlen (browser))
408     {
409       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
410           "Web browser not specified.\n"
411           "Please correct the web browser setting in the Preferences dialog.");
412       g_free (browser);
413       return FALSE;
414     }
415
416   /* conver the path to a URI */
417   argument = filename2uri (path);
418
419   /* replace %s with URL */
420   if (strstr (browser, "%s"))
421     cmd = strreplace (browser, "%s", argument);
422   else
423     cmd = g_strconcat (browser, " ", argument, NULL);
424
425   g_free (argument);
426
427   /* parse the cmd line */
428   if (! g_shell_parse_argv (cmd, NULL, &argv, &error))
429     {
430       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
431           "%sCould not parse web browser command: \"%s\"%s\n\n\"%s\"\n\n%s",
432           simple_dialog_primary_start(), browser, simple_dialog_primary_end(),
433           error->message,
434           "Please correct the web browser setting in the Preferences dialog.");
435       g_error_free (error);
436       return FALSE;
437     }
438
439   /*
440    * XXX - use g_spawn_on_screen() so the browser window shows up on
441    * the same screen?
442    */
443   retval = g_spawn_async (NULL, argv, NULL,
444                           G_SPAWN_SEARCH_PATH,
445                           NULL, NULL,
446                           NULL, &error);
447
448   if (! retval)
449     {
450       simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
451           "%sCould not execute web browser: \"%s\"%s\n\n\"%s\"\n\n%s",
452           simple_dialog_primary_start(), browser, simple_dialog_primary_end(),
453           error->message,
454           "Please correct the web browser setting in the Preferences dialog.");
455       g_error_free (error);
456     }
457
458   g_free (browser);
459   g_free (cmd);
460   g_strfreev (argv);
461
462   return retval;
463 #endif
464 }
465
466 #ifdef MUST_LAUNCH_BROWSER_OURSELVES
467
468 static gchar*
469 strreplace (const gchar *string,
470             const gchar *delimiter,
471             const gchar *replacement)
472 {
473   gchar  *ret;
474   gchar **tmp;
475
476   g_return_val_if_fail (string != NULL, NULL);
477   g_return_val_if_fail (delimiter != NULL, NULL);
478   g_return_val_if_fail (replacement != NULL, NULL);
479
480   tmp = g_strsplit (string, delimiter, 0);
481   ret = g_strjoinv (replacement, tmp);
482   g_strfreev (tmp);
483
484   return ret;
485 }
486
487 #endif /* MUST_LAUNCH_BROWSER_OURSELVES */
488
489
490 /* browse a file relative to the data dir */
491 void
492 browser_open_data_file(const gchar *filename)
493 {
494     gchar *file_path;
495     gchar *uri;
496
497     /* build filename */
498 #ifdef G_OS_WIN32
499     if((strlen(filename) > 2) && (filename[1] == ':'))
500       file_path = g_strdup(filename);
501 #else
502     /* XXX: is this correct for MacOS/Linux ? */
503     if((strlen(filename) > 1) && (filename[0] == '/'))
504       file_path = g_strdup(filename);
505 #endif 
506     else
507
508     file_path = g_strdup_printf("%s/%s", get_datafile_dir(), filename);
509
510     /* XXX - check, if the file is really existing, otherwise display a simple_dialog about the problem */
511
512     /* convert filename to uri */
513     uri = filename2uri(file_path);
514
515     /* show the uri */
516     browser_open_url (uri);
517
518     g_free(file_path);
519     g_free(uri);
520 }