2 * launcher.c -- functions for launching web browsers for browser-switchboard
4 * Copyright (C) 2009-2010 Steven Luo
5 * Derived from a Python implementation by Jason Simpson and Steven Luo
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.
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.
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,
27 #include <sys/types.h>
31 #include <dbus/dbus-glib.h>
34 #include <dbus/dbus.h>
37 #include <sys/ptrace.h>
38 #include <sys/inotify.h>
40 #define DEFAULT_HOMEDIR "/home/user"
41 #define MICROB_PROFILE_DIR "/.mozilla/microb"
42 #define MICROB_LOCKFILE "lock"
45 #include "browser-switchboard.h"
47 #include "dbus-server-bindings.h"
49 #define LAUNCH_DEFAULT_BROWSER launch_microb
52 static int microb_started = 0;
54 /* Check to see whether MicroB is ready to handle D-Bus requests yet
55 See the comments in launch_microb to understand how this works. */
56 static DBusHandlerResult check_microb_started(DBusConnection *connection,
60 char *name, *old, *new;
62 printf("Checking to see if MicroB is ready\n");
63 dbus_error_init(&error);
64 if (!dbus_message_get_args(message, &error,
65 DBUS_TYPE_STRING, &name,
66 DBUS_TYPE_STRING, &old,
67 DBUS_TYPE_STRING, &new,
69 printf("%s\n", error.message);
70 dbus_error_free(&error);
71 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
73 /* If old is an empty string, then the name has been acquired, and
74 MicroB should be ready to handle our request */
75 if (strlen(old) == 0) {
76 printf("MicroB ready\n");
80 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
83 /* Get a browserd PID from the corresponding Mozilla profile lockfile */
84 static pid_t get_browserd_pid(const char *lockfile) {
87 /* The lockfile is a symlink pointing to "[ipaddr]:+[pid]", so read in
88 the target of the symlink and parse it that way */
89 memset(buf, '\0', 256);
90 if (readlink(lockfile, buf, 255) == -1)
92 if (!(tmp = strstr(buf, ":+")))
94 tmp += 2; /* Skip over the ":+" */
100 /* Close stdin/stdout/stderr and replace with /dev/null */
101 static int close_stdio(void) {
104 if ((fd = open("/dev/null", O_RDWR)) == -1)
107 if (dup2(fd, 0) == -1 || dup2(fd, 1) == -1 || dup2(fd, 2) == -1)
114 static void launch_tear(struct swb_context *ctx, char *uri) {
116 static DBusGProxy *tear_proxy = NULL;
117 GError *error = NULL;
123 printf("launch_tear with uri '%s'\n", uri);
125 /* We should be able to just call the D-Bus service to open Tear ...
126 but if Tear's not open, that cuases D-Bus to start Tear and then
127 pass it the OpenAddress call, which results in two browser windows.
128 Properly fixing this probably requires Tear to provide a D-Bus
129 method that opens an address in an existing window, but for now work
130 around by just invoking Tear with exec() if it's not running. */
131 status = system("pidof tear > /dev/null");
132 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
134 if (!(tear_proxy = dbus_g_proxy_new_for_name(
138 "com.nokia.Tear"))) {
139 printf("Failed to create proxy for com.nokia.Tear D-Bus interface\n");
144 if (!dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
145 G_TYPE_STRING, uri, G_TYPE_INVALID,
147 printf("Opening window failed: %s\n", error->message);
150 if (!ctx->continuous_mode)
153 if (ctx->continuous_mode) {
154 if ((pid = fork()) != 0) {
155 /* Parent process or error in fork() */
156 printf("child: %d\n", (int)pid);
163 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
167 void launch_microb(struct swb_context *ctx, char *uri) {
168 int kill_browserd = 0;
172 char *homedir, *microb_profile_dir, *microb_lockfile;
175 DBusConnection *raw_connection;
176 DBusError dbus_error;
177 DBusHandleMessageFunction filter_func;
179 GError *gerror = NULL;
182 struct inotify_event *event;
183 pid_t browserd_pid, waited_pid;
184 struct sigaction act, oldact;
191 printf("launch_microb with uri '%s'\n", uri);
193 /* Launch browserd if it's not running */
194 status = system("pidof browserd > /dev/null");
195 if (WIFEXITED(status) && WEXITSTATUS(status)) {
198 system("/usr/sbin/browserd -d -b > /dev/null 2>&1");
200 system("/usr/sbin/browserd -d > /dev/null 2>&1");
204 /* Release the osso_browser D-Bus name so that MicroB can take it */
205 dbus_release_osso_browser_name(ctx);
208 /* Put together the path to the MicroB browserd lockfile */
209 if (!(homedir = getenv("HOME")))
210 homedir = DEFAULT_HOMEDIR;
211 len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) + 1;
212 if (!(microb_profile_dir = calloc(len, sizeof(char)))) {
213 printf("calloc() failed\n");
216 snprintf(microb_profile_dir, len, "%s%s",
217 homedir, MICROB_PROFILE_DIR);
218 len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) +
219 strlen("/") + strlen(MICROB_LOCKFILE) + 1;
220 if (!(microb_lockfile = calloc(len, sizeof(char)))) {
221 printf("calloc() failed\n");
224 snprintf(microb_lockfile, len, "%s%s/%s",
225 homedir, MICROB_PROFILE_DIR, MICROB_LOCKFILE);
227 /* Watch for the creation of a MicroB browserd lockfile
228 NB: The watch has to be set up here, before the browser
229 is launched, to make sure there's no race between browserd
230 starting and us creating the watch */
231 if ((fd = inotify_init()) == -1) {
232 perror("inotify_init");
235 if ((inot_wd = inotify_add_watch(fd, microb_profile_dir,
237 perror("inotify_add_watch");
240 free(microb_profile_dir);
242 if ((pid = fork()) == -1) {
249 /* Wait for our child to start the browser UI process and
250 for it to acquire the com.nokia.osso_browser D-Bus name,
251 then make the appropriate method call to open the browser
254 Ideas for how to do this monitoring derived from the
255 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
258 dbus_error_init(&dbus_error);
260 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
262 if (!raw_connection) {
264 "Failed to open connection to session bus: %s\n",
266 dbus_error_free(&dbus_error);
270 dbus_bus_add_match(raw_connection,
271 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
273 if (dbus_error_is_set(&dbus_error)) {
275 "Failed to set up watch for browser UI start: %s\n",
277 dbus_error_free(&dbus_error);
280 filter_func = check_microb_started;
281 if (!dbus_connection_add_filter(raw_connection,
282 filter_func, NULL, NULL)) {
283 fprintf(stderr, "Failed to set up watch filter!\n");
286 printf("Waiting for MicroB to start\n");
287 while (!microb_started &&
288 dbus_connection_read_write_dispatch(raw_connection,
290 dbus_connection_remove_filter(raw_connection,
292 dbus_bus_remove_match(raw_connection,
293 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
295 if (dbus_error_is_set(&dbus_error))
296 /* Don't really care -- about to disconnect from the
298 dbus_error_free(&dbus_error);
299 dbus_connection_close(raw_connection);
300 dbus_connection_unref(raw_connection);
302 /* Browser UI's started, send it the request for a new window
304 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
305 "com.nokia.osso_browser",
306 "/com/nokia/osso_browser/request",
307 "com.nokia.osso_browser");
309 printf("Couldn't get a com.nokia.osso_browser proxy\n");
312 if (!strcmp(uri, "new_window")) {
313 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
314 corner case where, if the user just closes the bookmark window
315 without opening any browser windows, we don't kill off MicroB or
316 resume handling com.nokia.osso_browser */
317 if (!dbus_g_proxy_call(g_proxy, "top_application",
318 &gerror, G_TYPE_INVALID,
320 printf("Opening window failed: %s\n",
325 if (!dbus_g_proxy_call(g_proxy, "load_url",
327 G_TYPE_STRING, "about:blank",
330 printf("Opening window failed: %s\n",
335 if (!dbus_g_proxy_call(g_proxy, "load_url",
340 printf("Opening window failed: %s\n",
345 g_object_unref(g_proxy);
347 /* Workaround: the browser process we started is going to want
348 to hang around forever, hogging the com.nokia.osso_browser
349 D-Bus interface while at it. To fix this, we notice that
350 when the last browser window closes, the browser UI restarts
351 its attached browserd process. Get the browserd process's
352 PID and use ptrace() to watch for process termination.
354 This has the problem of not being able to detect whether
355 the bookmark window is open and/or in use, but it's the best
356 that I can think of. Better suggestions would be greatly
359 /* Wait for the MicroB browserd lockfile to be created */
360 printf("Waiting for browserd lockfile to be created\n");
361 memset(buf, '\0', 256);
362 /* read() blocks until there are events to be read */
363 while ((bytes_read = read(fd, buf, 255)) > 0) {
365 /* Loop until we see the event we're looking for
366 or until all the events are processed */
367 while (pos && (pos-buf) < bytes_read) {
368 event = (struct inotify_event *)pos;
369 len = sizeof(struct inotify_event)
371 if (!strcmp(MICROB_LOCKFILE,
373 /* Lockfile created */
376 } else if ((pos-buf) + len < bytes_read)
377 /* More events to process */
380 /* All events processed */
381 pos = buf + bytes_read;
384 /* Event found, stop looking */
386 memset(buf, '\0', 256);
388 inotify_rm_watch(fd, inot_wd);
391 /* Get the PID of the browserd from the lockfile */
392 if ((browserd_pid = get_browserd_pid(microb_lockfile)) <= 0) {
393 if (browserd_pid == 0)
394 printf("Profile lockfile link lacks PID\n");
396 printf("readlink() on lockfile failed: %s\n",
397 strerror(-browserd_pid));
400 free(microb_lockfile);
402 /* Wait for the browserd to close */
403 printf("Waiting for MicroB (browserd pid %d) to finish\n",
405 /* Clear any existing SIGCHLD handler to prevent interference
407 act.sa_handler = SIG_DFL;
409 sigemptyset(&(act.sa_mask));
410 if (sigaction(SIGCHLD, &act, &oldact) == -1) {
411 perror("clearing SIGCHLD handler failed");
415 /* Trace the browserd to get a close notification */
417 if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) {
418 perror("PTRACE_ATTACH");
421 ptrace(PTRACE_CONT, browserd_pid, NULL, NULL);
422 while ((waited_pid = wait(&status)) > 0) {
423 if (waited_pid != browserd_pid)
424 /* Not interested in other processes */
426 if (WIFEXITED(status) || WIFSIGNALED(status))
427 /* browserd exited */
429 else if (WIFSTOPPED(status)) {
430 /* browserd was sent a signal
431 We're responsible for making sure this
432 signal gets delivered */
433 if (ignore_sigstop &&
434 WSTOPSIG(status) == SIGSTOP) {
435 /* Ignore the first SIGSTOP received
436 This is raised for some reason
437 immediately after we start tracing
438 the process, and won't be followed
439 by a SIGCONT at any point */
440 printf("Ignoring first SIGSTOP\n");
441 ptrace(PTRACE_CONT, browserd_pid,
446 printf("Forwarding signal %d to browserd\n",
448 ptrace(PTRACE_CONT, browserd_pid,
449 NULL, WSTOPSIG(status));
453 /* Kill off browser UI
454 XXX: There is a race here with the restarting of the closed
455 browserd; if that happens before we kill the browser UI, the
456 newly started browserd may not close with the UI
457 XXX: Hope we don't cause data loss here! */
458 printf("Killing MicroB\n");
460 waitpid(pid, &status, 0);
462 /* Restore old SIGCHLD handler */
463 if (sigaction(SIGCHLD, &oldact, NULL) == -1) {
464 perror("restoring old SIGCHLD handler failed");
472 /* exec maemo-invoker directly instead of relying on the
473 /usr/bin/browser symlink, since /usr/bin/browser may have
474 been replaced with a shell script calling us via D-Bus */
475 /* Launch the browser in the background -- our parent will
476 wait for it to claim the D-Bus name and then display the
477 window using D-Bus */
478 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
480 #else /* !FREMANTLE */
481 if ((pid = fork()) == -1) {
488 waitpid(pid, &status, 0);
493 /* exec maemo-invoker directly instead of relying on the
494 /usr/bin/browser symlink, since /usr/bin/browser may have
495 been replaced with a shell script calling us via D-Bus */
496 if (!strcmp(uri, "new_window")) {
497 execl("/usr/bin/maemo-invoker",
498 "browser", (char *)NULL);
500 execl("/usr/bin/maemo-invoker",
501 "browser", "--url", uri, (char *)NULL);
504 #endif /* FREMANTLE */
506 /* Kill off browserd if we started it */
508 system("kill `pidof browserd`");
510 if (!ctx || !ctx->continuous_mode)
513 dbus_request_osso_browser_name(ctx);
516 static void launch_other_browser(struct swb_context *ctx, char *uri) {
518 char *quoted_uri, *quote;
520 size_t cmdlen, urilen;
521 size_t quoted_uri_size;
524 if (!uri || !strcmp(uri, "new_window"))
527 printf("launch_other_browser with uri '%s'\n", uri);
529 if ((urilen = strlen(uri)) > 0) {
530 /* Quote the URI to prevent the shell from interpreting it */
531 /* urilen+3 = length of URI + 2x \' + \0 */
532 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
534 snprintf(quoted_uri, urilen+3, "'%s'", uri);
536 /* If there are any 's in the original URI, URL-escape them
537 (replace them with %27) */
538 quoted_uri_size = urilen + 3;
539 quote = quoted_uri + 1;
540 while ((quote = strchr(quote, '\'')) &&
541 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
542 /* Check to make sure we don't shrink the memory area
543 as a result of integer overflow */
544 if (quoted_uri_size+2 <= quoted_uri_size)
547 /* Grow the memory area;
548 2 = strlen("%27")-strlen("'") */
549 if (!(quoted_uri = realloc(quoted_uri,
552 quoted_uri_size = quoted_uri_size + 2;
554 /* Recalculate the location of the ' character --
555 realloc() may have moved the string in memory */
556 quote = quoted_uri + offset;
558 /* Move the string after the ', including the \0,
560 memmove(quote+3, quote+1, strlen(quote));
561 memcpy(quote, "%27", 3);
564 urilen = strlen(quoted_uri);
568 cmdlen = strlen(ctx->other_browser_cmd);
570 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
571 replace "%s"), but is needed in the case other_browser_cmd has no %s
573 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
575 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
576 printf("command: '%s'\n", command);
578 if (ctx->continuous_mode) {
580 /* Parent process or error in fork() */
590 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
593 /* Use launch_other_browser as the default browser launcher, with the string
594 passed in as the other_browser_cmd
595 Resulting other_browser_cmd is always safe to free(), even if a pointer
596 to a string constant is passed in */
597 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
598 size_t len = strlen(cmd);
600 free(ctx->other_browser_cmd);
601 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
602 if (!ctx->other_browser_cmd) {
603 printf("malloc failed!\n");
604 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
606 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
608 ctx->default_browser_launcher = launch_other_browser;
612 void update_default_browser(struct swb_context *ctx, char *default_browser) {
616 if (!default_browser) {
617 /* No default_browser configured -- use built-in default */
618 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
622 if (!strcmp(default_browser, "tear"))
623 ctx->default_browser_launcher = launch_tear;
624 else if (!strcmp(default_browser, "microb"))
625 ctx->default_browser_launcher = launch_microb;
626 else if (!strcmp(default_browser, "fennec"))
627 /* Cheat and reuse launch_other_browser, since we don't appear
628 to need to do anything special */
629 use_other_browser_cmd(ctx, "fennec %s");
630 else if (!strcmp(default_browser, "midori"))
631 use_other_browser_cmd(ctx, "midori %s");
632 else if (!strcmp(default_browser, "other")) {
633 if (ctx->other_browser_cmd)
634 ctx->default_browser_launcher = launch_other_browser;
636 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
637 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
640 printf("Unknown default_browser %s, using default", default_browser);
641 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
645 void launch_browser(struct swb_context *ctx, char *uri) {
646 if (ctx && ctx->default_browser_launcher)
647 ctx->default_browser_launcher(ctx, uri);