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>
29 #include <dbus/dbus-glib.h>
32 #include <dbus/dbus-glib-lowlevel.h>
33 #include <dbus/dbus.h>
36 #include "browser-switchboard.h"
38 #include "dbus-server-bindings.h"
40 #define LAUNCH_DEFAULT_BROWSER launch_microb
43 static int microb_started = 0;
44 static int kill_microb = 0;
46 /* Check to see whether MicroB is ready to handle D-Bus requests yet
47 See the comments in launch_microb to understand how this works. */
48 static DBusHandlerResult check_microb_started(DBusConnection *connection,
52 char *name, *old, *new;
54 printf("Checking to see if MicroB is ready\n");
55 dbus_error_init(&error);
56 if (!dbus_message_get_args(message, &error,
57 DBUS_TYPE_STRING, &name,
58 DBUS_TYPE_STRING, &old,
59 DBUS_TYPE_STRING, &new,
61 printf("%s\n", error.message);
62 dbus_error_free(&error);
63 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
65 /* If old is an empty string, then the name has been acquired, and
66 MicroB should be ready to handle our request */
67 if (strlen(old) == 0) {
68 printf("MicroB ready\n");
72 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
75 /* Check to see whether the last MicroB window has closed
76 See the comments in launch_microb to understand how this works. */
77 static DBusHandlerResult check_microb_finished(DBusConnection *connection,
81 char *name, *old, *new;
83 printf("Checking to see if we should kill MicroB\n");
84 /* Check to make sure that the Mozilla.MicroB name is being released,
85 not acquired -- if it's being acquired, we might be seeing an event
86 at MicroB startup, in which case killing the browser isn't
88 dbus_error_init(&error);
89 if (!dbus_message_get_args(message, &error,
90 DBUS_TYPE_STRING, &name,
91 DBUS_TYPE_STRING, &old,
92 DBUS_TYPE_STRING, &new,
94 printf("%s\n", error.message);
95 dbus_error_free(&error);
96 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
98 /* If old isn't an empty string, the name is being released, and
99 we should now kill MicroB */
103 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
107 static void launch_tear(struct swb_context *ctx, char *uri) {
109 static DBusGProxy *tear_proxy = NULL;
110 GError *error = NULL;
116 printf("launch_tear with uri '%s'\n", uri);
118 /* We should be able to just call the D-Bus service to open Tear ...
119 but if Tear's not open, that cuases D-Bus to start Tear and then
120 pass it the OpenAddress call, which results in two browser windows.
121 Properly fixing this probably requires Tear to provide a D-Bus
122 method that opens an address in an existing window, but for now work
123 around by just invoking Tear with exec() if it's not running. */
124 status = system("pidof tear > /dev/null");
125 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
127 tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
128 "com.nokia.tear", "/com/nokia/tear",
130 dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
131 G_TYPE_STRING, uri, G_TYPE_INVALID);
132 if (!ctx->continuous_mode)
135 if (ctx->continuous_mode) {
136 if ((pid = fork()) != 0) {
137 /* Parent process or error in fork() */
138 printf("child: %d\n", (int)pid);
144 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
148 void launch_microb(struct swb_context *ctx, char *uri) {
149 int kill_browserd = 0;
153 DBusConnection *raw_connection;
154 DBusError dbus_error;
155 DBusHandleMessageFunction filter_func;
157 GError *gerror = NULL;
163 printf("launch_microb with uri '%s'\n", uri);
165 /* Launch browserd if it's not running */
166 status = system("pidof /usr/sbin/browserd > /dev/null");
167 if (WIFEXITED(status) && WEXITSTATUS(status)) {
170 system("/usr/sbin/browserd -d -b");
172 system("/usr/sbin/browserd -d");
176 /* Release the osso_browser D-Bus name so that MicroB can take it */
177 dbus_release_osso_browser_name(ctx);
179 if ((pid = fork()) == -1) {
186 /* Wait for our child to start the browser UI process and
187 for it to acquire the com.nokia.osso_browser D-Bus name,
188 then make the appropriate method call to open the browser
191 Ideas for how to do this monitoring derived from the
192 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
195 dbus_error_init(&dbus_error);
197 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
199 if (!raw_connection) {
201 "Failed to open connection to session bus: %s\n",
203 dbus_error_free(&dbus_error);
207 dbus_bus_add_match(raw_connection,
208 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
210 if (dbus_error_is_set(&dbus_error)) {
212 "Failed to set up watch for browser UI start: %s\n",
214 dbus_error_free(&dbus_error);
217 filter_func = check_microb_started;
218 if (!dbus_connection_add_filter(raw_connection,
219 filter_func, NULL, NULL)) {
220 fprintf(stderr, "Failed to set up watch filter!\n");
223 printf("Waiting for MicroB to start\n");
224 while (!microb_started &&
225 dbus_connection_read_write_dispatch(raw_connection,
227 dbus_connection_remove_filter(raw_connection,
229 dbus_bus_remove_match(raw_connection,
230 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
232 if (dbus_error_is_set(&dbus_error)) {
234 "Failed to remove watch for browser UI start: %s\n",
236 dbus_error_free(&dbus_error);
240 /* Browser UI's started, send it the request for a new window
242 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
243 "com.nokia.osso_browser",
244 "/com/nokia/osso_browser/request",
245 "com.nokia.osso_browser");
247 printf("Couldn't get a com.nokia.osso_browser proxy\n");
250 if (!strcmp(uri, "new_window")) {
251 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
252 corner case where, if the user just closes the bookmark window
253 without opening any browser windows, we don't kill off MicroB or
254 resume handling com.nokia.osso_browser */
255 if (!dbus_g_proxy_call(g_proxy, "top_application",
256 &gerror, G_TYPE_INVALID,
258 printf("Opening window failed: %s\n",
263 if (!dbus_g_proxy_call(g_proxy, "load_url",
265 G_TYPE_STRING, "about:blank",
268 printf("Opening window failed: %s\n",
273 if (!dbus_g_proxy_call(g_proxy, "load_url",
278 printf("Opening window failed: %s\n",
284 /* Workaround: the browser process we started is going to want
285 to hang around forever, hogging the com.nokia.osso_browser
286 D-Bus interface while at it. To fix this, we notice that
287 when the last browser window closes, the browser UI restarts
288 its attached browserd process, which causes an observable
289 change in the ownership of the Mozilla.MicroB D-Bus name.
290 Watch for this change and kill off the browser UI process
293 This has the problem of not being able to detect whether
294 the bookmark window is open and/or in use, but it's the best
295 that I can think of. Better suggestions would be greatly
298 dbus_bus_add_match(raw_connection,
299 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
301 if (dbus_error_is_set(&dbus_error)) {
303 "Failed to set up watch for browserd restart: %s\n",
305 dbus_error_free(&dbus_error);
308 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
309 to com.nokia.microb-engine; look for this too */
310 dbus_bus_add_match(raw_connection,
311 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
313 if (dbus_error_is_set(&dbus_error)) {
315 "Failed to set up watch for browserd restart: %s\n",
317 dbus_error_free(&dbus_error);
320 filter_func = check_microb_finished;
321 if (!dbus_connection_add_filter(raw_connection,
322 filter_func, NULL, NULL)) {
323 fprintf(stderr, "Failed to set up watch filter!\n");
326 while (!kill_microb &&
327 dbus_connection_read_write_dispatch(raw_connection,
329 dbus_connection_remove_filter(raw_connection,
331 dbus_bus_remove_match(raw_connection,
332 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
334 if (dbus_error_is_set(&dbus_error))
335 /* Don't really care -- about to disconnect from the
337 dbus_error_free(&dbus_error);
338 dbus_bus_remove_match(raw_connection,
339 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
341 if (dbus_error_is_set(&dbus_error))
342 dbus_error_free(&dbus_error);
343 dbus_connection_close(raw_connection);
344 dbus_connection_unref(raw_connection);
346 /* Tell browser UI to exit nicely */
347 printf("Closing MicroB\n");
348 if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror,
349 G_TYPE_INVALID, G_TYPE_INVALID)) {
350 /* We don't expect a reply; any other error indicates
352 if (gerror->domain != DBUS_GERROR ||
353 gerror->code != DBUS_GERROR_NO_REPLY) {
354 printf("exit_browser failed: %s\n",
359 g_object_unref(g_proxy);
362 /* exec maemo-invoker directly instead of relying on the
363 /usr/bin/browser symlink, since /usr/bin/browser may have
364 been replaced with a shell script calling us via D-Bus */
365 /* Launch the browser in the background -- our parent will
366 wait for it to claim the D-Bus name and then display the
367 window using D-Bus */
368 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
370 #else /* !FREMANTLE */
373 waitpid(pid, &status, 0);
376 /* exec maemo-invoker directly instead of relying on the
377 /usr/bin/browser symlink, since /usr/bin/browser may have
378 been replaced with a shell script calling us via D-Bus */
379 if (!strcmp(uri, "new_window")) {
380 execl("/usr/bin/maemo-invoker",
381 "browser", (char *)NULL);
383 execl("/usr/bin/maemo-invoker",
384 "browser", "--url", uri, (char *)NULL);
387 #endif /* FREMANTLE */
389 /* Kill off browserd if we started it */
391 system("kill `pidof /usr/sbin/browserd`");
393 if (!ctx || !ctx->continuous_mode)
396 dbus_request_osso_browser_name(ctx);
399 static void launch_other_browser(struct swb_context *ctx, char *uri) {
401 char *quoted_uri, *quote;
403 size_t cmdlen, urilen;
404 size_t quoted_uri_size;
407 if (!uri || !strcmp(uri, "new_window"))
410 printf("launch_other_browser with uri '%s'\n", uri);
412 if ((urilen = strlen(uri)) > 0) {
413 /* Quote the URI to prevent the shell from interpreting it */
414 /* urilen+3 = length of URI + 2x \' + \0 */
415 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
417 snprintf(quoted_uri, urilen+3, "'%s'", uri);
419 /* If there are any 's in the original URI, URL-escape them
420 (replace them with %27) */
421 quoted_uri_size = urilen + 3;
422 quote = quoted_uri + 1;
423 while ((quote = strchr(quote, '\'')) &&
424 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
425 /* Check to make sure we don't shrink the memory area
426 as a result of integer overflow */
427 if (quoted_uri_size+2 <= quoted_uri_size)
430 /* Grow the memory area;
431 2 = strlen("%27")-strlen("'") */
432 if (!(quoted_uri = realloc(quoted_uri,
435 quoted_uri_size = quoted_uri_size + 2;
437 /* Recalculate the location of the ' character --
438 realloc() may have moved the string in memory */
439 quote = quoted_uri + offset;
441 /* Move the string after the ', including the \0,
443 memmove(quote+3, quote+1, strlen(quote));
444 memcpy(quote, "%27", 3);
447 urilen = strlen(quoted_uri);
451 cmdlen = strlen(ctx->other_browser_cmd);
453 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
454 replace "%s"), but is needed in the case other_browser_cmd has no %s
456 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
458 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
459 printf("command: '%s'\n", command);
461 if (ctx->continuous_mode) {
463 /* Parent process or error in fork() */
472 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
475 /* Use launch_other_browser as the default browser launcher, with the string
476 passed in as the other_browser_cmd
477 Resulting other_browser_cmd is always safe to free(), even if a pointer
478 to a string constant is passed in */
479 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
480 size_t len = strlen(cmd);
482 free(ctx->other_browser_cmd);
483 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
484 if (!ctx->other_browser_cmd) {
485 printf("malloc failed!\n");
486 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
488 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
490 ctx->default_browser_launcher = launch_other_browser;
494 void update_default_browser(struct swb_context *ctx, char *default_browser) {
498 if (!default_browser) {
499 /* No default_browser configured -- use built-in default */
500 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
504 if (!strcmp(default_browser, "tear"))
505 ctx->default_browser_launcher = launch_tear;
506 else if (!strcmp(default_browser, "microb"))
507 ctx->default_browser_launcher = launch_microb;
508 else if (!strcmp(default_browser, "fennec"))
509 /* Cheat and reuse launch_other_browser, since we don't appear
510 to need to do anything special */
511 use_other_browser_cmd(ctx, "fennec %s");
512 else if (!strcmp(default_browser, "midori"))
513 use_other_browser_cmd(ctx, "midori %s");
514 else if (!strcmp(default_browser, "other")) {
515 if (ctx->other_browser_cmd)
516 ctx->default_browser_launcher = launch_other_browser;
518 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
519 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
522 printf("Unknown default_browser %s, using default", default_browser);
523 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
527 void launch_browser(struct swb_context *ctx, char *uri) {
528 if (ctx && ctx->default_browser_launcher)
529 ctx->default_browser_launcher(ctx, uri);