terminal.c 21.8 KB
Newer Older
1
/*
2 3 4 5
 * Copyright © 2001, 2002 Havoc Pennington
 * Copyright © 2002 Red Hat, Inc.
 * Copyright © 2002 Sun Microsystems
 * Copyright © 2003 Mariano Suarez-Alvarez
6
 * Copyright © 2008, 2010, 2011 Christian Persch
7
 *
8
 * This program is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11 12
 * (at your option) any later version.
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
17
 *
18 19
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 21
 */

22
#include "config.h"
23

24
#include <errno.h>
25
#include <locale.h>
26 27 28
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
29
#include <sys/wait.h>
30

31
#include <glib.h>
32
#include <glib/gstdio.h>
33
#include <gio/gio.h>
34
#include <glib/gi18n.h>
35

36
#include <gtk/gtk.h>
37

38
#include "terminal-debug.h"
39
#include "terminal-defines.h"
40
#include "terminal-i18n.h"
41
#include "terminal-options.h"
42 43
#include "terminal-gdbus-generated.h"
#include "terminal-defines.h"
44
#include "terminal-client-utils.h"
45 46 47 48
#include "terminal-libgsystem.h"

GS_DEFINE_CLEANUP_FUNCTION0(TerminalOptions*, gs_local_options_free, terminal_options_free)
#define gs_free_options __attribute__ ((cleanup(gs_local_options_free)))
49

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
/* Wait-for-exit helper */

typedef struct {
  GMainLoop *loop;
  int status;
} RunData;

static void
receiver_child_exited_cb (TerminalReceiver *receiver,
                          int status,
                          RunData *data)
{
  data->status = status;

  if (g_main_loop_is_running (data->loop))
    g_main_loop_quit (data->loop);
}

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
static void
factory_name_owner_notify_cb (TerminalFactory *factory,
                              GParamSpec *pspec,
                              RunData *data)
{
  /* Name owner change to NULL can only mean that the server
   * went away before it could send out our child-exited signal.
   * Assume the server was killed and thus our child process
   * too, and return with the corresponding exit code.
   */
  if (g_dbus_proxy_get_name_owner(G_DBUS_PROXY (factory)) != NULL)
    return;

  data->status = W_EXITCODE(0, SIGKILL);

  if (g_main_loop_is_running (data->loop))
    g_main_loop_quit (data->loop);
}

87
static int
88 89
run_receiver (TerminalFactory *factory,
              TerminalReceiver *receiver)
90 91
{
  RunData data = { g_main_loop_new (NULL, FALSE), 0 };
92 93 94 95
  gulong receiver_exited_id = g_signal_connect (receiver, "child-exited",
                                                G_CALLBACK (receiver_child_exited_cb), &data);
  gulong factory_notify_id = g_signal_connect (factory, "notify::g-name-owner",
                                               G_CALLBACK (factory_name_owner_notify_cb), &data);
96
  g_main_loop_run (data.loop);
97 98
  g_signal_handler_disconnect (receiver, receiver_exited_id);
  g_signal_handler_disconnect (factory, factory_notify_id);
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  g_main_loop_unref (data.loop);

  /* Mangle the exit status */
  int exit_code;
  if (WIFEXITED (data.status))
    exit_code = WEXITSTATUS (data.status);
  else if (WIFSIGNALED (data.status))
    exit_code = 128 + (int) WTERMSIG (data.status);
  else if (WCOREDUMP (data.status))
    exit_code = 127;
  else
    exit_code = 127;

  return exit_code;
}

/* Factory helpers */

117
static gboolean
118
get_factory_exit_status (const char *service_name,
119
                         const char *message,
120 121 122 123 124 125 126 127 128
                         int *exit_status)
{
  gs_free char *pattern = NULL, *number = NULL;
  gs_unref_regex GRegex *regex = NULL;
  gs_free_match_info GMatchInfo *match_info = NULL;
  gint64 v;
  char *end;
  GError *err = NULL;

129
  pattern = g_strdup_printf ("org.freedesktop.DBus.Error.Spawn.ChildExited: Process %s exited with status (\\d+)$",
130
                             service_name);
131 132 133 134 135 136 137
  regex = g_regex_new (pattern, 0, 0, &err);
  g_assert_no_error (err);

  if (!g_regex_match (regex, message, 0, &match_info))
    return FALSE;

  number = g_match_info_fetch (match_info, 1);
138
  g_assert_nonnull (number);
139 140 141 142 143 144 145 146 147 148 149

  errno = 0;
  v = g_ascii_strtoll (number, &end, 10);
  if (errno || end == number || *end != '\0' || v < 0 || v > G_MAXINT)
    return FALSE;

  *exit_status = (int)v;
  return TRUE;
}

