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 /* Set up the D-Bus eavesdropping we'll use to watch for MicroB
243 acquiring the com.nokia.osso_browser D-Bus name. Again, this needs
244 to happen before the browser is launched, so that there's no race
245 between establishing the watch and browser startup.
247 Ideas for how to do this monitoring derived from the dbus-monitor
248 code (tools/dbus-monitor.c in the D-Bus codebase). */
249 dbus_error_init(&dbus_error);
251 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION, &dbus_error);
252 if (!raw_connection) {
254 "Failed to open connection to session bus: %s\n",
256 dbus_error_free(&dbus_error);
260 dbus_bus_add_match(raw_connection,
261 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
263 if (dbus_error_is_set(&dbus_error)) {
265 "Failed to set up watch for browser UI start: %s\n",
267 dbus_error_free(&dbus_error);
270 filter_func = check_microb_started;
271 if (!dbus_connection_add_filter(raw_connection,
272 filter_func, NULL, NULL)) {
273 fprintf(stderr, "Failed to set up watch filter!\n");
277 if ((pid = fork()) == -1) {
284 /* Wait for our child to start the browser UI process and
285 for it to acquire the com.nokia.osso_browser D-Bus name,
286 then make the appropriate method call to open the browser
289 printf("Waiting for MicroB to start\n");
290 while (!microb_started &&
291 dbus_connection_read_write_dispatch(raw_connection,
293 dbus_connection_remove_filter(raw_connection,
295 dbus_bus_remove_match(raw_connection,
296 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
298 if (dbus_error_is_set(&dbus_error))
299 /* Don't really care -- about to disconnect from the
301 dbus_error_free(&dbus_error);
302 dbus_connection_close(raw_connection);
303 dbus_connection_unref(raw_connection);
305 /* Browser UI's started, send it the request for a new window
307 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
308 "com.nokia.osso_browser",
309 "/com/nokia/osso_browser/request",
310 "com.nokia.osso_browser");
312 printf("Couldn't get a com.nokia.osso_browser proxy\n");
315 if (!strcmp(uri, "new_window")) {
316 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
317 corner case where, if the user just closes the bookmark window
318 without opening any browser windows, we don't kill off MicroB or
319 resume handling com.nokia.osso_browser */
320 if (!dbus_g_proxy_call(g_proxy, "top_application",
321 &gerror, G_TYPE_INVALID,
323 printf("Opening window failed: %s\n",
328 if (!dbus_g_proxy_call(g_proxy, "load_url",
330 G_TYPE_STRING, "about:blank",
333 printf("Opening window failed: %s\n",
338 if (!dbus_g_proxy_call(g_proxy, "load_url",
343 printf("Opening window failed: %s\n",
348 g_object_unref(g_proxy);
350 /* Workaround: the browser process we started is going to want
351 to hang around forever, hogging the com.nokia.osso_browser
352 D-Bus interface while at it. To fix this, we notice that
353 when the last browser window closes, the browser UI restarts
354 its attached browserd process. Get the browserd process's
355 PID and use ptrace() to watch for process termination.
357 This has the problem of not being able to detect whether
358 the bookmark window is open and/or in use, but it's the best
359 that I can think of. Better suggestions would be greatly
362 /* Wait for the MicroB browserd lockfile to be created */
363 printf("Waiting for browserd lockfile to be created\n");
364 memset(buf, '\0', 256);
365 /* read() blocks until there are events to be read */
366 while ((bytes_read = read(fd, buf, 255)) > 0) {
368 /* Loop until we see the event we're looking for
369 or until all the events are processed */
370 while (pos && (pos-buf) < bytes_read) {
371 event = (struct inotify_event *)pos;
372 len = sizeof(struct inotify_event)
374 if (!strcmp(MICROB_LOCKFILE,
376 /* Lockfile created */
379 } else if ((pos-buf) + len < bytes_read)
380 /* More events to process */
383 /* All events processed */
387 /* Event found, stop looking */
389 memset(buf, '\0', 256);
391 inotify_rm_watch(fd, inot_wd);
394 /* Get the PID of the browserd from the lockfile */
395 if ((browserd_pid = get_browserd_pid(microb_lockfile)) <= 0) {
396 if (browserd_pid == 0)
397 printf("Profile lockfile link lacks PID\n");
399 printf("readlink() on lockfile failed: %s\n",
400 strerror(-browserd_pid));
403 free(microb_lockfile);
405 /* Wait for the browserd to close */
406 printf("Waiting for MicroB (browserd pid %d) to finish\n",
408 /* Clear any existing SIGCHLD handler to prevent interference
410 act.sa_handler = SIG_DFL;
412 sigemptyset(&(act.sa_mask));
413 if (sigaction(SIGCHLD, &act, &oldact) == -1) {
414 perror("clearing SIGCHLD handler failed");
418 /* Trace the browserd to get a close notification */
420 if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) {
421 perror("PTRACE_ATTACH");
424 ptrace(PTRACE_CONT, browserd_pid, NULL, NULL);
425 while ((waited_pid = wait(&status)) > 0) {
426 if (waited_pid != browserd_pid)
427 /* Not interested in other processes */
429 if (WIFEXITED(status) || WIFSIGNALED(status))
430 /* browserd exited */
432 else if (WIFSTOPPED(status)) {
433 /* browserd was sent a signal
434 We're responsible for making sure this
435 signal gets delivered */
436 if (ignore_sigstop &&
437 WSTOPSIG(status) == SIGSTOP) {
438 /* Ignore the first SIGSTOP received
439 This is raised for some reason
440 immediately after we start tracing
441 the process, and won't be followed
442 by a SIGCONT at any point */
443 printf("Ignoring first SIGSTOP\n");
444 ptrace(PTRACE_CONT, browserd_pid,
449 printf("Forwarding signal %d to browserd\n",
451 ptrace(PTRACE_CONT, browserd_pid,
452 NULL, WSTOPSIG(status));
456 /* Kill off browser UI
457 XXX: There is a race here with the restarting of the closed
458 browserd; if that happens before we kill the browser UI, the
459 newly started browserd may not close with the UI
460 XXX: Hope we don't cause data loss here! */
461 printf("Killing MicroB\n");
463 waitpid(pid, &status, 0);
465 /* Restore old SIGCHLD handler */
466 if (sigaction(SIGCHLD, &oldact, NULL) == -1) {
467 perror("restoring old SIGCHLD handler failed");
472 dbus_connection_close(raw_connection);
473 dbus_connection_unref(raw_connection);
477 /* exec maemo-invoker directly instead of relying on the
478 /usr/bin/browser symlink, since /usr/bin/browser may have
479 been replaced with a shell script calling us via D-Bus */
480 /* Launch the browser in the background -- our parent will
481 wait for it to claim the D-Bus name and then display the
482 window using D-Bus */
483 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
485 #else /* !FREMANTLE */
486 if ((pid = fork()) == -1) {
493 waitpid(pid, &status, 0);
498 /* exec maemo-invoker directly instead of relying on the
499 /usr/bin/browser symlink, since /usr/bin/browser may have
500 been replaced with a shell script calling us via D-Bus */
501 if (!strcmp(uri, "new_window")) {
502 execl("/usr/bin/maemo-invoker",
503 "browser", (char *)NULL);
505 execl("/usr/bin/maemo-invoker",
506 "browser", "--url", uri, (char *)NULL);
509 #endif /* FREMANTLE */
511 /* Kill off browserd if we started it */
513 system("kill `pidof browserd`");
515 if (!ctx || !ctx->continuous_mode)
518 dbus_request_osso_browser_name(ctx);
521 static void launch_other_browser(struct swb_context *ctx, char *uri) {
523 char *quoted_uri, *quote;
525 size_t cmdlen, urilen;
526 size_t quoted_uri_size;
529 if (!uri || !strcmp(uri, "new_window"))
532 printf("launch_other_browser with uri '%s'\n", uri);
534 if ((urilen = strlen(uri)) > 0) {
535 /* Quote the URI to prevent the shell from interpreting it */
536 /* urilen+3 = length of URI + 2x \' + \0 */
537 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
539 snprintf(quoted_uri, urilen+3, "'%s'", uri);
541 /* If there are any 's in the original URI, URL-escape them
542 (replace them with %27) */
543 quoted_uri_size = urilen + 3;
544 quote = quoted_uri + 1;
545 while ((quote = strchr(quote, '\'')) &&
546 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
547 /* Check to make sure we don't shrink the memory area
548 as a result of integer overflow */
549 if (quoted_uri_size+2 <= quoted_uri_size)
552 /* Grow the memory area;
553 2 = strlen("%27")-strlen("'") */
554 if (!(quoted_uri = realloc(quoted_uri,
557 quoted_uri_size = quoted_uri_size + 2;
559 /* Recalculate the location of the ' character --
560 realloc() may have moved the string in memory */
561 quote = quoted_uri + offset;
563 /* Move the string after the ', including the \0,
565 memmove(quote+3, quote+1, strlen(quote));
566 memcpy(quote, "%27", 3);
569 urilen = strlen(quoted_uri);
573 cmdlen = strlen(ctx->other_browser_cmd);
575 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
576 replace "%s"), but is needed in the case other_browser_cmd has no %s
578 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
580 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
581 printf("command: '%s'\n", command);
583 if (ctx->continuous_mode) {
585 /* Parent process or error in fork() */
595 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
598 /* Use launch_other_browser as the default browser launcher, with the string
599 passed in as the other_browser_cmd
600 Resulting other_browser_cmd is always safe to free(), even if a pointer
601 to a string constant is passed in */
602 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
603 size_t len = strlen(cmd);
605 free(ctx->other_browser_cmd);
606 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
607 if (!ctx->other_browser_cmd) {
608 printf("malloc failed!\n");
609 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
611 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
613 ctx->default_browser_launcher = launch_other_browser;
617 void update_default_browser(struct swb_context *ctx, char *default_browser) {
621 if (!default_browser) {
622 /* No default_browser configured -- use built-in default */
623 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
627 if (!strcmp(default_browser, "tear"))
628 ctx->default_browser_launcher = launch_tear;
629 else if (!strcmp(default_browser, "microb"))
630 ctx->default_browser_launcher = launch_microb;
631 else if (!strcmp(default_browser, "fennec"))
632 /* Cheat and reuse launch_other_browser, since we don't appear
633 to need to do anything special */
634 use_other_browser_cmd(ctx, "fennec %s");
635 else if (!strcmp(default_browser, "midori"))
636 use_other_browser_cmd(ctx, "midori %s");
637 else if (!strcmp(default_browser, "other")) {
638 if (ctx->other_browser_cmd)
639 ctx->default_browser_launcher = launch_other_browser;
641 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
642 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
645 printf("Unknown default_browser %s, using default", default_browser);
646 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
650 void launch_browser(struct swb_context *ctx, char *uri) {
651 if (ctx && ctx->default_browser_launcher)
652 ctx->default_browser_launcher(ctx, uri);