ephy-main.c 14.7 KB
Newer Older
1
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
2
/*
3
 *  Copyright © 2000-2002 Marco Pesenti Gritti
4
 *  Copyright © 2006, 2008 Christian Persch
5
 *  Copyright © 2011, 2012 Igalia S.L.
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
6
 *
Michael Catanzaro's avatar
Michael Catanzaro committed
7 8 9
 *  This file is part of Epiphany.
 *
 *  Epiphany is free software: you can redistribute it and/or modify
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
10
 *  it under the terms of the GNU General Public License as published by
Michael Catanzaro's avatar
Michael Catanzaro committed
11 12
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
13
 *
Michael Catanzaro's avatar
Michael Catanzaro committed
14
 *  Epiphany is distributed in the hope that it will be useful,
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
15 16 17 18 19
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
Michael Catanzaro's avatar
Michael Catanzaro committed
20
 *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
21 22
 */

23
#include "config.h"
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
24

25 26
#include "ephy-debug.h"
#include "ephy-file-helpers.h"
27
#include "ephy-profile-utils.h"
28
#include "ephy-session.h"
29
#include "ephy-settings.h"
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
30
#include "ephy-shell.h"
31
#include "ephy-string.h"
32
#include "ephy-vcs-version.h"
33
#include "ephy-web-app-utils.h"
34

35 36
#include <errno.h>
#include <glib/gi18n.h>
37
#include <glib-unix.h>
38
#include <gtk/gtk.h>
Adrien Plazas's avatar
Adrien Plazas committed
39 40
#define HANDY_USE_UNSTABLE_API
#include <handy.h>
41
#include <libnotify/notify.h>
42
#include <libxml/xmlreader.h>
43
#include <libxml/xmlversion.h>
44
#include <signal.h>
45
#include <string.h>
46
#include <stdlib.h>
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
47

Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
48
static gboolean open_in_new_tab = FALSE;
49
static gboolean open_in_new_window = FALSE;
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
50

Christian Persch's avatar
Christian Persch committed
51 52 53
static char *session_filename = NULL;
static char *bookmark_url = NULL;
static char *bookmarks_file = NULL;
54
static char **arguments = NULL;
55
static char *application_to_delete = NULL;
56

Christian Persch's avatar
Christian Persch committed
57
static gboolean private_instance = FALSE;
58
static gboolean incognito_mode = FALSE;
59
static gboolean application_mode = FALSE;
60
static gboolean automation_mode = FALSE;
61
static char *desktop_file_basename = NULL;
Christian Persch's avatar
Christian Persch committed
62 63
static char *profile_directory = NULL;

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
static EphyShell *ephy_shell = NULL;
static int shutdown_signum = 0;

static gboolean
handle_shutdown_signal (gpointer user_data)
{
  shutdown_signum = GPOINTER_TO_INT (user_data);

  /* Note that this function executes on the main loop AFTER the signal handler
   * has returned, so we don't have to worry about async signal safety.
   */
  g_assert (ephy_shell != NULL);
  ephy_shell_try_quit (ephy_shell);

  /* Goals:
   *
   * (1) Shutdown safely and cleanly if signal is received once.
   * (2) Shutdown unsafely but immediately if signal is received twice.
   * (3) Always re-raise the signal so the parent process knows what happened.
   *
   * Removing this source is required by goals (2) and (3).
   */
  return G_SOURCE_REMOVE;
}

89 90 91 92 93 94 95
static gboolean
application_mode_cb (const gchar *option_name,
                     const gchar *value,
                     gpointer     data,
                     GError     **error)
{
  application_mode = TRUE;
96
  desktop_file_basename = g_strdup (value);
97 98 99
  return TRUE;
}

100 101 102 103 104 105
static gboolean
option_version_cb (const gchar *option_name,
                   const gchar *value,
                   gpointer     data,
                   GError     **error)
{
106
  g_print ("%s %s\n", _("Web"), VCSVERSION);
107 108

  exit (EXIT_SUCCESS);
109
  return FALSE;
110
}
Michael Catanzaro's avatar
Michael Catanzaro committed
111

