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>
38 #include "browser-switchboard.h"
40 #include "dbus-server-bindings.h"
42 #define LAUNCH_DEFAULT_BROWSER launch_microb
45 static int microb_started = 0;
46 static int kill_microb = 0;
48 /* Check to see whether MicroB is ready to handle D-Bus requests yet
49 See the comments in launch_microb to understand how this works. */
50 static DBusHandlerResult check_microb_started(DBusConnection *connection,
54 char *name, *old, *new;
56 printf("Checking to see if MicroB is ready\n");
57 dbus_error_init(&error);
58 if (!dbus_message_get_args(message, &error,
59 DBUS_TYPE_STRING, &name,
60 DBUS_TYPE_STRING, &old,
61 DBUS_TYPE_STRING, &new,
63 printf("%s\n", error.message);
64 dbus_error_free(&error);
65 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
67 /* If old is an empty string, then the name has been acquired, and
68 MicroB should be ready to handle our request */
69 if (strlen(old) == 0) {
70 printf("MicroB ready\n");
74 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
77 /* Check to see whether the last MicroB window has closed
78 See the comments in launch_microb to understand how this works. */
79 static DBusHandlerResult check_microb_finished(DBusConnection *connection,
83 char *name, *old, *new;
85 printf("Checking to see if we should kill MicroB\n");
86 /* Check to make sure that the Mozilla.MicroB name is being released,
87 not acquired -- if it's being acquired, we might be seeing an event
88 at MicroB startup, in which case killing the browser isn't
90 dbus_error_init(&error);
91 if (!dbus_message_get_args(message, &error,
92 DBUS_TYPE_STRING, &name,
93 DBUS_TYPE_STRING, &old,
94 DBUS_TYPE_STRING, &new,
96 printf("%s\n", error.message);
97 dbus_error_free(&error);
98 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
100 /* If old isn't an empty string, the name is being released, and
101 we should now kill MicroB */
105 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
109 /* Close stdin/stdout/stderr and replace with /dev/null */
110 static int close_stdio(void) {
113 if ((fd = open("/dev/null", O_RDWR)) == -1)
116 if (dup2(fd, 0) == -1 || dup2(fd, 1) == -1 || dup2(fd, 2) == -1)
123 static void launch_tear(struct swb_context *ctx, char *uri) {
125 static DBusGProxy *tear_proxy = NULL;
126 GError *error = NULL;
132 printf("launch_tear with uri '%s'\n", uri);
134 /* We should be able to just call the D-Bus service to open Tear ...
135 but if Tear's not open, that cuases D-Bus to start Tear and then
136 pass it the OpenAddress call, which results in two browser windows.
137 Properly fixing this probably requires Tear to provide a D-Bus
138 method that opens an address in an existing window, but for now work
139 around by just invoking Tear with exec() if it's not running. */
140 status = system("pidof tear > /dev/null");
141 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
143 if (!(tear_proxy = dbus_g_proxy_new_for_name(
147 "com.nokia.Tear"))) {
148 printf("Failed to create proxy for com.nokia.Tear D-Bus interface\n");
153 if (!dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
154 G_TYPE_STRING, uri, G_TYPE_INVALID,
156 printf("Opening window failed: %s\n", error->message);
159 if (!ctx->continuous_mode)
162 if (ctx->continuous_mode) {
163 if ((pid = fork()) != 0) {
164 /* Parent process or error in fork() */
165 printf("child: %d\n", (int)pid);
172 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
176 void launch_microb(struct swb_context *ctx, char *uri) {
177 int kill_browserd = 0;
181 DBusConnection *raw_connection;
182 DBusError dbus_error;
183 DBusHandleMessageFunction filter_func;
185 GError *gerror = NULL;
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);
207 if ((pid = fork()) == -1) {
214 /* Wait for our child to start the browser UI process and
215 for it to acquire the com.nokia.osso_browser D-Bus name,
216 then make the appropriate method call to open the browser
219 Ideas for how to do this monitoring derived from the
220 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
223 dbus_error_init(&dbus_error);
225 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
227 if (!raw_connection) {
229 "Failed to open connection to session bus: %s\n",
231 dbus_error_free(&dbus_error);
235 dbus_bus_add_match(raw_connection,
236 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
238 if (dbus_error_is_set(&dbus_error)) {
240 "Failed to set up watch for browser UI start: %s\n",
242 dbus_error_free(&dbus_error);
245 filter_func = check_microb_started;
246 if (!dbus_connection_add_filter(raw_connection,
247 filter_func, NULL, NULL)) {
248 fprintf(stderr, "Failed to set up watch filter!\n");
251 printf("Waiting for MicroB to start\n");
252 while (!microb_started &&
253 dbus_connection_read_write_dispatch(raw_connection,
255 dbus_connection_remove_filter(raw_connection,
257 dbus_bus_remove_match(raw_connection,
258 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
260 if (dbus_error_is_set(&dbus_error)) {
262 "Failed to remove watch for browser UI start: %s\n",
264 dbus_error_free(&dbus_error);
268 /* Browser UI's started, send it the request for a new window
270 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
271 "com.nokia.osso_browser",
272 "/com/nokia/osso_browser/request",
273 "com.nokia.osso_browser");
275 printf("Couldn't get a com.nokia.osso_browser proxy\n");
278 if (!strcmp(uri, "new_window")) {
279 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
280 corner case where, if the user just closes the bookmark window
281 without opening any browser windows, we don't kill off MicroB or
282 resume handling com.nokia.osso_browser */
283 if (!dbus_g_proxy_call(g_proxy, "top_application",
284 &gerror, G_TYPE_INVALID,
286 printf("Opening window failed: %s\n",
291 if (!dbus_g_proxy_call(g_proxy, "load_url",
293 G_TYPE_STRING, "about:blank",
296 printf("Opening window failed: %s\n",
301 if (!dbus_g_proxy_call(g_proxy, "load_url",
306 printf("Opening window failed: %s\n",
311 g_object_unref(g_proxy);
313 /* Workaround: the browser process we started is going to want
314 to hang around forever, hogging the com.nokia.osso_browser
315 D-Bus interface while at it. To fix this, we notice that
316 when the last browser window closes, the browser UI restarts
317 its attached browserd process, which causes an observable
318 change in the ownership of the Mozilla.MicroB D-Bus name.
319 Watch for this change and kill off the browser UI process
322 This has the problem of not being able to detect whether
323 the bookmark window is open and/or in use, but it's the best
324 that I can think of. Better suggestions would be greatly
327 dbus_bus_add_match(raw_connection,
328 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
330 if (dbus_error_is_set(&dbus_error)) {
332 "Failed to set up watch for browserd restart: %s\n",
334 dbus_error_free(&dbus_error);
337 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
338 to com.nokia.microb-engine; look for this too */
339 dbus_bus_add_match(raw_connection,
340 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
342 if (dbus_error_is_set(&dbus_error)) {
344 "Failed to set up watch for browserd restart: %s\n",
346 dbus_error_free(&dbus_error);
349 filter_func = check_microb_finished;
350 if (!dbus_connection_add_filter(raw_connection,
351 filter_func, NULL, NULL)) {
352 fprintf(stderr, "Failed to set up watch filter!\n");
355 while (!kill_microb &&
356 dbus_connection_read_write_dispatch(raw_connection,
358 dbus_connection_remove_filter(raw_connection,
360 dbus_bus_remove_match(raw_connection,
361 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
363 if (dbus_error_is_set(&dbus_error))
364 /* Don't really care -- about to disconnect from the
366 dbus_error_free(&dbus_error);
367 dbus_bus_remove_match(raw_connection,
368 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
370 if (dbus_error_is_set(&dbus_error))
371 dbus_error_free(&dbus_error);
372 dbus_connection_close(raw_connection);
373 dbus_connection_unref(raw_connection);
375 /* Kill off browser UI
376 XXX: Hope we don't cause data loss here! */
377 printf("Killing MicroB\n");
383 /* exec maemo-invoker directly instead of relying on the
384 /usr/bin/browser symlink, since /usr/bin/browser may have
385 been replaced with a shell script calling us via D-Bus */
386 /* Launch the browser in the background -- our parent will
387 wait for it to claim the D-Bus name and then display the
388 window using D-Bus */
389 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
391 #else /* !FREMANTLE */
394 waitpid(pid, &status, 0);
399 /* exec maemo-invoker directly instead of relying on the
400 /usr/bin/browser symlink, since /usr/bin/browser may have
401 been replaced with a shell script calling us via D-Bus */
402 if (!strcmp(uri, "new_window")) {
403 execl("/usr/bin/maemo-invoker",
404 "browser", (char *)NULL);
406 execl("/usr/bin/maemo-invoker",
407 "browser", "--url", uri, (char *)NULL);
410 #endif /* FREMANTLE */
412 /* Kill off browserd if we started it */
414 system("kill `pidof browserd`");
416 if (!ctx || !ctx->continuous_mode)
419 dbus_request_osso_browser_name(ctx);
422 static void launch_other_browser(struct swb_context *ctx, char *uri) {
424 char *quoted_uri, *quote;
426 size_t cmdlen, urilen;
427 size_t quoted_uri_size;
430 if (!uri || !strcmp(uri, "new_window"))
433 printf("launch_other_browser with uri '%s'\n", uri);
435 if ((urilen = strlen(uri)) > 0) {
436 /* Quote the URI to prevent the shell from interpreting it */
437 /* urilen+3 = length of URI + 2x \' + \0 */
438 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
440 snprintf(quoted_uri, urilen+3, "'%s'", uri);
442 /* If there are any 's in the original URI, URL-escape them
443 (replace them with %27) */
444 quoted_uri_size = urilen + 3;
445 quote = quoted_uri + 1;
446 while ((quote = strchr(quote, '\'')) &&
447 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
448 /* Check to make sure we don't shrink the memory area
449 as a result of integer overflow */
450 if (quoted_uri_size+2 <= quoted_uri_size)
453 /* Grow the memory area;
454 2 = strlen("%27")-strlen("'") */
455 if (!(quoted_uri = realloc(quoted_uri,
458 quoted_uri_size = quoted_uri_size + 2;
460 /* Recalculate the location of the ' character --
461 realloc() may have moved the string in memory */
462 quote = quoted_uri + offset;
464 /* Move the string after the ', including the \0,
466 memmove(quote+3, quote+1, strlen(quote));
467 memcpy(quote, "%27", 3);
470 urilen = strlen(quoted_uri);
474 cmdlen = strlen(ctx->other_browser_cmd);
476 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
477 replace "%s"), but is needed in the case other_browser_cmd has no %s
479 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
481 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
482 printf("command: '%s'\n", command);
484 if (ctx->continuous_mode) {
486 /* Parent process or error in fork() */
496 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
499 /* Use launch_other_browser as the default browser launcher, with the string
500 passed in as the other_browser_cmd
501 Resulting other_browser_cmd is always safe to free(), even if a pointer
502 to a string constant is passed in */
503 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
504 size_t len = strlen(cmd);
506 free(ctx->other_browser_cmd);
507 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
508 if (!ctx->other_browser_cmd) {
509 printf("malloc failed!\n");
510 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
512 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
514 ctx->default_browser_launcher = launch_other_browser;
518 void update_default_browser(struct swb_context *ctx, char *default_browser) {
522 if (!default_browser) {
523 /* No default_browser configured -- use built-in default */
524 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
528 if (!strcmp(default_browser, "tear"))
529 ctx->default_browser_launcher = launch_tear;
530 else if (!strcmp(default_browser, "microb"))
531 ctx->default_browser_launcher = launch_microb;
532 else if (!strcmp(default_browser, "fennec"))
533 /* Cheat and reuse launch_other_browser, since we don't appear
534 to need to do anything special */
535 use_other_browser_cmd(ctx, "fennec %s");
536 else if (!strcmp(default_browser, "midori"))
537 use_other_browser_cmd(ctx, "midori %s");
538 else if (!strcmp(default_browser, "other")) {
539 if (ctx->other_browser_cmd)
540 ctx->default_browser_launcher = launch_other_browser;
542 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
543 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
546 printf("Unknown default_browser %s, using default", default_browser);
547 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
551 void launch_browser(struct swb_context *ctx, char *uri) {
552 if (ctx && ctx->default_browser_launcher)
553 ctx->default_browser_launcher(ctx, uri);