static gboolean
150
handle_factory_error (const char *service_name,
151
                      GError *error)
152 153 154 155 156
{
  int exit_status;

  if (!g_dbus_error_is_remote_error (error) ||
      !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_CHILD_EXITED) ||
157
      !get_factory_exit_status (service_name, error->message, &exit_status))
158 159 160
    return FALSE;

  g_dbus_error_strip_remote_error (error);
161
  terminal_printerr ("%s\n\n", error->message);
162 163 164

  switch (exit_status) {
  case _EXIT_FAILURE_WRONG_ID:
165
    terminal_printerr ("You tried to run gnome-terminal-server with elevated privileged. This is not supported.\n");
166 167
    break;
  case _EXIT_FAILURE_NO_UTF8:
168
    terminal_printerr ("The environment that gnome-terminal-server was launched with specified a non-UTF-8 locale. This is not supported.\n");
169 170
    break;
  case _EXIT_FAILURE_UNSUPPORTED_LOCALE:
171
    terminal_printerr ("The environment that gnome-terminal-server was launched with specified an unsupported locale.\n");
172 173
    break;
  case _EXIT_FAILURE_GTK_INIT:
174
    terminal_printerr ("The environment that gnome-terminal-server was launched with most likely contained an incorrect or unset \"DISPLAY\" variable.\n");
175 176 177 178
    break;
  default:
    break;
  }
179
  terminal_printerr ("See https://wiki.gnome.org/Apps/Terminal/FAQ#Exit_status_%d for more information.\n", exit_status);
180 181 182 183 184

  return TRUE;
}

static gboolean
185
handle_create_instance_error (const char *service_name,
186
                              GError *error)
187
{
188
  if (handle_factory_error (service_name, error))
189 190 191
    return TRUE;

  g_dbus_error_strip_remote_error (error);
192
  terminal_printerr ("Error creating terminal: %s\n", error->message);
193 194 195 196
  return FALSE; /* don't abort */
}

static gboolean
197
handle_create_receiver_proxy_error (const char *service_name,
198
                                    GError *error)
199
{
200
  if (handle_factory_error (service_name, error))
201 202 203
    return TRUE;

  g_dbus_error_strip_remote_error (error);
204
  terminal_printerr ("Failed to create proxy for terminal: %s\n", error->message);
205 206 207 208
  return FALSE; /* don't abort */
}

static gboolean
209
handle_exec_error (const char *service_name,
210
                   GError *error)
211
{
212
  if (handle_factory_error (service_name, error))
213 214 215
    return TRUE;

  g_dbus_error_strip_remote_error (error);
216
  terminal_printerr ("Error: %s\n", error->message);
217 218 219
  return FALSE; /* don't abort */
}

220 221
static gboolean
factory_proxy_new_for_service_name (const char *service_name,
222
                                    gboolean ping_server,
223
                                    gboolean connect_signals,
224 225 226 227 228 229 230 231 232 233 234
                                    TerminalFactory **factory_ptr,
                                    char **service_name_ptr,
                                    GError **error)
{
  if (service_name == NULL)
    service_name = TERMINAL_APPLICATION_ID;

  gs_free_error GError *err = NULL;
  gs_unref_object TerminalFactory *factory =
    terminal_factory_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                             G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
235
                                             connect_signals ? 0 : G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
236 237 238 239 240 241 242 243 244 245 246 247 248
                                             service_name,
                                             TERMINAL_FACTORY_OBJECT_PATH,
                                             NULL /* cancellable */,
                                             &err);
  if (factory == NULL) {
    if (!handle_factory_error (service_name, err))
      terminal_printerr ("Error constructing proxy for %s:%s: %s\n",
                         service_name, TERMINAL_FACTORY_OBJECT_PATH, err->message);
    g_propagate_error (error, err);
    err = NULL;
    return FALSE;
  }

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
  if (ping_server) {
    /* If we try to use the environment specified server, we need to make
     * sure it actually exists so we can later fall back to the default name.
     * There doesn't appear to a way to fail proxy creation above if the
     * unique name doesn't exist; so we do it this way.
     */
    gs_unref_variant GVariant *v = g_dbus_proxy_call_sync (G_DBUS_PROXY (factory),
                                                           "org.freedesktop.DBus.Peer.Ping",
                                                           g_variant_new ("()"),
                                                           G_DBUS_CALL_FLAGS_NONE,
                                                           1000 /* 1s */,
                                                           NULL /* cancelleable */,
                                                           &err);
    if (v == NULL) {
      g_propagate_error (error, err);
      err = NULL;
      return FALSE;
    }
  }

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
  gs_transfer_out_value (factory_ptr, &factory);
  *service_name_ptr = g_strdup (service_name);
  return TRUE;
}