Michael Catanzaro's avatar
Michael Catanzaro committed
112
/* If you're modifying this array then you need to update the manpage. */
113 114
static const GOptionEntry option_entries[] =
{
115 116 117 118 119 120 121 122 123 124 125 126
  { "new-tab", 'n', 0, G_OPTION_ARG_NONE, &open_in_new_tab,
    N_("Open a new tab in an existing browser window"), NULL },
  { "new-window", 0, 0, G_OPTION_ARG_NONE, &open_in_new_window,
    N_("Open a new browser window"), NULL },
  { "import-bookmarks", '\0', 0, G_OPTION_ARG_FILENAME, &bookmarks_file,
    N_("Import bookmarks from the given file"), N_("FILE") },
  { "load-session", 'l', 0, G_OPTION_ARG_FILENAME, &session_filename,
    N_("Load the given session file"), N_("FILE") },
  { "add-bookmark", 't', 0, G_OPTION_ARG_STRING, &bookmark_url,
    N_("Add a bookmark"), N_("URL") },
  { "private-instance", 'p', 0, G_OPTION_ARG_NONE, &private_instance,
    N_("Start a private instance"), NULL },
127 128
  { "incognito-mode", 'i', 0, G_OPTION_ARG_NONE, &incognito_mode,
    N_("Start an instance in incognito mode"), NULL },
129 130
  { "application-mode", 'a', G_OPTION_FLAG_FILENAME | G_OPTION_FLAG_OPTIONAL_ARG,
    G_OPTION_ARG_CALLBACK, application_mode_cb,
131
    N_("Start the browser in application mode"), NULL },
132 133
  { "automation-mode", 0, 0, G_OPTION_ARG_NONE, &automation_mode,
    N_("Start an instance in automation mode"), NULL },
134 135 136
  { "profile", 0, 0, G_OPTION_ARG_STRING, &profile_directory,
    N_("Profile directory to use in the private instance"), N_("DIR") },
  { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &arguments,
Michael Catanzaro's avatar
Michael Catanzaro committed
137 138
    "", N_("URL …") },
  { "version", 0, G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_HIDDEN,
139
    G_OPTION_ARG_CALLBACK, option_version_cb, NULL, NULL },
140 141
  { "delete-application", 0, 0, G_OPTION_ARG_STRING | G_OPTION_FLAG_HIDDEN,
    &application_to_delete, NULL, NULL },
142
  { NULL }
143
};
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
144

145 146 147 148
/* adapted from gtk+/gdk/x11/gdkdisplay-x11.c */
static guint32
get_startup_id (void)
{
149 150 151 152
  const char *startup_id, *time_str;
  guint32 retval = 0;

  startup_id = g_getenv ("DESKTOP_STARTUP_ID");
Michael Catanzaro's avatar
Michael Catanzaro committed
153 154
  if (startup_id == NULL)
    return 0;
155 156 157 158 159 160 161 162 163

  /* Find the launch time from the startup_id, if it's there.  Newer spec
   * states that the startup_id is of the form <unique>_TIME<timestamp>
   */
  time_str = g_strrstr (startup_id, "_TIME");
  if (time_str != NULL) {
    gulong value;
    gchar *end;
    errno = 0;
Michael Catanzaro's avatar
Michael Catanzaro committed
164

165 166
    /* Skip past the "_TIME" part */
    time_str += 5;
Michael Catanzaro's avatar
Michael Catanzaro committed
167

168 169
    value = strtoul (time_str, &end, 0);
    if (end != time_str && errno == 0)
Michael Catanzaro's avatar
Michael Catanzaro committed
170
      retval = (guint32)value;
171 172 173
  }

  return retval;
174 175
}

