gb-terminal-view.c 26.8 KB
Newer Older
1
/* gb-terminal-view.c
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * 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
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

Christian Hergert's avatar
Christian Hergert committed
19
#define G_LOG_DOMAIN "gb-terminal-view"
kritarth's avatar
kritarth committed
20
#define PCRE2_CODE_UNIT_WIDTH 0
Christian Hergert's avatar
Christian Hergert committed
21 22

#include "config.h"
23

24
#include <fcntl.h>
25 26
#include <glib/gi18n.h>
#include <ide.h>
kritarth's avatar
kritarth committed
27
#include <pcre2.h>
28
#include <stdlib.h>
29
#include <vte/vte.h>
30
#include <unistd.h>
31

32
#include "gb-terminal.h"
33
#include "gb-terminal-util.h"
34
#include "gb-terminal-view.h"
35 36
#include "gb-terminal-view-private.h"
#include "gb-terminal-view-actions.h"
37

38
G_DEFINE_TYPE (GbTerminalView, gb_terminal_view, IDE_TYPE_LAYOUT_VIEW)
39

40 41 42
enum {
  PROP_0,
  PROP_FONT_NAME,
43 44
  PROP_MANAGE_SPAWN,
  PROP_PTY,
45
  PROP_RUNTIME,
46 47 48
  LAST_PROP
};

kritarth's avatar
kritarth committed
49
static GParamSpec *properties[LAST_PROP];
50
static gchar *cached_shell;
51

Christian Hergert's avatar
Christian Hergert committed
52
/* TODO: allow palette to come from gnome-terminal. */
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
static const GdkRGBA solarized_palette[] =
{
  /*
   * Solarized palette (1.0.0beta2):
   * http://ethanschoonover.com/solarized
   */
  { 0.02745,  0.211764, 0.258823, 1 },
  { 0.862745, 0.196078, 0.184313, 1 },
  { 0.521568, 0.6,      0,        1 },
  { 0.709803, 0.537254, 0,        1 },
  { 0.149019, 0.545098, 0.823529, 1 },
  { 0.82745,  0.211764, 0.509803, 1 },
  { 0.164705, 0.631372, 0.596078, 1 },
  { 0.933333, 0.909803, 0.835294, 1 },
  { 0,        0.168627, 0.211764, 1 },
  { 0.796078, 0.294117, 0.086274, 1 },
  { 0.345098, 0.431372, 0.458823, 1 },
  { 0.396078, 0.482352, 0.513725, 1 },
  { 0.513725, 0.580392, 0.588235, 1 },
  { 0.423529, 0.443137, 0.768627, 1 },
  { 0.57647,  0.631372, 0.631372, 1 },
  { 0.992156, 0.964705, 0.890196, 1 },
};

77
static void gb_terminal_view_connect_terminal (GbTerminalView *self,
78 79 80 81
                                               VteTerminal    *terminal);
static void gb_terminal_respawn               (GbTerminalView *self,
                                               VteTerminal    *terminal);

82
static gchar *
83 84 85
gb_terminal_view_discover_shell (GbTerminalView  *self,
                                 GCancellable    *cancellable,
                                 GError         **error)
86 87 88 89 90
{
  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
  g_autoptr(IdeSubprocess) subprocess = NULL;
  g_autofree gchar *command = NULL;
  g_autofree gchar *stdout_buf = NULL;
91
  g_auto(GStrv) argv = NULL;
92 93 94

  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));

Christian Hergert's avatar
Christian Hergert committed
95
  if (cached_shell != NULL)
96
    return g_strdup (cached_shell);
97

98
  command = g_strdup_printf ("sh -c 'getent passwd | grep ^%s: | cut -f 7 -d :'",
99 100
                             g_get_user_name ());

101 102 103
  if (!g_shell_parse_argv (command, NULL, &argv, error))
    return NULL;

104 105 106 107 108
  /*
   * We don't use the runtime shell here, because we want to know
   * what the host thinks the user shell should be.
   */
  launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
109

110 111 112
  ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
  ide_subprocess_launcher_set_cwd (launcher, g_get_home_dir ());