static gboolean
factory_proxy_new (TerminalOptions *options,
                   TerminalFactory **factory_ptr,
                   char **service_name_ptr,
                   char **parent_screen_object_path_ptr,
                   GError **error)
{
  const char *service_name = options->server_app_id;

  /* If --app-id was specified, or the environment does not specify
   * the server to use, create the factory proxy from the given (or default)
   * name, with no fallback.
   *
   * If the server specified by the environment doesn't exist, fall back to the
   * default server, and ignore the environment-specified parent screen.
   */
  if (options->server_app_id == NULL &&
      options->server_unique_name != NULL) {
    gs_free_error GError *err = NULL;
    if (factory_proxy_new_for_service_name (options->server_unique_name,
294
                                            TRUE,
295
                                            options->wait,
296 297 298 299 300 301 302
                                            factory_ptr,
                                            service_name_ptr,
                                            &err)) {
      *parent_screen_object_path_ptr = g_strdup (options->parent_screen_object_path);
      return TRUE;
    }

303 304 305 306
    terminal_printerr ("Failed to use specified server: %s\n",
                       err->message);
    terminal_printerr ("Falling back to default server.\n");

307 308 309 310 311 312 313
    /* Fall back to the default */
    service_name = NULL;
  }

  *parent_screen_object_path_ptr = NULL;

  return factory_proxy_new_for_service_name (service_name,
314
                                             FALSE,
315
                                             options->wait,
316 317 318 319 320
                                             factory_ptr,
                                             service_name_ptr,
                                             error);
}

321
static void
322 323
handle_show_preferences (TerminalOptions *options,
                         const char *service_name)
324 325 326 327 328 329 330 331
{
  gs_free_error GError *error = NULL;
  gs_unref_object GDBusConnection *bus = NULL;
  gs_free char *object_path = NULL;
  GVariantBuilder builder;

  bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
  if (bus == NULL) {
332
    terminal_printerr ("Failed to get session bus: %s\n", error->message);
333 334 335 336 337 338
    return;
  }

  /* For reasons (!?), the org.gtk.Actions interface's object path
   * is derived from the service name, i.e. for service name
   * "foo.bar.baz" the object path is "/foo/bar/baz".
339 340
   * This means that without the name (like when given only the unique name),
   * we cannot activate the action.
341
   */
342 343 344 345 346
  if (g_dbus_is_unique_name(service_name)) {
    terminal_printerr ("Cannot call this function from within gnome-terminal.\n");
    return;
  }

347 348 349 350 351 352 353
  object_path = g_strdelimit (g_strdup_printf (".%s", service_name), ".", '/');

  g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sava{sv})"));
  g_variant_builder_add (&builder, "s", "preferences");
  g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
  g_variant_builder_close (&builder);
  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
354 355 356
  if (options->startup_id)
    g_variant_builder_add (&builder, "{sv}",
                           "desktop-startup-id", g_variant_new_string (options->startup_id));
357 358 359 360 361 362 363 364 365 366 367 368 369
  g_variant_builder_close (&builder);

  if (!g_dbus_connection_call_sync (bus,
                                    service_name,
                                    object_path,
                                    "org.gtk.Actions",
                                    "Activate",
                                    g_variant_builder_end (&builder),
                                    G_VARIANT_TYPE ("()"),
                                    G_DBUS_CALL_FLAGS_NO_AUTO_START,
                                    30 * 1000 /* ms timeout */,
                                    NULL /* cancelleable */,
                                    &error)) {
370
    terminal_printerr ("Activate call failed: %s\n", error->message);
371 372 373 374
    return;
  }
}

