Initial support for launching MicroB on Fremantle
[browser-switch] / launcher.c
index b6c9ea4..f229469 100644 (file)
 #include <sys/wait.h>
 #include <dbus/dbus-glib.h>
 
 #include <sys/wait.h>
 #include <dbus/dbus-glib.h>
 
+#ifdef FREMANTLE
+#include <signal.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus.h>
+#include <sys/inotify.h>
+#include <poll.h>
+#endif
+
 #include "browser-switchboard.h"
 #include "launcher.h"
 #include "dbus-server-bindings.h"
 
 #include "browser-switchboard.h"
 #include "launcher.h"
 #include "dbus-server-bindings.h"
 
-#define LAUNCH_DEFAULT_BROWSER launch_tear
+#define LAUNCH_DEFAULT_BROWSER launch_microb
+
+#ifdef FREMANTLE
+static int microb_started = 0;
+static int kill_microb = 0;
+
+/* Check to see whether MicroB is ready to handle D-Bus requests yet
+   See the comments in launch_microb to understand how this works. */
+static DBusHandlerResult check_microb_started(DBusConnection *connection,
+                                    DBusMessage *message,
+                                    void *user_data) {
+       DBusError error;
+       char *name, *old, *new;
+
+       printf("Checking to see if MicroB is ready\n");
+       dbus_error_init(&error);
+       if (!dbus_message_get_args(message, &error,
+                                  DBUS_TYPE_STRING, &name,
+                                  DBUS_TYPE_STRING, &old,
+                                  DBUS_TYPE_STRING, &new,
+                                  DBUS_TYPE_INVALID)) {
+               printf("%s\n", error.message);
+               dbus_error_free(&error);
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+       /* If old is an empty string, then the name has been acquired, and
+          MicroB should be ready to handle our request */
+       if (strlen(old) == 0) {
+               printf("MicroB ready\n");
+               microb_started = 1;
+       }
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/* Check to see whether the last MicroB window has closed
+   See the comments in launch_microb to understand how this works. */
+static DBusHandlerResult check_microb_finished(DBusConnection *connection,
+                                    DBusMessage *message,
+                                    void *user_data) {
+       DBusError error;
+       char *name, *old, *new;
+
+       printf("Checking to see if we should kill MicroB\n");
+       /* Check to make sure that the Mozilla.MicroB name is being released,
+          not acquired -- if it's being acquired, we might be seeing an event
+          at MicroB startup, in which case killing the browser isn't
+          appropriate */
+       dbus_error_init(&error);
+       if (!dbus_message_get_args(message, &error,
+                                  DBUS_TYPE_STRING, &name,
+                                  DBUS_TYPE_STRING, &old,
+                                  DBUS_TYPE_STRING, &new,
+                                  DBUS_TYPE_INVALID)) {
+               printf("%s\n", error.message);
+               dbus_error_free(&error);
+               return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+       }
+       /* If old isn't an empty string, the name is being released, and
+          we should now kill MicroB */
+       if (strlen(old) > 0)
+               kill_microb = 1;
+
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+#endif
 
 
-static void launch_tear(struct swb_context * ctx, char * uri) {
+static void launch_tear(struct swb_context *ctx, char *uri) {
        int status;
        int status;
-       static DBusGProxy * tear_proxy = NULL;
-       GError * error = NULL;
+       static DBusGProxy *tear_proxy = NULL;
+       GError *error = NULL;
        pid_t pid;
 
        if (!uri)
        pid_t pid;
 
        if (!uri)
@@ -45,18 +118,20 @@ static void launch_tear(struct swb_context * ctx, char * uri) {
 
        printf("launch_tear with uri '%s'\n", uri);
 
 
        printf("launch_tear with uri '%s'\n", uri);
 
+       /* We should be able to just call the D-Bus service to open Tear ...
+          but if Tear's not open, that cuases D-Bus to start Tear and then
+          pass it the OpenAddress call, which results in two browser windows.
+          Properly fixing this probably requires Tear to provide a D-Bus
+          method that opens an address in an existing window, but for now work
+          around by just invoking Tear with exec() if it's not running. */
        status = system("pidof tear > /dev/null");
        status = system("pidof tear > /dev/null");
-       if (!WIFEXITED(status))
-               exit(1);
-       if (!WEXITSTATUS(status)) {
+       if (WIFEXITED(status) && !WEXITSTATUS(status)) {
                if (!tear_proxy)
                        tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
                                        "com.nokia.tear", "/com/nokia/tear",
                                        "com.nokia.Tear");
                if (!tear_proxy)
                        tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
                                        "com.nokia.tear", "/com/nokia/tear",
                                        "com.nokia.Tear");
-               dbus_g_proxy_call(tear_proxy, "OpenAddress",
-                               &error,
-                               G_TYPE_STRING, uri,
-                               G_TYPE_INVALID);
+               dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
+                                 G_TYPE_STRING, uri, G_TYPE_INVALID);
                if (!ctx->continuous_mode)
                        exit(0);
        } else {
                if (!ctx->continuous_mode)
                        exit(0);
        } else {
@@ -73,42 +148,248 @@ static void launch_tear(struct swb_context * ctx, char * uri) {
        }
 }
 
        }
 }
 
-void launch_microb(struct swb_context * ctx, char * uri) {
+void launch_microb(struct swb_context *ctx, char *uri) {
        int kill_browserd = 0;
        int status;
        pid_t pid;
        int kill_browserd = 0;
        int status;
        pid_t pid;
+#ifdef FREMANTLE
+       DBusConnection *raw_connection;
+       DBusError dbus_error;
+       DBusHandleMessageFunction filter_func;
+       DBusGProxy *g_proxy;
+       GError *gerror = NULL;
+#endif
 
        if (!uri)
                uri = "new_window";
 
 
        if (!uri)
                uri = "new_window";
 
+       printf("launch_microb with uri '%s'\n", uri);
+
+       /* Launch browserd if it's not running */
        status = system("pidof /usr/sbin/browserd > /dev/null");
        status = system("pidof /usr/sbin/browserd > /dev/null");
-       if (!WIFEXITED(status))
-               exit(1);
-       if (WEXITSTATUS(status)) {
+       if (WIFEXITED(status) && WEXITSTATUS(status)) {
                kill_browserd = 1;
                kill_browserd = 1;
+#ifdef FREMANTLE
+               system("/usr/sbin/browserd -d -b");
+#else
                system("/usr/sbin/browserd -d");
                system("/usr/sbin/browserd -d");
+#endif
        }
 
        }
 
+       /* Release the osso_browser D-Bus name so that MicroB can take it */
        dbus_release_osso_browser_name(ctx);
 
        if ((pid = fork()) == -1) {
                perror("fork");
                exit(1);
        }
        dbus_release_osso_browser_name(ctx);
 
        if ((pid = fork()) == -1) {
                perror("fork");
                exit(1);
        }
+#ifdef FREMANTLE
+       if (pid > 0) {
+               /* Parent process */
+               /* Wait for our child to start the browser UI process and
+                  for it to acquire the com.nokia.osso_browser D-Bus name,
+                  then make the appropriate method call to open the browser
+                  window.
+
+                  Ideas for how to do this monitoring derived from the
+                  dbus-monitor code (tools/dbus-monitor.c in the D-Bus
+                  codebase). */
+               microb_started = 0;
+               dbus_error_init(&dbus_error);
+
+               raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
+                                                     &dbus_error);
+               if (!raw_connection) {
+                       fprintf(stderr,
+                               "Failed to open connection to session bus: %s\n",
+                               dbus_error.message);
+                       dbus_error_free(&dbus_error);
+                       exit(1);
+               }
+
+               dbus_bus_add_match(raw_connection,
+                                  "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
+                                  &dbus_error);
+               if (dbus_error_is_set(&dbus_error)) {
+                       fprintf(stderr,
+                               "Failed to set up watch for browser UI start: %s\n",
+                               dbus_error.message);
+                       dbus_error_free(&dbus_error);
+                       exit(1);
+               }
+               filter_func = check_microb_started;
+               if (!dbus_connection_add_filter(raw_connection,
+                                               filter_func, NULL, NULL)) {
+                       fprintf(stderr, "Failed to set up watch filter!\n");
+                       exit(1);
+               }
+               printf("Waiting for MicroB to start\n");
+               while (!microb_started &&
+                      dbus_connection_read_write_dispatch(raw_connection,
+                                                          -1));
+               dbus_connection_remove_filter(raw_connection,
+                                             filter_func, NULL);
+               dbus_bus_remove_match(raw_connection,
+                                     "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
+                                     &dbus_error);
+               if (dbus_error_is_set(&dbus_error)) {
+                       fprintf(stderr,
+                               "Failed to remove watch for browser UI start: %s\n",
+                               dbus_error.message);
+                       dbus_error_free(&dbus_error);
+                       exit(1);
+               }
+
+               /* Browser UI's started, send it the request for a new window
+                  via D-Bus */
+               g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
+                               "com.nokia.osso_browser",
+                               "/com/nokia/osso_browser/request",
+                               "com.nokia.osso_browser");
+               if (!g_proxy) {
+                       printf("Couldn't get a com.nokia.osso_browser proxy\n");
+                       exit(1);
+               }
+               if (!strcmp(uri, "new_window")) {
+#if 0 /* Since we can't detect when the bookmark window closes, we'd have a
+        corner case where, if the user just closes the bookmark window
+        without opening any browser windows, we don't kill off MicroB or
+        resume handling com.nokia.osso_browser */
+                       if (!dbus_g_proxy_call(g_proxy, "top_application",
+                                              &gerror, G_TYPE_INVALID,
+                                              G_TYPE_INVALID)) {
+                               printf("Opening window failed: %s\n",
+                                      gerror->message);
+                               exit(1);
+                       }
+#endif
+                       if (!dbus_g_proxy_call(g_proxy, "load_url",
+                                              &gerror,
+                                              G_TYPE_STRING, "about:blank",
+                                              G_TYPE_INVALID,
+                                              G_TYPE_INVALID)) {
+                               printf("Opening window failed: %s\n",
+                                      gerror->message);
+                               exit(1);
+                       }
+               } else {
+                       if (!dbus_g_proxy_call(g_proxy, "load_url",
+                                              &gerror,
+                                              G_TYPE_STRING, uri,
+                                              G_TYPE_INVALID,
+                                              G_TYPE_INVALID)) {
+                               printf("Opening window failed: %s\n",
+                                      gerror->message);
+                               exit(1);
+                       }
+               }
+
+               /* Workaround: the browser process we started is going to want
+                  to hang around forever, hogging the com.nokia.osso_browser
+                  D-Bus interface while at it.  To fix this, we notice that
+                  when the last browser window closes, the browser UI restarts
+                  its attached browserd process, which causes an observable
+                  change in the ownership of the Mozilla.MicroB D-Bus name.
+                  Watch for this change and kill off the browser UI process
+                  when it happens.
+
+                  This has the problem of not being able to detect whether
+                  the bookmark window is open and/or in use, but it's the best
+                  that I can think of.  Better suggestions would be greatly
+                  appreciated. */
+               kill_microb = 0;
+               dbus_bus_add_match(raw_connection,
+                                  "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
+                                  &dbus_error);
+               if (dbus_error_is_set(&dbus_error)) {
+                       fprintf(stderr,
+                               "Failed to set up watch for browserd restart: %s\n",
+                               dbus_error.message);
+                       dbus_error_free(&dbus_error);
+                       exit(1);
+               }
+               /* Maemo 5 PR1.1 seems to have changed the name browserd takes
+                  to com.nokia.microb-engine; look for this too */
+               dbus_bus_add_match(raw_connection,
+                                  "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
+                                  &dbus_error);
+               if (dbus_error_is_set(&dbus_error)) {
+                       fprintf(stderr,
+                               "Failed to set up watch for browserd restart: %s\n",
+                               dbus_error.message);
+                       dbus_error_free(&dbus_error);
+                       exit(1);
+               }
+               filter_func = check_microb_finished;
+               if (!dbus_connection_add_filter(raw_connection,
+                                               filter_func, NULL, NULL)) {
+                       fprintf(stderr, "Failed to set up watch filter!\n");
+                       exit(1);
+               }
+               while (!kill_microb &&
+                      dbus_connection_read_write_dispatch(raw_connection,
+                                                          -1));
+               dbus_connection_remove_filter(raw_connection,
+                                             filter_func, NULL);
+               dbus_bus_remove_match(raw_connection,
+                                  "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
+                                  &dbus_error);
+               if (dbus_error_is_set(&dbus_error))
+                       /* Don't really care -- about to disconnect from the
+                          bus anyhow */
+                       dbus_error_free(&dbus_error);
+               dbus_bus_remove_match(raw_connection,
+                                  "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
+                                  &dbus_error);
+               if (dbus_error_is_set(&dbus_error))
+                       dbus_error_free(&dbus_error);
+               dbus_connection_close(raw_connection);
+               dbus_connection_unref(raw_connection);
+
+               /* Tell browser UI to exit nicely */
+               printf("Closing MicroB\n");
+               if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror,
+                                      G_TYPE_INVALID, G_TYPE_INVALID)) {
+                       /* We don't expect a reply; any other error indicates
+                          a problem */
+                       if (gerror->domain != DBUS_GERROR ||
+                           gerror->code != DBUS_GERROR_NO_REPLY) {
+                               printf("exit_browser failed: %s\n",
+                                      gerror->message);
+                               exit(1);
+                       }
+               }
+               g_object_unref(g_proxy);
+       } else {
+               /* Child process */
+               /* exec maemo-invoker directly instead of relying on the
+                  /usr/bin/browser symlink, since /usr/bin/browser may have
+                  been replaced with a shell script calling us via D-Bus */
+               /* Launch the browser in the background -- our parent will
+                  wait for it to claim the D-Bus name and then display the
+                  window using D-Bus */
+               execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
+       }
+#else /* !FREMANTLE */
        if (pid > 0) {
                /* Parent process */
                waitpid(pid, &status, 0);
        } else {
                /* Child process */
        if (pid > 0) {
                /* Parent process */
                waitpid(pid, &status, 0);
        } else {
                /* Child process */
+               /* exec maemo-invoker directly instead of relying on the
+                  /usr/bin/browser symlink, since /usr/bin/browser may have
+                  been replaced with a shell script calling us via D-Bus */
                if (!strcmp(uri, "new_window")) {
                        execl("/usr/bin/maemo-invoker",
                if (!strcmp(uri, "new_window")) {
                        execl("/usr/bin/maemo-invoker",
-                                      "browser", (char *)NULL);
+                             "browser", (char *)NULL);
                } else {
                        execl("/usr/bin/maemo-invoker",
                } else {
                        execl("/usr/bin/maemo-invoker",
-                                       "browser", "--url", uri, (char *)NULL);
+                             "browser", "--url", uri, (char *)NULL);
                }
        }
                }
        }
