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);
197 /* Put together the path to the MicroB browserd lockfile */
198 if (!(homedir = getenv("HOME")))
199 homedir = DEFAULT_HOMEDIR;
200 len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) + 1;
201 if (!(microb_profile_dir = calloc(len, sizeof(char)))) {
202 printf("calloc() failed\n");
205 snprintf(microb_profile_dir, len, "%s%s",
206 homedir, MICROB_PROFILE_DIR);
207 len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) +
208 strlen("/") + strlen(MICROB_LOCKFILE) + 1;
209 if (!(microb_lockfile = calloc(len, sizeof(char)))) {
210 printf("calloc() failed\n");
213 snprintf(microb_lockfile, len, "%s%s/%s",
214 homedir, MICROB_PROFILE_DIR, MICROB_LOCKFILE);
216 /* Watch for the creation of a MicroB browserd lockfile
217 NB: The watch has to be set up here, before the browser
218 is launched, to make sure there's no race between browserd
219 starting and us creating the watch */
220 if ((fd = inotify_init()) == -1) {
221 perror("inotify_init");
224 if ((inot_wd = inotify_add_watch(fd, microb_profile_dir,
226 perror("inotify_add_watch");
229 free(microb_profile_dir);
231 if ((pid = fork()) == -1) {
238 /* Wait for our child to start the browser UI process and
239 for it to acquire the com.nokia.osso_browser D-Bus name,
240 then make the appropriate method call to open the browser
243 Ideas for how to do this monitoring derived from the
244 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
247 dbus_error_init(&dbus_error);
249 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
251 if (!raw_connection) {
253 "Failed to open connection to session bus: %s\n",
255 dbus_error_free(&dbus_error);
259 dbus_bus_add_match(raw_connection,
260 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
262 if (dbus_error_is_set(&dbus_error)) {
264 "Failed to set up watch for browser UI start: %s\n",
266 dbus_error_free(&dbus_error);
269 filter_func = check_microb_started;
270 if (!dbus_connection_add_filter(raw_connection,
271 filter_func, NULL, NULL)) {
272 fprintf(stderr, "Failed to set up watch filter!\n");
275 printf("Waiting for MicroB to start\n");
276 while (!microb_started &&
277 dbus_connection_read_write_dispatch(raw_connection,
279 dbus_connection_remove_filter(raw_connection,
281 dbus_bus_remove_match(raw_connection,
282 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
284 if (dbus_error_is_set(&dbus_error))
285 /* Don't really care -- about to disconnect from the
287 dbus_error_free(&dbus_error);
288 dbus_connection_close(raw_connection);
289 dbus_connection_unref(raw_connection);
291 /* Browser UI's started, send it the request for a new window
293 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
294 "com.nokia.osso_browser",
295 "/com/nokia/osso_browser/request",
296 "com.nokia.osso_browser");
298 printf("Couldn't get a com.nokia.osso_browser proxy\n");
301 if (!strcmp(uri, "new_window")) {
302 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
303 corner case where, if the user just closes the bookmark window
304 without opening any browser windows, we don't kill off MicroB or
305 resume handling com.nokia.osso_browser */
306 if (!dbus_g_proxy_call(g_proxy, "top_application",
307 &gerror, G_TYPE_INVALID,
309 printf("Opening window failed: %s\n",
314 if (!dbus_g_proxy_call(g_proxy, "load_url",
316 G_TYPE_STRING, "about:blank",
319 printf("Opening window failed: %s\n",
324 if (!dbus_g_proxy_call(g_proxy, "load_url",
329 printf("Opening window failed: %s\n",
334 g_object_unref(g_proxy);
336 /* Workaround: the browser process we started is going to want
337 to hang around forever, hogging the com.nokia.osso_browser
338 D-Bus interface while at it. To fix this, we notice that
339 when the last browser window closes, the browser UI restarts
340 its attached browserd process. Get the browserd process's
341 PID and use ptrace() to watch for process termination.
343 This has the problem of not being able to detect whether
344 the bookmark window is open and/or in use, but it's the best
345 that I can think of. Better suggestions would be greatly
348 /* Wait for the MicroB browserd lockfile to be created */
349 printf("Waiting for browserd lockfile to be created\n");
350 memset(buf, '\0', 256);
351 /* read() blocks until there are events to be read */
352 while ((bytes_read = read(fd, buf, 255)) > 0) {
354 /* Loop until we see the event we're looking for
355 or until all the events are processed */
356 while (pos && (pos-buf) < bytes_read) {
357 event = (struct inotify_event *)pos;
358 len = sizeof(struct inotify_event)
360 if (!strcmp(MICROB_LOCKFILE,
362 /* Lockfile created */
365 } else if ((pos-buf) + len < bytes_read)
366 /* More events to process */
369 /* All events processed */
370 pos = buf + bytes_read;
373 /* Event found, stop looking */
375 memset(buf, '\0', 256);
377 inotify_rm_watch(fd, inot_wd);
380 /* Get the PID of the browserd from the lockfile */
381 if ((browserd_pid = get_browserd_pid(microb_lockfile)) <= 0) {
382 if (browserd_pid == 0)
383 printf("Profile lockfile link lacks PID\n");
385 printf("readlink() on lockfile failed: %s\n",
386 strerror(-browserd_pid));
389 free(microb_lockfile);
391 /* Wait for the browserd to close */
392 printf("Waiting for MicroB (browserd pid %d) to finish\n",
394 /* Clear any existing SIGCHLD handler to prevent interference
396 act.sa_handler = SIG_DFL;
398 sigemptyset(&(act.sa_mask));
399 if (sigaction(SIGCHLD, &act, &oldact) == -1) {
400 perror("clearing SIGCHLD handler failed");
404 /* Trace the browserd to get a close notification */
406 if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) {
407 perror("PTRACE_ATTACH");
410 ptrace(PTRACE_CONT, browserd_pid, NULL, NULL);
411 while ((waited_pid = wait(&status)) > 0) {
412 if (waited_pid != browserd_pid)
413 /* Not interested in other processes */
415 if (WIFEXITED(status) || WIFSIGNALED(status))
416 /* browserd exited */
418 else if (WIFSTOPPED(status)) {
419 /* browserd was sent a signal
420 We're responsible for making sure this
421 signal gets delivered */
422 if (ignore_sigstop &&
423 WSTOPSIG(status) == SIGSTOP) {
424 /* Ignore the first SIGSTOP received
425 This is raised for some reason
426 immediately after we start tracing
427 the process, and won't be followed
428 by a SIGCONT at any point */
429 printf("Ignoring first SIGSTOP\n");
430 ptrace(PTRACE_CONT, browserd_pid,
435 printf("Forwarding signal %d to browserd\n",
437 ptrace(PTRACE_CONT, browserd_pid,
438 NULL, WSTOPSIG(status));
442 /* Kill off browser UI
443 XXX: There is a race here with the restarting of the closed
444 browserd; if that happens before we kill the browser UI, the
445 newly started browserd may not close with the UI
446 XXX: Hope we don't cause data loss here! */
447 printf("Killing MicroB\n");
449 waitpid(pid, &status, 0);
451 /* Restore old SIGCHLD handler */
452 if (sigaction(SIGCHLD, &oldact, NULL) == -1) {
453 perror("restoring old SIGCHLD handler failed");
461 /* exec maemo-invoker directly instead of relying on the
462 /usr/bin/browser symlink, since /usr/bin/browser may have
463 been replaced with a shell script calling us via D-Bus */
464 /* Launch the browser in the background -- our parent will
465 wait for it to claim the D-Bus name and then display the
466 window using D-Bus */
467 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
469 #else /* !FREMANTLE */
470 if ((pid = fork()) == -1) {
477 waitpid(pid, &status, 0);
482 /* exec maemo-invoker directly instead of relying on the
483 /usr/bin/browser symlink, since /usr/bin/browser may have
484 been replaced with a shell script calling us via D-Bus */
485 if (!strcmp(uri, "new_window")) {
486 execl("/usr/bin/maemo-invoker",
487 "browser", (char *)NULL);
489 execl("/usr/bin/maemo-invoker",
490 "browser", "--url", uri, (char *)NULL);
493 #endif /* FREMANTLE */
495 /* Kill off browserd if we started it */
497 system("kill `pidof browserd`");
499 if (!ctx || !ctx->continuous_mode)
502 dbus_request_osso_browser_name(ctx);
505 static void launch_other_browser(struct swb_context *ctx, char *uri) {
507 char *quoted_uri, *quote;
509 size_t cmdlen, urilen;
510 size_t quoted_uri_size;
513 if (!uri || !strcmp(uri, "new_window"))
516 printf("launch_other_browser with uri '%s'\n", uri);
518 if ((urilen = strlen(uri)) > 0) {
519 /* Quote the URI to prevent the shell from interpreting it */
520 /* urilen+3 = length of URI + 2x \' + \0 */
521 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
523 snprintf(quoted_uri, urilen+3, "'%s'", uri);
525 /* If there are any 's in the original URI, URL-escape them
526 (replace them with %27) */
527 quoted_uri_size = urilen + 3;
528 quote = quoted_uri + 1;
529 while ((quote = strchr(quote, '\'')) &&
530 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
531 /* Check to make sure we don't shrink the memory area
532 as a result of integer overflow */
533 if (quoted_uri_size+2 <= quoted_uri_size)
536 /* Grow the memory area;
537 2 = strlen("%27")-strlen("'") */
538 if (!(quoted_uri = realloc(quoted_uri,
541 quoted_uri_size = quoted_uri_size + 2;
543 /* Recalculate the location of the ' character --
544 realloc() may have moved the string in memory */
545 quote = quoted_uri + offset;
547 /* Move the string after the ', including the \0,
549 memmove(quote+3, quote+1, strlen(quote));
550 memcpy(quote, "%27", 3);
553 urilen = strlen(quoted_uri);
557 cmdlen = strlen(ctx->other_browser_cmd);
559 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
560 replace "%s"), but is needed in the case other_browser_cmd has no %s
562 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
564 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
565 printf("command: '%s'\n", command);
567 if (ctx->continuous_mode) {
569 /* Parent process or error in fork() */
579 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
582 /* Use launch_other_browser as the default browser launcher, with the string
583 passed in as the other_browser_cmd
584 Resulting other_browser_cmd is always safe to free(), even if a pointer
585 to a string constant is passed in */
586 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
587 size_t len = strlen(cmd);
589 free(ctx->other_browser_cmd);
590 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
591 if (!ctx->other_browser_cmd) {
592 printf("malloc failed!\n");
593 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
595 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
597 ctx->default_browser_launcher = launch_other_browser;
601 void update_default_browser(struct swb_context *ctx, char *default_browser) {
605 if (!default_browser) {
606 /* No default_browser configured -- use built-in default */
607 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
611 if (!strcmp(default_browser, "tear"))
612 ctx->default_browser_launcher = launch_tear;
613 else if (!strcmp(default_browser, "microb"))
614 ctx->default_browser_launcher = launch_microb;
615 else if (!strcmp(default_browser, "fennec"))
616 /* Cheat and reuse launch_other_browser, since we don't appear
617 to need to do anything special */
618 use_other_browser_cmd(ctx, "fennec %s");
619 else if (!strcmp(default_browser, "midori"))
620 use_other_browser_cmd(ctx, "midori %s");
621 else if (!strcmp(default_browser, "other")) {
622 if (ctx->other_browser_cmd)
623 ctx->default_browser_launcher = launch_other_browser;
625 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
626 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
629 printf("Unknown default_browser %s, using default", default_browser);
630 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
634 void launch_browser(struct swb_context *ctx, char *uri) {
635 if (ctx && ctx->default_browser_launcher)
636 ctx->default_browser_launcher(ctx, uri);