375 376 377 378 379 380
/**
 * handle_options:
 * @app:
 * @options: a #TerminalOptions
 * @allow_resume: whether to merge the terminal configuration from the
 *   saved session on resume
381
 * @wait_for_receiver: location to store the #TerminalReceiver to wait for
382 383 384 385 386 387 388 389
 *
 * Processes @options. It loads or saves the terminal configuration, or
 * opens the specified windows and tabs.
 *
 * Returns: %TRUE if @options could be successfully handled, or %FALSE on
 *   error
 */
static gboolean
390 391
handle_options (TerminalOptions *options,
                TerminalFactory *factory,
392 393
                const char *service_name,
                const char *parent_screen_object_path,
394
                TerminalReceiver **wait_for_receiver)
395 396
{

397
  /* We need to forward the locale encoding to the server, see bug #732128 */
398
  const char *encoding;
399
  g_get_charset (&encoding);
400

401
  if (options->show_preferences) {
402
    handle_show_preferences (options, service_name);
403 404 405 406
  } else {
    /* Make sure we open at least one window */
    terminal_options_ensure_window (options);
  }
407

408 409 410
  const char *factory_unique_name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (factory));

  for (GList *lw = options->initial_windows;  lw != NULL; lw = lw->next)
411 412
    {
      InitialWindow *iw = lw->data;
413

414 415 416
      g_assert_nonnull (iw);

      guint window_id = 0;
417

418 419
      gs_free char *previous_screen_object_path = NULL;
      if (iw->implicit_first_window)
420
        previous_screen_object_path = g_strdup (parent_screen_object_path);
421

422
      /* Now add the tabs */
423
      for (GList *lt = iw->tabs; lt != NULL; lt = lt->next)
424 425
        {
          InitialTab *it = lt->data;
426
          g_assert_nonnull (it);
427

428
          GVariantBuilder builder;
429 430 431 432 433 434 435 436
          g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));

          terminal_client_append_create_instance_options (&builder,
                                                          options->display_name,
                                                          options->startup_id,
                                                          iw->geometry,
                                                          iw->role,
                                                          it->profile ? it->profile : options->default_profile,
437
                                                          encoding,
438
                                                          it->title ? it->title : options->default_title,
439
                                                          it->active,
440 441 442
                                                          iw->start_maximized,
                                                          iw->start_fullscreen);

443
          /* This will be used to apply missing defaults */
444
          if (parent_screen_object_path != NULL)
445
            g_variant_builder_add (&builder, "{sv}",
446
                                   "parent-screen", g_variant_new_object_path (parent_screen_object_path));
447 448 449 450 451

          /* This will be used to get the parent window */
          if (previous_screen_object_path)
            g_variant_builder_add (&builder, "{sv}",
                                   "window-from-screen", g_variant_new_object_path (previous_screen_object_path));
452 453 454
          if (window_id)
            g_variant_builder_add (&builder, "{sv}",
                                   "window-id", g_variant_new_uint32 (window_id));
455 456 457 458 459 460 461
          /* Restored windows shouldn't demand attention; see bug #586308. */
          if (iw->source_tag == SOURCE_SESSION)
            g_variant_builder_add (&builder, "{sv}",
                                   "present-window", g_variant_new_boolean (FALSE));
          if (options->zoom_set || it->zoom_set)
            g_variant_builder_add (&builder, "{sv}",
                                   "zoom", g_variant_new_double (it->zoom_set ? it->zoom : options->zoom));
462 463 464
          if (iw->force_menubar_state)
            g_variant_builder_add (&builder, "{sv}",
                                   "show-menubar", g_variant_new_boolean (iw->menubar_state));
465

466 467 468
          gs_free_error GError *err = NULL;
          gs_free char *object_path = NULL;
          if (!terminal_factory_call_create_instance_sync
469 470 471 472 473
                 (factory,
                  g_variant_builder_end (&builder),
                  &object_path,
                  NULL /* cancellable */,
                  &err)) {
474
            if (handle_create_instance_error (service_name, err))
475 476 477
              return FALSE;
            else
              continue; /* Continue processing the remaining options! */
478 479
          }

480 481
          /* Deprecated and not working on new server anymore */
          char *p = strstr (object_path, "/window/");