+#endif /* FREMANTLE */
 
 
+       /* Kill off browserd if we started it */
        if (kill_browserd)
                system("kill `pidof /usr/sbin/browserd`");
 
        if (kill_browserd)
                system("kill `pidof /usr/sbin/browserd`");
 
@@ -118,38 +399,51 @@ void launch_microb(struct swb_context * ctx, char * uri) {
        dbus_request_osso_browser_name(ctx);
 }
 
        dbus_request_osso_browser_name(ctx);
 }
 
-static void launch_other_browser(struct swb_context * ctx, char * uri) {
-       char * command;
-       char * quoted_uri;
+static void launch_other_browser(struct swb_context *ctx, char *uri) {
+       char *command;
+       char *quoted_uri, *quote;
+
        size_t cmdlen, urilen;
        size_t cmdlen, urilen;
+       size_t quoted_uri_size;
+       size_t offset;
 
        if (!uri || !strcmp(uri, "new_window"))
                uri = "";
 
        if (!uri || !strcmp(uri, "new_window"))
                uri = "";
-       urilen = strlen(uri);
-       if (urilen > 0) {
-               char * quote;
 
 
-               /* Quote the URI */
+       printf("launch_other_browser with uri '%s'\n", uri);
+
+       if ((urilen = strlen(uri)) > 0) {
+               /* Quote the URI to prevent the shell from interpreting it */
                /* urilen+3 = length of URI + 2x \' + \0 */
                if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
                        exit(1);
                /* urilen+3 = length of URI + 2x \' + \0 */
                if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
                        exit(1);
-               strncpy(quoted_uri+1, uri, urilen);
-               quoted_uri[0] = quoted_uri[urilen+1] = '\'';
-               /* calloc zeroes the memory, so string is automatically
-                  null terminated */
+               snprintf(quoted_uri, urilen+3, "'%s'", uri);
 
                /* If there are any 's in the original URI, URL-escape them
                   (replace them with %27) */
 
                /* If there are any 's in the original URI, URL-escape them
                   (replace them with %27) */
+               quoted_uri_size = urilen + 3;
                quote = quoted_uri + 1;
                while ((quote = strchr(quote, '\'')) &&
                quote = quoted_uri + 1;
                while ((quote = strchr(quote, '\'')) &&
-                               (quote-quoted_uri) < strlen(quoted_uri)-1) {
-                       /* 3 = strlen("%27")-strlen("'") + \0 */
+                      (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
+                       /* Check to make sure we don't shrink the memory area
+                          as a result of integer overflow */
+                       if (quoted_uri_size+2 <= quoted_uri_size)
+                               exit(1);
+
+                       /* Grow the memory area;
+                          2 = strlen("%27")-strlen("'") */
                        if (!(quoted_uri = realloc(quoted_uri,
                        if (!(quoted_uri = realloc(quoted_uri,
-                                                       strlen(quoted_uri)+3)))
+                                                  quoted_uri_size+2)))
                                exit(1);
                                exit(1);
+                       quoted_uri_size = quoted_uri_size + 2;
+
+                       /* Recalculate the location of the ' character --
+                          realloc() may have moved the string in memory */
+                       quote = quoted_uri + offset;
+
                        /* Move the string after the ', including the \0,
                           over two chars */
                        /* Move the string after the ', including the \0,
                           over two chars */
-                       memmove(quote+3, quote+1, strlen(quote)+1);
+                       memmove(quote+3, quote+1, strlen(quote));
                        memcpy(quote, "%27", 3);
                        quote = quote + 3;
                }
                        memcpy(quote, "%27", 3);
                        quote = quote + 3;
                }
@@ -181,7 +475,11 @@ static void launch_other_browser(struct swb_context * ctx, char * uri) {
        execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
 }
 
        execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
 }
 
-static void use_other_browser_cmd(struct swb_context * ctx, char * cmd) {
+/* Use launch_other_browser as the default browser launcher, with the string
+   passed in as the other_browser_cmd
+   Resulting other_browser_cmd is always safe to free(), even if a pointer
+   to a string constant is passed in */
+static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
        size_t len = strlen(cmd);
 
        free(ctx->other_browser_cmd);
        size_t len = strlen(cmd);
 
        free(ctx->other_browser_cmd);
@@ -191,17 +489,17 @@ static void use_other_browser_cmd(struct swb_context * ctx, char * cmd) {
                ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
        } else {
                ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
                ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
        } else {
                ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
-                               cmd, len);
-               ctx->other_browser_cmd[len] = '\0';
+                                                cmd, len+1);
                ctx->default_browser_launcher = launch_other_browser;
        }
 }
 
                ctx->default_browser_launcher = launch_other_browser;
        }
 }
 