113
  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
114

115
  subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, error);
116 117 118 119 120 121 122

  if (subprocess == NULL)
    return NULL;

  if (!ide_subprocess_communicate_utf8 (subprocess, NULL, cancellable, &stdout_buf, NULL, error))
    return NULL;

123 124 125 126 127 128
  if (stdout_buf != NULL)
    {
      g_strstrip (stdout_buf);
      if (stdout_buf[0] == '/')
        cached_shell = g_steal_pointer (&stdout_buf);
    }
129

130 131 132 133 134 135
  if (cached_shell == NULL)
    g_set_error_literal (error,
                         G_IO_ERROR,
                         G_IO_ERROR_FAILED,
                         "Unknown error when discovering user shell");

136
  return g_strdup (cached_shell);
137 138
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
static void
gb_terminal_view_wait_cb (GObject      *object,
                          GAsyncResult *result,
                          gpointer      user_data)
{
  IdeSubprocess *subprocess = (IdeSubprocess *)object;
  VteTerminal *terminal = user_data;
  GbTerminalView *self;
  g_autoptr(GError) error = NULL;

  IDE_ENTRY;

  g_assert (IDE_IS_SUBPROCESS (subprocess));
  g_assert (G_IS_ASYNC_RESULT (result));
  g_assert (VTE_IS_TERMINAL (terminal));

  if (!ide_subprocess_wait_finish (subprocess, result, &error))
    {
      g_warning ("%s", error->message);
      IDE_GOTO (failure);
    }

  self = (GbTerminalView *)gtk_widget_get_ancestor (GTK_WIDGET (terminal), GB_TYPE_TERMINAL_VIEW);
  if (self == NULL)
    IDE_GOTO (failure);

  if (!ide_widget_action (GTK_WIDGET (self), "view-stack", "close", NULL))
    {
      if (!gtk_widget_in_destruction (GTK_WIDGET (terminal)))
        gb_terminal_respawn (self, terminal);
    }

failure:
  g_clear_object (&terminal);

  IDE_EXIT;
}
176

177 178 179 180 181 182 183 184 185 186 187 188 189
static gboolean
terminal_has_notification_signal (void)
{
  GQuark quark;
  guint signal_id;

  return g_signal_parse_name ("notification-received",
                              VTE_TYPE_TERMINAL,
                              &signal_id,
                              &quark,
                              FALSE);
}

190 191 192
static void
gb_terminal_respawn (GbTerminalView *self,
                     VteTerminal    *terminal)
