Remove use_other_browser_cmd()
[browser-switch] / launcher.c
1 /*
2  * launcher.c -- functions for launching web browsers for browser-switchboard
3  *
4  * Copyright (C) 2009-2010 Steven Luo
5  * Derived from a Python implementation by Jason Simpson and Steven Luo
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20  * USA.
21  */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <dbus/dbus-glib.h>
33
34 #ifdef FREMANTLE
35 #include <dbus/dbus.h>
36 #include <signal.h>
37 #include <sys/ptrace.h>
38 #include <sys/inotify.h>
39
40 #define DEFAULT_HOMEDIR "/home/user"
41 #define MICROB_PROFILE_DIR "/.mozilla/microb"
42 #define MICROB_LOCKFILE "lock"
43 #endif
44
45 #include "browser-switchboard.h"
46 #include "launcher.h"
47 #include "dbus-server-bindings.h"
48 #include "log.h"
49
50 struct browser_launcher {
51         char *name;
52         void (*launcher)(struct swb_context *, char *);
53         char *other_browser_cmd;
54 };
55
56 #ifdef FREMANTLE
57 static int microb_started = 0;
58
59 /* Check to see whether MicroB is ready to handle D-Bus requests yet
60    See the comments in launch_microb to understand how this works. */
61 static DBusHandlerResult check_microb_started(DBusConnection *connection,
62                                      DBusMessage *message,
63                                      void *user_data) {
64         DBusError error;
65         char *name, *old, *new;
66
67         log_msg("Checking to see if MicroB is ready\n");
68         dbus_error_init(&error);
69         if (!dbus_message_get_args(message, &error,
70                                    DBUS_TYPE_STRING, &name,
71                                    DBUS_TYPE_STRING, &old,
72                                    DBUS_TYPE_STRING, &new,
73                                    DBUS_TYPE_INVALID)) {
74                 log_msg("%s\n", error.message);
75                 dbus_error_free(&error);
76                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
77         }
78         /* If old is an empty string, then the name has been acquired, and
79            MicroB should be ready to handle our request */
80         if (strlen(old) == 0) {
81                 log_msg("MicroB ready\n");
82                 microb_started = 1;
83         }
84
85         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
86 }
87
88 /* Get a browserd PID from the corresponding Mozilla profile lockfile */
89 static pid_t get_browserd_pid(const char *lockfile) {
90         char buf[256], *tmp;
91
92         /* The lockfile is a symlink pointing to "[ipaddr]:+[pid]", so read in
93            the target of the symlink and parse it that way */
94         memset(buf, '\0', 256);
95         if (readlink(lockfile, buf, 255) == -1)
96                 return -errno;
97         if (!(tmp = strstr(buf, ":+")))
98                 return 0;
99         tmp += 2; /* Skip over the ":+" */
100
101         return atoi(tmp);
102 }
103 #endif
104
105 /* Close stdin/stdout/stderr and replace with /dev/null */
106 static int close_stdio(void) {
107         int fd;
108
109         if ((fd = open("/dev/null", O_RDWR)) == -1)
110                 return -1;
111
112         if (dup2(fd, 0) == -1 || dup2(fd, 1) == -1 || dup2(fd, 2) == -1)
113                 return -1;
114
115         close(fd);
116         return 0;
117 }
118
119 static void launch_tear(struct swb_context *ctx, char *uri) {
120         int status;
121         static DBusGProxy *tear_proxy = NULL;
122         GError *error = NULL;
123         pid_t pid;
124
125         if (!uri)
126                 uri = "new_window";
127
128         log_msg("launch_tear with uri '%s'\n", uri);
129
130         /* We should be able to just call the D-Bus service to open Tear ...
131            but if Tear's not open, that cuases D-Bus to start Tear and then
132            pass it the OpenAddress call, which results in two browser windows.
133            Properly fixing this probably requires Tear to provide a D-Bus
134            method that opens an address in an existing window, but for now work
135            around by just invoking Tear with exec() if it's not running. */
136         status = system("pidof tear > /dev/null");
137         if (WIFEXITED(status) && !WEXITSTATUS(status)) {
138                 if (!tear_proxy) {
139                         if (!(tear_proxy = dbus_g_proxy_new_for_name(
140                                                 ctx->session_bus,
141                                                 "com.nokia.tear",
142                                                 "/com/nokia/tear",
143                                                 "com.nokia.Tear"))) {
144                                 log_msg("Failed to create proxy for com.nokia.Tear D-Bus interface\n");
145                                 exit(1);
146                         }
147                 }
148
149                 if (!dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
150                                        G_TYPE_STRING, uri, G_TYPE_INVALID,
151                                        G_TYPE_INVALID)) {
152                         log_msg("Opening window failed: %s\n", error->message);
153                         exit(1);
154                 }
155                 if (!ctx->continuous_mode)
156                         exit(0);
157         } else {
158                 if (ctx->continuous_mode) {
159                         if ((pid = fork()) != 0) {
160                                 /* Parent process or error in fork() */
161                                 log_msg("child: %d\n", (int)pid);
162                                 return;
163                         }
164                         /* Child process */
165                         setsid();
166                         close_stdio();
167                 }
168                 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
169         }
170 }
171
172 void launch_microb(struct swb_context *ctx, char *uri) {
173         int kill_browserd = 0;
174         int status;
175         pid_t pid;
176 #ifdef FREMANTLE
177         char *homedir, *microb_profile_dir, *microb_lockfile;
178         size_t len;
179         int fd, inot_wd;
180         DBusConnection *raw_connection;
181         DBusError dbus_error;
182         DBusHandleMessageFunction filter_func;
183         DBusGProxy *g_proxy;
184         GError *gerror = NULL;
185         int bytes_read;
186         char buf[256], *pos;
187         struct inotify_event *event;
188         pid_t browserd_pid, waited_pid;
189         struct sigaction act, oldact;
190         int ignore_sigstop;
191 #endif
192
193         if (!uri)
194                 uri = "new_window";
195
196         log_msg("launch_microb with uri '%s'\n", uri);
197
198         /* Launch browserd if it's not running */
199         status = system("pidof browserd > /dev/null");
200         if (WIFEXITED(status) && WEXITSTATUS(status)) {
201                 kill_browserd = 1;
202 #ifdef FREMANTLE
203                 system("/usr/sbin/browserd -d -b > /dev/null 2>&1");
204 #else
205                 system("/usr/sbin/browserd -d > /dev/null 2>&1");
206 #endif
207         }
208
209         /* Release the osso_browser D-Bus name so that MicroB can take it */
210         dbus_release_osso_browser_name(ctx);
211
212 #ifdef FREMANTLE
213         /* Put together the path to the MicroB browserd lockfile */
214         if (!(homedir = getenv("HOME")))
215                 homedir = DEFAULT_HOMEDIR;
216         len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) + 1;
217         if (!(microb_profile_dir = calloc(len, sizeof(char)))) {
218                 log_msg("calloc() failed\n");
219                 exit(1);
220         }
221         snprintf(microb_profile_dir, len, "%s%s",
222                  homedir, MICROB_PROFILE_DIR);
223         len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) +
224               strlen("/") + strlen(MICROB_LOCKFILE) + 1;
225         if (!(microb_lockfile = calloc(len, sizeof(char)))) {
226                 log_msg("calloc() failed\n");
227                 exit(1);
228         }
229         snprintf(microb_lockfile, len, "%s%s/%s",
230                  homedir, MICROB_PROFILE_DIR, MICROB_LOCKFILE);
231
232         /* Watch for the creation of a MicroB browserd lockfile
233            NB: The watch has to be set up here, before the browser
234            is launched, to make sure there's no race between browserd
235            starting and us creating the watch */
236         if ((fd = inotify_init()) == -1) {
237                 log_perror(errno, "inotify_init");
238                 exit(1);
239         }
240         if ((inot_wd = inotify_add_watch(fd, microb_profile_dir,
241                                          IN_CREATE)) == -1) {
242                 log_perror(errno, "inotify_add_watch");
243                 exit(1);
244         }
245         free(microb_profile_dir);
246
247         /* Set up the D-Bus eavesdropping we'll use to watch for MicroB
248            acquiring the com.nokia.osso_browser D-Bus name.  Again, this needs
249            to happen before the browser is launched, so that there's no race
250            between establishing the watch and browser startup.
251
252            Ideas for how to do this monitoring derived from the dbus-monitor
253            code (tools/dbus-monitor.c in the D-Bus codebase). */
254         dbus_error_init(&dbus_error);
255
256         raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION, &dbus_error);
257         if (!raw_connection) {
258                 log_msg("Failed to open connection to session bus: %s\n",
259                         dbus_error.message);
260                 dbus_error_free(&dbus_error);
261                 exit(1);
262         }
263
264         dbus_bus_add_match(raw_connection,
265                            "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
266                            &dbus_error);
267         if (dbus_error_is_set(&dbus_error)) {
268                 log_msg("Failed to set up watch for browser UI start: %s\n",
269                         dbus_error.message);
270                 dbus_error_free(&dbus_error);
271                 exit(1);
272         }
273         filter_func = check_microb_started;
274         if (!dbus_connection_add_filter(raw_connection,
275                                         filter_func, NULL, NULL)) {
276                 log_msg("Failed to set up watch filter!\n");
277                 exit(1);
278         }
279
280         if ((pid = fork()) == -1) {
281                 log_perror(errno, "fork");
282                 exit(1);
283         }
284
285         if (pid > 0) {
286                 /* Parent process */
287                 /* Wait for our child to start the browser UI process and
288                    for it to acquire the com.nokia.osso_browser D-Bus name,
289                    then make the appropriate method call to open the browser
290                    window. */
291                 microb_started = 0;
292                 log_msg("Waiting for MicroB to start\n");
293                 while (!microb_started &&
294                        dbus_connection_read_write_dispatch(raw_connection,
295                                                            -1));
296                 dbus_connection_remove_filter(raw_connection,
297                                               filter_func, NULL);
298                 dbus_bus_remove_match(raw_connection,
299                                       "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
300                                       &dbus_error);
301                 if (dbus_error_is_set(&dbus_error))
302                         /* Don't really care -- about to disconnect from the
303                            bus anyhow */
304                         dbus_error_free(&dbus_error);
305                 dbus_connection_close(raw_connection);
306                 dbus_connection_unref(raw_connection);
307
308                 /* Browser UI's started, send it the request for a new window
309                    via D-Bus */
310                 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
311                                 "com.nokia.osso_browser",
312                                 "/com/nokia/osso_browser/request",
313                                 "com.nokia.osso_browser");
314                 if (!g_proxy) {
315                         log_msg("Couldn't get a com.nokia.osso_browser proxy\n");
316                         exit(1);
317                 }
318                 if (!strcmp(uri, "new_window")) {
319 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
320          corner case where, if the user just closes the bookmark window
321          without opening any browser windows, we don't kill off MicroB or
322          resume handling com.nokia.osso_browser */
323                         if (!dbus_g_proxy_call(g_proxy, "top_application",
324                                                &gerror, G_TYPE_INVALID,
325                                                G_TYPE_INVALID)) {
326                                 log_msg("Opening window failed: %s\n",
327                                         gerror->message);
328                                 exit(1);
329                         }
330 #endif
331                         if (!dbus_g_proxy_call(g_proxy, "load_url",
332                                                &gerror,
333                                                G_TYPE_STRING, "about:blank",
334                                                G_TYPE_INVALID,
335                                                G_TYPE_INVALID)) {
336                                 log_msg("Opening window failed: %s\n",
337                                         gerror->message);
338                                 exit(1);
339                         }
340                 } else {
341                         if (!dbus_g_proxy_call(g_proxy, "load_url",
342                                                &gerror,
343                                                G_TYPE_STRING, uri,
344                                                G_TYPE_INVALID,
345                                                G_TYPE_INVALID)) {
346                                 log_msg("Opening window failed: %s\n",
347                                         gerror->message);
348                                 exit(1);
349                         }
350                 }
351                 g_object_unref(g_proxy);
352
353                 /* Workaround: the browser process we started is going to want
354                    to hang around forever, hogging the com.nokia.osso_browser
355                    D-Bus interface while at it.  To fix this, we notice that
356                    when the last browser window closes, the browser UI restarts
357                    its attached browserd process.  Get the browserd process's
358                    PID and use ptrace() to watch for process termination.
359
360                    This has the problem of not being able to detect whether
361                    the bookmark window is open and/or in use, but it's the best
362                    that I can think of.  Better suggestions would be greatly
363                    appreciated. */
364
365                 /* Wait for the MicroB browserd lockfile to be created */
366                 log_msg("Waiting for browserd lockfile to be created\n");
367                 memset(buf, '\0', 256);
368                 /* read() blocks until there are events to be read */
369                 while ((bytes_read = read(fd, buf, 255)) > 0) {
370                         pos = buf;
371                         /* Loop until we see the event we're looking for
372                            or until all the events are processed */
373                         while (pos && (pos-buf) < bytes_read) {
374                                 event = (struct inotify_event *)pos;
375                                 len = sizeof(struct inotify_event)
376                                       + event->len;
377                                 if (!strcmp(MICROB_LOCKFILE,
378                                             event->name)) {
379                                         /* Lockfile created */
380                                         pos = NULL;
381                                         break;
382                                 } else if ((pos-buf) + len < bytes_read)
383                                         /* More events to process */
384                                         pos += len;
385                                 else
386                                         /* All events processed */
387                                         break;
388                         }
389                         if (!pos)
390                                 /* Event found, stop looking */
391                                 break;
392                         memset(buf, '\0', 256);
393                 }
394                 inotify_rm_watch(fd, inot_wd);
395                 close(fd);
396
397                 /* Get the PID of the browserd from the lockfile */
398                 if ((browserd_pid = get_browserd_pid(microb_lockfile)) <= 0) {
399                         if (browserd_pid == 0)
400                                 log_msg("Profile lockfile link lacks PID\n");
401                         else
402                                 log_perror(-browserd_pid,
403                                            "readlink() on lockfile failed");
404                         exit(1);
405                 }
406                 free(microb_lockfile);
407
408                 /* Wait for the browserd to close */
409                 log_msg("Waiting for MicroB (browserd pid %d) to finish\n",
410                         browserd_pid);
411                 /* Clear any existing SIGCHLD handler to prevent interference
412                    with our wait() */
413                 act.sa_handler = SIG_DFL;
414                 act.sa_flags = 0;
415                 sigemptyset(&(act.sa_mask));
416                 if (sigaction(SIGCHLD, &act, &oldact) == -1) {
417                         log_perror(errno, "clearing SIGCHLD handler failed");
418                         exit(1);
419                 }
420
421                 /* Trace the browserd to get a close notification */
422                 ignore_sigstop = 1;
423                 if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) {
424                         log_perror(errno, "PTRACE_ATTACH");
425                         exit(1);
426                 }
427                 ptrace(PTRACE_CONT, browserd_pid, NULL, NULL);
428                 while ((waited_pid = wait(&status)) > 0) {
429                         if (waited_pid != browserd_pid)
430                                 /* Not interested in other processes */
431                                 continue;
432                         if (WIFEXITED(status) || WIFSIGNALED(status))
433                                 /* browserd exited */
434                                 break;
435                         else if (WIFSTOPPED(status)) {
436                                 /* browserd was sent a signal
437                                    We're responsible for making sure this
438                                    signal gets delivered */
439                                 if (ignore_sigstop &&
440                                     WSTOPSIG(status) == SIGSTOP) {
441                                         /* Ignore the first SIGSTOP received
442                                            This is raised for some reason
443                                            immediately after we start tracing
444                                            the process, and won't be followed
445                                            by a SIGCONT at any point */
446                                         log_msg("Ignoring first SIGSTOP\n");
447                                         ptrace(PTRACE_CONT, browserd_pid,
448                                                NULL, NULL);
449                                         ignore_sigstop = 0;
450                                         continue;
451                                 }
452                                 log_msg("Forwarding signal %d to browserd\n",
453                                         WSTOPSIG(status));
454                                 ptrace(PTRACE_CONT, browserd_pid,
455                                        NULL, WSTOPSIG(status));
456                         }
457                 }
458
459                 /* Kill off browser UI
460                    XXX: There is a race here with the restarting of the closed
461                    browserd; if that happens before we kill the browser UI, the
462                    newly started browserd may not close with the UI
463                    XXX: Hope we don't cause data loss here! */
464                 log_msg("Killing MicroB\n");
465                 kill(pid, SIGTERM);
466                 waitpid(pid, &status, 0);
467
468                 /* Restore old SIGCHLD handler */
469                 if (sigaction(SIGCHLD, &oldact, NULL) == -1) {
470                         log_perror(errno,
471                                    "restoring old SIGCHLD handler failed");
472                         exit(1);
473                 }
474         } else {
475                 /* Child process */
476                 dbus_connection_close(raw_connection);
477                 dbus_connection_unref(raw_connection);
478                 close(fd);
479                 close_stdio();
480
481                 /* exec maemo-invoker directly instead of relying on the
482                    /usr/bin/browser symlink, since /usr/bin/browser may have
483                    been replaced with a shell script calling us via D-Bus */
484                 /* Launch the browser in the background -- our parent will
485                    wait for it to claim the D-Bus name and then display the
486                    window using D-Bus */
487                 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
488         }
489 #else /* !FREMANTLE */
490         if ((pid = fork()) == -1) {
491                 log_perror(errno, "fork");
492                 exit(1);
493         }
494
495         if (pid > 0) {
496                 /* Parent process */
497                 waitpid(pid, &status, 0);
498         } else {
499                 /* Child process */
500                 close_stdio();
501
502                 /* exec maemo-invoker directly instead of relying on the
503                    /usr/bin/browser symlink, since /usr/bin/browser may have
504                    been replaced with a shell script calling us via D-Bus */
505                 if (!strcmp(uri, "new_window")) {
506                         execl("/usr/bin/maemo-invoker",
507                               "browser", (char *)NULL);
508                 } else {
509                         execl("/usr/bin/maemo-invoker",
510                               "browser", "--url", uri, (char *)NULL);
511                 }
512         }
513 #endif /* FREMANTLE */
514
515         /* Kill off browserd if we started it */
516         if (kill_browserd)
517                 system("kill `pidof browserd`");
518
519         if (!ctx || !ctx->continuous_mode) 
520                 exit(0);
521
522         dbus_request_osso_browser_name(ctx);
523 }
524
525 static void launch_other_browser(struct swb_context *ctx, char *uri) {
526         char *command;
527         char *quoted_uri, *quote;
528
529         size_t cmdlen, urilen;
530         size_t quoted_uri_size;
531         size_t offset;
532
533         if (!uri || !strcmp(uri, "new_window"))
534                 uri = "";
535
536         log_msg("launch_other_browser with uri '%s'\n", uri);
537
538         if ((urilen = strlen(uri)) > 0) {
539                 /* Quote the URI to prevent the shell from interpreting it */
540                 /* urilen+3 = length of URI + 2x \' + \0 */
541                 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
542                         exit(1);
543                 snprintf(quoted_uri, urilen+3, "'%s'", uri);
544
545                 /* If there are any 's in the original URI, URL-escape them
546                    (replace them with %27) */
547                 quoted_uri_size = urilen + 3;
548                 quote = quoted_uri + 1;
549                 while ((quote = strchr(quote, '\'')) &&
550                        (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
551                         /* Check to make sure we don't shrink the memory area
552                            as a result of integer overflow */
553                         if (quoted_uri_size+2 <= quoted_uri_size)
554                                 exit(1);
555
556                         /* Grow the memory area;
557                            2 = strlen("%27")-strlen("'") */
558                         if (!(quoted_uri = realloc(quoted_uri,
559                                                    quoted_uri_size+2)))
560                                 exit(1);
561                         quoted_uri_size = quoted_uri_size + 2;
562
563                         /* Recalculate the location of the ' character --
564                            realloc() may have moved the string in memory */
565                         quote = quoted_uri + offset;
566
567                         /* Move the string after the ', including the \0,
568                            over two chars */
569                         memmove(quote+3, quote+1, strlen(quote));
570                         memcpy(quote, "%27", 3);
571                         quote = quote + 3;
572                 }
573                 urilen = strlen(quoted_uri);
574         } else
575                 quoted_uri = uri;
576
577         cmdlen = strlen(ctx->other_browser_cmd);
578
579         /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
580            replace "%s"), but is needed in the case other_browser_cmd has no %s
581            and urilen < 2 */
582         if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
583                 exit(1);
584         snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
585         log_msg("command: '%s'\n", command);
586
587         if (ctx->continuous_mode) {
588                 if (fork() != 0) {
589                         /* Parent process or error in fork() */
590                         if (urilen > 0)
591                                 free(quoted_uri);
592                         free(command);  
593                         return;
594                 }
595                 /* Child process */
596                 setsid();
597                 close_stdio();
598         }
599         execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
600 }
601
602
603 /* The list of known browsers and how to launch them */
604 static struct browser_launcher browser_launchers[] = {
605         { "microb", launch_microb, NULL }, /* First entry is the default! */
606         { "tear", launch_tear, NULL },
607         { "fennec", NULL, "fennec %s" },
608         { "opera", NULL, "opera %s" },
609         { "midori", NULL, "midori %s" },
610         { NULL, NULL, NULL },
611 };
612
613 static void use_launcher_as_default(struct swb_context *ctx,
614                                     struct browser_launcher *browser) {
615         if (!ctx || !browser)
616                 return;
617
618         if (browser->launcher)
619                 ctx->default_browser_launcher = browser->launcher;
620         else if (browser->other_browser_cmd) {
621                 free(ctx->other_browser_cmd);
622
623                 /* Make a copy of the string constant so that
624                    ctx->other_browser_cmd is safe to free() */
625                 ctx->other_browser_cmd = strdup(browser->other_browser_cmd);
626                 if (!ctx->other_browser_cmd) {
627                         log_msg("malloc failed!\n");
628                         /* Ideally, we'd configure the built-in default here --
629                            but it's possible we could be called in that path */
630                         exit(1);
631                 } else
632                         ctx->default_browser_launcher = launch_other_browser;
633         }
634
635         return;
636 }
637
638 void update_default_browser(struct swb_context *ctx, char *default_browser) {
639         struct browser_launcher *browser;
640
641         if (!ctx)
642                 return;
643
644         /* Configure the built-in default to start -- that way, we can
645            handle errors by just returning */
646         use_launcher_as_default(ctx, &browser_launchers[0]);
647
648         if (!default_browser)
649                 /* No default_browser configured -- use built-in default */
650                 return;
651
652         /* Go through the list of known browser launchers and use one if
653            it matches */
654         for (browser = browser_launchers; browser->name; ++browser)
655                 if (!strcmp(default_browser, browser->name)) {
656                         use_launcher_as_default(ctx, browser);
657                         return;
658                 }
659
660         /* Deal with default_browser = "other" */
661         if (!strcmp(default_browser, "other")) {
662                 if (ctx->other_browser_cmd)
663                         ctx->default_browser_launcher = launch_other_browser;
664                 else
665                         log_msg("default_browser is 'other', but no other_browser_cmd set -- using default\n");
666                 return;
667         }
668
669         /* Unknown value of default_browser */
670         log_msg("Unknown default_browser %s, using default\n", default_browser);
671         return;
672 }
673
674 void launch_browser(struct swb_context *ctx, char *uri) {
675         if (ctx && ctx->default_browser_launcher)
676                 ctx->default_browser_launcher(ctx, uri);
677 }