2 * launcher.c -- functions for launching web browsers for browser-switchboard
4 * Copyright (C) 2009 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 tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
135 "com.nokia.tear", "/com/nokia/tear",
137 dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
138 G_TYPE_STRING, uri, G_TYPE_INVALID);
139 if (!ctx->continuous_mode)
142 if (ctx->continuous_mode) {
143 if ((pid = fork()) != 0) {
144 /* Parent process or error in fork() */
145 printf("child: %d\n", (int)pid);
152 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
156 void launch_microb(struct swb_context *ctx, char *uri) {
157 int kill_browserd = 0;
161 char *homedir, *microb_profile_dir, *microb_lockfile;
164 DBusConnection *raw_connection;
165 DBusError dbus_error;
166 DBusHandleMessageFunction filter_func;
168 GError *gerror = NULL;
171 struct inotify_event *event;
172 pid_t browserd_pid, waited_pid;
173 struct sigaction act, oldact;
180 printf("launch_microb with uri '%s'\n", uri);
182 /* Launch browserd if it's not running */
183 status = system("pidof browserd > /dev/null");
184 if (WIFEXITED(status) && WEXITSTATUS(status)) {
187 system("/usr/sbin/browserd -d -b > /dev/null 2>&1");
189 system("/usr/sbin/browserd -d > /dev/null 2>&1");
193 /* Release the osso_browser D-Bus name so that MicroB can take it */
194 dbus_release_osso_browser_name(ctx);
196 if ((pid = fork()) == -1) {
201 /* Put together the path to the MicroB browserd lockfile */
202 if (!(homedir = getenv("HOME")))
203 homedir = DEFAULT_HOMEDIR;
204 len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) + 1;
205 if (!(microb_profile_dir = calloc(len, sizeof(char)))) {
206 printf("calloc() failed\n");
209 snprintf(microb_profile_dir, len, "%s%s",
210 homedir, MICROB_PROFILE_DIR);
211 len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) +
212 strlen("/") + strlen(MICROB_LOCKFILE) + 1;
213 if (!(microb_lockfile = calloc(len, sizeof(char)))) {
214 printf("calloc() failed\n");
217 snprintf(microb_lockfile, len, "%s%s/%s",
218 homedir, MICROB_PROFILE_DIR, MICROB_LOCKFILE);
220 /* Watch for the creation of a MicroB browserd lockfile
221 NB: The watch has to be set up here, before the browser
222 is launched, to make sure there's no race between browserd
223 starting and us creating the watch */
224 if ((fd = inotify_init()) == -1) {
225 perror("inotify_init");
228 if ((inot_wd = inotify_add_watch(fd, microb_profile_dir,
230 perror("inotify_add_watch");
233 free(microb_profile_dir);
237 /* Wait for our child to start the browser UI process and
238 for it to acquire the com.nokia.osso_browser D-Bus name,
239 then make the appropriate method call to open the browser
242 Ideas for how to do this monitoring derived from the
243 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
246 dbus_error_init(&dbus_error);
248 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
250 if (!raw_connection) {
252 "Failed to open connection to session bus: %s\n",
254 dbus_error_free(&dbus_error);
258 dbus_bus_add_match(raw_connection,
259 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
261 if (dbus_error_is_set(&dbus_error)) {
263 "Failed to set up watch for browser UI start: %s\n",
265 dbus_error_free(&dbus_error);
268 filter_func = check_microb_started;
269 if (!dbus_connection_add_filter(raw_connection,
270 filter_func, NULL, NULL)) {
271 fprintf(stderr, "Failed to set up watch filter!\n");
274 printf("Waiting for MicroB to start\n");
275 while (!microb_started &&
276 dbus_connection_read_write_dispatch(raw_connection,
278 dbus_connection_remove_filter(raw_connection,
280 dbus_bus_remove_match(raw_connection,
281 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
283 if (dbus_error_is_set(&dbus_error))
284 /* Don't really care -- about to disconnect from the
286 dbus_error_free(&dbus_error);
287 dbus_connection_close(raw_connection);
288 dbus_connection_unref(raw_connection);
290 /* Browser UI's started, send it the request for a new window
292 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
293 "com.nokia.osso_browser",
294 "/com/nokia/osso_browser/request",
295 "com.nokia.osso_browser");
297 printf("Couldn't get a com.nokia.osso_browser proxy\n");
300 if (!strcmp(uri, "new_window")) {
301 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
302 corner case where, if the user just closes the bookmark window
303 without opening any browser windows, we don't kill off MicroB or
304 resume handling com.nokia.osso_browser */
305 if (!dbus_g_proxy_call(g_proxy, "top_application",
306 &gerror, G_TYPE_INVALID,
308 printf("Opening window failed: %s\n",
313 if (!dbus_g_proxy_call(g_proxy, "load_url",
315 G_TYPE_STRING, "about:blank",
318 printf("Opening window failed: %s\n",
323 if (!dbus_g_proxy_call(g_proxy, "load_url",
328 printf("Opening window failed: %s\n",
333 g_object_unref(g_proxy);
335 /* Workaround: the browser process we started is going to want
336 to hang around forever, hogging the com.nokia.osso_browser
337 D-Bus interface while at it. To fix this, we notice that
338 when the last browser window closes, the browser UI restarts
339 its attached browserd process. Get the browserd process's
340 PID and use ptrace() to watch for process termination.
342 This has the problem of not being able to detect whether
343 the bookmark window is open and/or in use, but it's the best
344 that I can think of. Better suggestions would be greatly
347 /* Wait for the MicroB browserd lockfile to be created */
348 printf("Waiting for browserd lockfile to be created\n");
349 memset(buf, '\0', 256);
350 /* read() blocks until there are events to be read */
351 while ((bytes_read = read(fd, buf, 255)) > 0) {
353 /* Loop until we see the event we're looking for
354 or until all the events are processed */
355 while (pos && (pos-buf) < bytes_read) {
356 event = (struct inotify_event *)pos;
357 len = sizeof(struct inotify_event)
359 if (!strcmp(MICROB_LOCKFILE,
361 /* Lockfile created */
364 } else if ((pos-buf) + len < bytes_read)
365 /* More events to process */
368 /* All events processed */
369 pos = buf + bytes_read;
372 /* Event found, stop looking */
374 memset(buf, '\0', 256);
376 inotify_rm_watch(fd, inot_wd);
379 /* Get the PID of the browserd from the lockfile */
380 if ((browserd_pid = get_browserd_pid(microb_lockfile)) <= 0) {
381 if (browserd_pid == 0)
382 printf("Profile lockfile link lacks PID\n");
384 printf("readlink() on lockfile failed: %s\n",
385 strerror(-browserd_pid));
388 free(microb_lockfile);
390 /* Wait for the browserd to close */
391 printf("Waiting for MicroB (browserd pid %d) to finish\n",
393 /* Clear any existing SIGCHLD handler to prevent interference
395 act.sa_handler = SIG_DFL;
397 sigemptyset(&(act.sa_mask));
398 if (sigaction(SIGCHLD, &act, &oldact) == -1) {
399 perror("clearing SIGCHLD handler failed");
403 /* Trace the browserd to get a close notification */
405 if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) {
406 perror("PTRACE_ATTACH");
409 ptrace(PTRACE_CONT, browserd_pid, NULL, NULL);
410 while ((waited_pid = wait(&status)) > 0) {
411 if (waited_pid != browserd_pid)
412 /* Not interested in other processes */
414 if (WIFEXITED(status) || WIFSIGNALED(status))
415 /* browserd exited */
417 else if (WIFSTOPPED(status)) {
418 /* browserd was sent a signal
419 We're responsible for making sure this
420 signal gets delivered */
421 if (ignore_sigstop &&
422 WSTOPSIG(status) == SIGSTOP) {
423 /* Ignore the first SIGSTOP received
424 This is raised for some reason
425 immediately after we start tracing
426 the process, and won't be followed
427 by a SIGCONT at any point */
428 printf("Ignoring first SIGSTOP\n");
429 ptrace(PTRACE_CONT, browserd_pid,
434 printf("Forwarding signal %d to browserd\n",
436 ptrace(PTRACE_CONT, browserd_pid,
437 NULL, WSTOPSIG(status));
441 /* Kill off browser UI
442 XXX: There is a race here with the restarting of the closed
443 browserd; if that happens before we kill the browser UI, the
444 newly started browserd may not close with the UI
445 XXX: Hope we don't cause data loss here! */
446 printf("Killing MicroB\n");
448 waitpid(pid, &status, 0);
450 /* Restore old SIGCHLD handler */
451 if (sigaction(SIGCHLD, &oldact, NULL) == -1) {
452 perror("restoring old SIGCHLD handler failed");
460 /* exec maemo-invoker directly instead of relying on the
461 /usr/bin/browser symlink, since /usr/bin/browser may have
462 been replaced with a shell script calling us via D-Bus */
463 /* Launch the browser in the background -- our parent will
464 wait for it to claim the D-Bus name and then display the
465 window using D-Bus */
466 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
468 #else /* !FREMANTLE */
471 waitpid(pid, &status, 0);
476 /* exec maemo-invoker directly instead of relying on the
477 /usr/bin/browser symlink, since /usr/bin/browser may have
478 been replaced with a shell script calling us via D-Bus */
479 if (!strcmp(uri, "new_window")) {
480 execl("/usr/bin/maemo-invoker",
481 "browser", (char *)NULL);
483 execl("/usr/bin/maemo-invoker",
484 "browser", "--url", uri, (char *)NULL);
487 #endif /* FREMANTLE */
489 /* Kill off browserd if we started it */
491 system("kill `pidof browserd`");
493 if (!ctx || !ctx->continuous_mode)
496 dbus_request_osso_browser_name(ctx);
499 static void launch_other_browser(struct swb_context *ctx, char *uri) {
501 char *quoted_uri, *quote;
503 size_t cmdlen, urilen;
504 size_t quoted_uri_size;
507 if (!uri || !strcmp(uri, "new_window"))
510 printf("launch_other_browser with uri '%s'\n", uri);
512 if ((urilen = strlen(uri)) > 0) {
513 /* Quote the URI to prevent the shell from interpreting it */
514 /* urilen+3 = length of URI + 2x \' + \0 */
515 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
517 snprintf(quoted_uri, urilen+3, "'%s'", uri);
519 /* If there are any 's in the original URI, URL-escape them
520 (replace them with %27) */
521 quoted_uri_size = urilen + 3;
522 quote = quoted_uri + 1;
523 while ((quote = strchr(quote, '\'')) &&
524 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
525 /* Check to make sure we don't shrink the memory area
526 as a result of integer overflow */
527 if (quoted_uri_size+2 <= quoted_uri_size)
530 /* Grow the memory area;
531 2 = strlen("%27")-strlen("'") */
532 if (!(quoted_uri = realloc(quoted_uri,
535 quoted_uri_size = quoted_uri_size + 2;
537 /* Recalculate the location of the ' character --
538 realloc() may have moved the string in memory */
539 quote = quoted_uri + offset;
541 /* Move the string after the ', including the \0,
543 memmove(quote+3, quote+1, strlen(quote));
544 memcpy(quote, "%27", 3);
547 urilen = strlen(quoted_uri);
551 cmdlen = strlen(ctx->other_browser_cmd);
553 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
554 replace "%s"), but is needed in the case other_browser_cmd has no %s
556 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
558 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
559 printf("command: '%s'\n", command);
561 if (ctx->continuous_mode) {
563 /* Parent process or error in fork() */
573 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
576 /* Use launch_other_browser as the default browser launcher, with the string
577 passed in as the other_browser_cmd
578 Resulting other_browser_cmd is always safe to free(), even if a pointer
579 to a string constant is passed in */
580 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
581 size_t len = strlen(cmd);
583 free(ctx->other_browser_cmd);
584 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
585 if (!ctx->other_browser_cmd) {
586 printf("malloc failed!\n");
587 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
589 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
591 ctx->default_browser_launcher = launch_other_browser;
595 void update_default_browser(struct swb_context *ctx, char *default_browser) {
599 if (!default_browser) {
600 /* No default_browser configured -- use built-in default */
601 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
605 if (!strcmp(default_browser, "tear"))
606 ctx->default_browser_launcher = launch_tear;
607 else if (!strcmp(default_browser, "microb"))
608 ctx->default_browser_launcher = launch_microb;
609 else if (!strcmp(default_browser, "fennec"))
610 /* Cheat and reuse launch_other_browser, since we don't appear
611 to need to do anything special */
612 use_other_browser_cmd(ctx, "fennec %s");
613 else if (!strcmp(default_browser, "midori"))
614 use_other_browser_cmd(ctx, "midori %s");
615 else if (!strcmp(default_browser, "other")) {
616 if (ctx->other_browser_cmd)
617 ctx->default_browser_launcher = launch_other_browser;
619 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
620 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
623 printf("Unknown default_browser %s, using default", default_browser);
624 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
628 void launch_browser(struct swb_context *ctx, char *uri) {
629 if (ctx && ctx->default_browser_launcher)
630 ctx->default_browser_launcher(ctx, uri);