482 483 484 485 486 487 488 489 490 491 492
          if (p) {
            char *end = NULL;
            guint64 value;

            errno = 0;
            p += strlen ("/window/");
            value = g_ascii_strtoull (p, &end, 10);
            if (errno == 0 && end != p && *end == '/')
              window_id = (guint) value;
          }

493 494 495 496 497 498 499 500 501 502 503
          g_free (previous_screen_object_path);
          previous_screen_object_path = g_strdup (object_path);

          gs_unref_object TerminalReceiver *receiver =
            terminal_receiver_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
                                                      G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
                                                      (it->wait ? 0 : G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
                                                      factory_unique_name,
                                                      object_path,
                                                      NULL /* cancellable */,
                                                      &err);
504
          if (receiver == NULL) {
505
            if (handle_create_receiver_proxy_error (service_name, err))
506 507 508
              return FALSE;
            else
              continue; /* Continue processing the remaining options! */
509 510 511 512
          }

          g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));

513 514
          char **argv = it->exec_argv ? it->exec_argv : options->exec_argv;
          int argc = argv ? g_strv_length (argv) : 0;
515

516 517 518
          PassFdElement *fd_array = it->fd_array ? (PassFdElement*)it->fd_array->data : NULL;
          gsize fd_array_len = it->fd_array ? it->fd_array->len : 0;

519
          terminal_client_append_exec_options (&builder,
520
                                               it->working_dir ? it->working_dir
521
                                                               : options->default_working_dir,
522
                                               fd_array, fd_array_len,
523 524
                                               argc == 0);

525 526 527
          if (!terminal_receiver_call_exec_sync (receiver,
                                                 g_variant_builder_end (&builder),
                                                 g_variant_new_bytestring_array ((const char * const *) argv, argc),
528 529 530
                                                 it->fd_list, NULL /* outfdlist */,
                                                 NULL /* cancellable */,
                                                 &err)) {
531
            if (handle_exec_error (service_name, err))
532 533 534
              return FALSE;
            else
              continue; /* Continue processing the remaining options! */
535
          }
536 537 538

          if (it->wait)
            gs_transfer_out_value (wait_for_receiver, &receiver);
539 540 541

          if (options->print_environment)
            g_print ("%s=%s\n", TERMINAL_ENV_SCREEN, object_path);
542 543 544 545 546
        }
    }

  return TRUE;
}
547

548 549 550
int
main (int argc, char **argv)
{
551
  int exit_code = EXIT_FAILURE;
552 553 554 555 556

#if GLIB_CHECK_VERSION (2, 50, 0)
  g_log_set_writer_func (terminal_log_writer, NULL, NULL);
#endif

557
  g_set_prgname ("gnome-terminal");
558

559 560
  setlocale (LC_ALL, "");

561
  terminal_i18n_init (TRUE);
562

563 564
  _terminal_debug_init ();

565 566
  gs_free_error GError *error = NULL;
  gs_free_options TerminalOptions *options = terminal_options_parse (&argc, &argv, &error);
567
  if (options == NULL) {
568
    terminal_printerr (_("Failed to parse arguments: %s\n"), error->message);
569
    return exit_code;
570
  }
571

572
  g_set_application_name (_("Terminal"));
573

574 575 576 577 578 579 580 581 582
  gs_unref_object TerminalFactory *factory = NULL;
  gs_free char *service_name = NULL;
  gs_free char *parent_screen_object_path = NULL;
  if (!factory_proxy_new (options,
                          &factory,
                          &service_name,
                          &parent_screen_object_path,
                          &error))
    return exit_code;
583

584 585 586 587 588
  if (options->print_environment) {
    const char *name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (factory));
    if (name_owner != NULL)
      g_print ("%s=%s\n", TERMINAL_ENV_SERVICE_NAME, name_owner);
    else
589
      return exit_code;
590 591
  }

592
  TerminalReceiver *receiver = NULL;
593 594
  if (!handle_options (options, factory, service_name, parent_screen_object_path, &receiver))
    return exit_code;
595 596

  if (receiver != NULL) {
597
    exit_code = run_receiver (factory, receiver);
598 599
    g_object_unref (receiver);
  } else
600
    exit_code = EXIT_SUCCESS;
601

602
  return exit_code;
603
}