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>
33 #include <dbus/dbus-glib-lowlevel.h>
34 #include <dbus/dbus.h>
35 #include <sys/inotify.h>
39 #include "browser-switchboard.h"
41 #include "dbus-server-bindings.h"
43 #define LAUNCH_DEFAULT_BROWSER launch_microb
46 static int microb_started = 0;
47 static int kill_microb = 0;
49 /* Check to see whether MicroB is ready to handle D-Bus requests yet
50 See the comments in launch_microb to understand how this works. */
51 static DBusHandlerResult check_microb_started(DBusConnection *connection,
55 char *name, *old, *new;
57 printf("Checking to see if MicroB is ready\n");
58 dbus_error_init(&error);
59 if (!dbus_message_get_args(message, &error,
60 DBUS_TYPE_STRING, &name,
61 DBUS_TYPE_STRING, &old,
62 DBUS_TYPE_STRING, &new,
64 printf("%s\n", error.message);
65 dbus_error_free(&error);
66 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
68 /* If old is an empty string, then the name has been acquired, and
69 MicroB should be ready to handle our request */
70 if (strlen(old) == 0) {
71 printf("MicroB ready\n");
75 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
78 /* Check to see whether the last MicroB window has closed
79 See the comments in launch_microb to understand how this works. */
80 static DBusHandlerResult check_microb_finished(DBusConnection *connection,
84 char *name, *old, *new;
86 printf("Checking to see if we should kill MicroB\n");
87 /* Check to make sure that the Mozilla.MicroB name is being released,
88 not acquired -- if it's being acquired, we might be seeing an event
89 at MicroB startup, in which case killing the browser isn't
91 dbus_error_init(&error);
92 if (!dbus_message_get_args(message, &error,
93 DBUS_TYPE_STRING, &name,
94 DBUS_TYPE_STRING, &old,
95 DBUS_TYPE_STRING, &new,
97 printf("%s\n", error.message);
98 dbus_error_free(&error);
99 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
101 /* If old isn't an empty string, the name is being released, and
102 we should now kill MicroB */
106 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
110 static void launch_tear(struct swb_context *ctx, char *uri) {
112 static DBusGProxy *tear_proxy = NULL;
113 GError *error = NULL;
119 printf("launch_tear with uri '%s'\n", uri);
121 /* We should be able to just call the D-Bus service to open Tear ...
122 but if Tear's not open, that cuases D-Bus to start Tear and then
123 pass it the OpenAddress call, which results in two browser windows.
124 Properly fixing this probably requires Tear to provide a D-Bus
125 method that opens an address in an existing window, but for now work
126 around by just invoking Tear with exec() if it's not running. */
127 status = system("pidof tear > /dev/null");
128 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
130 tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
131 "com.nokia.tear", "/com/nokia/tear",
133 dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
134 G_TYPE_STRING, uri, G_TYPE_INVALID);
135 if (!ctx->continuous_mode)
138 if (ctx->continuous_mode) {
139 if ((pid = fork()) != 0) {
140 /* Parent process or error in fork() */
141 printf("child: %d\n", (int)pid);
147 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
151 void launch_microb(struct swb_context *ctx, char *uri) {
152 int kill_browserd = 0;
156 DBusConnection *raw_connection;
157 DBusError dbus_error;
158 DBusHandleMessageFunction filter_func;
160 GError *gerror = NULL;
166 printf("launch_microb with uri '%s'\n", uri);
168 /* Launch browserd if it's not running */
169 status = system("pidof /usr/sbin/browserd > /dev/null");
170 if (WIFEXITED(status) && WEXITSTATUS(status)) {
173 system("/usr/sbin/browserd -d -b");
175 system("/usr/sbin/browserd -d");
179 /* Release the osso_browser D-Bus name so that MicroB can take it */
180 dbus_release_osso_browser_name(ctx);
182 if ((pid = fork()) == -1) {
189 /* Wait for our child to start the browser UI process and
190 for it to acquire the com.nokia.osso_browser D-Bus name,
191 then make the appropriate method call to open the browser
194 Ideas for how to do this monitoring derived from the
195 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
198 dbus_error_init(&dbus_error);
200 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
202 if (!raw_connection) {
204 "Failed to open connection to session bus: %s\n",
206 dbus_error_free(&dbus_error);
210 dbus_bus_add_match(raw_connection,
211 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
213 if (dbus_error_is_set(&dbus_error)) {
215 "Failed to set up watch for browser UI start: %s\n",
217 dbus_error_free(&dbus_error);
220 filter_func = check_microb_started;
221 if (!dbus_connection_add_filter(raw_connection,
222 filter_func, NULL, NULL)) {
223 fprintf(stderr, "Failed to set up watch filter!\n");
226 printf("Waiting for MicroB to start\n");
227 while (!microb_started &&
228 dbus_connection_read_write_dispatch(raw_connection,
230 dbus_connection_remove_filter(raw_connection,
232 dbus_bus_remove_match(raw_connection,
233 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
235 if (dbus_error_is_set(&dbus_error)) {
237 "Failed to remove watch for browser UI start: %s\n",
239 dbus_error_free(&dbus_error);
243 /* Browser UI's started, send it the request for a new window
245 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
246 "com.nokia.osso_browser",
247 "/com/nokia/osso_browser/request",
248 "com.nokia.osso_browser");
250 printf("Couldn't get a com.nokia.osso_browser proxy\n");
253 if (!strcmp(uri, "new_window")) {
254 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
255 corner case where, if the user just closes the bookmark window
256 without opening any browser windows, we don't kill off MicroB or
257 resume handling com.nokia.osso_browser */
258 if (!dbus_g_proxy_call(g_proxy, "top_application",
259 &gerror, G_TYPE_INVALID,
261 printf("Opening window failed: %s\n",
266 if (!dbus_g_proxy_call(g_proxy, "load_url",
268 G_TYPE_STRING, "about:blank",
271 printf("Opening window failed: %s\n",
276 if (!dbus_g_proxy_call(g_proxy, "load_url",
281 printf("Opening window failed: %s\n",
287 /* Workaround: the browser process we started is going to want
288 to hang around forever, hogging the com.nokia.osso_browser
289 D-Bus interface while at it. To fix this, we notice that
290 when the last browser window closes, the browser UI restarts
291 its attached browserd process, which causes an observable
292 change in the ownership of the Mozilla.MicroB D-Bus name.
293 Watch for this change and kill off the browser UI process
296 This has the problem of not being able to detect whether
297 the bookmark window is open and/or in use, but it's the best
298 that I can think of. Better suggestions would be greatly
301 dbus_bus_add_match(raw_connection,
302 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
304 if (dbus_error_is_set(&dbus_error)) {
306 "Failed to set up watch for browserd restart: %s\n",
308 dbus_error_free(&dbus_error);
311 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
312 to com.nokia.microb-engine; look for this too */
313 dbus_bus_add_match(raw_connection,
314 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
316 if (dbus_error_is_set(&dbus_error)) {
318 "Failed to set up watch for browserd restart: %s\n",
320 dbus_error_free(&dbus_error);
323 filter_func = check_microb_finished;
324 if (!dbus_connection_add_filter(raw_connection,
325 filter_func, NULL, NULL)) {
326 fprintf(stderr, "Failed to set up watch filter!\n");
329 while (!kill_microb &&
330 dbus_connection_read_write_dispatch(raw_connection,
332 dbus_connection_remove_filter(raw_connection,
334 dbus_bus_remove_match(raw_connection,
335 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
337 if (dbus_error_is_set(&dbus_error))
338 /* Don't really care -- about to disconnect from the
340 dbus_error_free(&dbus_error);
341 dbus_bus_remove_match(raw_connection,
342 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
344 if (dbus_error_is_set(&dbus_error))
345 dbus_error_free(&dbus_error);
346 dbus_connection_close(raw_connection);
347 dbus_connection_unref(raw_connection);
349 /* Tell browser UI to exit nicely */
350 printf("Closing MicroB\n");
351 if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror,
352 G_TYPE_INVALID, G_TYPE_INVALID)) {
353 /* We don't expect a reply; any other error indicates
355 if (gerror->domain != DBUS_GERROR ||
356 gerror->code != DBUS_GERROR_NO_REPLY) {
357 printf("exit_browser failed: %s\n",
362 g_object_unref(g_proxy);
365 /* exec maemo-invoker directly instead of relying on the
366 /usr/bin/browser symlink, since /usr/bin/browser may have
367 been replaced with a shell script calling us via D-Bus */
368 /* Launch the browser in the background -- our parent will
369 wait for it to claim the D-Bus name and then display the
370 window using D-Bus */
371 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
373 #else /* !FREMANTLE */
376 waitpid(pid, &status, 0);
379 /* exec maemo-invoker directly instead of relying on the
380 /usr/bin/browser symlink, since /usr/bin/browser may have
381 been replaced with a shell script calling us via D-Bus */
382 if (!strcmp(uri, "new_window")) {
383 execl("/usr/bin/maemo-invoker",
384 "browser", (char *)NULL);
386 execl("/usr/bin/maemo-invoker",
387 "browser", "--url", uri, (char *)NULL);
390 #endif /* FREMANTLE */
392 /* Kill off browserd if we started it */
394 system("kill `pidof /usr/sbin/browserd`");
396 if (!ctx || !ctx->continuous_mode)
399 dbus_request_osso_browser_name(ctx);
402 static void launch_other_browser(struct swb_context *ctx, char *uri) {
404 char *quoted_uri, *quote;
406 size_t cmdlen, urilen;
407 size_t quoted_uri_size;
410 if (!uri || !strcmp(uri, "new_window"))
413 printf("launch_other_browser with uri '%s'\n", uri);
415 if ((urilen = strlen(uri)) > 0) {
416 /* Quote the URI to prevent the shell from interpreting it */
417 /* urilen+3 = length of URI + 2x \' + \0 */
418 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
420 snprintf(quoted_uri, urilen+3, "'%s'", uri);
422 /* If there are any 's in the original URI, URL-escape them
423 (replace them with %27) */
424 quoted_uri_size = urilen + 3;
425 quote = quoted_uri + 1;
426 while ((quote = strchr(quote, '\'')) &&
427 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
428 /* Check to make sure we don't shrink the memory area
429 as a result of integer overflow */
430 if (quoted_uri_size+2 <= quoted_uri_size)
433 /* Grow the memory area;
434 2 = strlen("%27")-strlen("'") */
435 if (!(quoted_uri = realloc(quoted_uri,
438 quoted_uri_size = quoted_uri_size + 2;
440 /* Recalculate the location of the ' character --
441 realloc() may have moved the string in memory */
442 quote = quoted_uri + offset;
444 /* Move the string after the ', including the \0,
446 memmove(quote+3, quote+1, strlen(quote));
447 memcpy(quote, "%27", 3);
450 urilen = strlen(quoted_uri);
454 cmdlen = strlen(ctx->other_browser_cmd);
456 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
457 replace "%s"), but is needed in the case other_browser_cmd has no %s
459 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
461 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
462 printf("command: '%s'\n", command);
464 if (ctx->continuous_mode) {
466 /* Parent process or error in fork() */
475 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
478 /* Use launch_other_browser as the default browser launcher, with the string
479 passed in as the other_browser_cmd
480 Resulting other_browser_cmd is always safe to free(), even if a pointer
481 to a string constant is passed in */
482 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
483 size_t len = strlen(cmd);
485 free(ctx->other_browser_cmd);
486 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
487 if (!ctx->other_browser_cmd) {
488 printf("malloc failed!\n");
489 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
491 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
493 ctx->default_browser_launcher = launch_other_browser;
497 void update_default_browser(struct swb_context *ctx, char *default_browser) {
501 if (!default_browser) {
502 /* No default_browser configured -- use built-in default */
503 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
507 if (!strcmp(default_browser, "tear"))
508 ctx->default_browser_launcher = launch_tear;
509 else if (!strcmp(default_browser, "microb"))
510 ctx->default_browser_launcher = launch_microb;
511 else if (!strcmp(default_browser, "fennec"))
512 /* Cheat and reuse launch_other_browser, since we don't appear
513 to need to do anything special */
514 use_other_browser_cmd(ctx, "fennec %s");
515 else if (!strcmp(default_browser, "midori"))
516 use_other_browser_cmd(ctx, "midori %s");
517 else if (!strcmp(default_browser, "other")) {
518 if (ctx->other_browser_cmd)
519 ctx->default_browser_launcher = launch_other_browser;
521 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
522 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
525 printf("Unknown default_browser %s, using default", default_browser);
526 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
530 void launch_browser(struct swb_context *ctx, char *uri) {
531 if (ctx && ctx->default_browser_launcher)
532 ctx->default_browser_launcher(ctx, uri);