193
{
194 195
  g_autoptr(IdeSubprocess) subprocess = NULL;
  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
196
  g_autofree gchar *workpath = NULL;
197
  g_autofree gchar *shell = NULL;
198 199 200 201
  GtkWidget *toplevel;
  GError *error = NULL;
  IdeContext *context;
  IdeVcs *vcs;
202
  VtePty *pty = NULL;
203
  GFile *workdir;
204
  gint64 now;
205
  int tty_fd = -1;
206 207
  gint stdout_fd = -1;
  gint stderr_fd = -1;
208 209

  IDE_ENTRY;
210

211
  g_assert (GB_IS_TERMINAL_VIEW (self));
212

213
  vte_terminal_reset (terminal, TRUE, TRUE);
214 215

  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
216
  if (!IDE_IS_WORKBENCH (toplevel))
217
    IDE_EXIT;
218

219 220 221
  /* Prevent flapping */
  now = g_get_monotonic_time ();
  if ((now - self->last_respawn) < (G_USEC_PER_SEC / 10))
222
    IDE_EXIT;
223 224
  self->last_respawn = now;

225
  context = ide_workbench_get_context (IDE_WORKBENCH (toplevel));
226 227 228 229
  vcs = ide_context_get_vcs (context);
  workdir = ide_vcs_get_working_directory (vcs);
  workpath = g_file_get_path (workdir);

230
  shell = gb_terminal_view_discover_shell (self, NULL, &error);
231

232
  if (shell == NULL)
233 234
    {
      g_warning ("Failed to discover user shell: %s", error->message);
235 236 237 238 239 240 241

      /* We prefer bash in flatpak over sh */
      if (ide_is_flatpak ())
        shell = g_strdup ("/bin/bash");
      else
        shell = vte_get_user_shell ();

242 243
      g_clear_error (&error);
    }
244

245 246 247 248 249 250 251 252 253
  pty = vte_terminal_pty_new_sync (terminal,
                                   VTE_PTY_DEFAULT | VTE_PTY_NO_LASTLOG | VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP,
                                   NULL,
                                   &error);
  if (pty == NULL)
    IDE_GOTO (failure);

  vte_terminal_set_pty (terminal, pty);

254
  if (-1 == (tty_fd = gb_vte_pty_create_slave (pty)))
255 256
    IDE_GOTO (failure);

257 258 259 260
  /* dup() is safe as it will inherit O_CLOEXEC */
  if (-1 == (stdout_fd = dup (tty_fd)) || -1 == (stderr_fd = dup (tty_fd)))
    IDE_GOTO (failure);

261 262 263 264 265 266 267
  if (self->runtime != NULL)
    launcher = ide_runtime_create_launcher (self->runtime, NULL);

  if (launcher == NULL)
    launcher = ide_subprocess_launcher_new (0);

  ide_subprocess_launcher_set_flags (launcher, 0);
268 269 270
  ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
  ide_subprocess_launcher_set_cwd (launcher, workpath);
271
  ide_subprocess_launcher_push_argv (launcher, shell);
272 273 274
  ide_subprocess_launcher_take_stdin_fd (launcher, tty_fd);
  ide_subprocess_launcher_take_stdout_fd (launcher, stdout_fd);
  ide_subprocess_launcher_take_stderr_fd (launcher, stderr_fd);
275
  ide_subprocess_launcher_setenv (launcher, "TERM", "xterm-256color", TRUE);
276
  ide_subprocess_launcher_setenv (launcher, "INSIDE_GNOME_BUILDER", PACKAGE_VERSION, TRUE);
277
  ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE);
278

