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>
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 tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
144 "com.nokia.tear", "/com/nokia/tear",
146 dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
147 G_TYPE_STRING, uri, G_TYPE_INVALID);
148 if (!ctx->continuous_mode)
151 if (ctx->continuous_mode) {
152 if ((pid = fork()) != 0) {
153 /* Parent process or error in fork() */
154 printf("child: %d\n", (int)pid);
161 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
165 void launch_microb(struct swb_context *ctx, char *uri) {
166 int kill_browserd = 0;
170 DBusConnection *raw_connection;
171 DBusError dbus_error;
172 DBusHandleMessageFunction filter_func;
174 GError *gerror = NULL;
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) {
203 /* Wait for our child to start the browser UI process and
204 for it to acquire the com.nokia.osso_browser D-Bus name,
205 then make the appropriate method call to open the browser
208 Ideas for how to do this monitoring derived from the
209 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
212 dbus_error_init(&dbus_error);
214 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
216 if (!raw_connection) {
218 "Failed to open connection to session bus: %s\n",
220 dbus_error_free(&dbus_error);
224 dbus_bus_add_match(raw_connection,
225 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
227 if (dbus_error_is_set(&dbus_error)) {
229 "Failed to set up watch for browser UI start: %s\n",
231 dbus_error_free(&dbus_error);
234 filter_func = check_microb_started;
235 if (!dbus_connection_add_filter(raw_connection,
236 filter_func, NULL, NULL)) {
237 fprintf(stderr, "Failed to set up watch filter!\n");
240 printf("Waiting for MicroB to start\n");
241 while (!microb_started &&
242 dbus_connection_read_write_dispatch(raw_connection,
244 dbus_connection_remove_filter(raw_connection,
246 dbus_bus_remove_match(raw_connection,
247 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
249 if (dbus_error_is_set(&dbus_error)) {
251 "Failed to remove watch for browser UI start: %s\n",
253 dbus_error_free(&dbus_error);
257 /* Browser UI's started, send it the request for a new window
259 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
260 "com.nokia.osso_browser",
261 "/com/nokia/osso_browser/request",
262 "com.nokia.osso_browser");
264 printf("Couldn't get a com.nokia.osso_browser proxy\n");
267 if (!strcmp(uri, "new_window")) {
268 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
269 corner case where, if the user just closes the bookmark window
270 without opening any browser windows, we don't kill off MicroB or
271 resume handling com.nokia.osso_browser */
272 if (!dbus_g_proxy_call(g_proxy, "top_application",
273 &gerror, G_TYPE_INVALID,
275 printf("Opening window failed: %s\n",
280 if (!dbus_g_proxy_call(g_proxy, "load_url",
282 G_TYPE_STRING, "about:blank",
285 printf("Opening window failed: %s\n",
290 if (!dbus_g_proxy_call(g_proxy, "load_url",
295 printf("Opening window failed: %s\n",
300 g_object_unref(g_proxy);
302 /* Workaround: the browser process we started is going to want
303 to hang around forever, hogging the com.nokia.osso_browser
304 D-Bus interface while at it. To fix this, we notice that
305 when the last browser window closes, the browser UI restarts
306 its attached browserd process, which causes an observable
307 change in the ownership of the Mozilla.MicroB D-Bus name.
308 Watch for this change and kill off the browser UI process
311 This has the problem of not being able to detect whether
312 the bookmark window is open and/or in use, but it's the best
313 that I can think of. Better suggestions would be greatly
316 dbus_bus_add_match(raw_connection,
317 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
319 if (dbus_error_is_set(&dbus_error)) {
321 "Failed to set up watch for browserd restart: %s\n",
323 dbus_error_free(&dbus_error);
326 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
327 to com.nokia.microb-engine; look for this too */
328 dbus_bus_add_match(raw_connection,
329 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
331 if (dbus_error_is_set(&dbus_error)) {
333 "Failed to set up watch for browserd restart: %s\n",
335 dbus_error_free(&dbus_error);
338 filter_func = check_microb_finished;
339 if (!dbus_connection_add_filter(raw_connection,
340 filter_func, NULL, NULL)) {
341 fprintf(stderr, "Failed to set up watch filter!\n");
344 while (!kill_microb &&
345 dbus_connection_read_write_dispatch(raw_connection,
347 dbus_connection_remove_filter(raw_connection,
349 dbus_bus_remove_match(raw_connection,
350 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
352 if (dbus_error_is_set(&dbus_error))
353 /* Don't really care -- about to disconnect from the
355 dbus_error_free(&dbus_error);
356 dbus_bus_remove_match(raw_connection,
357 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
359 if (dbus_error_is_set(&dbus_error))
360 dbus_error_free(&dbus_error);
361 dbus_connection_close(raw_connection);
362 dbus_connection_unref(raw_connection);
364 /* Kill off browser UI
365 XXX: Hope we don't cause data loss here! */
366 printf("Killing MicroB\n");
372 /* exec maemo-invoker directly instead of relying on the
373 /usr/bin/browser symlink, since /usr/bin/browser may have
374 been replaced with a shell script calling us via D-Bus */
375 /* Launch the browser in the background -- our parent will
376 wait for it to claim the D-Bus name and then display the
377 window using D-Bus */
378 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
380 #else /* !FREMANTLE */
383 waitpid(pid, &status, 0);
388 /* exec maemo-invoker directly instead of relying on the
389 /usr/bin/browser symlink, since /usr/bin/browser may have
390 been replaced with a shell script calling us via D-Bus */
391 if (!strcmp(uri, "new_window")) {
392 execl("/usr/bin/maemo-invoker",
393 "browser", (char *)NULL);
395 execl("/usr/bin/maemo-invoker",
396 "browser", "--url", uri, (char *)NULL);
399 #endif /* FREMANTLE */
401 /* Kill off browserd if we started it */
403 system("kill `pidof browserd`");
405 if (!ctx || !ctx->continuous_mode)
408 dbus_request_osso_browser_name(ctx);
411 static void launch_other_browser(struct swb_context *ctx, char *uri) {
413 char *quoted_uri, *quote;
415 size_t cmdlen, urilen;
416 size_t quoted_uri_size;
419 if (!uri || !strcmp(uri, "new_window"))
422 printf("launch_other_browser with uri '%s'\n", uri);
424 if ((urilen = strlen(uri)) > 0) {
425 /* Quote the URI to prevent the shell from interpreting it */
426 /* urilen+3 = length of URI + 2x \' + \0 */
427 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
429 snprintf(quoted_uri, urilen+3, "'%s'", uri);
431 /* If there are any 's in the original URI, URL-escape them
432 (replace them with %27) */
433 quoted_uri_size = urilen + 3;
434 quote = quoted_uri + 1;
435 while ((quote = strchr(quote, '\'')) &&
436 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
437 /* Check to make sure we don't shrink the memory area
438 as a result of integer overflow */
439 if (quoted_uri_size+2 <= quoted_uri_size)
442 /* Grow the memory area;
443 2 = strlen("%27")-strlen("'") */
444 if (!(quoted_uri = realloc(quoted_uri,
447 quoted_uri_size = quoted_uri_size + 2;
449 /* Recalculate the location of the ' character --
450 realloc() may have moved the string in memory */
451 quote = quoted_uri + offset;
453 /* Move the string after the ', including the \0,
455 memmove(quote+3, quote+1, strlen(quote));
456 memcpy(quote, "%27", 3);
459 urilen = strlen(quoted_uri);
463 cmdlen = strlen(ctx->other_browser_cmd);
465 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
466 replace "%s"), but is needed in the case other_browser_cmd has no %s
468 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
470 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
471 printf("command: '%s'\n", command);
473 if (ctx->continuous_mode) {
475 /* Parent process or error in fork() */
485 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
488 /* Use launch_other_browser as the default browser launcher, with the string
489 passed in as the other_browser_cmd
490 Resulting other_browser_cmd is always safe to free(), even if a pointer
491 to a string constant is passed in */
492 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
493 size_t len = strlen(cmd);
495 free(ctx->other_browser_cmd);
496 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
497 if (!ctx->other_browser_cmd) {
498 printf("malloc failed!\n");
499 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
501 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
503 ctx->default_browser_launcher = launch_other_browser;
507 void update_default_browser(struct swb_context *ctx, char *default_browser) {
511 if (!default_browser) {
512 /* No default_browser configured -- use built-in default */
513 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
517 if (!strcmp(default_browser, "tear"))
518 ctx->default_browser_launcher = launch_tear;
519 else if (!strcmp(default_browser, "microb"))
520 ctx->default_browser_launcher = launch_microb;
521 else if (!strcmp(default_browser, "fennec"))
522 /* Cheat and reuse launch_other_browser, since we don't appear
523 to need to do anything special */
524 use_other_browser_cmd(ctx, "fennec %s");
525 else if (!strcmp(default_browser, "midori"))
526 use_other_browser_cmd(ctx, "midori %s");
527 else if (!strcmp(default_browser, "other")) {
528 if (ctx->other_browser_cmd)
529 ctx->default_browser_launcher = launch_other_browser;
531 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
532 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
535 printf("Unknown default_browser %s, using default", default_browser);
536 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
540 void launch_browser(struct swb_context *ctx, char *uri) {
541 if (ctx && ctx->default_browser_launcher)
542 ctx->default_browser_launcher(ctx, uri);