176 177
static EphyStartupFlags
get_startup_flags (void)
178
{
179
  EphyStartupFlags flags = 0;
180

181 182 183 184
  if (open_in_new_tab)
    flags |= EPHY_STARTUP_NEW_TAB;
  if (open_in_new_window)
    flags |= EPHY_STARTUP_NEW_WINDOW;
185

186
  return flags;
187 188
}

Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
189
int
Michael Catanzaro's avatar
Michael Catanzaro committed
190
main (int   argc,
191
      char *argv[])
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
192
{
193 194 195 196 197
  GOptionContext *option_context;
  GOptionGroup *option_group;
  GError *error = NULL;
  guint32 user_time;
  gboolean arbitrary_url;
198
  EphyShellStartupContext *ctx;
199
  EphyStartupFlags startup_flags;
200
  EphyEmbedShellMode mode;
201
  int status;
202
  EphyFileHelpersFlags flags;
203
  GDesktopAppInfo *desktop_info = NULL;
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
204

205 206 207 208
#if DEVELOPER_MODE
  g_setenv ("GSETTINGS_SCHEMA_DIR", BUILD_ROOT "/data", FALSE);
#endif

209
  /* Initialize the i18n stuff */
Michael Catanzaro's avatar
Michael Catanzaro committed
210
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
211 212
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
213

214 215 216 217 218
  /* check libxml2 API version epiphany was compiled with against the
   * version we're running with.
   */
  LIBXML_TEST_VERSION;

219
  notify_init ("epiphany");
220

221 222 223 224
  /* If we're given -remote arguments, translate them */
  if (argc >= 2 && strcmp (argv[1], "-remote") == 0) {
    const char *opening, *closing;
    char *command, *argument;
225
    char **arg_list;
Michael Catanzaro's avatar
Michael Catanzaro committed
226

227 228 229 230
    if (argc != 3) {
      g_print ("-remote allows exactly one argument\n");
      exit (1);
    }
Michael Catanzaro's avatar
Michael Catanzaro committed
231

232 233
    opening = strchr (argv[2], '(');
    closing = strchr (argv[2], ')');
Michael Catanzaro's avatar
Michael Catanzaro committed
234

235 236 237 238 239 240 241
    if (opening == NULL ||
        closing == NULL ||
        opening == argv[2] ||
        opening + 1 >= closing) {
      g_print ("Invalid argument for -remote\n");
      exit (1);
    }
Michael Catanzaro's avatar
Michael Catanzaro committed
242

243
    command = g_strstrip (g_strndup (argv[2], opening - argv[2]));
Michael Catanzaro's avatar
Michael Catanzaro committed
244

245 246 247 248 249 250 251 252
    /* See http://lxr.mozilla.org/seamonkey/source/xpfe/components/xremote/src/XRemoteService.cpp
     * for the commands that mozilla supports; we'll just support openURL here.
     */
    if (g_ascii_strcasecmp (command, "openURL") != 0) {
      g_print ("-remote command \"%s\" not supported\n", command);
      g_free (command);
      exit (1);
    }
Michael Catanzaro's avatar
Michael Catanzaro committed
253

254
    g_free (command);
Michael Catanzaro's avatar
Michael Catanzaro committed
255

256
    argument = g_strstrip (g_strndup (opening + 1, closing - opening - 1));
257
    arg_list = g_strsplit (argument, ",", -1);
258
    g_free (argument);
259
    if (arg_list == NULL) {
260
      g_print ("Invalid argument for -remote\n");
Michael Catanzaro's avatar
Michael Catanzaro committed
261

262 263
      exit (1);
    }
Michael Catanzaro's avatar
Michael Catanzaro committed
264

265
    /* replace arguments */
266
    argv[1] = g_strstrip (g_strdup (arg_list[0]));
267
    argc = 2;
Michael Catanzaro's avatar
Michael Catanzaro committed
268

269
    g_strfreev (arg_list);
270
  }
Michael Catanzaro's avatar
Michael Catanzaro committed
271

272 273
  /* Initialise our debug helpers */
  ephy_debug_init ();
Michael Catanzaro's avatar
Michael Catanzaro committed
274

275 276 277 278 279
  /* get this early, since gdk will unset the env var */
  user_time = get_startup_id ();

  option_context = g_option_context_new ("");
  option_group = g_option_group_new ("epiphany",
280 281
                                     N_("Web"),
                                     N_("Web options"),
282 283 284 285 286 287 288
                                     NULL, NULL);

  g_option_group_set_translation_domain (option_group, GETTEXT_PACKAGE);

  g_option_group_add_entries (option_group, option_entries);

  g_option_context_set_main_group (option_context, option_group);
289

290
  g_option_context_add_group (option_context, gtk_get_option_group (TRUE));
291

292 293 294 295 296 297
  if (!g_option_context_parse (option_context, &argc, &argv, &error)) {
    g_print ("Failed to parse arguments: %s\n", error->message);
    g_error_free (error);
    g_option_context_free (option_context);
    exit (1);
  }
Michael Catanzaro's avatar
Michael Catanzaro committed
298

299 300 301
  g_option_context_free (option_context);

  /* Some argument sanity checks*/
302 303 304 305 306
  if (application_to_delete != NULL && argc > 3) {
    g_print ("Cannot pass any other parameter when using --delete-application\n");
    exit (1);
  }

307 308 309 310 311
  if (private_instance == TRUE && application_mode == TRUE) {
    g_print ("Cannot use --private-instance and --application-mode at the same time\n");
    exit (1);
  }

312 313 314 315 316
  if (automation_mode && (private_instance || incognito_mode || application_mode || profile_directory)) {
    g_print ("Cannot use --automation-mode and --private-instance, --incognito-mode, --application-mode, or --profile at the same time\n");
    exit (1);
  }

317
  if (application_mode && profile_directory && !g_file_test (profile_directory, G_FILE_TEST_IS_DIR)) {
Michael Catanzaro's avatar
Michael Catanzaro committed
318 319
    g_print ("--profile must be an existing directory when --application-mode is requested\n");
    exit (1);
320 321 322
  }

  if (application_mode && !profile_directory) {
323 324
    if (desktop_file_basename) {
      desktop_info = g_desktop_app_info_new (desktop_file_basename);
325 326 327

      if (desktop_info)
        profile_directory = ephy_web_application_ensure_for_app_info (G_APP_INFO (desktop_info));
328 329 330 331 332

      if (!profile_directory) {
        g_print ("Invalid desktop file passed to --application-mode\n");
        exit (1);
      }
333 334 335
    }
  }

336
  if (application_mode && profile_directory == NULL) {
337
    g_print ("--profile must be used when --application-mode is requested without desktop file path\n");
338 339 340
    exit (1);
  }

341
  if (incognito_mode && profile_directory == NULL)
342
    profile_directory = ephy_default_dot_dir ();
343

344
  /* Start our services */
345
  flags = !application_mode ? EPHY_FILE_HELPERS_ENSURE_EXISTS : 0;
346

347
  if (incognito_mode || private_instance || application_mode || automation_mode)
348
    flags |= EPHY_FILE_HELPERS_PRIVATE_PROFILE;
349 350
  if (incognito_mode)
    flags |= EPHY_FILE_HELPERS_STEAL_DATA;
351
  if (profile_directory && !incognito_mode)
352
    flags |= EPHY_FILE_HELPERS_KEEP_DIR;
353

354 355
  if (!ephy_file_helpers_init (profile_directory, flags, &error)) {
    g_error ("Fatal initialization error: %s", error->message);
356 357
  }

358 359
  /* Run the migration in all cases, except when running a private
     instance without a given profile directory or running in
360 361
     incognito or automation mode. */
  if (!(private_instance && profile_directory == NULL) && !incognito_mode && !automation_mode) {
362 363
    /* If the migration fails we don't really want to continue. */
    if (!ephy_profile_utils_do_migration ((const char *)profile_directory, -1, FALSE)) {
364
      g_print ("Failed to run the migrator process, Web will now abort.\n");
365 366 367 368
      exit (1);
    }
  }

369 370 371 372
  /* Ignore arguments in automation mode */
  if (automation_mode)
    g_clear_pointer (&arguments, g_strfreev);

373 374 375 376
  arbitrary_url = g_settings_get_boolean (EPHY_SETTINGS_LOCKDOWN,
                                          EPHY_PREFS_LOCKDOWN_ARBITRARY_URL);

  if (arguments != NULL && arbitrary_url) {
377
    g_print ("URL loading is locked down.\n");
378 379 380 381 382 383 384 385 386 387 388 389 390 391
    exit (1);
  }

  /* convert arguments to uris or at least to utf8 */
  if (arguments != NULL) {
    char **args = ephy_string_commandline_args_to_uris (arguments,
                                                        &error);
    if (error) {
      g_print ("Could not convert to UTF-8: %s!\n",
               error->message);
      g_error_free (error);
      exit (1);
    }
    g_strfreev (arguments);
392 393
    arguments = args;
  }
394

395 396 397
  /* Delete the requested web application, if any. Must happen after
   * ephy_file_helpers_init (). */
  if (application_to_delete) {
398
    ephy_web_application_delete (application_to_delete);
399 400 401
    exit (0);
  }

402 403
  startup_flags = get_startup_flags ();

404
  /* Now create the shell */
405
  if (private_instance) {
406
    mode = EPHY_EMBED_SHELL_MODE_PRIVATE;
407 408 409 410
    /* In private mode the session autoresume will always open an empty window.
     * If there are arguments, we want the URIs to be opened in thet existing window. */
    startup_flags |= EPHY_STARTUP_NEW_TAB;
  } else if (incognito_mode) {
411
    mode = EPHY_EMBED_SHELL_MODE_INCOGNITO;
412 413
  } else if (automation_mode) {
    mode = EPHY_EMBED_SHELL_MODE_AUTOMATION;
414
  } else if (application_mode) {
415
    mode = EPHY_EMBED_SHELL_MODE_APPLICATION;
416

417 418 419 420 421
    if (desktop_info) {
      ephy_web_application_setup_from_desktop_file (desktop_info);
      g_object_unref (desktop_info);
    } else {
      ephy_web_application_setup_from_profile_directory (profile_directory);
422
    }
423 424 425 426 427 428
  } else if (profile_directory) {
    /* This mode exists purely for letting EphyShell know it should
     * not consider this instance part of the unique application
     * represented by the BROWSER mode.
     */
    mode = EPHY_EMBED_SHELL_MODE_STANDALONE;
429
  } else {
430 431
    mode = EPHY_EMBED_SHELL_MODE_BROWSER;

432
    g_set_prgname ("epiphany");
433
    g_set_application_name (_("Web"));
434

Michael Catanzaro's avatar
Michael Catanzaro committed
435
    gtk_window_set_default_icon_name ("org.gnome.Epiphany");
436 437
  }

Adrien Plazas's avatar
Adrien Plazas committed
438 439
  hdy_init (&argc, &argv);

440
  _ephy_shell_create_instance (mode);
441

442 443 444 445 446 447
  ctx = ephy_shell_startup_context_new (startup_flags,
                                        bookmarks_file,
                                        session_filename,
                                        bookmark_url,
                                        arguments,
                                        user_time);
448
  g_strfreev (arguments);
449
  ephy_shell = ephy_shell_get_default ();
450
  ephy_shell_set_startup_context (ephy_shell, ctx);
451 452 453 454

  g_unix_signal_add (SIGINT, (GSourceFunc)handle_shutdown_signal, GINT_TO_POINTER (SIGINT));
  g_unix_signal_add (SIGTERM, (GSourceFunc)handle_shutdown_signal, GINT_TO_POINTER (SIGTERM));

455
  status = g_application_run (G_APPLICATION (ephy_shell), argc, argv);
456 457

  /* Shutdown */
458
  /* FIXME: should free this stuff even when exiting early */
459
  g_object_unref (ephy_shell);
460
  g_free (desktop_file_basename);
461
  g_free (profile_directory);
462

463 464 465
  if (notify_is_initted ())
    notify_uninit ();

466 467 468 469
  ephy_settings_shutdown ();
  ephy_file_helpers_shutdown ();
  xmlCleanupParser ();

470 471 472
  if (shutdown_signum != 0)
    raise (shutdown_signum);

473
  return status;
Marco Pesenti Gritti's avatar
Marco Pesenti Gritti committed
474
}