-void update_default_browser(struct swb_context * ctx, char * default_browser) {
+void update_default_browser(struct swb_context *ctx, char *default_browser) {
        if (!ctx)
                return;
 
        if (!default_browser) {
        if (!ctx)
                return;
 
        if (!default_browser) {
+               /* No default_browser configured -- use built-in default */
                ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
                return;
        }
                ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
                return;
        }
@@ -211,6 +509,8 @@ void update_default_browser(struct swb_context * ctx, char * default_browser) {
        else if (!strcmp(default_browser, "microb"))
                ctx->default_browser_launcher = launch_microb;
        else if (!strcmp(default_browser, "fennec"))
        else if (!strcmp(default_browser, "microb"))
                ctx->default_browser_launcher = launch_microb;
        else if (!strcmp(default_browser, "fennec"))
+               /* Cheat and reuse launch_other_browser, since we don't appear
+                  to need to do anything special */
                use_other_browser_cmd(ctx, "fennec %s");
        else if (!strcmp(default_browser, "midori"))
                use_other_browser_cmd(ctx, "midori %s");
                use_other_browser_cmd(ctx, "fennec %s");
        else if (!strcmp(default_browser, "midori"))
                use_other_browser_cmd(ctx, "midori %s");
@@ -227,7 +527,7 @@ void update_default_browser(struct swb_context * ctx, char * default_browser) {
        }
 }
 
        }
 }
 
-void launch_browser(struct swb_context * ctx, char * uri) {
+void launch_browser(struct swb_context *ctx, char *uri) {
        if (ctx && ctx->default_browser_launcher)
                ctx->default_browser_launcher(ctx, uri);
 }
        if (ctx && ctx->default_browser_launcher)
                ctx->default_browser_launcher(ctx, uri);
 }