Revert "Drop the Depends on tablet-browser-ui for Diablo, for now"
[browser-switch] / launcher.c
1 /*
2  * launcher.c -- functions for launching web browsers for browser-switchboard
3  *
4  * Copyright (C) 2009 Steven Luo
5  * Derived from a Python implementation by Jason Simpson and Steven Luo
6  *
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.
11  *
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.
16  *
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,
20  * USA.
21  */
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/wait.h>
29 #include <dbus/dbus-glib.h>
30
31 #ifdef FREMANTLE
32 #include <dbus/dbus.h>
33 #endif
34
35 #include "browser-switchboard.h"
36 #include "launcher.h"
37 #include "dbus-server-bindings.h"
38
39 #define LAUNCH_DEFAULT_BROWSER launch_microb
40
41 #ifdef FREMANTLE
42 static int microb_started = 0;
43 static int kill_microb = 0;
44
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,
48                                      DBusMessage *message,
49                                      void *user_data) {
50         DBusError error;
51         char *name, *old, *new;
52
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,
59                                    DBUS_TYPE_INVALID)) {
60                 printf("%s\n", error.message);
61                 dbus_error_free(&error);
62                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
63         }
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");
68                 microb_started = 1;
69         }
70
71         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
72 }
73
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,
77                                      DBusMessage *message,
78                                      void *user_data) {
79         DBusError error;
80         char *name, *old, *new;
81
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
86            appropriate */
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,
92                                    DBUS_TYPE_INVALID)) {
93                 printf("%s\n", error.message);
94                 dbus_error_free(&error);
95                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
96         }
97         /* If old isn't an empty string, the name is being released, and
98            we should now kill MicroB */
99         if (strlen(old) > 0)
100                 kill_microb = 1;
101
102         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
103 }
104 #endif
105
106 static void launch_tear(struct swb_context *ctx, char *uri) {
107         int status;
108         static DBusGProxy *tear_proxy = NULL;
109         GError *error = NULL;
110         pid_t pid;
111
112         if (!uri)
113                 uri = "new_window";
114
115         printf("launch_tear with uri '%s'\n", uri);
116
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)) {
125                 if (!tear_proxy)
126                         tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus,
127                                         "com.nokia.tear", "/com/nokia/tear",
128                                         "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)
132                         exit(0);
133         } else {
134                 if (ctx->continuous_mode) {
135                         if ((pid = fork()) != 0) {
136                                 /* Parent process or error in fork() */
137                                 printf("child: %d\n", (int)pid);
138                                 return;
139                         }
140                         /* Child process */
141                         setsid();
142                 }
143                 execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL);
144         }
145 }
146
147 void launch_microb(struct swb_context *ctx, char *uri) {
148         int kill_browserd = 0;
149         int status;
150         pid_t pid;
151 #ifdef FREMANTLE
152         DBusConnection *raw_connection;
153         DBusError dbus_error;
154         DBusHandleMessageFunction filter_func;
155         DBusGProxy *g_proxy;
156         GError *gerror = NULL;
157 #endif
158
159         if (!uri)
160                 uri = "new_window";
161
162         printf("launch_microb with uri '%s'\n", uri);
163
164         /* Launch browserd if it's not running */
165         status = system("pidof /usr/sbin/browserd > /dev/null");
166         if (WIFEXITED(status) && WEXITSTATUS(status)) {
167                 kill_browserd = 1;
168 #ifdef FREMANTLE
169                 system("/usr/sbin/browserd -d -b");
170 #else
171                 system("/usr/sbin/browserd -d");
172 #endif
173         }
174
175         /* Release the osso_browser D-Bus name so that MicroB can take it */
176         dbus_release_osso_browser_name(ctx);
177
178         if ((pid = fork()) == -1) {
179                 perror("fork");
180                 exit(1);
181         }
182 #ifdef FREMANTLE
183         if (pid > 0) {
184                 /* Parent process */
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
188                    window.
189
190                    Ideas for how to do this monitoring derived from the
191                    dbus-monitor code (tools/dbus-monitor.c in the D-Bus
192                    codebase). */
193                 microb_started = 0;
194                 dbus_error_init(&dbus_error);
195
196                 raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION,
197                                                       &dbus_error);
198                 if (!raw_connection) {
199                         fprintf(stderr,
200                                 "Failed to open connection to session bus: %s\n",
201                                 dbus_error.message);
202                         dbus_error_free(&dbus_error);
203                         exit(1);
204                 }
205
206                 dbus_bus_add_match(raw_connection,
207                                    "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
208                                    &dbus_error);
209                 if (dbus_error_is_set(&dbus_error)) {
210                         fprintf(stderr,
211                                 "Failed to set up watch for browser UI start: %s\n",
212                                 dbus_error.message);
213                         dbus_error_free(&dbus_error);
214                         exit(1);
215                 }
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");
220                         exit(1);
221                 }
222                 printf("Waiting for MicroB to start\n");
223                 while (!microb_started &&
224                        dbus_connection_read_write_dispatch(raw_connection,
225                                                            -1));
226                 dbus_connection_remove_filter(raw_connection,
227                                               filter_func, NULL);
228                 dbus_bus_remove_match(raw_connection,
229                                       "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'",
230                                       &dbus_error);
231                 if (dbus_error_is_set(&dbus_error)) {
232                         fprintf(stderr,
233                                 "Failed to remove watch for browser UI start: %s\n",
234                                 dbus_error.message);
235                         dbus_error_free(&dbus_error);
236                         exit(1);
237                 }
238
239                 /* Browser UI's started, send it the request for a new window
240                    via D-Bus */
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");
245                 if (!g_proxy) {
246                         printf("Couldn't get a com.nokia.osso_browser proxy\n");
247                         exit(1);
248                 }
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,
256                                                G_TYPE_INVALID)) {
257                                 printf("Opening window failed: %s\n",
258                                        gerror->message);
259                                 exit(1);
260                         }
261 #endif
262                         if (!dbus_g_proxy_call(g_proxy, "load_url",
263                                                &gerror,
264                                                G_TYPE_STRING, "about:blank",
265                                                G_TYPE_INVALID,
266                                                G_TYPE_INVALID)) {
267                                 printf("Opening window failed: %s\n",
268                                        gerror->message);
269                                 exit(1);
270                         }
271                 } else {
272                         if (!dbus_g_proxy_call(g_proxy, "load_url",
273                                                &gerror,
274                                                G_TYPE_STRING, uri,
275                                                G_TYPE_INVALID,
276                                                G_TYPE_INVALID)) {
277                                 printf("Opening window failed: %s\n",
278                                        gerror->message);
279                                 exit(1);
280                         }
281                 }
282
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
290                    when it happens.
291
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
295                    appreciated. */
296                 kill_microb = 0;
297                 dbus_bus_add_match(raw_connection,
298                                    "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
299                                    &dbus_error);
300                 if (dbus_error_is_set(&dbus_error)) {
301                         fprintf(stderr,
302                                 "Failed to set up watch for browserd restart: %s\n",
303                                 dbus_error.message);
304                         dbus_error_free(&dbus_error);
305                         exit(1);
306                 }
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'",
311                                    &dbus_error);
312                 if (dbus_error_is_set(&dbus_error)) {
313                         fprintf(stderr,
314                                 "Failed to set up watch for browserd restart: %s\n",
315                                 dbus_error.message);
316                         dbus_error_free(&dbus_error);
317                         exit(1);
318                 }
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");
323                         exit(1);
324                 }
325                 while (!kill_microb &&
326                        dbus_connection_read_write_dispatch(raw_connection,
327                                                            -1));
328                 dbus_connection_remove_filter(raw_connection,
329                                               filter_func, NULL);
330                 dbus_bus_remove_match(raw_connection,
331                                    "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'",
332                                    &dbus_error);
333                 if (dbus_error_is_set(&dbus_error))
334                         /* Don't really care -- about to disconnect from the
335                            bus anyhow */
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'",
339                                    &dbus_error);
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);
344
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
350                            a problem */
351                         if (gerror->domain != DBUS_GERROR ||
352                             gerror->code != DBUS_GERROR_NO_REPLY) {
353                                 printf("exit_browser failed: %s\n",
354                                        gerror->message);
355                                 exit(1);
356                         }
357                 }
358                 g_object_unref(g_proxy);
359         } else {
360                 /* Child process */
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);
368         }
369 #else /* !FREMANTLE */
370         if (pid > 0) {
371                 /* Parent process */
372                 waitpid(pid, &status, 0);
373         } else {
374                 /* Child process */
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);
381                 } else {
382                         execl("/usr/bin/maemo-invoker",
383                               "browser", "--url", uri, (char *)NULL);
384                 }
385         }
386 #endif /* FREMANTLE */
387
388         /* Kill off browserd if we started it */
389         if (kill_browserd)
390                 system("kill `pidof /usr/sbin/browserd`");
391
392         if (!ctx || !ctx->continuous_mode) 
393                 exit(0);
394
395         dbus_request_osso_browser_name(ctx);
396 }
397
398 static void launch_other_browser(struct swb_context *ctx, char *uri) {
399         char *command;
400         char *quoted_uri, *quote;
401
402         size_t cmdlen, urilen;
403         size_t quoted_uri_size;
404         size_t offset;
405
406         if (!uri || !strcmp(uri, "new_window"))
407                 uri = "";
408
409         printf("launch_other_browser with uri '%s'\n", uri);
410
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))))
415                         exit(1);
416                 snprintf(quoted_uri, urilen+3, "'%s'", uri);
417
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)
427                                 exit(1);
428
429                         /* Grow the memory area;
430                            2 = strlen("%27")-strlen("'") */
431                         if (!(quoted_uri = realloc(quoted_uri,
432                                                    quoted_uri_size+2)))
433                                 exit(1);
434                         quoted_uri_size = quoted_uri_size + 2;
435
436                         /* Recalculate the location of the ' character --
437                            realloc() may have moved the string in memory */
438                         quote = quoted_uri + offset;
439
440                         /* Move the string after the ', including the \0,
441                            over two chars */
442                         memmove(quote+3, quote+1, strlen(quote));
443                         memcpy(quote, "%27", 3);
444                         quote = quote + 3;
445                 }
446                 urilen = strlen(quoted_uri);
447         } else
448                 quoted_uri = uri;
449
450         cmdlen = strlen(ctx->other_browser_cmd);
451
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
454            and urilen < 2 */
455         if (!(command = calloc(cmdlen+urilen+1, sizeof(char))))
456                 exit(1);
457         snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri);
458         printf("command: '%s'\n", command);
459
460         if (ctx->continuous_mode) {
461                 if (fork() != 0) {
462                         /* Parent process or error in fork() */
463                         if (urilen > 0)
464                                 free(quoted_uri);
465                         free(command);  
466                         return;
467                 }
468                 /* Child process */
469                 setsid();
470         }
471         execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL);
472 }
473
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);
480
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;
486         } else {
487                 ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd,
488                                                  cmd, len+1);
489                 ctx->default_browser_launcher = launch_other_browser;
490         }
491 }
492
493 void update_default_browser(struct swb_context *ctx, char *default_browser) {
494         if (!ctx)
495                 return;
496
497         if (!default_browser) {
498                 /* No default_browser configured -- use built-in default */
499                 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
500                 return;
501         }
502
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;
516                 else {
517                         printf("default_browser is 'other', but no other_browser_cmd set -- using default\n");
518                         ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
519                 }
520         } else {
521                 printf("Unknown default_browser %s, using default", default_browser);
522                 ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER;
523         }
524 }
525
526 void launch_browser(struct swb_context *ctx, char *uri) {
527         if (ctx && ctx->default_browser_launcher)
528                 ctx->default_browser_launcher(ctx, uri);
529 }