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 "browser-switchboard.h"
39 #include "dbus-server-bindings.h"
41 #define LAUNCH_DEFAULT_BROWSER launch_microb
44 static int microb_started = 0;
45 static int kill_microb = 0;
47 /* Check to see whether MicroB is ready to handle D-Bus requests yet
48 See the comments in launch_microb to understand how this works. */
49 static DBusHandlerResult check_microb_started(DBusConnection *connection,
53 char *name, *old, *new;
55 printf("Checking to see if MicroB is ready\n");
56 dbus_error_init(&error);
57 if (!dbus_message_get_args(message, &error,
58 DBUS_TYPE_STRING, &name,
59 DBUS_TYPE_STRING, &old,
60 DBUS_TYPE_STRING, &new,
62 printf("%s\n", error.message);
63 dbus_error_free(&error);
64 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
66 /* If old is an empty string, then the name has been acquired, and
67 MicroB should be ready to handle our request */
68 if (strlen(old) == 0) {
69 printf("MicroB ready\n");
73 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
76 /* Check to see whether the last MicroB window has closed
77 See the comments in launch_microb to understand how this works. */
78 static DBusHandlerResult check_microb_finished(DBusConnection *connection,
82 char *name, *old, *new;
84 printf("Checking to see if we should kill MicroB\n");
85 /* Check to make sure that the Mozilla.MicroB name is being released,
86 not acquired -- if it's being acquired, we might be seeing an event
87 at MicroB startup, in which case killing the browser isn't
89 dbus_error_init(&error);
90 if (!dbus_message_get_args(message, &error,
91 DBUS_TYPE_STRING, &name,
92 DBUS_TYPE_STRING, &old,
93 DBUS_TYPE_STRING, &new,
95 printf("%s\n", error.message);
96 dbus_error_free(&error);
97 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
99 /* If old isn't an empty string, the name is being released, and
100 we should now kill MicroB */
104 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
108 /* Close stdin/stdout/stderr and replace with /dev/null */
109 static int close_stdio(void) {
112 if ((fd = open("/dev/null", O_RDWR)) == -1)
115 if (dup2(fd, 0) == -1 || dup2(fd, 1) == -1 || dup2(fd, 2) == -1)
122 static void launch_tear(struct swb_context *ctx, char *uri) {
124 static DBusGProxy *tear_proxy = NULL;
125 GError *error = NULL;
131 printf("launch_tear with uri '%s'\n", uri);
133 /* We should be able to just call the D-Bus service to open Tear ...
134 but if Tear's not open, that cuases D-Bus to start Tear and then
135 pass it the OpenAddress call, which results in two browser windows.
136 Properly fixing this probably requires Tear to provide a D-Bus
137 method that opens an address in an existing window, but for now work
138 around by just invoking Tear with exec() if it's not running. */
139 status = system("pidof tear > /dev/null");
140 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
142 tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
143 "com.nokia.tear", "/com/nokia/tear",
145 dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
146 G_TYPE_STRING, uri, G_TYPE_INVALID);
147 if (!ctx->continuous_mode)
150 if (ctx->continuous_mode) {
151 if ((pid = fork()) != 0) {
152 /* Parent process or error in fork() */
153 printf("child: %d\n", (int)pid);
160 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
164 void launch_microb(struct swb_context *ctx, char *uri) {
165 int kill_browserd = 0;
169 DBusConnection *raw_connection;
170 DBusError dbus_error;
171 DBusHandleMessageFunction filter_func;
173 GError *gerror = NULL;
179 printf("launch_microb with uri '%s'\n", uri);
181 /* Launch browserd if it's not running */
182 status = system("pidof /usr/sbin/browserd > /dev/null");
183 if (WIFEXITED(status) && WEXITSTATUS(status)) {
186 system("/usr/sbin/browserd -d -b");
188 system("/usr/sbin/browserd -d");
192 /* Release the osso_browser D-Bus name so that MicroB can take it */
193 dbus_release_osso_browser_name(ctx);
195 if ((pid = fork()) == -1) {
202 /* Wait for our child to start the browser UI process and
203 for it to acquire the com.nokia.osso_browser D-Bus name,
204 then make the appropriate method call to open the browser
207 Ideas for how to do this monitoring derived from the
208 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
211 dbus_error_init(&dbus_error);
213 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
215 if (!raw_connection) {
217 "Failed to open connection to session bus: %s\n",
219 dbus_error_free(&dbus_error);
223 dbus_bus_add_match(raw_connection,
224 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
226 if (dbus_error_is_set(&dbus_error)) {
228 "Failed to set up watch for browser UI start: %s\n",
230 dbus_error_free(&dbus_error);
233 filter_func = check_microb_started;
234 if (!dbus_connection_add_filter(raw_connection,
235 filter_func, NULL, NULL)) {
236 fprintf(stderr, "Failed to set up watch filter!\n");
239 printf("Waiting for MicroB to start\n");
240 while (!microb_started &&
241 dbus_connection_read_write_dispatch(raw_connection,
243 dbus_connection_remove_filter(raw_connection,
245 dbus_bus_remove_match(raw_connection,
246 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
248 if (dbus_error_is_set(&dbus_error)) {
250 "Failed to remove watch for browser UI start: %s\n",
252 dbus_error_free(&dbus_error);
256 /* Browser UI's started, send it the request for a new window
258 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
259 "com.nokia.osso_browser",
260 "/com/nokia/osso_browser/request",
261 "com.nokia.osso_browser");
263 printf("Couldn't get a com.nokia.osso_browser proxy\n");
266 if (!strcmp(uri, "new_window")) {
267 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
268 corner case where, if the user just closes the bookmark window
269 without opening any browser windows, we don't kill off MicroB or
270 resume handling com.nokia.osso_browser */
271 if (!dbus_g_proxy_call(g_proxy, "top_application",
272 &gerror, G_TYPE_INVALID,
274 printf("Opening window failed: %s\n",
279 if (!dbus_g_proxy_call(g_proxy, "load_url",
281 G_TYPE_STRING, "about:blank",
284 printf("Opening window failed: %s\n",
289 if (!dbus_g_proxy_call(g_proxy, "load_url",
294 printf("Opening window failed: %s\n",
300 /* Workaround: the browser process we started is going to want
301 to hang around forever, hogging the com.nokia.osso_browser
302 D-Bus interface while at it. To fix this, we notice that
303 when the last browser window closes, the browser UI restarts
304 its attached browserd process, which causes an observable
305 change in the ownership of the Mozilla.MicroB D-Bus name.
306 Watch for this change and kill off the browser UI process
309 This has the problem of not being able to detect whether
310 the bookmark window is open and/or in use, but it's the best
311 that I can think of. Better suggestions would be greatly
314 dbus_bus_add_match(raw_connection,
315 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
317 if (dbus_error_is_set(&dbus_error)) {
319 "Failed to set up watch for browserd restart: %s\n",
321 dbus_error_free(&dbus_error);
324 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
325 to com.nokia.microb-engine; look for this too */
326 dbus_bus_add_match(raw_connection,
327 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
329 if (dbus_error_is_set(&dbus_error)) {
331 "Failed to set up watch for browserd restart: %s\n",
333 dbus_error_free(&dbus_error);
336 filter_func = check_microb_finished;
337 if (!dbus_connection_add_filter(raw_connection,
338 filter_func, NULL, NULL)) {
339 fprintf(stderr, "Failed to set up watch filter!\n");
342 while (!kill_microb &&
343 dbus_connection_read_write_dispatch(raw_connection,
345 dbus_connection_remove_filter(raw_connection,
347 dbus_bus_remove_match(raw_connection,
348 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
350 if (dbus_error_is_set(&dbus_error))
351 /* Don't really care -- about to disconnect from the
353 dbus_error_free(&dbus_error);
354 dbus_bus_remove_match(raw_connection,
355 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
357 if (dbus_error_is_set(&dbus_error))
358 dbus_error_free(&dbus_error);
359 dbus_connection_close(raw_connection);
360 dbus_connection_unref(raw_connection);
362 /* Tell browser UI to exit nicely */
363 printf("Closing MicroB\n");
364 if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror,
365 G_TYPE_INVALID, G_TYPE_INVALID)) {
366 /* We don't expect a reply; any other error indicates
368 if (gerror->domain != DBUS_GERROR ||
369 gerror->code != DBUS_GERROR_NO_REPLY) {
370 printf("exit_browser failed: %s\n",
375 g_object_unref(g_proxy);
380 /* exec maemo-invoker directly instead of relying on the
381 /usr/bin/browser symlink, since /usr/bin/browser may have
382 been replaced with a shell script calling us via D-Bus */
383 /* Launch the browser in the background -- our parent will
384 wait for it to claim the D-Bus name and then display the
385 window using D-Bus */
386 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
388 #else /* !FREMANTLE */
391 waitpid(pid, &status, 0);
396 /* exec maemo-invoker directly instead of relying on the
397 /usr/bin/browser symlink, since /usr/bin/browser may have
398 been replaced with a shell script calling us via D-Bus */
399 if (!strcmp(uri, "new_window")) {
400 execl("/usr/bin/maemo-invoker",
401 "browser", (char *)NULL);
403 execl("/usr/bin/maemo-invoker",
404 "browser", "--url", uri, (char *)NULL);
407 #endif /* FREMANTLE */
409 /* Kill off browserd if we started it */
411 system("kill `pidof /usr/sbin/browserd`");
413 if (!ctx || !ctx->continuous_mode)
416 dbus_request_osso_browser_name(ctx);
419 static void launch_other_browser(struct swb_context *ctx, char *uri) {
421 char *quoted_uri, *quote;
423 size_t cmdlen, urilen;
424 size_t quoted_uri_size;
427 if (!uri || !strcmp(uri, "new_window"))
430 printf("launch_other_browser with uri '%s'\n", uri);
432 if ((urilen = strlen(uri)) > 0) {
433 /* Quote the URI to prevent the shell from interpreting it */
434 /* urilen+3 = length of URI + 2x \' + \0 */
435 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
437 snprintf(quoted_uri, urilen+3, "'%s'", uri);
439 /* If there are any 's in the original URI, URL-escape them
440 (replace them with %27) */
441 quoted_uri_size = urilen + 3;
442 quote = quoted_uri + 1;
443 while ((quote = strchr(quote, '\'')) &&
444 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
445 /* Check to make sure we don't shrink the memory area
446 as a result of integer overflow */
447 if (quoted_uri_size+2 <= quoted_uri_size)
450 /* Grow the memory area;
451 2 = strlen("%27")-strlen("'") */
452 if (!(quoted_uri = realloc(quoted_uri,
455 quoted_uri_size = quoted_uri_size + 2;
457 /* Recalculate the location of the ' character --
458 realloc() may have moved the string in memory */
459 quote = quoted_uri + offset;
461 /* Move the string after the ', including the \0,
463 memmove(quote+3, quote+1, strlen(quote));
464 memcpy(quote, "%27", 3);
467 urilen = strlen(quoted_uri);
471 cmdlen = strlen(ctx->other_browser_cmd);
473 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
474 replace "%s"), but is needed in the case other_browser_cmd has no %s
476 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
478 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
479 printf("command: '%s'\n", command);
481 if (ctx->continuous_mode) {
483 /* Parent process or error in fork() */
493 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
496 /* Use launch_other_browser as the default browser launcher, with the string
497 passed in as the other_browser_cmd
498 Resulting other_browser_cmd is always safe to free(), even if a pointer
499 to a string constant is passed in */
500 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
501 size_t len = strlen(cmd);
503 free(ctx->other_browser_cmd);
504 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
505 if (!ctx->other_browser_cmd) {
506 printf("malloc failed!\n");
507 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
509 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
511 ctx->default_browser_launcher = launch_other_browser;
515 void update_default_browser(struct swb_context *ctx, char *default_browser) {
519 if (!default_browser) {
520 /* No default_browser configured -- use built-in default */
521 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
525 if (!strcmp(default_browser, "tear"))
526 ctx->default_browser_launcher = launch_tear;
527 else if (!strcmp(default_browser, "microb"))
528 ctx->default_browser_launcher = launch_microb;
529 else if (!strcmp(default_browser, "fennec"))
530 /* Cheat and reuse launch_other_browser, since we don't appear
531 to need to do anything special */
532 use_other_browser_cmd(ctx, "fennec %s");
533 else if (!strcmp(default_browser, "midori"))
534 use_other_browser_cmd(ctx, "midori %s");
535 else if (!strcmp(default_browser, "other")) {
536 if (ctx->other_browser_cmd)
537 ctx->default_browser_launcher = launch_other_browser;
539 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
540 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
543 printf("Unknown default_browser %s, using default", default_browser);
544 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
548 void launch_browser(struct swb_context *ctx, char *uri) {
549 if (ctx && ctx->default_browser_launcher)
550 ctx->default_browser_launcher(ctx, uri);