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>
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 if (!(tear_proxy = dbus_g_proxy_new_for_name(
146 "com.nokia.Tear"))) {
147 printf("Failed to create proxy for com.nokia.Tear D-Bus interface\n");
152 if (!dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
153 G_TYPE_STRING, uri, G_TYPE_INVALID,
155 printf("Opening window failed: %s\n", error->message);
158 if (!ctx->continuous_mode)
161 if (ctx->continuous_mode) {
162 if ((pid = fork()) != 0) {
163 /* Parent process or error in fork() */
164 printf("child: %d\n", (int)pid);
171 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
175 void launch_microb(struct swb_context *ctx, char *uri) {
176 int kill_browserd = 0;
180 DBusConnection *raw_connection;
181 DBusError dbus_error;
182 DBusHandleMessageFunction filter_func;
184 GError *gerror = NULL;
190 printf("launch_microb with uri '%s'\n", uri);
192 /* Launch browserd if it's not running */
193 status = system("pidof /usr/sbin/browserd > /dev/null");
194 if (WIFEXITED(status) && WEXITSTATUS(status)) {
197 system("/usr/sbin/browserd -d -b > /dev/null 2>&1");
199 system("/usr/sbin/browserd -d > /dev/null 2>&1");
203 /* Release the osso_browser D-Bus name so that MicroB can take it */
204 dbus_release_osso_browser_name(ctx);
206 if ((pid = fork()) == -1) {
213 /* Wait for our child to start the browser UI process and
214 for it to acquire the com.nokia.osso_browser D-Bus name,
215 then make the appropriate method call to open the browser
218 Ideas for how to do this monitoring derived from the
219 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
222 dbus_error_init(&dbus_error);
224 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
226 if (!raw_connection) {
228 "Failed to open connection to session bus: %s\n",
230 dbus_error_free(&dbus_error);
234 dbus_bus_add_match(raw_connection,
235 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
237 if (dbus_error_is_set(&dbus_error)) {
239 "Failed to set up watch for browser UI start: %s\n",
241 dbus_error_free(&dbus_error);
244 filter_func = check_microb_started;
245 if (!dbus_connection_add_filter(raw_connection,
246 filter_func, NULL, NULL)) {
247 fprintf(stderr, "Failed to set up watch filter!\n");
250 printf("Waiting for MicroB to start\n");
251 while (!microb_started &&
252 dbus_connection_read_write_dispatch(raw_connection,
254 dbus_connection_remove_filter(raw_connection,
256 dbus_bus_remove_match(raw_connection,
257 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
259 if (dbus_error_is_set(&dbus_error)) {
261 "Failed to remove watch for browser UI start: %s\n",
263 dbus_error_free(&dbus_error);
267 /* Browser UI's started, send it the request for a new window
269 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
270 "com.nokia.osso_browser",
271 "/com/nokia/osso_browser/request",
272 "com.nokia.osso_browser");
274 printf("Couldn't get a com.nokia.osso_browser proxy\n");
277 if (!strcmp(uri, "new_window")) {
278 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
279 corner case where, if the user just closes the bookmark window
280 without opening any browser windows, we don't kill off MicroB or
281 resume handling com.nokia.osso_browser */
282 if (!dbus_g_proxy_call(g_proxy, "top_application",
283 &gerror, G_TYPE_INVALID,
285 printf("Opening window failed: %s\n",
290 if (!dbus_g_proxy_call(g_proxy, "load_url",
292 G_TYPE_STRING, "about:blank",
295 printf("Opening window failed: %s\n",
300 if (!dbus_g_proxy_call(g_proxy, "load_url",
305 printf("Opening window failed: %s\n",
311 /* Workaround: the browser process we started is going to want
312 to hang around forever, hogging the com.nokia.osso_browser
313 D-Bus interface while at it. To fix this, we notice that
314 when the last browser window closes, the browser UI restarts
315 its attached browserd process, which causes an observable
316 change in the ownership of the Mozilla.MicroB D-Bus name.
317 Watch for this change and kill off the browser UI process
320 This has the problem of not being able to detect whether
321 the bookmark window is open and/or in use, but it's the best
322 that I can think of. Better suggestions would be greatly
325 dbus_bus_add_match(raw_connection,
326 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
328 if (dbus_error_is_set(&dbus_error)) {
330 "Failed to set up watch for browserd restart: %s\n",
332 dbus_error_free(&dbus_error);
335 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
336 to com.nokia.microb-engine; look for this too */
337 dbus_bus_add_match(raw_connection,
338 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
340 if (dbus_error_is_set(&dbus_error)) {
342 "Failed to set up watch for browserd restart: %s\n",
344 dbus_error_free(&dbus_error);
347 filter_func = check_microb_finished;
348 if (!dbus_connection_add_filter(raw_connection,
349 filter_func, NULL, NULL)) {
350 fprintf(stderr, "Failed to set up watch filter!\n");
353 while (!kill_microb &&
354 dbus_connection_read_write_dispatch(raw_connection,
356 dbus_connection_remove_filter(raw_connection,
358 dbus_bus_remove_match(raw_connection,
359 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
361 if (dbus_error_is_set(&dbus_error))
362 /* Don't really care -- about to disconnect from the
364 dbus_error_free(&dbus_error);
365 dbus_bus_remove_match(raw_connection,
366 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
368 if (dbus_error_is_set(&dbus_error))
369 dbus_error_free(&dbus_error);
370 dbus_connection_close(raw_connection);
371 dbus_connection_unref(raw_connection);
373 /* Tell browser UI to exit nicely */
374 printf("Closing MicroB\n");
375 if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror,
376 G_TYPE_INVALID, G_TYPE_INVALID)) {
377 /* We don't expect a reply; any other error indicates
379 if (gerror->domain != DBUS_GERROR ||
380 gerror->code != DBUS_GERROR_NO_REPLY) {
381 printf("exit_browser failed: %s\n",
386 g_object_unref(g_proxy);
391 /* exec maemo-invoker directly instead of relying on the
392 /usr/bin/browser symlink, since /usr/bin/browser may have
393 been replaced with a shell script calling us via D-Bus */
394 /* Launch the browser in the background -- our parent will
395 wait for it to claim the D-Bus name and then display the
396 window using D-Bus */
397 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
399 #else /* !FREMANTLE */
402 waitpid(pid, &status, 0);
407 /* exec maemo-invoker directly instead of relying on the
408 /usr/bin/browser symlink, since /usr/bin/browser may have
409 been replaced with a shell script calling us via D-Bus */
410 if (!strcmp(uri, "new_window")) {
411 execl("/usr/bin/maemo-invoker",
412 "browser", (char *)NULL);
414 execl("/usr/bin/maemo-invoker",
415 "browser", "--url", uri, (char *)NULL);
418 #endif /* FREMANTLE */
420 /* Kill off browserd if we started it */
422 system("kill `pidof /usr/sbin/browserd`");
424 if (!ctx || !ctx->continuous_mode)
427 dbus_request_osso_browser_name(ctx);
430 static void launch_other_browser(struct swb_context *ctx, char *uri) {
432 char *quoted_uri, *quote;
434 size_t cmdlen, urilen;
435 size_t quoted_uri_size;
438 if (!uri || !strcmp(uri, "new_window"))
441 printf("launch_other_browser with uri '%s'\n", uri);
443 if ((urilen = strlen(uri)) > 0) {
444 /* Quote the URI to prevent the shell from interpreting it */
445 /* urilen+3 = length of URI + 2x \' + \0 */
446 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
448 snprintf(quoted_uri, urilen+3, "'%s'", uri);
450 /* If there are any 's in the original URI, URL-escape them
451 (replace them with %27) */
452 quoted_uri_size = urilen + 3;
453 quote = quoted_uri + 1;
454 while ((quote = strchr(quote, '\'')) &&
455 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
456 /* Check to make sure we don't shrink the memory area
457 as a result of integer overflow */
458 if (quoted_uri_size+2 <= quoted_uri_size)
461 /* Grow the memory area;
462 2 = strlen("%27")-strlen("'") */
463 if (!(quoted_uri = realloc(quoted_uri,
466 quoted_uri_size = quoted_uri_size + 2;
468 /* Recalculate the location of the ' character --
469 realloc() may have moved the string in memory */
470 quote = quoted_uri + offset;
472 /* Move the string after the ', including the \0,
474 memmove(quote+3, quote+1, strlen(quote));
475 memcpy(quote, "%27", 3);
478 urilen = strlen(quoted_uri);
482 cmdlen = strlen(ctx->other_browser_cmd);
484 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
485 replace "%s"), but is needed in the case other_browser_cmd has no %s
487 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
489 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
490 printf("command: '%s'\n", command);
492 if (ctx->continuous_mode) {
494 /* Parent process or error in fork() */
504 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
507 /* Use launch_other_browser as the default browser launcher, with the string
508 passed in as the other_browser_cmd
509 Resulting other_browser_cmd is always safe to free(), even if a pointer
510 to a string constant is passed in */
511 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
512 size_t len = strlen(cmd);
514 free(ctx->other_browser_cmd);
515 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
516 if (!ctx->other_browser_cmd) {
517 printf("malloc failed!\n");
518 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
520 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
522 ctx->default_browser_launcher = launch_other_browser;
526 void update_default_browser(struct swb_context *ctx, char *default_browser) {
530 if (!default_browser) {
531 /* No default_browser configured -- use built-in default */
532 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
536 if (!strcmp(default_browser, "tear"))
537 ctx->default_browser_launcher = launch_tear;
538 else if (!strcmp(default_browser, "microb"))
539 ctx->default_browser_launcher = launch_microb;
540 else if (!strcmp(default_browser, "fennec"))
541 /* Cheat and reuse launch_other_browser, since we don't appear
542 to need to do anything special */
543 use_other_browser_cmd(ctx, "fennec %s");
544 else if (!strcmp(default_browser, "midori"))
545 use_other_browser_cmd(ctx, "midori %s");
546 else if (!strcmp(default_browser, "other")) {
547 if (ctx->other_browser_cmd)
548 ctx->default_browser_launcher = launch_other_browser;
550 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
551 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
554 printf("Unknown default_browser %s, using default", default_browser);
555 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
559 void launch_browser(struct swb_context *ctx, char *uri) {
560 if (ctx && ctx->default_browser_launcher)
561 ctx->default_browser_launcher(ctx, uri);