279 280 281 282 283
  tty_fd = -1;
  stdout_fd = -1;
  stderr_fd = -1;

  if (NULL == (subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
284 285 286 287 288 289 290 291 292 293 294
    IDE_GOTO (failure);

  ide_subprocess_wait_async (subprocess,
                             NULL,
                             gb_terminal_view_wait_cb,
                             g_object_ref (terminal));

failure:
  if (tty_fd != -1)
    close (tty_fd);

295 296 297
  if (stdout_fd != -1)
    close (stdout_fd);

298
  g_clear_object (&pty);
299 300 301 302 303 304 305

  if (error != NULL)
    {
      g_warning ("%s", error->message);
      g_clear_error (&error);
    }

306
  IDE_EXIT;
307 308 309 310 311
}

static void
gb_terminal_realize (GtkWidget *widget)
{
312
  GbTerminalView *self = (GbTerminalView *)widget;
313

314
  g_assert (GB_IS_TERMINAL_VIEW (self));
315

316
  GTK_WIDGET_CLASS (gb_terminal_view_parent_class)->realize (widget);
317

318
  if (self->manage_spawn && !self->top_has_spawned)
319
    {
320 321
      self->top_has_spawned = TRUE;
      gb_terminal_respawn (self, self->terminal_top);
322
    }
323 324 325

  if (!self->manage_spawn && self->pty != NULL)
    vte_terminal_set_pty (self->terminal_top, self->pty);
326 327 328
}

static void
329 330 331
size_allocate_cb (VteTerminal    *terminal,
                  GtkAllocation  *alloc,
                  GbTerminalView *self)
332 333 334 335 336 337 338 339
{
  glong width;
  glong height;
  glong columns;
  glong rows;

  g_assert (VTE_IS_TERMINAL (terminal));
  g_assert (alloc != NULL);
340
  g_assert (GB_IS_TERMINAL_VIEW (self));
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356

  if ((alloc->width == 0) || (alloc->height == 0))
    return;

  width = vte_terminal_get_char_width (terminal);
  height = vte_terminal_get_char_height (terminal);

  if ((width == 0) || (height == 0))
    return;

  columns = alloc->width / width;
  rows = alloc->height / height;

  if ((columns < 2) || (rows < 2))
    return;

357
  vte_terminal_set_size (terminal, columns, rows);
358 359 360 361 362 363 364 365 366 367
}

static void
gb_terminal_get_preferred_width (GtkWidget *widget,
                                 gint      *min_width,
                                 gint      *nat_width)
{
  /*
   * Since we are placing the terminal in a GtkStack, we need
   * to fake the size a bit. Otherwise, GtkStack tries to keep the
Matthew Leeds's avatar
Matthew Leeds committed
368
   * widget at its natural size (which prevents us from getting
369 370
   * appropriate size requests.
   */
371
  GTK_WIDGET_CLASS (gb_terminal_view_parent_class)->get_preferred_width (widget, min_width, nat_width);
372 373 374 375 376 377 378 379 380 381 382
  *nat_width = *min_width;
}

static void
gb_terminal_get_preferred_height (GtkWidget *widget,
                                  gint      *min_height,
                                  gint      *nat_height)
{
  /*
   * Since we are placing the terminal in a GtkStack, we need
   * to fake the size a bit. Otherwise, GtkStack tries to keep the
Matthew Leeds's avatar
Matthew Leeds committed
383
   * widget at its natural size (which prevents us from getting
384 385
   * appropriate size requests.
   */
386
  GTK_WIDGET_CLASS (gb_terminal_view_parent_class)->get_preferred_height (widget, min_height, nat_height);
387 388 389 390
  *nat_height = *min_height;
}

static void
391 392 393
gb_terminal_set_needs_attention (GbTerminalView  *self,
                                 gboolean         needs_attention,
                                 GtkPositionType  position)
394 395 396
{
  GtkWidget *parent;

397
  g_assert (GB_IS_TERMINAL_VIEW (self));
398 399 400

  parent = gtk_widget_get_parent (GTK_WIDGET (self));

401 402 403
  if (GTK_IS_STACK (parent) &&
      !gtk_widget_in_destruction (GTK_WIDGET (self)) &&
      !gtk_widget_in_destruction (parent))
404
    {
405 406 407 408 409 410 411 412 413 414 415
      if (position == GTK_POS_TOP &&
          !gtk_widget_in_destruction (GTK_WIDGET (self->terminal_top)))
        {
          self->top_has_needs_attention = TRUE;
        }
      else if (position == GTK_POS_BOTTOM &&
               self->terminal_bottom != NULL &&
               !gtk_widget_in_destruction (GTK_WIDGET (self->terminal_bottom)))
        {
          self->bottom_has_needs_attention = TRUE;
        }
416

417
      gtk_container_child_set (GTK_CONTAINER (parent), GTK_WIDGET (self),
418 419 420
                               "needs-attention",
                               !!(self->top_has_needs_attention || self->bottom_has_needs_attention) &&
                               needs_attention,
421 422 423 424 425
                               NULL);
    }
}

static void
426 427 428 429
notification_received_cb (VteTerminal    *terminal,
                          const gchar    *summary,
                          const gchar    *body,
                          GbTerminalView *self)
430 431
{
  g_assert (VTE_IS_TERMINAL (terminal));
432
  g_assert (GB_IS_TERMINAL_VIEW (self));
433 434

  if (!gtk_widget_has_focus (GTK_WIDGET (terminal)))
435 436 437 438 439 440 441 442
    {
      if (terminal == self->terminal_top)
        gb_terminal_set_needs_attention (self, TRUE, GTK_POS_TOP);
      else if (terminal == self->terminal_bottom)
        gb_terminal_set_needs_attention (self, TRUE, GTK_POS_BOTTOM);
    }
}

443
static gchar *
444
gb_terminal_get_title (IdeLayoutView *view)
445
{
446
  const gchar *title = NULL;
447 448 449 450 451 452 453 454 455
  GbTerminalView *self = (GbTerminalView *)view;

  g_assert (GB_IS_TERMINAL_VIEW (self));

  if (self->bottom_has_focus)
    title = vte_terminal_get_window_title (self->terminal_bottom);
  else
    title = vte_terminal_get_window_title (self->terminal_top);

456 457 458
  if (title == NULL)
    title = _("Untitled terminal");

459
  return g_strdup (title);
460 461 462
}

static gboolean
463 464 465
focus_in_event_cb (VteTerminal    *terminal,
                   GdkEvent       *event,
                   GbTerminalView *self)
466 467
{
  g_assert (VTE_IS_TERMINAL (terminal));
468
  g_assert (GB_IS_TERMINAL_VIEW (self));
469

470
  self->bottom_has_focus = (terminal != self->terminal_top);
471

472 473 474 475
  if (terminal == self->terminal_top)
    {
      self->top_has_needs_attention = FALSE;
      gb_terminal_set_needs_attention (self, FALSE, GTK_POS_TOP);
kritarth's avatar
kritarth committed
476
      gtk_revealer_set_reveal_child (self->search_revealer_top, FALSE);
477 478 479 480 481
    }
  else if (terminal == self->terminal_bottom)
    {
      self->bottom_has_needs_attention = FALSE;
      gb_terminal_set_needs_attention (self, FALSE, GTK_POS_BOTTOM);
kritarth's avatar
kritarth committed
482
      gtk_revealer_set_reveal_child (self->search_revealer_bottom, FALSE);
483
    }
484

485
  return GDK_EVENT_PROPAGATE;
486 487 488
}

static void
489 490
window_title_changed_cb (VteTerminal    *terminal,
                         GbTerminalView *self)
491
{
492
  g_assert (VTE_IS_TERMINAL (terminal));
493
  g_assert (GB_IS_TERMINAL_VIEW (self));
494

495
  g_object_notify (G_OBJECT (self), "title");
496 497
}

498 499 500 501
static void
style_context_changed (GtkStyleContext *style_context,
                       GbTerminalView  *self)
{
502
  GtkStateFlags state;
503 504 505 506 507 508
  GdkRGBA fg;
  GdkRGBA bg;

  g_assert (GTK_IS_STYLE_CONTEXT (style_context));
  g_assert (GB_IS_TERMINAL_VIEW (self));

509 510
  state = gtk_style_context_get_state (style_context);

511
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
512 513
  gtk_style_context_get_color (style_context, state, &fg);
  gtk_style_context_get_background_color (style_context, state, &bg);
514 515 516 517 518 519 520
  G_GNUC_END_IGNORE_DEPRECATIONS;

  if (bg.alpha == 0.0)
    {
      gdk_rgba_parse (&bg, "#f6f7f8");
    }

521
  vte_terminal_set_colors (self->terminal_top, &fg, &bg,
522 523
                           solarized_palette,
                           G_N_ELEMENTS (solarized_palette));
524 525 526 527 528

  if (self->terminal_bottom)
    vte_terminal_set_colors (self->terminal_bottom, &fg, &bg,
                             solarized_palette,
                             G_N_ELEMENTS (solarized_palette));
529 530
}

531
static IdeLayoutView *
532 533
gb_terminal_create_split (IdeLayoutView *view,
                          GFile         *file)
534
{
535
  IdeLayoutView *new_view;
536

537 538 539 540 541
  g_assert (GB_IS_TERMINAL_VIEW (view));

  new_view = g_object_new (GB_TYPE_TERMINAL_VIEW,
                          "visible", TRUE,
                          NULL);
542

543
  return new_view;
544 545
}

546 547 548 549 550 551 552 553 554 555 556 557 558
static void
gb_terminal_view_set_font_name (GbTerminalView *self,
                                const gchar    *font_name)
{
  PangoFontDescription *font_desc = NULL;

  g_assert (GB_IS_TERMINAL_VIEW (self));

  if (font_name != NULL)
    font_desc = pango_font_description_from_string (font_name);

  if (font_desc != NULL)
    {
559 560 561 562 563
      vte_terminal_set_font (self->terminal_top, font_desc);

      if (self->terminal_bottom)
        vte_terminal_set_font (self->terminal_bottom, font_desc);

564 565 566 567
      pango_font_description_free (font_desc);
    }
}

568 569 570 571 572 573 574 575 576 577
static gboolean
gb_terminal_get_split_view (IdeLayoutView *view)
{
  GbTerminalView *self = (GbTerminalView *)view;

  g_assert (GB_IS_TERMINAL_VIEW (self));

  return (self->terminal_bottom != NULL);
}

578
static void
579
gb_terminal_set_split_view (IdeLayoutView   *view,
Matthew Leeds's avatar
Matthew Leeds committed
580
                            gboolean         split_view)
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
{
  GbTerminalView *self = (GbTerminalView *)view;
  GtkStyleContext *style_context;

  g_assert (GB_IS_TERMINAL_VIEW (self));
  g_return_if_fail (GB_IS_TERMINAL_VIEW (self));

  if (split_view && (self->terminal_bottom != NULL))
    return;

  if (!split_view && (self->terminal_bottom == NULL))
    return;

  if (split_view)
    {
      style_context = gtk_widget_get_style_context (GTK_WIDGET (view));

598
      self->terminal_bottom = g_object_new (GB_TYPE_TERMINAL,
599
                                            "audible-bell", FALSE,
600
                                            "scrollback-lines", G_MAXUINT,
601 602 603
                                            "expand", TRUE,
                                            "visible", TRUE,
                                            NULL);
604 605 606 607
      gtk_container_add_with_properties (GTK_CONTAINER (self->bottom_container),
                                         GTK_WIDGET (self->terminal_bottom),
                                         "position", 0,
                                         NULL);
kritarth's avatar
kritarth committed
608 609 610
      gtk_widget_show ( GTK_WIDGET (self->terminal_overlay_bottom));

      self->bsearch = g_object_new (GB_TYPE_TERMINAL_SEARCH, NULL);
611
      self->search_revealer_bottom = gb_terminal_search_get_revealer (self->bsearch);
kritarth's avatar
kritarth committed
612 613 614

      gtk_overlay_add_overlay (self->terminal_overlay_bottom,
                               GTK_WIDGET (self->search_revealer_bottom));
615 616

      gb_terminal_view_connect_terminal (self, self->terminal_bottom);
kritarth's avatar
kritarth committed
617 618 619

      gb_terminal_search_set_terminal (self->bsearch, self->terminal_bottom);

620 621 622 623 624 625 626 627 628 629 630 631
      style_context_changed (style_context, GB_TERMINAL_VIEW (view));

      gtk_widget_grab_focus (GTK_WIDGET (self->terminal_bottom));

      if (!self->bottom_has_spawned)
        {
          self->bottom_has_spawned = TRUE;
          gb_terminal_respawn (self, self->terminal_bottom);
        }
    }
  else
    {
kritarth's avatar
kritarth committed
632 633
      gtk_container_remove (GTK_CONTAINER (self->terminal_overlay_bottom),
                            GTK_WIDGET (self->search_revealer_bottom));
634
      gtk_container_remove (GTK_CONTAINER (self->bottom_container),
635
                            GTK_WIDGET (self->terminal_bottom));
kritarth's avatar
kritarth committed
636
      gtk_widget_hide ( GTK_WIDGET (self->terminal_overlay_bottom));
637 638

      self->terminal_bottom = NULL;
kritarth's avatar
kritarth committed
639
      self->search_revealer_bottom = NULL;
640
      self->bottom_has_focus = FALSE;
641
      self->bottom_has_spawned = FALSE;
642 643
      self->bottom_has_needs_attention = FALSE;
      g_clear_object (&self->save_as_file_bottom);
kritarth's avatar
kritarth committed
644
      g_clear_object (&self->bsearch);
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
      gtk_widget_grab_focus (GTK_WIDGET (self->terminal_top));
    }
}

static void
gb_terminal_grab_focus (GtkWidget *widget)
{
  GbTerminalView *self = (GbTerminalView *)widget;

  g_assert (GB_IS_TERMINAL_VIEW (self));

  if (self->bottom_has_focus && self->terminal_bottom)
    gtk_widget_grab_focus (GTK_WIDGET (self->terminal_bottom));
  else
    gtk_widget_grab_focus (GTK_WIDGET (self->terminal_top));
}

static void
gb_terminal_view_connect_terminal (GbTerminalView *self,
                                   VteTerminal    *terminal)
{
666
  GtkAdjustment *vadj;
667

668 669 670 671 672 673 674
  vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (terminal));

  if (terminal == self->terminal_top)
    gtk_range_set_adjustment (GTK_RANGE (self->top_scrollbar), vadj);
  else
    gtk_range_set_adjustment (GTK_RANGE (self->bottom_scrollbar), vadj);

675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
  g_signal_connect_object (terminal,
                           "size-allocate",
                           G_CALLBACK (size_allocate_cb),
                           self,
                           0);

  g_signal_connect_object (terminal,
                           "focus-in-event",
                           G_CALLBACK (focus_in_event_cb),
                           self,
                           0);

  g_signal_connect_object (terminal,
                           "window-title-changed",
                           G_CALLBACK (window_title_changed_cb),
                           self,
                           0);

693
  if (terminal_has_notification_signal ())
694 695 696 697 698 699 700 701 702 703 704 705 706 707
    {
      g_signal_connect_object (terminal,
                               "notification-received",
                               G_CALLBACK (notification_received_cb),
                               self,
                               0);
    }
}

