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.h>
35 #include "browser-switchboard.h"
37 #include "dbus-server-bindings.h"
39 #define LAUNCH_DEFAULT_BROWSER launch_microb
42 static int microb_started = 0;
43 static int kill_microb = 0;
45 /* Check to see whether MicroB is ready to handle D-Bus requests yet
46 See the comments in launch_microb to understand how this works. */
47 static DBusHandlerResult check_microb_started(DBusConnection *connection,
51 char *name, *old, *new;
53 printf("Checking to see if MicroB is ready\n");
54 dbus_error_init(&error);
55 if (!dbus_message_get_args(message, &error,
56 DBUS_TYPE_STRING, &name,
57 DBUS_TYPE_STRING, &old,
58 DBUS_TYPE_STRING, &new,
60 printf("%s\n", error.message);
61 dbus_error_free(&error);
62 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
64 /* If old is an empty string, then the name has been acquired, and
65 MicroB should be ready to handle our request */
66 if (strlen(old) == 0) {
67 printf("MicroB ready\n");
71 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
74 /* Check to see whether the last MicroB window has closed
75 See the comments in launch_microb to understand how this works. */
76 static DBusHandlerResult check_microb_finished(DBusConnection *connection,
80 char *name, *old, *new;
82 printf("Checking to see if we should kill MicroB\n");
83 /* Check to make sure that the Mozilla.MicroB name is being released,
84 not acquired -- if it's being acquired, we might be seeing an event
85 at MicroB startup, in which case killing the browser isn't
87 dbus_error_init(&error);
88 if (!dbus_message_get_args(message, &error,
89 DBUS_TYPE_STRING, &name,
90 DBUS_TYPE_STRING, &old,
91 DBUS_TYPE_STRING, &new,
93 printf("%s\n", error.message);
94 dbus_error_free(&error);
95 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
97 /* If old isn't an empty string, the name is being released, and
98 we should now kill MicroB */
102 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
106 static void launch_tear(struct swb_context *ctx, char *uri) {
108 static DBusGProxy *tear_proxy = NULL;
109 GError *error = NULL;
115 printf("launch_tear with uri '%s'\n", uri);
117 /* We should be able to just call the D-Bus service to open Tear ...
118 but if Tear's not open, that cuases D-Bus to start Tear and then
119 pass it the OpenAddress call, which results in two browser windows.
120 Properly fixing this probably requires Tear to provide a D-Bus
121 method that opens an address in an existing window, but for now work
122 around by just invoking Tear with exec() if it's not running. */
123 status = system("pidof tear > /dev/null");
124 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
126 tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
127 "com.nokia.tear", "/com/nokia/tear",
129 dbus_g_proxy_call(tear_proxy, "OpenAddress", &error,
130 G_TYPE_STRING, uri, G_TYPE_INVALID);
131 if (!ctx->continuous_mode)
134 if (ctx->continuous_mode) {
135 if ((pid = fork()) != 0) {
136 /* Parent process or error in fork() */
137 printf("child: %d\n", (int)pid);
143 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
147 void launch_microb(struct swb_context *ctx, char *uri) {
148 int kill_browserd = 0;
152 DBusConnection *raw_connection;
153 DBusError dbus_error;
154 DBusHandleMessageFunction filter_func;
156 GError *gerror = NULL;
162 printf("launch_microb with uri '%s'\n", uri);
164 /* Launch browserd if it's not running */
165 status = system("pidof /usr/sbin/browserd > /dev/null");
166 if (WIFEXITED(status) && WEXITSTATUS(status)) {
169 system("/usr/sbin/browserd -d -b");
171 system("/usr/sbin/browserd -d");
175 /* Release the osso_browser D-Bus name so that MicroB can take it */
176 dbus_release_osso_browser_name(ctx);
178 if ((pid = fork()) == -1) {
185 /* Wait for our child to start the browser UI process and
186 for it to acquire the com.nokia.osso_browser D-Bus name,
187 then make the appropriate method call to open the browser
190 Ideas for how to do this monitoring derived from the
191 dbus-monitor code (tools/dbus-monitor.c in the D-Bus
194 dbus_error_init(&dbus_error);
196 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
198 if (!raw_connection) {
200 "Failed to open connection to session bus: %s\n",
202 dbus_error_free(&dbus_error);
206 dbus_bus_add_match(raw_connection,
207 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
209 if (dbus_error_is_set(&dbus_error)) {
211 "Failed to set up watch for browser UI start: %s\n",
213 dbus_error_free(&dbus_error);
216 filter_func = check_microb_started;
217 if (!dbus_connection_add_filter(raw_connection,
218 filter_func, NULL, NULL)) {
219 fprintf(stderr, "Failed to set up watch filter!\n");
222 printf("Waiting for MicroB to start\n");
223 while (!microb_started &&
224 dbus_connection_read_write_dispatch(raw_connection,
226 dbus_connection_remove_filter(raw_connection,
228 dbus_bus_remove_match(raw_connection,
229 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
231 if (dbus_error_is_set(&dbus_error)) {
233 "Failed to remove watch for browser UI start: %s\n",
235 dbus_error_free(&dbus_error);
239 /* Browser UI's started, send it the request for a new window
241 g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
242 "com.nokia.osso_browser",
243 "/com/nokia/osso_browser/request",
244 "com.nokia.osso_browser");
246 printf("Couldn't get a com.nokia.osso_browser proxy\n");
249 if (!strcmp(uri, "new_window")) {
250 #if 0 /* Since we can't detect when the bookmark window closes, we'd have a
251 corner case where, if the user just closes the bookmark window
252 without opening any browser windows, we don't kill off MicroB or
253 resume handling com.nokia.osso_browser */
254 if (!dbus_g_proxy_call(g_proxy, "top_application",
255 &gerror, G_TYPE_INVALID,
257 printf("Opening window failed: %s\n",
262 if (!dbus_g_proxy_call(g_proxy, "load_url",
264 G_TYPE_STRING, "about:blank",
267 printf("Opening window failed: %s\n",
272 if (!dbus_g_proxy_call(g_proxy, "load_url",
277 printf("Opening window failed: %s\n",
283 /* Workaround: the browser process we started is going to want
284 to hang around forever, hogging the com.nokia.osso_browser
285 D-Bus interface while at it. To fix this, we notice that
286 when the last browser window closes, the browser UI restarts
287 its attached browserd process, which causes an observable
288 change in the ownership of the Mozilla.MicroB D-Bus name.
289 Watch for this change and kill off the browser UI process
292 This has the problem of not being able to detect whether
293 the bookmark window is open and/or in use, but it's the best
294 that I can think of. Better suggestions would be greatly
297 dbus_bus_add_match(raw_connection,
298 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
300 if (dbus_error_is_set(&dbus_error)) {
302 "Failed to set up watch for browserd restart: %s\n",
304 dbus_error_free(&dbus_error);
307 /* Maemo 5 PR1.1 seems to have changed the name browserd takes
308 to com.nokia.microb-engine; look for this too */
309 dbus_bus_add_match(raw_connection,
310 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'",
312 if (dbus_error_is_set(&dbus_error)) {
314 "Failed to set up watch for browserd restart: %s\n",
316 dbus_error_free(&dbus_error);
319 filter_func = check_microb_finished;
320 if (!dbus_connection_add_filter(raw_connection,
321 filter_func, NULL, NULL)) {
322 fprintf(stderr, "Failed to set up watch filter!\n");
325 while (!kill_microb &&
326 dbus_connection_read_write_dispatch(raw_connection,
328 dbus_connection_remove_filter(raw_connection,
330 dbus_bus_remove_match(raw_connection,
331 "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
333 if (dbus_error_is_set(&dbus_error))
334 /* Don't really care -- about to disconnect from the
336 dbus_error_free(&dbus_error);
337 dbus_bus_remove_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))
341 dbus_error_free(&dbus_error);
342 dbus_connection_close(raw_connection);
343 dbus_connection_unref(raw_connection);
345 /* Tell browser UI to exit nicely */
346 printf("Closing MicroB\n");
347 if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror,
348 G_TYPE_INVALID, G_TYPE_INVALID)) {
349 /* We don't expect a reply; any other error indicates
351 if (gerror->domain != DBUS_GERROR ||
352 gerror->code != DBUS_GERROR_NO_REPLY) {
353 printf("exit_browser failed: %s\n",
358 g_object_unref(g_proxy);
361 /* exec maemo-invoker directly instead of relying on the
362 /usr/bin/browser symlink, since /usr/bin/browser may have
363 been replaced with a shell script calling us via D-Bus */
364 /* Launch the browser in the background -- our parent will
365 wait for it to claim the D-Bus name and then display the
366 window using D-Bus */
367 execl("/usr/bin/maemo-invoker", "browser", (char *)NULL);
369 #else /* !FREMANTLE */
372 waitpid(pid, &status, 0);
375 /* exec maemo-invoker directly instead of relying on the
376 /usr/bin/browser symlink, since /usr/bin/browser may have
377 been replaced with a shell script calling us via D-Bus */
378 if (!strcmp(uri, "new_window")) {
379 execl("/usr/bin/maemo-invoker",
380 "browser", (char *)NULL);
382 execl("/usr/bin/maemo-invoker",
383 "browser", "--url", uri, (char *)NULL);
386 #endif /* FREMANTLE */
388 /* Kill off browserd if we started it */
390 system("kill `pidof /usr/sbin/browserd`");
392 if (!ctx || !ctx->continuous_mode)
395 dbus_request_osso_browser_name(ctx);
398 static void launch_other_browser(struct swb_context *ctx, char *uri) {
400 char *quoted_uri, *quote;
402 size_t cmdlen, urilen;
403 size_t quoted_uri_size;
406 if (!uri || !strcmp(uri, "new_window"))
409 printf("launch_other_browser with uri '%s'\n", uri);
411 if ((urilen = strlen(uri)) > 0) {
412 /* Quote the URI to prevent the shell from interpreting it */
413 /* urilen+3 = length of URI + 2x \' + \0 */
414 if (!(quoted_uri = calloc(urilen+3, sizeof(char))))
416 snprintf(quoted_uri, urilen+3, "'%s'", uri);
418 /* If there are any 's in the original URI, URL-escape them
419 (replace them with %27) */
420 quoted_uri_size = urilen + 3;
421 quote = quoted_uri + 1;
422 while ((quote = strchr(quote, '\'')) &&
423 (offset = quote-quoted_uri) < strlen(quoted_uri)-1) {
424 /* Check to make sure we don't shrink the memory area
425 as a result of integer overflow */
426 if (quoted_uri_size+2 <= quoted_uri_size)
429 /* Grow the memory area;
430 2 = strlen("%27")-strlen("'") */
431 if (!(quoted_uri = realloc(quoted_uri,
434 quoted_uri_size = quoted_uri_size + 2;
436 /* Recalculate the location of the ' character --
437 realloc() may have moved the string in memory */
438 quote = quoted_uri + offset;
440 /* Move the string after the ', including the \0,
442 memmove(quote+3, quote+1, strlen(quote));
443 memcpy(quote, "%27", 3);
446 urilen = strlen(quoted_uri);
450 cmdlen = strlen(ctx->other_browser_cmd);
452 /* cmdlen+urilen+1 is normally two bytes longer than we need (uri will
453 replace "%s"), but is needed in the case other_browser_cmd has no %s
455 if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
457 snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
458 printf("command: '%s'\n", command);
460 if (ctx->continuous_mode) {
462 /* Parent process or error in fork() */
471 execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
474 /* Use launch_other_browser as the default browser launcher, with the string
475 passed in as the other_browser_cmd
476 Resulting other_browser_cmd is always safe to free(), even if a pointer
477 to a string constant is passed in */
478 static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) {
479 size_t len = strlen(cmd);
481 free(ctx->other_browser_cmd);
482 ctx->other_browser_cmd = calloc(len+1, sizeof(char));
483 if (!ctx->other_browser_cmd) {
484 printf("malloc failed!\n");
485 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
487 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
489 ctx->default_browser_launcher = launch_other_browser;
493 void update_default_browser(struct swb_context *ctx, char *default_browser) {
497 if (!default_browser) {
498 /* No default_browser configured -- use built-in default */
499 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
503 if (!strcmp(default_browser, "tear"))
504 ctx->default_browser_launcher = launch_tear;
505 else if (!strcmp(default_browser, "microb"))
506 ctx->default_browser_launcher = launch_microb;
507 else if (!strcmp(default_browser, "fennec"))
508 /* Cheat and reuse launch_other_browser, since we don't appear
509 to need to do anything special */
510 use_other_browser_cmd(ctx, "fennec %s");
511 else if (!strcmp(default_browser, "midori"))
512 use_other_browser_cmd(ctx, "midori %s");
513 else if (!strcmp(default_browser, "other")) {
514 if (ctx->other_browser_cmd)
515 ctx->default_browser_launcher = launch_other_browser;
517 printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
518 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
521 printf("Unknown default_browser %s, using default", default_browser);
522 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
526 void launch_browser(struct swb_context *ctx, char *uri) {
527 if (ctx && ctx->default_browser_launcher)
528 ctx->default_browser_launcher(ctx, uri);