static void
gb_terminal_view_finalize (GObject *object)
{
  GbTerminalView *self = GB_TERMINAL_VIEW (object);

708 709 710
  g_clear_object (&self->save_as_file_top);
  g_clear_object (&self->save_as_file_bottom);
  g_clear_pointer (&self->selection_buffer, g_free);
711
  g_clear_object (&self->pty);
712
  g_clear_object (&self->runtime);
713 714 715 716

  G_OBJECT_CLASS (gb_terminal_view_parent_class)->finalize (object);
}

717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734
static void
gb_terminal_view_get_property (GObject    *object,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  GbTerminalView *self = GB_TERMINAL_VIEW (object);

  switch (prop_id)
    {
    case PROP_MANAGE_SPAWN:
      g_value_set_boolean (value, self->manage_spawn);
      break;

    case PROP_PTY:
      g_value_set_object (value, self->pty);
      break;

735 736 737 738
    case PROP_RUNTIME:
      g_value_set_object (value, self->runtime);
      break;

739 740 741 742 743
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

744 745 746 747 748 749
static void
gb_terminal_view_set_property (GObject      *object,
                               guint         prop_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
750
  GbTerminalView *self = GB_TERMINAL_VIEW (object);
751 752 753 754 755 756 757

  switch (prop_id)
    {
    case PROP_FONT_NAME:
      gb_terminal_view_set_font_name (self, g_value_get_string (value));
      break;

758 759 760 761 762 763 764 765
    case PROP_MANAGE_SPAWN:
      self->manage_spawn = g_value_get_boolean (value);
      break;

    case PROP_PTY:
      self->pty = g_value_dup_object (value);
      break;

766 767 768 769
    case PROP_RUNTIME:
      self->runtime = g_value_dup_object (value);
      break;

770 771 772 773 774
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

775
static void
776
gb_terminal_view_class_init (GbTerminalViewClass *klass)
777
{
778
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
779
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
780
  IdeLayoutViewClass *view_class = IDE_LAYOUT_VIEW_CLASS (klass);
781

782
  object_class->finalize = gb_terminal_view_finalize;
783
  object_class->get_property = gb_terminal_view_get_property;
784 785
  object_class->set_property = gb_terminal_view_set_property;

786 787 788
  widget_class->realize = gb_terminal_realize;
  widget_class->get_preferred_width = gb_terminal_get_preferred_width;
  widget_class->get_preferred_height = gb_terminal_get_preferred_height;
789 790 791
  widget_class->grab_focus = gb_terminal_grab_focus;

  view_class->get_title = gb_terminal_get_title;
792
  view_class->create_split = gb_terminal_create_split;
793
  view_class->get_split_view =  gb_terminal_get_split_view;
794 795 796 797
  view_class->set_split_view =  gb_terminal_set_split_view;

  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/plugins/terminal/gb-terminal-view.ui");
  gtk_widget_class_bind_template_child (widget_class, GbTerminalView, terminal_top);
798
  gtk_widget_class_bind_template_child (widget_class, GbTerminalView, bottom_container);
799 800
  gtk_widget_class_bind_template_child (widget_class, GbTerminalView, top_scrollbar);
  gtk_widget_class_bind_template_child (widget_class, GbTerminalView, bottom_scrollbar);
kritarth's avatar
kritarth committed
801 802
  gtk_widget_class_bind_template_child (widget_class, GbTerminalView, terminal_overlay_top);
  gtk_widget_class_bind_template_child (widget_class, GbTerminalView, terminal_overlay_bottom);
803 804 805

  g_type_ensure (VTE_TYPE_TERMINAL);

806
  properties [PROP_FONT_NAME] =
807
    g_param_spec_string ("font-name",
Piotr Drąg's avatar
Piotr Drąg committed
808 809
                         "Font Name",
                         "Font Name",
810 811 812
                         NULL,
                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));

813 814 815 816 817 818 819 820 821 822 823 824 825 826
  properties [PROP_MANAGE_SPAWN] =
    g_param_spec_boolean ("manage-spawn",
                          "Manage Spawn",
                          "Manage Spawn",
                          TRUE,
                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  properties [PROP_PTY] =
    g_param_spec_object ("pty",
                         "Pty",
                         "The psuedo terminal to use",
                         VTE_TYPE_PTY,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

827 828 829 830 831 832 833
  properties [PROP_RUNTIME] =
    g_param_spec_object ("runtime",
                         "Runtime",
                         "The runtime to use for spawning",
                         IDE_TYPE_RUNTIME,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

834
  g_object_class_install_properties (object_class, LAST_PROP, properties);
835 836

  g_type_ensure (GB_TYPE_TERMINAL);
837 838 839
}

static void
840
gb_terminal_view_init (GbTerminalView *self)
841
{
842
  GtkStyleContext *style_context;
843
  g_autoptr(GSettings) settings = NULL;
844

845 846
  self->manage_spawn = TRUE;

kritarth's avatar
kritarth committed
847
  self->tsearch = g_object_new (GB_TYPE_TERMINAL_SEARCH, NULL);
848
  self->search_revealer_top = gb_terminal_search_get_revealer (self->tsearch);
kritarth's avatar
kritarth committed
849

850 851
  gtk_widget_init_template (GTK_WIDGET (self));

kritarth's avatar
kritarth committed
852 853 854
  gtk_overlay_add_overlay (self->terminal_overlay_top,
                           GTK_WIDGET (self->search_revealer_top));

855
  gb_terminal_view_connect_terminal (self, self->terminal_top);
kritarth's avatar
kritarth committed
856 857 858

  gb_terminal_search_set_terminal (self->tsearch, self->terminal_top);

859
  gb_terminal_view_actions_init (self);
860

861
  settings = g_settings_new ("org.gnome.builder.terminal");
862 863
  g_settings_bind (settings, "font-name", self, "font-name", G_SETTINGS_BIND_GET);

864
  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
865
  gtk_style_context_add_class (style_context, "terminal");
866 867 868 869 870 871
  g_signal_connect_object (style_context,
                           "changed",
                           G_CALLBACK (style_context_changed),
                           self,
                           0);
  style_context_changed (style_context, self);
Debarshi's avatar
Debarshi committed
872 873

  gtk_widget_set_can_focus (GTK_WIDGET (self->terminal_top), TRUE);
874
}
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894

void
gb_terminal_view_set_pty (GbTerminalView *self,
                          VtePty         *pty)
{
  g_return_if_fail (GB_IS_TERMINAL_VIEW (self));
  g_return_if_fail (VTE_IS_PTY (pty));

  if (self->manage_spawn)
    {
      g_warning ("Cannot set pty when GbTerminalView manages tty");
      return;
    }

  if (self->terminal_top)
    {
      vte_terminal_reset (self->terminal_top, TRUE, TRUE);
      vte_terminal_set_pty (self->terminal_top, pty);
    }
}