[gnome-builder] libide-terminal: port to GTK 4 and IdeRunContext



commit 4c502626469e1d8a1257e11df12c63a940bcb1bb
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 22:03:54 2022 -0700

    libide-terminal: port to GTK 4 and IdeRunContext
    
     - Remove libdazzle usage
     - Use VTE GTK 4 port (still very much WIP)
     - Port to libpanel
     - Revamp IdeTerminalLauncher to be cleaner thanks to IdeRunContext
       and IdeRunCommand.
     - Remove use of IdeTerminalSurface/IdeTerminalWorkspace as they are not
       used. We could bring back the terminal workspace at some point, but it
       would need really strong direction up-front to replace other tooling
       used as container front-ends if we're to do that.
     - Update availability macros

 ...de-terminal-workspace.h => ide-terminal-init.c} |  40 +-
 src/libide/terminal/ide-terminal-launcher.c        | 829 +++------------------
 src/libide/terminal/ide-terminal-launcher.h        |  67 +-
 src/libide/terminal/ide-terminal-page-actions.c    |  22 +-
 src/libide/terminal/ide-terminal-page-private.h    |   9 +-
 src/libide/terminal/ide-terminal-page.c            | 303 +++-----
 src/libide/terminal/ide-terminal-page.h            |  30 +-
 src/libide/terminal/ide-terminal-page.ui           |  20 +-
 src/libide/terminal/ide-terminal-popover.c         |  54 +-
 src/libide/terminal/ide-terminal-popover.h         |   6 +-
 src/libide/terminal/ide-terminal-private.h         |   4 +-
 ...urface.h => ide-terminal-run-command-private.h} |  28 +-
 src/libide/terminal/ide-terminal-run-command.c     | 144 ++++
 src/libide/terminal/ide-terminal-search-private.h  |  10 +-
 src/libide/terminal/ide-terminal-search.c          |  48 +-
 src/libide/terminal/ide-terminal-search.h          |  18 +-
 src/libide/terminal/ide-terminal-search.ui         | 277 +++----
 src/libide/terminal/ide-terminal-surface.c         | 108 ---
 src/libide/terminal/ide-terminal-surface.ui        |  10 -
 src/libide/terminal/ide-terminal-util.c            | 113 +--
 src/libide/terminal/ide-terminal-util.h            |   6 +-
 src/libide/terminal/ide-terminal-workspace.c       |  97 ---
 src/libide/terminal/ide-terminal-workspace.ui      |  45 --
 src/libide/terminal/ide-terminal.c                 | 296 +++++---
 src/libide/terminal/ide-terminal.h                 |  14 +-
 src/libide/terminal/libide-terminal.gresource.xml  |   2 -
 src/libide/terminal/libide-terminal.h              |   2 -
 src/libide/terminal/meson.build                    |  10 +-
 28 files changed, 799 insertions(+), 1813 deletions(-)
---
diff --git a/src/libide/terminal/ide-terminal-workspace.h b/src/libide/terminal/ide-terminal-init.c
similarity index 52%
rename from src/libide/terminal/ide-terminal-workspace.h
rename to src/libide/terminal/ide-terminal-init.c
index 23a3acd9f..d80246221 100644
--- a/src/libide/terminal/ide-terminal-workspace.h
+++ b/src/libide/terminal/ide-terminal-init.c
@@ -1,6 +1,6 @@
-/* ide-terminal-workspace.h
+/* ide-terminal-init.c
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2022 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -18,23 +18,19 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#pragma once
-
-#if !defined (IDE_TERMINAL_INSIDE) && !defined (IDE_TERMINAL_COMPILATION)
-# error "Only <libide-terminal.h> can be included directly."
-#endif
-
-#include <libide-core.h>
-#include <libide-gui.h>
-
-G_BEGIN_DECLS
-
-#define IDE_TYPE_TERMINAL_WORKSPACE (ide_terminal_workspace_get_type())
-
-IDE_AVAILABLE_IN_3_32
-G_DECLARE_FINAL_TYPE (IdeTerminalWorkspace, ide_terminal_workspace, IDE, TERMINAL_WORKSPACE, IdeWorkspace)
-
-IDE_AVAILABLE_IN_3_34
-IdeTerminalWorkspace *ide_terminal_workspace_new (IdeApplication *application);
-
-G_END_DECLS
+#define G_LOG_DOMAIN "ide-terminal-init"
+
+#include "ide-terminal.h"
+#include "ide-terminal-launcher.h"
+#include "ide-terminal-page.h"
+#include "ide-terminal-private.h"
+#include "ide-terminal-search.h"
+
+void
+_ide_terminal_init (void)
+{
+  g_type_ensure (IDE_TYPE_TERMINAL);
+  g_type_ensure (IDE_TYPE_TERMINAL_LAUNCHER);
+  g_type_ensure (IDE_TYPE_TERMINAL_PAGE);
+  g_type_ensure (IDE_TYPE_TERMINAL_SEARCH);
+}
diff --git a/src/libide/terminal/ide-terminal-launcher.c b/src/libide/terminal/ide-terminal-launcher.c
index 7c8e76c26..728727f4a 100644
--- a/src/libide/terminal/ide-terminal-launcher.c
+++ b/src/libide/terminal/ide-terminal-launcher.c
@@ -23,175 +23,31 @@
 #include "config.h"
 
 #include <errno.h>
+
 #include <glib/gi18n.h>
+
 #include <libide-foundry.h>
 #include <libide-threading.h>
 
-#include "ide-private.h"
-
 #include "ide-terminal-launcher.h"
-#include "ide-terminal-util.h"
-
-typedef enum
-{
-  LAUNCHER_KIND_HOST = 0,
-  LAUNCHER_KIND_DEBUG,
-  LAUNCHER_KIND_RUNTIME,
-  LAUNCHER_KIND_RUNNER,
-  LAUNCHER_KIND_LAUNCHER,
-  LAUNCHER_KIND_CONFIG,
-} LauncherKind;
 
 struct _IdeTerminalLauncher
 {
-  GObject                parent_instance;
-  gchar                 *cwd;
-  gchar                 *shell;
-  gchar                 *title;
-  gchar                **args;
-  IdeRuntime            *runtime;
-  IdeContext            *context;
-  IdeConfig             *config;
-  IdeSubprocessLauncher *launcher;
-  LauncherKind           kind;
+  GObject        parent_instance;
+  IdeRunCommand *run_command;
+  IdeContext    *context;
 };
 
 G_DEFINE_FINAL_TYPE (IdeTerminalLauncher, ide_terminal_launcher, G_TYPE_OBJECT)
 
 enum {
   PROP_0,
-  PROP_ARGS,
-  PROP_CWD,
-  PROP_SHELL,
-  PROP_TITLE,
+  PROP_CONTEXT,
+  PROP_RUN_COMMAND,
   N_PROPS
 };
 
 static GParamSpec *properties [N_PROPS];
-static const struct {
-  const gchar *key;
-  const gchar *value;
-} default_environment[] = {
-  { "INSIDE_GNOME_BUILDER", PACKAGE_VERSION },
-  { "TERM", "xterm-256color" },
-};
-
-static gboolean
-shell_supports_login (const gchar *shell)
-{
-  g_autofree gchar *name = NULL;
-
-  /* Shells that support --login */
-  static const gchar *supported[] = {
-    "sh", "bash",
-  };
-
-  if (shell == NULL)
-    return FALSE;
-
-  if (!(name = g_path_get_basename (shell)))
-    return FALSE;
-
-  for (guint i = 0; i < G_N_ELEMENTS (supported); i++)
-    {
-      if (g_str_equal (name, supported[i]))
-        return TRUE;
-    }
-
-  return FALSE;
-}
-
-static void
-copy_envvars (gpointer instance)
-{
-  static const gchar *copy_env[] = {
-    "AT_SPI_BUS_ADDRESS",
-    "COLORTERM",
-    "DBUS_SESSION_BUS_ADDRESS",
-    "DBUS_SYSTEM_BUS_ADDRESS",
-    "DESKTOP_SESSION",
-    "DISPLAY",
-    "LANG",
-    "SHELL",
-    "SSH_AUTH_SOCK",
-    "USER",
-    "WAYLAND_DISPLAY",
-    "XAUTHORITY",
-    "XDG_CURRENT_DESKTOP",
-#if 0
-    /* Can't copy these as they could mess up Flatpak */
-    "XDG_DATA_DIRS",
-    "XDG_RUNTIME_DIR",
-#endif
-    "XDG_MENU_PREFIX",
-    "XDG_SEAT",
-    "XDG_SESSION_DESKTOP",
-    "XDG_SESSION_ID",
-    "XDG_SESSION_TYPE",
-    "XDG_VTNR",
-  };
-  const gchar * const *host_environ;
-  IdeEnvironment *env = NULL;
-
-  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (instance) || IDE_IS_RUNNER (instance));
-
-  if (IDE_IS_RUNNER (instance))
-    env = ide_runner_get_environment (instance);
-
-  host_environ = _ide_host_environ ();
-
-  for (guint i = 0; i < G_N_ELEMENTS (copy_env); i++)
-    {
-      const gchar *val = g_environ_getenv ((gchar **)host_environ, copy_env[i]);
-
-      if (val != NULL)
-        {
-          if (IDE_IS_SUBPROCESS_LAUNCHER (instance))
-            ide_subprocess_launcher_setenv (instance, copy_env[i], val, FALSE);
-          else
-            ide_environment_setenv (env, copy_env[i], val);
-        }
-    }
-}
-
-static void
-apply_pipeline_info (gpointer   instance,
-                     IdeObject *object)
-{
-  g_autoptr(GFile) workdir = NULL;
-  IdeEnvironment *env = NULL;
-  IdeContext *context;
-
-  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (instance) || IDE_IS_RUNNER (instance));
-  g_assert (IDE_IS_OBJECT (object));
-
-  context = ide_object_get_context (object);
-  workdir = ide_context_ref_workdir (context);
-
-  if (IDE_IS_RUNNER (instance))
-    env = ide_runner_get_environment (instance);
-
-  if (IDE_IS_SUBPROCESS_LAUNCHER (instance))
-    ide_subprocess_launcher_setenv (instance, "SRCDIR", g_file_peek_path (workdir), FALSE);
-  else
-    ide_environment_setenv (env, "SRCDIR", g_file_peek_path (workdir));
-
-  if (ide_context_has_project (context))
-    {
-      IdeBuildManager *build_manager = ide_build_manager_from_context (context);
-      IdePipeline *pipeline = ide_build_manager_get_pipeline (build_manager);
-
-      if (pipeline != NULL)
-        {
-          const gchar *builddir = ide_pipeline_get_builddir (pipeline);
-
-          if (IDE_IS_SUBPROCESS_LAUNCHER (instance))
-            ide_subprocess_launcher_setenv (instance, "BUILDDIR", builddir, FALSE);
-          else
-            ide_environment_setenv (env, "BUILDDIR", builddir);
-        }
-    }
-}
 
 static void
 ide_terminal_launcher_wait_check_cb (GObject      *object,
@@ -202,6 +58,9 @@ ide_terminal_launcher_wait_check_cb (GObject      *object,
   g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
 
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_SUBPROCESS (subprocess));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_TASK (task));
@@ -210,281 +69,8 @@ ide_terminal_launcher_wait_check_cb (GObject      *object,
     ide_task_return_error (task, g_steal_pointer (&error));
   else
     ide_task_return_boolean (task, TRUE);
-}
-
-static void
-spawn_host_launcher (IdeTerminalLauncher *self,
-                     IdeTask             *task,
-                     gint                 pty_fd,
-                     gboolean             run_on_host)
-{
-  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(IdeSubprocess) subprocess = NULL;
-  g_autoptr(GError) error = NULL;
-  const gchar *shell;
-
-  g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
-  g_assert (IDE_IS_TASK (task));
-  g_assert (pty_fd >= 0);
-
-  if (!(shell = ide_terminal_launcher_get_shell (self)))
-    shell = ide_get_user_shell ();
-
-  /* We only have sh/bash in our flatpak */
-  if (self->kind == LAUNCHER_KIND_DEBUG && ide_is_flatpak ())
-    shell = "/bin/bash";
-
-  launcher = ide_subprocess_launcher_new (0);
-  ide_subprocess_launcher_set_run_on_host (launcher, run_on_host);
-  ide_subprocess_launcher_set_cwd (launcher, self->cwd ? self->cwd : g_get_home_dir ());
-  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
-
-  ide_subprocess_launcher_push_argv (launcher, shell);
-  if (shell_supports_login (shell))
-    ide_subprocess_launcher_push_argv (launcher, "--login");
-
-  ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd));
-  ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd));
-  ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd));
-
-  g_assert (ide_subprocess_launcher_get_needs_tty (launcher));
-
-  for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++)
-    ide_subprocess_launcher_setenv (launcher,
-                                    default_environment[i].key,
-                                    default_environment[i].value,
-                                    FALSE);
-
-  ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE);
-
-  if (self->context != NULL)
-    {
-      g_autoptr(GFile) workdir = ide_context_ref_workdir (self->context);
-
-      ide_subprocess_launcher_setenv (launcher,
-                                      "SRCDIR",
-                                      g_file_peek_path (workdir),
-                                      FALSE);
-    }
-
-  if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
-    ide_task_return_error (task, g_steal_pointer (&error));
-  else
-    ide_subprocess_wait_check_async (subprocess,
-                                     ide_task_get_cancellable (task),
-                                     ide_terminal_launcher_wait_check_cb,
-                                     g_object_ref (task));
-}
-
-static void
-spawn_launcher (IdeTerminalLauncher   *self,
-                IdeTask               *task,
-                IdeSubprocessLauncher *launcher,
-                gint                   pty_fd)
-{
-  g_autoptr(IdeSubprocess) subprocess = NULL;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
-  g_assert (IDE_IS_TASK (task));
-  g_assert (!launcher || IDE_IS_SUBPROCESS_LAUNCHER (launcher));
-  g_assert (pty_fd >= 0);
-
-  if (launcher == NULL)
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 "process may only be spawned once");
-      return;
-    }
-
-  ide_subprocess_launcher_set_flags (launcher, 0);
-
-  ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd));
-  ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd));
-  ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd));
-
-  if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
-    ide_task_return_error (task, g_steal_pointer (&error));
-  else
-    ide_subprocess_wait_check_async (subprocess,
-                                     ide_task_get_cancellable (task),
-                                     ide_terminal_launcher_wait_check_cb,
-                                     g_object_ref (task));
-}
-
-static void
-spawn_runtime_launcher (IdeTerminalLauncher *self,
-                        IdeTask             *task,
-                        IdeRuntime          *runtime,
-                        IdeConfig           *config,
-                        gint                 pty_fd)
-{
-  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(IdeSubprocess) subprocess = NULL;
-  g_autoptr(GError) error = NULL;
-  const gchar *shell;
-
-  g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
-  g_assert (IDE_IS_TASK (task));
-  g_assert (!runtime || IDE_IS_RUNTIME (runtime));
-  g_assert (pty_fd >= 0);
-
-  if (runtime == NULL)
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 _("Requested runtime is not installed"));
-      return;
-    }
-
-  if (!(shell = ide_terminal_launcher_get_shell (self)))
-    shell = ide_get_user_shell ();
-
-  if (!(launcher = ide_runtime_create_launcher (runtime, NULL)))
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 _("Failed to create shell within runtime ā€œ%sā€"),
-                                 ide_runtime_get_display_name (runtime));
-      return;
-    }
-
-  ide_subprocess_launcher_set_flags (launcher, 0);
-
-  if (!ide_runtime_contains_program_in_path (runtime, shell, NULL))
-    shell = "/bin/sh";
-
-  ide_subprocess_launcher_set_cwd (launcher, self->cwd ? self->cwd : g_get_home_dir ());
-
-  ide_subprocess_launcher_push_argv (launcher, shell);
-  if (shell_supports_login (shell))
-    ide_subprocess_launcher_push_argv (launcher, "--login");
-
-  ide_subprocess_launcher_take_stdin_fd (launcher, dup (pty_fd));
-  ide_subprocess_launcher_take_stdout_fd (launcher, dup (pty_fd));
-  ide_subprocess_launcher_take_stderr_fd (launcher, dup (pty_fd));
-
-  g_assert (ide_subprocess_launcher_get_needs_tty (launcher));
-
-  for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++)
-    ide_subprocess_launcher_setenv (launcher,
-                                    default_environment[i].key,
-                                    default_environment[i].value,
-                                    FALSE);
-
-  apply_pipeline_info (launcher, IDE_OBJECT (self->runtime));
-  copy_envvars (launcher);
-
-  ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE);
-
-  if (config != NULL)
-    ide_config_apply_path (config, launcher);
-
-  if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
-    ide_task_return_error (task, g_steal_pointer (&error));
-  else
-    ide_subprocess_wait_check_async (subprocess,
-                                     ide_task_get_cancellable (task),
-                                     ide_terminal_launcher_wait_check_cb,
-                                     g_object_ref (task));
-}
-
-static void
-ide_terminal_launcher_run_cb (GObject      *object,
-                              GAsyncResult *result,
-                              gpointer      user_data)
-{
-  IdeRunner *runner = (IdeRunner *)object;
-  g_autoptr(IdeTask) task = user_data;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_RUNNER (runner));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  if (!ide_runner_run_finish (runner, result, &error))
-    ide_task_return_error (task, g_steal_pointer (&error));
-  else
-    ide_task_return_boolean (task, TRUE);
-
-  ide_object_destroy (IDE_OBJECT (runner));
-}
-
-static void
-spawn_runner_launcher (IdeTerminalLauncher *self,
-                       IdeTask             *task,
-                       IdeRuntime          *runtime,
-                       gint                 pty_fd)
-{
-  g_autoptr(IdeSimpleBuildTarget) build_target = NULL;
-  g_autoptr(IdeRunner) runner = NULL;
-  g_autoptr(GPtrArray) argv = NULL;
-  IdeEnvironment *env;
-  const gchar *shell;
-
-  g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
-  g_assert (IDE_IS_TASK (task));
-  g_assert (!runtime || IDE_IS_RUNTIME (runtime));
-  g_assert (pty_fd >= 0);
-
-  if (runtime == NULL)
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 _("Requested runtime is not installed"));
-      return;
-    }
-
-  if (!(shell = ide_terminal_launcher_get_shell (self)))
-    shell = ide_get_user_shell ();
-
-  if (!ide_runtime_contains_program_in_path (runtime, shell, NULL))
-    shell = "/bin/sh";
-
-  argv = g_ptr_array_new ();
-  g_ptr_array_add (argv, (gchar *)shell);
-
-  if (self->args == NULL)
-    {
-      if (shell_supports_login (shell))
-        g_ptr_array_add (argv, (gchar *)"--login");
-    }
-  else
-    {
-      for (guint i = 0; self->args[i]; i++)
-        g_ptr_array_add (argv, self->args[i]);
-    }
-
-  g_ptr_array_add (argv, NULL);
-
-  build_target = ide_simple_build_target_new (NULL);
-  ide_simple_build_target_set_argv (build_target, (const gchar * const *)argv->pdata);
-  ide_simple_build_target_set_cwd (build_target, self->cwd ? self->cwd : g_get_home_dir ());
-
-  /* Creating runner should always succeed, but run_async() may fail */
-  runner = ide_runtime_create_runner (runtime, IDE_BUILD_TARGET (build_target));
-  env = ide_runner_get_environment (runner);
-  ide_runner_take_tty_fd (runner, dup (pty_fd));
 
-  for (guint i = 0; i < G_N_ELEMENTS (default_environment); i++)
-    ide_environment_setenv (env,
-                            default_environment[i].key,
-                            default_environment[i].value);
-
-  apply_pipeline_info (runner, IDE_OBJECT (self->runtime));
-  copy_envvars (runner);
-
-  ide_environment_setenv (env, "SHELL", shell);
-
-  ide_runner_run_async (runner,
-                        ide_task_get_cancellable (task),
-                        ide_terminal_launcher_run_cb,
-                        g_object_ref (task));
+  IDE_EXIT;
 }
 
 void
@@ -494,9 +80,14 @@ ide_terminal_launcher_spawn_async (IdeTerminalLauncher *self,
                                    GAsyncReadyCallback  callback,
                                    gpointer             user_data)
 {
+  g_autoptr(IdeRunContext) run_context = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
   g_autoptr(IdeTask) task = NULL;
-  gint pty_fd = -1;
+  g_autoptr(GError) error = NULL;
 
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
   g_assert (VTE_IS_PTY (pty));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
@@ -504,44 +95,38 @@ ide_terminal_launcher_spawn_async (IdeTerminalLauncher *self,
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, ide_terminal_launcher_spawn_async);
 
-  if ((pty_fd = ide_vte_pty_create_slave (pty)) == -1)
+  run_context = ide_run_context_new ();
+
+  /* Paranoia check to ensure we've been constructed right */
+  if (self->run_command == NULL || self->context == NULL)
     {
-      int errsv = errno;
       ide_task_return_new_error (task,
                                  G_IO_ERROR,
-                                 g_io_error_from_errno (errsv),
-                                 "%s", g_strerror (errsv));
-      return;
+                                 G_IO_ERROR_NOT_INITIALIZED,
+                                 "%s is improperly configured",
+                                 G_OBJECT_TYPE_NAME (self));
+      IDE_EXIT;
     }
 
-  switch (self->kind)
-    {
-    case LAUNCHER_KIND_RUNTIME:
-      spawn_runtime_launcher (self, task, self->runtime, NULL, pty_fd);
-      break;
+  ide_run_command_prepare_to_run (self->run_command, run_context, self->context);
 
-    case LAUNCHER_KIND_CONFIG:
-      spawn_runtime_launcher (self, task, self->runtime, self->config, pty_fd);
-      break;
+  /* Add some environment for custom bashrc, VTE, etc */
+  ide_run_context_setenv (run_context, "INSIDE_GNOME_BUILDER", PACKAGE_VERSION);
+  ide_run_context_setenv (run_context, "TERM", "xterm-256color");
 
-    case LAUNCHER_KIND_RUNNER:
-      spawn_runner_launcher (self, task, self->runtime, pty_fd);
-      break;
-
-    case LAUNCHER_KIND_LAUNCHER:
-      spawn_launcher (self, task, self->launcher, pty_fd);
-      g_clear_object (&self->launcher);
-      break;
+  /* Attach the PTY to stdin/stdout/stderr */
+  ide_run_context_set_pty (run_context, pty);
 
-    case LAUNCHER_KIND_DEBUG:
-    case LAUNCHER_KIND_HOST:
-    default:
-      spawn_host_launcher (self, task, pty_fd, self->kind == LAUNCHER_KIND_HOST);
-      break;
-    }
+  /* Now attempt to spawn the process */
+  if (!(subprocess = ide_run_context_spawn (run_context, &error)))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_subprocess_wait_check_async (subprocess,
+                                     cancellable,
+                                     ide_terminal_launcher_wait_check_cb,
+                                     g_steal_pointer (&task));
 
-  if (pty_fd != -1)
-    close (pty_fd);
+  IDE_EXIT;
 }
 
 /**
@@ -552,34 +137,33 @@ ide_terminal_launcher_spawn_async (IdeTerminalLauncher *self,
  *
  * Returns: %TRUE if the process executed successfully; otherwise %FALSE
  *   and @error is set.
- *
- * Since: 3.34
  */
 gboolean
 ide_terminal_launcher_spawn_finish (IdeTerminalLauncher  *self,
                                     GAsyncResult         *result,
                                     GError              **error)
 {
+  gboolean ret;
+
+  IDE_ENTRY;
+
   g_assert (IDE_IS_TERMINAL_LAUNCHER (self));
   g_assert (IDE_IS_TASK (result));
 
-  return ide_task_propagate_boolean (IDE_TASK (result), error);
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
 }
 
 static void
-ide_terminal_launcher_finalize (GObject *object)
+ide_terminal_launcher_dispose (GObject *object)
 {
   IdeTerminalLauncher *self = (IdeTerminalLauncher *)object;
 
-  g_clear_pointer (&self->args, g_strfreev);
-  g_clear_pointer (&self->cwd, g_free);
-  g_clear_pointer (&self->shell, g_free);
-  g_clear_pointer (&self->title, g_free);
-  g_clear_object (&self->launcher);
-  g_clear_object (&self->runtime);
-  g_clear_object (&self->config);
+  g_clear_object (&self->context);
+  g_clear_object (&self->run_command);
 
-  G_OBJECT_CLASS (ide_terminal_launcher_parent_class)->finalize (object);
+  G_OBJECT_CLASS (ide_terminal_launcher_parent_class)->dispose (object);
 }
 
 static void
@@ -592,20 +176,12 @@ ide_terminal_launcher_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_ARGS:
-      g_value_set_boxed (value, ide_terminal_launcher_get_args (self));
+    case PROP_CONTEXT:
+      g_value_set_object (value, self->context);
       break;
 
-    case PROP_CWD:
-      g_value_set_string (value, ide_terminal_launcher_get_cwd (self));
-      break;
-
-    case PROP_SHELL:
-      g_value_set_string (value, ide_terminal_launcher_get_shell (self));
-      break;
-
-    case PROP_TITLE:
-      g_value_set_string (value, ide_terminal_launcher_get_title (self));
+    case PROP_RUN_COMMAND:
+      g_value_set_object (value, self->run_command);
       break;
 
     default:
@@ -623,20 +199,12 @@ ide_terminal_launcher_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_ARGS:
-      ide_terminal_launcher_set_args (self, g_value_get_boxed (value));
+    case PROP_CONTEXT:
+      g_set_object (&self->context, g_value_get_object (value));
       break;
 
-    case PROP_CWD:
-      ide_terminal_launcher_set_cwd (self, g_value_get_string (value));
-      break;
-
-    case PROP_SHELL:
-      ide_terminal_launcher_set_shell (self, g_value_get_string (value));
-      break;
-
-    case PROP_TITLE:
-      ide_terminal_launcher_set_title (self, g_value_get_string (value));
+    case PROP_RUN_COMMAND:
+      g_set_object (&self->run_command, g_value_get_object (value));
       break;
 
     default:
@@ -649,36 +217,22 @@ ide_terminal_launcher_class_init (IdeTerminalLauncherClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  object_class->finalize = ide_terminal_launcher_finalize;
+  object_class->dispose = ide_terminal_launcher_dispose;
   object_class->get_property = ide_terminal_launcher_get_property;
   object_class->set_property = ide_terminal_launcher_set_property;
 
-  properties [PROP_ARGS] =
-    g_param_spec_boxed ("args",
-                         "Args",
-                         "Arguments to shell",
-                         G_TYPE_STRV,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_CWD] =
-    g_param_spec_string ("cwd",
-                         "Cwd",
-                         "The cwd to spawn in the subprocess",
-                         NULL,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_SHELL] =
-    g_param_spec_string ("shell",
-                         "Shell",
-                         "The shell to spawn in the subprocess",
-                         NULL,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_TITLE] =
-    g_param_spec_string ("title",
-                         "Title",
-                         "The title for the subprocess launcher",
-                         NULL,
+  properties [PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         "Context",
+                         "The context for the launcher",
+                         IDE_TYPE_CONTEXT,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RUN_COMMAND] =
+    g_param_spec_object ("run-command",
+                         "Run Command",
+                         "The run command to spawn",
+                         IDE_TYPE_RUN_COMMAND,
                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
@@ -687,248 +241,45 @@ ide_terminal_launcher_class_init (IdeTerminalLauncherClass *klass)
 static void
 ide_terminal_launcher_init (IdeTerminalLauncher *self)
 {
-  self->cwd = NULL;
-  self->shell = NULL;
-  self->title = g_strdup (_("Untitled Terminal"));
-}
-
-const gchar *
-ide_terminal_launcher_get_cwd (IdeTerminalLauncher *self)
-{
-  g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
-
-  return self->cwd;
-}
-
-void
-ide_terminal_launcher_set_cwd (IdeTerminalLauncher *self,
-                               const gchar         *cwd)
-{
-  g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
-
-  if (g_strcmp0 (self->cwd, cwd) != 0)
-    {
-      g_free (self->cwd);
-      self->cwd = g_strdup (cwd);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
-    }
-}
-
-const gchar *
-ide_terminal_launcher_get_shell (IdeTerminalLauncher *self)
-{
-  g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
-
-  return self->shell;
-}
-
-void
-ide_terminal_launcher_set_shell (IdeTerminalLauncher *self,
-                                 const gchar         *shell)
-{
-  g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
-
-  if (g_strcmp0 (self->shell, shell) != 0)
-    {
-      g_free (self->shell);
-      self->shell = g_strdup (shell);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHELL]);
-    }
-}
-
-const gchar *
-ide_terminal_launcher_get_title (IdeTerminalLauncher *self)
-{
-  g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
-
-  return self->title;
-}
-
-void
-ide_terminal_launcher_set_title (IdeTerminalLauncher *self,
-                                 const gchar         *title)
-{
-  g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
-
-  if (g_strcmp0 (self->title, title) != 0)
-    {
-      g_free (self->title);
-      self->title = g_strdup (title);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
-    }
 }
 
 /**
  * ide_terminal_launcher_new:
+ * @context: an #IdeContext
+ * @run_command: an #IdeRunCommand to spawn
  *
- * Create a new #IdeTerminalLauncher that will spawn a terminal on the host.
+ * Create an #IdeTerminalLauncher that spawns @run_command.
  *
  * Returns: (transfer full): a newly created #IdeTerminalLauncher
  */
 IdeTerminalLauncher *
-ide_terminal_launcher_new (IdeContext *context)
+ide_terminal_launcher_new (IdeContext    *context,
+                           IdeRunCommand *run_command)
 {
-  IdeTerminalLauncher *self;
-  g_autoptr(GFile) workdir = NULL;
-
   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+  g_return_val_if_fail (IDE_IS_RUN_COMMAND (run_command), NULL);
 
-  workdir = ide_context_ref_workdir (context);
-
-  self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
-  self->kind = LAUNCHER_KIND_HOST;
-  self->cwd = g_file_get_path (workdir);
-  self->context = g_object_ref (context);
-
-  return g_steal_pointer (&self);
-}
-
-/**
- * ide_terminal_launcher_new_for_launcher:
- * @launcher: an #IdeSubprocessLauncher
- *
- * Creates a new #IdeTerminalLauncher that can be used to launch a process
- * using the provided #IdeSubprocessLauncher.
- *
- * Returns: (transfer full): an #IdeTerminalLauncher
- *
- * Since: 3.34
- */
-IdeTerminalLauncher *
-ide_terminal_launcher_new_for_launcher (IdeSubprocessLauncher *launcher)
-{
-  IdeTerminalLauncher *self;
-
-  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (launcher), NULL);
-
-  self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
-  self->kind = LAUNCHER_KIND_LAUNCHER;
-  self->launcher = g_object_ref (launcher);
-
-  return g_steal_pointer (&self);
+  return g_object_new (IDE_TYPE_TERMINAL_LAUNCHER,
+                       "context", context,
+                       "run-command", run_command,
+                       NULL);
 }
 
 /**
- * ide_terminal_launcher_new_for_debug
+ * ide_terminal_launcher_copy:
+ * @self: an #IdeTerminalLauncher
  *
- * Create a new #IdeTerminalLauncher that will spawn a terminal on the host.
+ * Copies @self into a new launcher.
  *
  * Returns: (transfer full): a newly created #IdeTerminalLauncher
  */
 IdeTerminalLauncher *
-ide_terminal_launcher_new_for_debug (void)
-{
-  IdeTerminalLauncher *self;
-
-  self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
-  self->kind = LAUNCHER_KIND_DEBUG;
-
-  return g_steal_pointer (&self);
-}
-
-/**
- * ide_terminal_launcher_new_for_config:
- * @config: an #IdeConfig
- *
- * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime
- * of the configuration with various build options applied.
- *
- * Returns: (transfer full): a newly created #IdeTerminalLauncher
- */
-IdeTerminalLauncher *
-ide_terminal_launcher_new_for_config (IdeConfig *config)
-{
-  IdeTerminalLauncher *self;
-  IdeRuntime *runtime;
-
-  g_return_val_if_fail (IDE_IS_CONFIG (config), NULL);
-
-  runtime = ide_config_get_runtime (config);
-
-  self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
-  self->runtime = g_object_ref (runtime);
-  self->config = g_object_ref (config);
-  self->kind = LAUNCHER_KIND_CONFIG;
-
-  ide_terminal_launcher_set_title (self, ide_runtime_get_name (runtime));
-
-  return g_steal_pointer (&self);
-}
-
-/**
- * ide_terminal_launcher_new_for_runtime:
- * @runtime: an #IdeRuntime
- *
- * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime.
- *
- * Returns: (transfer full): a newly created #IdeTerminalLauncher
- */
-IdeTerminalLauncher *
-ide_terminal_launcher_new_for_runtime (IdeRuntime *runtime)
-{
-  IdeTerminalLauncher *self;
-
-  g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL);
-
-  self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
-  self->runtime = g_object_ref (runtime);
-  self->kind = LAUNCHER_KIND_RUNTIME;
-
-  ide_terminal_launcher_set_title (self, ide_runtime_get_name (runtime));
-
-  return g_steal_pointer (&self);
-}
-
-/**
- * ide_terminal_launcher_new_for_runner:
- * @runtime: an #IdeRuntime
- *
- * Create a new #IdeTerminalLauncher that will spawn a terminal in the runtime
- * but with a "runner" context similar to how the application would execute.
- *
- * Returns: (transfer full): a newly created #IdeTerminalLauncher
- */
-IdeTerminalLauncher *
-ide_terminal_launcher_new_for_runner (IdeRuntime *runtime)
-{
-  IdeTerminalLauncher *self;
-
-  g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL);
-
-  self = g_object_new (IDE_TYPE_TERMINAL_LAUNCHER, NULL);
-  self->runtime = g_object_ref (runtime);
-  self->kind = LAUNCHER_KIND_RUNNER;
-
-  return g_steal_pointer (&self);
-}
-
-gboolean
-ide_terminal_launcher_can_respawn (IdeTerminalLauncher *self)
-{
-  g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), FALSE);
-
-  return self->kind != LAUNCHER_KIND_LAUNCHER;
-}
-
-const gchar * const *
-ide_terminal_launcher_get_args (IdeTerminalLauncher *self)
+ide_terminal_launcher_copy (IdeTerminalLauncher *self)
 {
   g_return_val_if_fail (IDE_IS_TERMINAL_LAUNCHER (self), NULL);
 
-  return (const gchar * const *)self->args;
-}
-
-void
-ide_terminal_launcher_set_args (IdeTerminalLauncher *self,
-                                const gchar * const *args)
-{
-  g_return_if_fail (IDE_IS_TERMINAL_LAUNCHER (self));
-
-  if ((gchar **)args != self->args)
-    {
-      gchar **freeme = g_steal_pointer (&self->args);
-      self->args = g_strdupv ((gchar **)args);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGS]);
-      g_strfreev (freeme);
-    }
+  return g_object_new (IDE_TYPE_TERMINAL_LAUNCHER,
+                       "context", self->context,
+                       "run-command", self->run_command,
+                       NULL);
 }
diff --git a/src/libide/terminal/ide-terminal-launcher.h b/src/libide/terminal/ide-terminal-launcher.h
index 22edaf991..f6e66196d 100644
--- a/src/libide/terminal/ide-terminal-launcher.h
+++ b/src/libide/terminal/ide-terminal-launcher.h
@@ -20,59 +20,34 @@
 
 #pragma once
 
-#include <libide-gui.h>
+#if !defined (IDE_TERMINAL_INSIDE) && !defined (IDE_TERMINAL_COMPILATION)
+# error "Only <libide-terminal.h> can be included directly."
+#endif
+
+#include <libide-foundry.h>
 #include <libide-threading.h>
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_TERMINAL_LAUNCHER (ide_terminal_launcher_get_type())
 
-IDE_AVAILABLE_IN_3_34
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeTerminalLauncher, ide_terminal_launcher, IDE, TERMINAL_LAUNCHER, GObject)
 
-IDE_AVAILABLE_IN_3_34
-IdeTerminalLauncher *ide_terminal_launcher_new              (IdeContext             *context);
-IDE_AVAILABLE_IN_3_34
-IdeTerminalLauncher *ide_terminal_launcher_new_for_launcher (IdeSubprocessLauncher  *launcher);
-IDE_AVAILABLE_IN_42
-IdeTerminalLauncher *ide_terminal_launcher_new_for_config   (IdeConfig              *config);
-IDE_AVAILABLE_IN_3_34
-IdeTerminalLauncher *ide_terminal_launcher_new_for_debug    (void);
-IDE_AVAILABLE_IN_3_34
-IdeTerminalLauncher *ide_terminal_launcher_new_for_runtime  (IdeRuntime             *runtime);
-IDE_AVAILABLE_IN_3_34
-IdeTerminalLauncher *ide_terminal_launcher_new_for_runner   (IdeRuntime             *runtime);
-IDE_AVAILABLE_IN_3_34
-gboolean             ide_terminal_launcher_can_respawn      (IdeTerminalLauncher    *self);
-IDE_AVAILABLE_IN_3_34
-const gchar * const *ide_terminal_launcher_get_args         (IdeTerminalLauncher    *self);
-IDE_AVAILABLE_IN_3_34
-void                 ide_terminal_launcher_set_args         (IdeTerminalLauncher    *self,
-                                                             const gchar * const    *args);
-IDE_AVAILABLE_IN_3_34
-const gchar         *ide_terminal_launcher_get_cwd          (IdeTerminalLauncher    *self);
-IDE_AVAILABLE_IN_3_34
-void                 ide_terminal_launcher_set_cwd          (IdeTerminalLauncher    *self,
-                                                             const gchar            *cwd);
-IDE_AVAILABLE_IN_3_34
-const gchar         *ide_terminal_launcher_get_shell        (IdeTerminalLauncher    *self);
-IDE_AVAILABLE_IN_3_34
-void                 ide_terminal_launcher_set_shell        (IdeTerminalLauncher    *self,
-                                                             const gchar            *shell);
-IDE_AVAILABLE_IN_3_34
-const gchar         *ide_terminal_launcher_get_title        (IdeTerminalLauncher    *self);
-IDE_AVAILABLE_IN_3_34
-void                 ide_terminal_launcher_set_title        (IdeTerminalLauncher    *self,
-                                                             const gchar            *title);
-IDE_AVAILABLE_IN_3_34
-void                 ide_terminal_launcher_spawn_async      (IdeTerminalLauncher    *self,
-                                                             VtePty                 *pty,
-                                                             GCancellable           *cancellable,
-                                                             GAsyncReadyCallback     callback,
-                                                             gpointer                user_data);
-IDE_AVAILABLE_IN_3_34
-gboolean             ide_terminal_launcher_spawn_finish     (IdeTerminalLauncher    *self,
-                                                             GAsyncResult           *result,
-                                                             GError                **error);
+IDE_AVAILABLE_IN_ALL
+IdeTerminalLauncher *ide_terminal_launcher_new                   (IdeContext             *context,
+                                                                  IdeRunCommand          *run_command);
+IDE_AVAILABLE_IN_ALL
+IdeTerminalLauncher *ide_terminal_launcher_copy                  (IdeTerminalLauncher    *self);
+IDE_AVAILABLE_IN_ALL
+void                 ide_terminal_launcher_spawn_async           (IdeTerminalLauncher    *self,
+                                                                  VtePty                 *pty,
+                                                                  GCancellable           *cancellable,
+                                                                  GAsyncReadyCallback     callback,
+                                                                  gpointer                user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean             ide_terminal_launcher_spawn_finish          (IdeTerminalLauncher    *self,
+                                                                  GAsyncResult           *result,
+                                                                  GError                **error);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-page-actions.c b/src/libide/terminal/ide-terminal-page-actions.c
index ad54c6713..b231bf3b9 100644
--- a/src/libide/terminal/ide-terminal-page-actions.c
+++ b/src/libide/terminal/ide-terminal-page-actions.c
@@ -165,8 +165,8 @@ save_as_cb (GObject      *object,
     }
   else
     {
-      g_clear_object (&view->save_as_file_top);
-      view->save_as_file_top = file;
+      g_clear_object (&view->save_as_file);
+      view->save_as_file = file;
     }
 }
 
@@ -175,8 +175,8 @@ get_last_focused_terminal_file (IdeTerminalPage *view)
 {
   GFile *file = NULL;
 
-  if (G_IS_FILE (view->save_as_file_top))
-    file = view->save_as_file_top;
+  if (G_IS_FILE (view->save_as_file))
+    file = view->save_as_file;
 
   return file;
 }
@@ -184,13 +184,14 @@ get_last_focused_terminal_file (IdeTerminalPage *view)
 static VteTerminal *
 get_last_focused_terminal (IdeTerminalPage *view)
 {
-  return VTE_TERMINAL (view->terminal_top);
+  return VTE_TERMINAL (view->terminal);
 }
 
 static gchar *
 gb_terminal_get_selected_text (IdeTerminalPage  *view,
-                               VteTerminal    **terminal_p)
+                               VteTerminal     **terminal_p)
 {
+#if 0
   VteTerminal *terminal;
   gchar *buf = NULL;
 
@@ -205,6 +206,9 @@ gb_terminal_get_selected_text (IdeTerminalPage  *view,
     }
 
   return buf;
+#else
+  return g_strdup ("");
+#endif
 }
 
 static void
@@ -235,7 +239,7 @@ save_as_response (GtkWidget *widget,
       break;
     }
 
-  gtk_widget_destroy (widget);
+  gtk_window_destroy (GTK_WINDOW (chooser));
 }
 
 static void
@@ -256,7 +260,7 @@ ide_terminal_page_actions_save_as (GSimpleAction *action,
    */
   view->selection_buffer = gb_terminal_get_selected_text (view, NULL);
 
-  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+  toplevel = GTK_WIDGET (gtk_widget_get_native (GTK_WIDGET (view)));
   dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
                          "action", GTK_FILE_CHOOSER_ACTION_SAVE,
                          "do-overwrite-confirmation", TRUE,
@@ -280,7 +284,7 @@ ide_terminal_page_actions_save_as (GSimpleAction *action,
 
   suggested = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
   gtk_style_context_add_class (gtk_widget_get_style_context (suggested),
-                               GTK_STYLE_CLASS_SUGGESTED_ACTION);
+                               "suggested-action");
 
   g_signal_connect (dialog, "response", G_CALLBACK (save_as_response), g_object_ref (view));
 
diff --git a/src/libide/terminal/ide-terminal-page-private.h b/src/libide/terminal/ide-terminal-page-private.h
index 404921835..fd2d5d6a6 100644
--- a/src/libide/terminal/ide-terminal-page-private.h
+++ b/src/libide/terminal/ide-terminal-page-private.h
@@ -31,14 +31,14 @@ struct _IdeTerminalPage
   IdePage              parent_instance;
 
   IdeTerminalLauncher *launcher;
-  GFile               *save_as_file_top;
+  GFile               *save_as_file;
   gchar               *selection_buffer;
   VtePty              *pty;
 
   /* Template widgets */
-  GtkOverlay          *terminal_overlay_top;
-  GtkRevealer         *search_revealer_top;
-  IdeTerminal         *terminal_top;
+  GtkOverlay          *terminal_overlay;
+  GtkRevealer         *search_revealer;
+  IdeTerminal         *terminal;
   IdeTerminalSearch   *tsearch;
 
   gint64               last_respawn;
@@ -47,7 +47,6 @@ struct _IdeTerminalPage
   guint                manage_spawn : 1;
   guint                respawn_on_exit : 1;
   guint                close_on_exit : 1;
-  guint                needs_attention : 1;
   guint                exited : 1;
   guint                destroyed : 1;
 };
diff --git a/src/libide/terminal/ide-terminal-page.c b/src/libide/terminal/ide-terminal-page.c
index 27d913c32..b85039e65 100644
--- a/src/libide/terminal/ide-terminal-page.c
+++ b/src/libide/terminal/ide-terminal-page.c
@@ -37,6 +37,7 @@
 #include "ide-terminal-page.h"
 #include "ide-terminal-page-private.h"
 #include "ide-terminal-page-actions.h"
+#include "ide-terminal-run-command-private.h"
 
 #define FLAPPING_DURATION_USEC (G_USEC_PER_SEC / 20)
 
@@ -52,13 +53,7 @@ enum {
   N_PROPS
 };
 
-enum {
-  TEXT_INSERTED,
-  N_SIGNALS
-};
-
 static GParamSpec *properties [N_PROPS];
-static guint signals [N_SIGNALS];
 
 static void ide_terminal_page_connect_terminal (IdeTerminalPage *self,
                                                 VteTerminal     *terminal);
@@ -86,9 +81,7 @@ destroy_widget_in_idle (GtkWidget *widget)
   g_assert (IDE_IS_TERMINAL_PAGE (self));
 
   if (!self->destroyed)
-    gtk_widget_destroy (widget);
-
-  g_assert (self->destroyed);
+    panel_widget_close (PANEL_WIDGET (self));
 
   IDE_RETURN (G_SOURCE_REMOVE);
 }
@@ -127,10 +120,10 @@ ide_terminal_page_spawn_cb (GObject      *object,
     }
 
   title = g_strdup_printf ("%s (%s)",
-                           ide_page_get_title (IDE_PAGE (self)) ?: _("Untitled terminal"),
+                           panel_widget_get_title (PANEL_WIDGET (self)) ?: _("Untitled terminal"),
                            /* translators: exited describes that the terminal shell process has exited */
                            _("Exited"));
-  ide_page_set_title (IDE_PAGE (self), title);
+  panel_widget_set_title (PANEL_WIDGET (self), title);
 
   now = g_get_monotonic_time ();
   maybe_flapping = ABS (now - self->last_respawn) < FLAPPING_DURATION_USEC;
@@ -138,12 +131,12 @@ ide_terminal_page_spawn_cb (GObject      *object,
   if (!self->respawn_on_exit)
     {
       if (self->close_on_exit && !maybe_flapping)
-        gdk_threads_add_idle_full (G_PRIORITY_LOW + 1000,
-                                   (GSourceFunc) destroy_widget_in_idle,
-                                   g_object_ref (self),
-                                   g_object_unref);
+        g_idle_add_full (G_PRIORITY_LOW + 1000,
+                         (GSourceFunc) destroy_widget_in_idle,
+                         g_object_ref (self),
+                         g_object_unref);
       else
-        vte_terminal_set_input_enabled (VTE_TERMINAL (self->terminal_top), FALSE);
+        vte_terminal_set_input_enabled (VTE_TERMINAL (self->terminal), FALSE);
       IDE_EXIT;
     }
 
@@ -155,14 +148,14 @@ ide_terminal_page_spawn_cb (GObject      *object,
     }
 
   g_clear_object (&self->pty);
-  vte_terminal_reset (VTE_TERMINAL (self->terminal_top), TRUE, TRUE);
+  vte_terminal_reset (VTE_TERMINAL (self->terminal), TRUE, TRUE);
   self->pty = vte_pty_new_sync (VTE_PTY_DEFAULT, NULL, NULL);
-  vte_terminal_set_pty (VTE_TERMINAL (self->terminal_top), self->pty);
+  vte_terminal_set_pty (VTE_TERMINAL (self->terminal), self->pty);
 
   /* Spawn our terminal and wait for it to exit */
   self->last_respawn = now;
   self->exited = FALSE;
-  ide_page_set_title (IDE_PAGE (self), _("Untitled terminal"));
+  panel_widget_set_title (PANEL_WIDGET (self), _("Untitled terminal"));
   ide_terminal_launcher_spawn_async (self->launcher,
                                      self->pty,
                                      NULL,
@@ -195,7 +188,7 @@ ide_terminal_page_do_spawn_in_idle (IdeTerminalPage *self)
         }
     }
 
-  vte_terminal_set_pty (VTE_TERMINAL (self->terminal_top), self->pty);
+  vte_terminal_set_pty (VTE_TERMINAL (self->terminal), self->pty);
 
   if (!self->manage_spawn)
     IDE_RETURN (G_SOURCE_REMOVE);
@@ -234,59 +227,6 @@ ide_terminal_page_realize (GtkWidget *widget)
                    g_object_unref);
 }
 
-static void
-ide_terminal_page_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
-   * widget at its natural size (which prevents us from getting
-   * appropriate size requests.
-   */
-  GTK_WIDGET_CLASS (ide_terminal_page_parent_class)->get_preferred_width (widget, min_width, nat_width);
-  *nat_width = *min_width;
-}
-
-static void
-ide_terminal_page_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
-   * widget at its natural size (which prevents us from getting
-   * appropriate size requests.
-   */
-  GTK_WIDGET_CLASS (ide_terminal_page_parent_class)->get_preferred_height (widget, min_height, nat_height);
-  *nat_height = *min_height;
-}
-
-static void
-ide_terminal_page_set_needs_attention (IdeTerminalPage *self,
-                                       gboolean         needs_attention)
-{
-  GtkWidget *parent;
-
-  g_assert (IDE_IS_TERMINAL_PAGE (self));
-
-  parent = gtk_widget_get_parent (GTK_WIDGET (self));
-
-  if (GTK_IS_STACK (parent) &&
-      !gtk_widget_in_destruction (GTK_WIDGET (self)) &&
-      !gtk_widget_in_destruction (parent))
-    {
-      if (!gtk_widget_in_destruction (GTK_WIDGET (self->terminal_top)))
-        self->needs_attention = !!needs_attention;
-
-      gtk_container_child_set (GTK_CONTAINER (parent), GTK_WIDGET (self),
-                               "needs-attention", needs_attention,
-                               NULL);
-    }
-}
-
 static void
 notification_received_cb (VteTerminal     *terminal,
                           const gchar     *summary,
@@ -300,22 +240,18 @@ notification_received_cb (VteTerminal     *terminal,
     return;
 
   if (!gtk_widget_has_focus (GTK_WIDGET (terminal)))
-    ide_terminal_page_set_needs_attention (self, TRUE);
+    panel_widget_set_needs_attention (PANEL_WIDGET (self), TRUE);
 }
 
-static gboolean
-focus_in_event_cb (VteTerminal     *terminal,
-                   GdkEvent        *event,
-                   IdeTerminalPage *self)
+static void
+focus_in_event_cb (GtkEventControllerFocus *focus,
+                   IdeTerminalPage         *self)
 {
-  g_assert (VTE_IS_TERMINAL (terminal));
+  g_assert (GTK_IS_EVENT_CONTROLLER_FOCUS (focus));
   g_assert (IDE_IS_TERMINAL_PAGE (self));
 
-  self->needs_attention = FALSE;
-  ide_terminal_page_set_needs_attention (self, FALSE);
-  gtk_revealer_set_reveal_child (self->search_revealer_top, FALSE);
-
-  return GDK_EVENT_PROPAGATE;
+  panel_widget_set_needs_attention (PANEL_WIDGET (self), FALSE);
+  gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
 }
 
 static void
@@ -330,37 +266,12 @@ window_title_changed_cb (VteTerminal     *terminal,
   if (self->destroyed)
     return;
 
-  title = vte_terminal_get_window_title (VTE_TERMINAL (self->terminal_top));
+  title = vte_terminal_get_window_title (VTE_TERMINAL (self->terminal));
 
   if (title == NULL || title[0] == '\0')
     title = _("Untitled terminal");
 
-  ide_page_set_title (IDE_PAGE (self), title);
-}
-
-static void
-style_context_changed (GtkStyleContext *style_context,
-                       IdeTerminalPage *self)
-{
-  GtkStateFlags state;
-  GdkRGBA fg;
-  GdkRGBA bg;
-
-  g_assert (GTK_IS_STYLE_CONTEXT (style_context));
-  g_assert (IDE_IS_TERMINAL_PAGE (self));
-
-  state = gtk_style_context_get_state (style_context);
-
-  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
-  gtk_style_context_get_color (style_context, state, &fg);
-  gtk_style_context_get_background_color (style_context, state, &bg);
-  G_GNUC_END_IGNORE_DEPRECATIONS;
-
-  if (bg.alpha == 0.0)
-    gdk_rgba_parse (&bg, "#f6f7f8");
-
-  ide_page_set_primary_color_fg (IDE_PAGE (self), &fg);
-  ide_page_set_primary_color_bg (IDE_PAGE (self), &bg);
+  panel_widget_set_title (PANEL_WIDGET (self), title);
 }
 
 static IdePage *
@@ -380,31 +291,36 @@ ide_terminal_page_create_split (IdePage *page)
                        NULL);
 }
 
-static void
+static gboolean
 ide_terminal_page_grab_focus (GtkWidget *widget)
 {
   IdeTerminalPage *self = (IdeTerminalPage *)widget;
 
   g_assert (IDE_IS_TERMINAL_PAGE (self));
 
-  gtk_widget_grab_focus (GTK_WIDGET (self->terminal_top));
+  return gtk_widget_grab_focus (GTK_WIDGET (self->terminal));
 }
 
 static void
 ide_terminal_page_connect_terminal (IdeTerminalPage *self,
                                     VteTerminal     *terminal)
 {
+  GtkEventController *controller;
+
   g_assert (IDE_IS_TERMINAL_PAGE (self));
-  g_assert (VTE_IS_TERMINAL (terminal));
+  g_assert (IDE_IS_TERMINAL (terminal));
 
   if (self->destroyed)
     return;
 
-  g_signal_connect_object (terminal,
-                           "focus-in-event",
+  controller = gtk_event_controller_focus_new ();
+  g_signal_connect_object (controller,
+                           "enter",
                            G_CALLBACK (focus_in_event_cb),
                            self,
                            0);
+  gtk_widget_add_controller (GTK_WIDGET (terminal), controller);
+
 
   g_signal_connect_object (terminal,
                            "window-title-changed",
@@ -431,18 +347,14 @@ ide_terminal_page_context_set (GtkWidget  *widget,
   g_assert (IDE_IS_TERMINAL_PAGE (self));
   g_assert (!context || IDE_IS_CONTEXT (context));
 
-  if (self->launcher == NULL && context != NULL)
-    self->launcher = ide_terminal_launcher_new (context);
-}
-
-static void
-ide_terminal_page_on_text_inserted_cb (IdeTerminalPage *self,
-                                       VteTerminal     *terminal)
-{
-  g_assert (IDE_IS_TERMINAL_PAGE (self));
-  g_assert (VTE_IS_TERMINAL (terminal));
+  if (context == NULL)
+    return;
 
-  g_signal_emit (self, signals [TEXT_INSERTED], 0);
+  if (self->launcher == NULL)
+    {
+      g_autoptr(IdeRunCommand) run_command = ide_terminal_run_command_new (IDE_TERMINAL_RUN_ON_HOST);
+      self->launcher = ide_terminal_launcher_new (context, run_command);
+    }
 }
 
 static GFile *
@@ -456,8 +368,8 @@ ide_terminal_page_get_file_or_directory (IdePage *page)
   if (self->destroyed)
     return NULL;
 
-  if (!(uri = vte_terminal_get_current_file_uri (VTE_TERMINAL (self->terminal_top))))
-    uri = vte_terminal_get_current_directory_uri (VTE_TERMINAL (self->terminal_top));
+  if (!(uri = vte_terminal_get_current_file_uri (VTE_TERMINAL (self->terminal))))
+    uri = vte_terminal_get_current_directory_uri (VTE_TERMINAL (self->terminal));
 
   if (uri != NULL)
     return g_file_new_for_uri (uri);
@@ -466,26 +378,18 @@ ide_terminal_page_get_file_or_directory (IdePage *page)
 }
 
 static void
-ide_terminal_page_destroy (GtkWidget *widget)
+ide_terminal_page_dispose (GObject *object)
 {
-  IdeTerminalPage *self = (IdeTerminalPage *)widget;
+  IdeTerminalPage *self = IDE_TERMINAL_PAGE (object);
 
   self->destroyed = TRUE;
 
-  GTK_WIDGET_CLASS (ide_terminal_page_parent_class)->destroy (widget);
-}
-
-static void
-ide_terminal_page_finalize (GObject *object)
-{
-  IdeTerminalPage *self = IDE_TERMINAL_PAGE (object);
-
   g_clear_object (&self->launcher);
-  g_clear_object (&self->save_as_file_top);
+  g_clear_object (&self->save_as_file);
   g_clear_pointer (&self->selection_buffer, g_free);
   g_clear_object (&self->pty);
 
-  G_OBJECT_CLASS (ide_terminal_page_parent_class)->finalize (object);
+  G_OBJECT_CLASS (ide_terminal_page_parent_class)->dispose (object);
 }
 
 static void
@@ -565,22 +469,19 @@ ide_terminal_page_class_init (IdeTerminalPageClass *klass)
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   IdePageClass *page_class = IDE_PAGE_CLASS (klass);
 
-  object_class->finalize = ide_terminal_page_finalize;
+  object_class->dispose = ide_terminal_page_dispose;
   object_class->get_property = ide_terminal_page_get_property;
   object_class->set_property = ide_terminal_page_set_property;
 
   widget_class->realize = ide_terminal_page_realize;
-  widget_class->get_preferred_width = ide_terminal_page_get_preferred_width;
-  widget_class->get_preferred_height = ide_terminal_page_get_preferred_height;
   widget_class->grab_focus = ide_terminal_page_grab_focus;
-  widget_class->destroy = ide_terminal_page_destroy;
 
   page_class->create_split = ide_terminal_page_create_split;
   page_class->get_file_or_directory = ide_terminal_page_get_file_or_directory;
 
   gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-terminal/ui/ide-terminal-page.ui");
-  gtk_widget_class_bind_template_child (widget_class, IdeTerminalPage, terminal_top);
-  gtk_widget_class_bind_template_child (widget_class, IdeTerminalPage, terminal_overlay_top);
+  gtk_widget_class_bind_template_child (widget_class, IdeTerminalPage, terminal);
+  gtk_widget_class_bind_template_child (widget_class, IdeTerminalPage, terminal_overlay);
 
   properties [PROP_CLOSE_ON_EXIT] =
     g_param_spec_boolean ("close-on-exit",
@@ -615,28 +516,14 @@ ide_terminal_page_class_init (IdeTerminalPageClass *klass)
                          "Launcher",
                          "The launcher to use for spawning",
                          IDE_TYPE_TERMINAL_LAUNCHER,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
-
-  signals [TEXT_INSERTED] =
-    g_signal_new ("text-inserted",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_LAST,
-                  0,
-                  NULL, NULL,
-                  g_cclosure_marshal_VOID__VOID,
-                  G_TYPE_NONE, 0);
-  g_signal_set_va_marshaller (signals [TEXT_INSERTED],
-                              G_TYPE_FROM_CLASS (klass),
-                              g_cclosure_marshal_VOID__VOIDv);
 }
 
 static void
 ide_terminal_page_init (IdeTerminalPage *self)
 {
-  GtkStyleContext *style_context;
-
   self->close_on_exit = TRUE;
   self->respawn_on_exit = TRUE;
   self->manage_spawn = TRUE;
@@ -644,40 +531,39 @@ ide_terminal_page_init (IdeTerminalPage *self)
   self->tsearch = g_object_new (IDE_TYPE_TERMINAL_SEARCH,
                                 "visible", TRUE,
                                 NULL);
-  self->search_revealer_top = ide_terminal_search_get_revealer (self->tsearch);
+  self->search_revealer = ide_terminal_search_get_revealer (self->tsearch);
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
-  g_signal_connect_object (self->terminal_top,
-                           "text-inserted",
-                           G_CALLBACK (ide_terminal_page_on_text_inserted_cb),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  ide_page_set_icon_name (IDE_PAGE (self), "builder-terminal-symbolic");
+  panel_widget_set_icon_name (PANEL_WIDGET (self), "builder-terminal-symbolic");
   ide_page_set_can_split (IDE_PAGE (self), TRUE);
   ide_page_set_menu_id (IDE_PAGE (self), "ide-terminal-page-document-menu");
 
-  gtk_overlay_add_overlay (self->terminal_overlay_top, GTK_WIDGET (self->tsearch));
+  gtk_overlay_add_overlay (self->terminal_overlay, GTK_WIDGET (self->tsearch));
 
-  ide_terminal_page_connect_terminal (self, VTE_TERMINAL (self->terminal_top));
+  ide_terminal_page_connect_terminal (self, VTE_TERMINAL (self->terminal));
 
-  ide_terminal_search_set_terminal (self->tsearch, VTE_TERMINAL (self->terminal_top));
+  ide_terminal_search_set_terminal (self->tsearch, VTE_TERMINAL (self->terminal));
 
   ide_terminal_page_actions_init (self);
 
-  style_context = gtk_widget_get_style_context (GTK_WIDGET (self->terminal_top));
-  gtk_style_context_add_class (style_context, "terminal");
-  g_signal_connect_object (style_context,
-                           "changed",
-                           G_CALLBACK (style_context_changed),
-                           self,
-                           0);
-  style_context_changed (style_context, self);
+  ide_widget_set_context_handler (self, ide_terminal_page_context_set);
+}
 
-  gtk_widget_set_can_focus (GTK_WIDGET (self->terminal_top), TRUE);
+/**
+ * ide_terminal_page_get_pty:
+ * @self: a #IdeTerminalPage
+ *
+ * Gets the #VtePty for the page.
+ *
+ * Returns: (transfer none): a #VtePty
+ */
+VtePty *
+ide_terminal_page_get_pty (IdeTerminalPage *self)
+{
+  g_return_val_if_fail (IDE_IS_TERMINAL_PAGE (self), NULL);
 
-  ide_widget_set_context_handler (self, ide_terminal_page_context_set);
+  return self->pty;
 }
 
 void
@@ -692,8 +578,8 @@ ide_terminal_page_set_pty (IdeTerminalPage *self,
 
   if (g_set_object (&self->pty, pty))
     {
-      vte_terminal_reset (VTE_TERMINAL (self->terminal_top), TRUE, TRUE);
-      vte_terminal_set_pty (VTE_TERMINAL (self->terminal_top), pty);
+      vte_terminal_reset (VTE_TERMINAL (self->terminal), TRUE, TRUE);
+      vte_terminal_set_pty (VTE_TERMINAL (self->terminal), pty);
     }
 }
 
@@ -704,8 +590,8 @@ ide_terminal_page_feed (IdeTerminalPage *self,
   g_return_if_fail (IDE_IS_TERMINAL_PAGE (self));
   g_return_if_fail (self->destroyed == FALSE);
 
-  if (self->terminal_top != NULL)
-    vte_terminal_feed (VTE_TERMINAL (self->terminal_top), message, -1);
+  if (self->terminal != NULL)
+    vte_terminal_feed (VTE_TERMINAL (self->terminal), message, -1);
 }
 
 void
@@ -718,21 +604,8 @@ ide_terminal_page_set_launcher (IdeTerminalPage     *self,
 
   if (g_set_object (&self->launcher, launcher))
     {
-      gboolean can_split;
-
-      if (launcher != NULL)
-        {
-          const gchar *title = ide_terminal_launcher_get_title (launcher);
-          ide_page_set_title (IDE_PAGE (self), title);
-          can_split = ide_terminal_launcher_can_respawn (launcher);
-        }
-      else
-        {
-          self->manage_spawn = FALSE;
-          can_split = FALSE;
-        }
-
-      ide_page_set_can_split (IDE_PAGE (self), can_split);
+      ide_page_set_can_split (IDE_PAGE (self), TRUE);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LAUNCHER]);
     }
 }
 
@@ -742,5 +615,29 @@ ide_terminal_page_get_current_directory_uri (IdeTerminalPage *self)
   g_return_val_if_fail (IDE_IS_TERMINAL_PAGE (self), NULL);
   g_return_val_if_fail (self->destroyed == FALSE, NULL);
 
-  return vte_terminal_get_current_directory_uri (VTE_TERMINAL (self->terminal_top));
+  return vte_terminal_get_current_directory_uri (VTE_TERMINAL (self->terminal));
+}
+
+/**
+ * ide_terminal_page_get_launcher:
+ * @self: a #IdeTerminalPage
+ *
+ * Gets the launcher for the page.
+ *
+ * Returns: (transfer none) (nullable): an #IdeTerminalLauncher or %NULL
+ */
+IdeTerminalLauncher *
+ide_terminal_page_get_launcher (IdeTerminalPage *self)
+{
+  g_return_val_if_fail (IDE_IS_TERMINAL_PAGE (self), NULL);
+
+  return self->launcher;
+}
+
+gboolean
+ide_terminal_page_has_exited (IdeTerminalPage *self)
+{
+  g_return_val_if_fail (IDE_IS_TERMINAL_PAGE (self), FALSE);
+
+  return self->exited;
 }
diff --git a/src/libide/terminal/ide-terminal-page.h b/src/libide/terminal/ide-terminal-page.h
index 5e2211a4d..c6dc9d4ce 100644
--- a/src/libide/terminal/ide-terminal-page.h
+++ b/src/libide/terminal/ide-terminal-page.h
@@ -34,19 +34,25 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TERMINAL_PAGE (ide_terminal_page_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeTerminalPage, ide_terminal_page, IDE, TERMINAL_PAGE, IdePage)
 
-IDE_AVAILABLE_IN_3_34
-void         ide_terminal_page_set_launcher              (IdeTerminalPage     *self,
-                                                          IdeTerminalLauncher *launcher);
-IDE_AVAILABLE_IN_3_32
-void         ide_terminal_page_set_pty                   (IdeTerminalPage     *self,
-                                                          VtePty              *pty);
-IDE_AVAILABLE_IN_3_32
-void         ide_terminal_page_feed                      (IdeTerminalPage     *self,
-                                                          const gchar         *message);
-IDE_AVAILABLE_IN_3_34
-const gchar *ide_terminal_page_get_current_directory_uri (IdeTerminalPage     *self);
+IDE_AVAILABLE_IN_ALL
+void                 ide_terminal_page_set_launcher              (IdeTerminalPage     *self,
+                                                                  IdeTerminalLauncher *launcher);
+IDE_AVAILABLE_IN_ALL
+IdeTerminalLauncher *ide_terminal_page_get_launcher              (IdeTerminalPage     *self);
+IDE_AVAILABLE_IN_ALL
+VtePty              *ide_terminal_page_get_pty                   (IdeTerminalPage     *self);
+IDE_AVAILABLE_IN_ALL
+void                 ide_terminal_page_set_pty                   (IdeTerminalPage     *self,
+                                                                  VtePty              *pty);
+IDE_AVAILABLE_IN_ALL
+void                 ide_terminal_page_feed                      (IdeTerminalPage     *self,
+                                                                  const gchar         *message);
+IDE_AVAILABLE_IN_ALL
+const gchar         *ide_terminal_page_get_current_directory_uri (IdeTerminalPage     *self);
+IDE_AVAILABLE_IN_ALL
+gboolean             ide_terminal_page_has_exited                (IdeTerminalPage     *self);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-page.ui b/src/libide/terminal/ide-terminal-page.ui
index a8575b47e..81b06cdef 100644
--- a/src/libide/terminal/ide-terminal-page.ui
+++ b/src/libide/terminal/ide-terminal-page.ui
@@ -1,26 +1,22 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.16 -->
   <template class="IdeTerminalPage" parent="IdePage">
-    <property name="visible">true</property>
+    <property name="can-maximize">true</property>
+    <property name="hexpand">true</property>
+    <property name="vexpand">true</property>
+    <property name="title" translatable="yes">Untitled Terminal</property>
     <child>
       <object class="GtkPaned" id="paned">
-        <property name="expand">true</property>
         <property name="orientation">vertical</property>
-        <property name="visible">true</property>
         <child>
-          <object class="GtkOverlay" id="terminal_overlay_top">
-            <property name="expand">true</property>
-            <property name="visible">true</property>
+          <object class="GtkOverlay" id="terminal_overlay">
             <child>
               <object class="GtkScrolledWindow">
-                <property name="expand">true</property>
-                <property name="visible">true</property>
                 <child>
-                  <object class="IdeTerminal" id="terminal_top">
+                  <object class="IdeTerminal" id="terminal">
                     <property name="audible-bell">false</property>
-                    <property name="expand">true</property>
-                    <property name="visible">true</property>
+                    <property name="hexpand">true</property>
+                    <property name="vexpand">true</property>
                   </object>
                 </child>
               </object>
diff --git a/src/libide/terminal/ide-terminal-popover.c b/src/libide/terminal/ide-terminal-popover.c
index efb81519f..db5366c8a 100644
--- a/src/libide/terminal/ide-terminal-popover.c
+++ b/src/libide/terminal/ide-terminal-popover.c
@@ -22,7 +22,6 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <libide-foundry.h>
 #include <libide-gui.h>
 
@@ -33,7 +32,7 @@ struct _IdeTerminalPopover
 {
   GtkPopover          parent_instance;
 
-  DzlListModelFilter *filter;
+  GtkFilterListModel *filter;
   gchar              *selected;
 
   /* Template widgets */
@@ -43,21 +42,6 @@ struct _IdeTerminalPopover
 
 G_DEFINE_FINAL_TYPE (IdeTerminalPopover, ide_terminal_popover, GTK_TYPE_POPOVER)
 
-static void
-ide_terminal_popover_update_selected_cb (GtkWidget *widget,
-                                         gpointer   user_data)
-{
-  IdeTerminalPopoverRow *row = (IdeTerminalPopoverRow *)widget;
-  IdeRuntime *runtime = user_data;
-  gboolean selected;
-
-  g_assert (IDE_IS_TERMINAL_POPOVER_ROW (row));
-  g_assert (IDE_IS_RUNTIME (runtime));
-
-  selected = runtime == ide_terminal_popover_row_get_runtime (row);
-  ide_terminal_popover_row_set_selected (row, selected);
-}
-
 static void
 ide_terminal_popover_row_activated_cb (IdeTerminalPopover    *self,
                                        IdeTerminalPopoverRow *row,
@@ -74,9 +58,10 @@ ide_terminal_popover_row_activated_cb (IdeTerminalPopover    *self,
   g_free (self->selected);
   self->selected = g_strdup (ide_runtime_get_id (runtime));
 
-  gtk_container_foreach (GTK_CONTAINER (self->list_box),
-                         ide_terminal_popover_update_selected_cb,
-                         runtime);
+  for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->list_box));
+       child;
+       child = gtk_widget_get_next_sibling (child))
+    ide_terminal_popover_row_set_selected (row, runtime == ide_terminal_popover_row_get_runtime (row));
 }
 
 static GtkWidget *
@@ -96,23 +81,23 @@ ide_terminal_popover_create_row_cb (gpointer item,
 }
 
 static gboolean
-ide_terminal_popover_filter_func (GObject  *item,
-                                  gpointer  user_data)
+ide_terminal_popover_filter_func (gpointer item,
+                                  gpointer user_data)
 {
-  DzlPatternSpec *spec = user_data;
-  IdeRuntime *runtime = IDE_RUNTIME (item);
+  IdePatternSpec *spec = user_data;
+  IdeRuntime *runtime = item;
   const gchar *str;
 
   str = ide_runtime_get_id (runtime);
-  if (dzl_pattern_spec_match (spec, str))
+  if (ide_pattern_spec_match (spec, str))
     return TRUE;
 
   str = ide_runtime_get_category (runtime);
-  if (dzl_pattern_spec_match (spec, str))
+  if (ide_pattern_spec_match (spec, str))
     return TRUE;
 
   str = ide_runtime_get_display_name (runtime);
-  if (dzl_pattern_spec_match (spec, str))
+  if (ide_pattern_spec_match (spec, str))
     return TRUE;
 
   return FALSE;
@@ -122,6 +107,7 @@ static void
 ide_terminal_popover_search_changed_cb (IdeTerminalPopover *self,
                                         GtkSearchEntry     *entry)
 {
+  GtkCustomFilter *filter;
   const gchar *text;
 
   g_assert (IDE_IS_TERMINAL_POPOVER (self));
@@ -130,14 +116,14 @@ ide_terminal_popover_search_changed_cb (IdeTerminalPopover *self,
   if (self->filter == NULL)
     return;
 
-  text = gtk_entry_get_text (GTK_ENTRY (entry));
+  text = gtk_editable_get_text (GTK_EDITABLE (entry));
   if (ide_str_empty0 (text))
     text = NULL;
 
-  dzl_list_model_filter_set_filter_func (self->filter,
-                                         ide_terminal_popover_filter_func,
-                                         dzl_pattern_spec_new (text),
-                                         (GDestroyNotify) dzl_pattern_spec_unref);
+  filter = gtk_custom_filter_new (ide_terminal_popover_filter_func,
+                                  ide_pattern_spec_new (text),
+                                  (GDestroyNotify) ide_pattern_spec_unref);
+  gtk_filter_list_model_set_filter (self->filter, GTK_FILTER (filter));
 }
 
 static void
@@ -171,7 +157,7 @@ ide_terminal_popover_context_set_cb (GtkWidget  *widget,
     }
 
   g_clear_object (&self->filter);
-  self->filter = dzl_list_model_filter_new (G_LIST_MODEL (runtime_manager));
+  self->filter = gtk_filter_list_model_new (g_object_ref (G_LIST_MODEL (runtime_manager)), NULL);
 
   gtk_list_box_bind_model (self->list_box,
                            G_LIST_MODEL (self->filter),
@@ -222,8 +208,6 @@ ide_terminal_popover_new (void)
  * @self: a #IdeTerminalPopover
  *
  * Returns: (transfer none): an #IdeRuntime or %NULL
- *
- * Since: 3.32
  */
 IdeRuntime *
 ide_terminal_popover_get_runtime (IdeTerminalPopover *self)
diff --git a/src/libide/terminal/ide-terminal-popover.h b/src/libide/terminal/ide-terminal-popover.h
index 0d6a46105..247e60d0b 100644
--- a/src/libide/terminal/ide-terminal-popover.h
+++ b/src/libide/terminal/ide-terminal-popover.h
@@ -33,12 +33,12 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TERMINAL_POPOVER (ide_terminal_popover_get_type())
 
-IDE_AVAILABLE_IN_3_34
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeTerminalPopover, ide_terminal_popover, IDE, TERMINAL_POPOVER, GtkPopover)
 
-IDE_AVAILABLE_IN_3_34
+IDE_AVAILABLE_IN_ALL
 GtkWidget  *ide_terminal_popover_new         (void);
-IDE_AVAILABLE_IN_3_34
+IDE_AVAILABLE_IN_ALL
 IdeRuntime *ide_terminal_popover_get_runtime (IdeTerminalPopover *self);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-private.h b/src/libide/terminal/ide-terminal-private.h
index 9b53f3b25..d9015f5b6 100644
--- a/src/libide/terminal/ide-terminal-private.h
+++ b/src/libide/terminal/ide-terminal-private.h
@@ -1,6 +1,6 @@
 /* ide-terminal-private.h
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2022 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -24,6 +24,6 @@
 
 G_BEGIN_DECLS
 
-void _ide_guess_shell (void);
+void _ide_terminal_init (void);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-surface.h 
b/src/libide/terminal/ide-terminal-run-command-private.h
similarity index 56%
rename from src/libide/terminal/ide-terminal-surface.h
rename to src/libide/terminal/ide-terminal-run-command-private.h
index 4921aef4e..0ae78f6b8 100644
--- a/src/libide/terminal/ide-terminal-surface.h
+++ b/src/libide/terminal/ide-terminal-run-command-private.h
@@ -1,6 +1,6 @@
-/* ide-terminal-surface.h
+/* ide-terminal-run-command-private.h
  *
- * Copyright 2018 Christian Hergert <unknown domain org>
+ * Copyright 2022 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,20 +20,24 @@
 
 #pragma once
 
-#if !defined (IDE_TERMINAL_INSIDE) && !defined (IDE_TERMINAL_COMPILATION)
-# error "Only <libide-terminal.h> can be included directly."
-#endif
-
-#include <libide-gui.h>
+#include <libide-foundry.h>
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_TERMINAL_SURFACE (ide_terminal_surface_get_type())
+#define IDE_TYPE_TERMINAL_RUN_COMMAND (ide_terminal_run_command_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeTerminalRunCommand, ide_terminal_run_command, IDE, TERMINAL_RUN_COMMAND, 
IdeRunCommand)
+
+typedef enum
+{
+  IDE_TERMINAL_RUN_ON_HOST,
+  IDE_TERMINAL_RUN_AS_SUBPROCESS,
+  IDE_TERMINAL_RUN_IN_PIPELINE,
+  IDE_TERMINAL_RUN_IN_RUNTIME,
 
-IDE_AVAILABLE_IN_3_32
-G_DECLARE_FINAL_TYPE (IdeTerminalSurface, ide_terminal_surface, IDE, TERMINAL_SURFACE, IdeSurface)
+  IDE_TERMINAL_RUN_LAST
+} IdeTerminalRunLocality;
 
-IDE_AVAILABLE_IN_3_32
-IdeTerminalSurface *ide_terminal_surface_new (void);
+IdeRunCommand *ide_terminal_run_command_new (IdeTerminalRunLocality  locality);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-run-command.c b/src/libide/terminal/ide-terminal-run-command.c
new file mode 100644
index 000000000..03cbe65d3
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-run-command.c
@@ -0,0 +1,144 @@
+/* ide-terminal-run-command.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-terminal-run-command"
+
+#include "config.h"
+
+#include <libide-io.h>
+
+#include "ide-terminal-run-command-private.h"
+
+struct _IdeTerminalRunCommand
+{
+  IdeRunCommand          parent_instance;
+  IdeTerminalRunLocality locality;
+};
+
+G_DEFINE_FINAL_TYPE (IdeTerminalRunCommand, ide_terminal_run_command, IDE_TYPE_RUN_COMMAND)
+
+static void
+ide_terminal_run_command_prepare_to_run (IdeRunCommand *run_command,
+                                         IdeRunContext *run_context,
+                                         IdeContext    *context)
+{
+  IdeTerminalRunCommand *self = (IdeTerminalRunCommand *)run_command;
+  const char *user_shell;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_TERMINAL_RUN_COMMAND (self));
+  g_assert (IDE_IS_RUN_CONTEXT (run_context));
+  g_assert (IDE_IS_CONTEXT (context));
+
+  user_shell = ide_get_user_shell ();
+
+  switch (self->locality)
+    {
+    case IDE_TERMINAL_RUN_ON_HOST:
+      ide_run_context_push_host (run_context);
+      ide_run_context_add_minimal_environment (run_context);
+      ide_run_context_append_argv (run_context, user_shell);
+      if (ide_shell_supports_dash_login (user_shell))
+        ide_run_context_append_argv (run_context, "--login");
+      break;
+
+    case IDE_TERMINAL_RUN_AS_SUBPROCESS:
+      ide_run_context_add_minimal_environment (run_context);
+      if (g_find_program_in_path (user_shell))
+        {
+          ide_run_context_append_argv (run_context, user_shell);
+          if (ide_shell_supports_dash_login (user_shell))
+            ide_run_context_append_argv (run_context, "--login");
+        }
+      else
+        {
+          ide_run_context_append_argv (run_context, "/bin/sh");
+          ide_run_context_append_argv (run_context, "--login");
+        }
+      break;
+
+    case IDE_TERMINAL_RUN_IN_RUNTIME:
+    case IDE_TERMINAL_RUN_IN_PIPELINE:
+      {
+        IdeBuildManager *build_manager;
+        IdePipeline *pipeline;
+        IdeRuntime *runtime;
+
+        if (!ide_context_has_project (context) ||
+            !(build_manager = ide_build_manager_from_context (context)) ||
+            !(pipeline = ide_build_manager_get_pipeline (build_manager)) ||
+            !(runtime = ide_pipeline_get_runtime (pipeline)))
+          {
+            ide_run_context_push_error (run_context,
+                                        g_error_new (G_IO_ERROR,
+                                                     G_IO_ERROR_NOT_INITIALIZED,
+                                                     "Cannot spawn terminal without a pipeline"));
+            break;
+          }
+
+        if (!ide_runtime_contains_program_in_path (runtime, user_shell, NULL))
+          user_shell = "/bin/sh";
+
+        if (self->locality == IDE_TERMINAL_RUN_IN_PIPELINE)
+          ide_pipeline_prepare_run_context (pipeline, run_context);
+        else
+          ide_runtime_prepare_to_run (runtime, pipeline, run_context);
+
+        ide_run_context_append_argv (run_context, user_shell);
+        if (ide_shell_supports_dash_login (user_shell))
+          ide_run_context_append_argv (run_context, "--login");
+      }
+      break;
+
+    case IDE_TERMINAL_RUN_LAST:
+    default:
+      g_assert_not_reached ();
+    }
+
+  IDE_RUN_COMMAND_CLASS (ide_terminal_run_command_parent_class)->prepare_to_run (run_command, run_context, 
context);
+
+  IDE_EXIT;
+}
+
+static void
+ide_terminal_run_command_class_init (IdeTerminalRunCommandClass *klass)
+{
+  IdeRunCommandClass *run_command_class = IDE_RUN_COMMAND_CLASS (klass);
+
+  run_command_class->prepare_to_run = ide_terminal_run_command_prepare_to_run;
+}
+
+static void
+ide_terminal_run_command_init (IdeTerminalRunCommand *self)
+{
+}
+
+IdeRunCommand *
+ide_terminal_run_command_new (IdeTerminalRunLocality locality)
+{
+  IdeTerminalRunCommand *self;
+
+  self = g_object_new (IDE_TYPE_TERMINAL_RUN_COMMAND, NULL);
+  self->locality = locality;
+
+  return IDE_RUN_COMMAND (self);
+}
diff --git a/src/libide/terminal/ide-terminal-search-private.h 
b/src/libide/terminal/ide-terminal-search-private.h
index 2ace9737d..a61e68398 100644
--- a/src/libide/terminal/ide-terminal-search-private.h
+++ b/src/libide/terminal/ide-terminal-search-private.h
@@ -20,20 +20,21 @@
 
 #pragma once
 
-#include <gtk/gtk.h>
-#include <libide-gui.h>
+#include "ide-terminal-search.h"
+
+#include <libide-gtk.h>
 
 G_BEGIN_DECLS
 
 struct _IdeTerminalSearch
 {
-  GtkBin               parent_instance;
+  AdwBin               parent_instance;
 
   VteTerminal         *terminal;
 
   GtkRevealer         *search_revealer;
 
-  IdeTaggedEntry      *search_entry;
+  IdeSearchEntry      *search_entry;
 
   GtkButton           *search_prev_button;
   GtkButton           *search_next_button;
@@ -52,7 +53,6 @@ struct _IdeTerminalSearch
   gchar               *regex_pattern;
   VteRegex            *regex;
 
-  GtkClipboard        *clipboard;
   gchar               *selected_text;
   gchar               *selection_buffer;
 };
diff --git a/src/libide/terminal/ide-terminal-search.c b/src/libide/terminal/ide-terminal-search.c
index a7e1c5627..2d96fa62e 100644
--- a/src/libide/terminal/ide-terminal-search.c
+++ b/src/libide/terminal/ide-terminal-search.c
@@ -24,16 +24,19 @@
 #include "config.h"
 
 #include <fcntl.h>
-#include <glib/gi18n.h>
 #include <pcre2.h>
 #include <stdlib.h>
-#include <vte/vte.h>
 #include <unistd.h>
 
+#include <glib/gi18n.h>
+#include <vte/vte.h>
+
+#include <libide-gtk.h>
+
 #include "ide-terminal-search.h"
 #include "ide-terminal-search-private.h"
 
-G_DEFINE_FINAL_TYPE (IdeTerminalSearch, ide_terminal_search, GTK_TYPE_BIN)
+G_DEFINE_FINAL_TYPE (IdeTerminalSearch, ide_terminal_search, ADW_TYPE_BIN)
 
 enum {
   PROP_0,
@@ -101,7 +104,7 @@ update_regex (IdeTerminalSearch *self)
 
   g_assert (IDE_IS_TERMINAL_SEARCH (self));
 
-  search_text = gtk_entry_get_text (GTK_ENTRY (self->search_entry));
+  search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_entry));
   caseless = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->match_case_checkbutton));
 
   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->regex_checkbutton)))
@@ -150,7 +153,7 @@ update_regex (IdeTerminalSearch *self)
 }
 
 static void
-search_text_changed_cb (IdeTaggedEntry  *search_entry,
+search_text_changed_cb (IdeSearchEntry    *search_entry,
                         IdeTerminalSearch *self)
 {
   update_regex (self);
@@ -232,12 +235,19 @@ search_revealer_cb (GtkRevealer       *search_revealer,
 
   if (gtk_revealer_get_child_revealed (search_revealer))
     {
+#if 0
       if (vte_terminal_get_has_selection (self->terminal))
         {
           vte_terminal_copy_primary (self->terminal);
-          self->selected_text = gtk_clipboard_wait_for_text (self->clipboard);
-          gtk_entry_set_text (GTK_ENTRY (self->search_entry), self->selected_text);
+
+          g_clear_pointer (&self->selected_text, g_free);
+
+          /* TODO: Wait for async text read */
+
+          gtk_editable_set_text (GTK_EDITABLE (self->search_entry), self->selected_text);
         }
+#endif
+
       gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
     }
   else
@@ -246,6 +256,17 @@ search_revealer_cb (GtkRevealer       *search_revealer,
     }
 }
 
+static void
+search_hide_action (GtkWidget  *widget,
+                    const char *action_name,
+                    GVariant   *param)
+{
+  IdeTerminalSearch *self = IDE_TERMINAL_SEARCH (widget);
+
+  gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
+  gtk_widget_grab_focus (GTK_WIDGET (self->terminal));
+}
+
 static void
 ide_terminal_search_connect_terminal (IdeTerminalSearch *self)
 {
@@ -312,6 +333,8 @@ ide_terminal_search_class_init (IdeTerminalSearchClass *klass)
   gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_revealer);
   gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_options);
 
+  gtk_widget_class_install_action (widget_class, "search.hide", NULL, search_hide_action);
+
   signals[SEARCH] =
     g_signal_new ("search",
                   G_OBJECT_CLASS_TYPE (object_class),
@@ -341,14 +364,12 @@ ide_terminal_search_init (IdeTerminalSearch *self)
   self->regex_caseless = FALSE;
   self->regex_pattern = 0;
 
-  self->clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
-
   gtk_widget_init_template (GTK_WIDGET (self));
 
   g_signal_connect (self->search_prev_button, "clicked", G_CALLBACK (search_button_clicked_cb), self);
   g_signal_connect (self->search_next_button, "clicked", G_CALLBACK (search_button_clicked_cb), self);
   g_signal_connect (self->close_button, "clicked", G_CALLBACK (close_clicked_cb), self);
-  g_signal_connect (self->search_entry, "search-changed", G_CALLBACK (search_text_changed_cb), self);
+  g_signal_connect (self->search_entry, "changed", G_CALLBACK (search_text_changed_cb), self);
   g_signal_connect (self->match_case_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), 
self);
   g_signal_connect (self->entire_word_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), 
self);
   g_signal_connect (self->regex_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), self);
@@ -360,8 +381,6 @@ ide_terminal_search_init (IdeTerminalSearch *self)
 /**
  * ide_terminal_search_set_terminal:
  * @self: a #IdeTerminalSearch
- *
- * Since: 3.32
  */
 void
 ide_terminal_search_set_terminal (IdeTerminalSearch *self,
@@ -378,8 +397,6 @@ ide_terminal_search_set_terminal (IdeTerminalSearch *self,
  * @self: a #IdeTerminalSearch
  *
  * Returns: (transfer none) (nullable): a #VteRegex or %NULL.
- *
- * Since: 3.32
  */
 VteRegex *
 ide_terminal_search_get_regex (IdeTerminalSearch *self)
@@ -394,7 +411,6 @@ ide_terminal_search_get_regex (IdeTerminalSearch *self)
  * @self: a #IdeTerminalSearch
  *
  *
- * Since: 3.32
  */
 gboolean
 ide_terminal_search_get_wrap_around (IdeTerminalSearch *self)
@@ -411,8 +427,6 @@ ide_terminal_search_get_wrap_around (IdeTerminalSearch *self)
  * Gets the revealer widget used for the terminal search.
  *
  * Returns: (transfer none): a #GtkRevealer
- *
- * Since: 3.32
  */
 GtkRevealer *
 ide_terminal_search_get_revealer (IdeTerminalSearch *self)
diff --git a/src/libide/terminal/ide-terminal-search.h b/src/libide/terminal/ide-terminal-search.h
index 1081b0d05..bacee6b00 100644
--- a/src/libide/terminal/ide-terminal-search.h
+++ b/src/libide/terminal/ide-terminal-search.h
@@ -4,7 +4,7 @@
  *
  * 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
+ * the Free Software Foundation, either version ALL the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
@@ -15,7 +15,7 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
- * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-License-Identifier: GPL-ALLor-later
  */
 
 #pragma once
@@ -24,24 +24,26 @@
 # error "Only <libide-terminal.h> can be included directly."
 #endif
 
+#include <adwaita.h>
 #include <vte/vte.h>
+
 #include <libide-core.h>
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_TERMINAL_SEARCH (ide_terminal_search_get_type())
 
-IDE_AVAILABLE_IN_3_32
-G_DECLARE_FINAL_TYPE (IdeTerminalSearch, ide_terminal_search, IDE, TERMINAL_SEARCH, GtkBin)
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeTerminalSearch, ide_terminal_search, IDE, TERMINAL_SEARCH, AdwBin)
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 VteRegex    *ide_terminal_search_get_regex       (IdeTerminalSearch *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean     ide_terminal_search_get_wrap_around (IdeTerminalSearch *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void         ide_terminal_search_set_terminal    (IdeTerminalSearch *self,
                                                   VteTerminal       *terminal);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GtkRevealer *ide_terminal_search_get_revealer    (IdeTerminalSearch *self);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-search.ui b/src/libide/terminal/ide-terminal-search.ui
index e349e8927..bf995f65b 100644
--- a/src/libide/terminal/ide-terminal-search.ui
+++ b/src/libide/terminal/ide-terminal-search.ui
@@ -1,214 +1,141 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <template class="IdeTerminalSearch" parent="GtkBin">
+  <template class="IdeTerminalSearch" parent="AdwBin">
     <property name="halign">end</property>
     <property name="valign">start</property>
     <child>
       <object class="GtkRevealer" id="search_revealer">
-        <property name="visible">true</property>
         <child>
-          <object class="GtkFrame">
-            <property name="visible">true</property>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="spacing">7</property>
             <property name="margin-end">12</property>
             <style>
-              <class name="search-frame"/>
+              <class name="searchbar"/>
             </style>
             <child>
-              <object class="GtkBox">
-                <property name="visible">true</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">7</property>
+              <object class="GtkGrid">
+                <property name="row_spacing">8</property>
+                <property name="column_spacing">8</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">true</property>
-                    <property name="can_focus">false</property>
-                    <property name="row_spacing">8</property>
-                    <property name="column_spacing">8</property>
-                    <child>
-                      <object class="IdeTaggedEntry" id="search_entry">
-                        <property name="visible">true</property>
-                        <property name="can_focus">true</property>
-                        <property name="width-chars">20</property>
-                        <property name="max-width-chars">30</property>
-                        <property name="primary_icon_name">edit-find-symbolic</property>
-                        <property name="primary_icon_activatable">false</property>
-                        <property name="primary_icon_sensitive">false</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="homogeneous">true</property>
-                        <property name="visible">true</property>
-                        <property name="can_focus">false</property>
-                        <property name="valign">center</property>
-                        <style>
-                          <class name="linked"/>
-                        </style>
-                        <child>
-                          <object class="GtkButton" id="search_prev_button">
-                            <property name="visible">true</property>
-                            <property name="can_focus">false</property>
-                            <property name="receives_default">true</property>
-                            <child>
-                              <object class="GtkImage">
-                                <property name="visible">true</property>
-                                <property name="can_focus">false</property>
-                                <property name="icon_name">go-up-symbolic</property>
-                                <property name="icon_size">1</property>
-                              </object>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="expand">false</property>
-                            <property name="fill">true</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkButton" id="search_next_button">
-                            <property name="visible">true</property>
-                            <property name="can_focus">false</property>
-                            <property name="receives_default">true</property>
-                            <child>
-                              <object class="GtkImage">
-                                <property name="visible">true</property>
-                                <property name="can_focus">false</property>
-                                <property name="icon_name">go-down-symbolic</property>
-                                <property name="icon_size">1</property>
-                              </object>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="expand">false</property>
-                            <property name="fill">true</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
+                  <object class="IdeSearchEntry" id="search_entry">
+                    <property name="width-chars">20</property>
+                    <property name="max-width-chars">30</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="homogeneous">true</property>
+                    <property name="valign">center</property>
+                    <style>
+                      <class name="linked"/>
+                    </style>
                     <child>
-                      <object class="GtkToggleButton" id="reveal_button">
-                        <property name="action-target">true</property>
-                        <property name="tooltip-text" translatable="yes">Show or hide search options such as 
case sensitivity</property>
-                        <property name="visible">true</property>
-                        <property name="can_focus">true</property>
-                        <property name="receives_default">true</property>
+                      <object class="GtkButton" id="search_prev_button">
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">true</property>
-                            <property name="can_focus">false</property>
-                            <property name="icon_name">emblem-system-symbolic</property>
+                            <property name="icon_name">go-up-symbolic</property>
+                            <property name="icon_size">1</property>
                           </object>
                         </child>
                       </object>
-                      <packing>
-                        <property name="left_attach">2</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
                     <child>
-                      <object class="GtkButton" id="close_button">
-                        <property name="visible">true</property>
-                        <property name="halign">center</property>
-                        <property name="valign">center</property>
-                        <property name="focus_on_click">false</property>
-                        <style>
-                          <class name="close"/>
-                        </style>
+                      <object class="GtkButton" id="search_next_button">
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">true</property>
-                            <property name="icon_name">window-close-symbolic</property>
+                            <property name="icon_name">go-down-symbolic</property>
+                            <property name="icon_size">1</property>
                           </object>
                         </child>
                       </object>
-                      <packing>
-                        <property name="left_attach">3</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
+                    <layout>
+                      <property name="column">1</property>
+                      <property name="row">0</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="expand">false</property>
-                    <property name="fill">true</property>
-                    <property name="position">0</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="GtkGrid" id="search_options">
-                    <property name="visible">false</property>
-                    <property name="can_focus">false</property>
-                    <property name="column_spacing">8</property>
+                  <object class="GtkToggleButton" id="reveal_button">
+                    <property name="action-target">true</property>
+                    <property name="tooltip-text" translatable="yes">Show or hide search options such as 
case sensitivity</property>
                     <child>
-                      <object class="GtkCheckButton" id="regex_checkbutton">
-                        <property name="label" translatable="yes">Regex</property>
-                        <property name="visible">true</property>
-                        <property name="can_focus">false</property>
-                        <property name="receives_default">false</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">true</property>
+                      <object class="GtkImage">
+                        <property name="icon_name">emblem-system-symbolic</property>
                       </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="match_case_checkbutton">
-                        <property name="label" translatable="yes">Case sensitive</property>
-                        <property name="visible">true</property>
-                        <property name="can_focus">false</property>
-                        <property name="receives_default">false</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">true</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="entire_word_checkbutton">
-                        <property name="label" translatable="yes">Match whole word</property>
-                        <property name="visible">true</property>
-                        <property name="can_focus">false</property>
-                        <property name="receives_default">false</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">true</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">2</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
+                    <layout>
+                      <property name="column">2</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="close_button">
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                    <property name="focus_on_click">false</property>
+                    <style>
+                      <class name="flat"/>
+                      <class name="circular"/>
+                    </style>
                     <child>
-                      <object class="GtkCheckButton" id="wrap_around_checkbutton">
-                        <property name="label" translatable="yes">Wrap around</property>
-                        <property name="visible">true</property>
-                        <property name="can_focus">false</property>
-                        <property name="receives_default">false</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">true</property>
+                      <object class="GtkImage">
+                        <property name="icon_name">window-close-symbolic</property>
                       </object>
-                      <packing>
-                        <property name="left_attach">3</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
+                    <layout>
+                      <property name="column">3</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkGrid" id="search_options">
+                <property name="visible">false</property>
+                <property name="column_spacing">8</property>
+                <child>
+                  <object class="GtkCheckButton" id="regex_checkbutton">
+                    <property name="label" translatable="yes">Regex</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="match_case_checkbutton">
+                    <property name="label" translatable="yes">Case sensitive</property>
+                    <layout>
+                      <property name="column">1</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="entire_word_checkbutton">
+                    <property name="label" translatable="yes">Match whole word</property>
+                    <layout>
+                      <property name="column">2</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="wrap_around_checkbutton">
+                    <property name="label" translatable="yes">Wrap around</property>
+                    <layout>
+                      <property name="column">3</property>
+                      <property name="row">0</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="expand">false</property>
-                    <property name="fill">true</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
             </child>
diff --git a/src/libide/terminal/ide-terminal-util.c b/src/libide/terminal/ide-terminal-util.c
index 1f2370eed..49f07ef06 100644
--- a/src/libide/terminal/ide-terminal-util.c
+++ b/src/libide/terminal/ide-terminal-util.c
@@ -29,119 +29,18 @@
 #include <unistd.h>
 #include <vte/vte.h>
 
-#include "ide-terminal-private.h"
 #include "ide-terminal-util.h"
 
-static const gchar *user_shell = "/bin/sh";
-
-gint
-ide_vte_pty_create_slave (VtePty *pty)
+int
+ide_vte_pty_create_producer (VtePty *pty)
 {
-  gint master_fd;
+  int consumer_fd;
 
   g_return_val_if_fail (VTE_IS_PTY (pty), IDE_PTY_FD_INVALID);
 
-  master_fd = vte_pty_get_fd (pty);
-  if (master_fd == IDE_PTY_FD_INVALID)
+  consumer_fd = vte_pty_get_fd (pty);
+  if (consumer_fd == IDE_PTY_FD_INVALID)
     return IDE_PTY_FD_INVALID;
 
-  return ide_pty_intercept_create_slave (master_fd, TRUE);
-}
-
-/**
- * ide_get_user_shell:
- *
- * Gets the user preferred shell on the host.
- *
- * If the background shell discovery has not yet finished due to
- * slow or misconfigured getent on the host, this will provide a
- * sensible fallback.
- *
- * Returns: (not nullable): a shell such as "/bin/sh"
- *
- * Since: 3.32
- */
-const gchar *
-ide_get_user_shell (void)
-{
-  return user_shell;
-}
-
-static void
-ide_guess_shell_communicate_cb (GObject      *object,
-                                GAsyncResult *result,
-                                gpointer      user_data)
-{
-  IdeSubprocess *subprocess = (IdeSubprocess *)object;
-  g_autoptr(GError) error = NULL;
-  g_autofree gchar *stdout_buf = NULL;
-
-  g_assert (IDE_IS_SUBPROCESS (subprocess));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (user_data == NULL);
-
-  if (!ide_subprocess_communicate_utf8_finish (subprocess, result, &stdout_buf, NULL, &error))
-    {
-      g_warning ("Failed to parse result from getent: %s", error->message);
-      return;
-    }
-
-  if (stdout_buf != NULL)
-    {
-      g_strstrip (stdout_buf);
-
-      if (stdout_buf[0] == '/')
-        user_shell = g_steal_pointer (&stdout_buf);
-    }
-}
-
-void
-_ide_guess_shell (void)
-{
-  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(IdeSubprocess) subprocess = NULL;
-  g_autofree gchar *command = NULL;
-  g_autoptr(GError) error = NULL;
-  g_auto(GStrv) argv = NULL;
-  g_autofree gchar *shell = NULL;
-
-  /*
-   * First ask VTE to guess, so we can use that while we discover
-   * the real shell asynchronously (and possibly outside the container).
-   */
-  if ((shell = vte_get_user_shell ()))
-    user_shell = g_strdup (shell);
-
-  command = g_strdup_printf ("sh -c 'getent passwd %s | head -n1 | cut -f 7 -d :'",
-                             g_get_user_name ());
-
-  if (!g_shell_parse_argv (command, NULL, &argv, &error))
-    {
-      g_warning ("Failed to parse command into argv: %s",
-                 error ? error->message : "unknown error");
-      return;
-    }
-
-  /*
-   * 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);
-
-  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 ());
-  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
-
-  if (!(subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
-    {
-      g_warning ("Failed to spawn getent: %s", error->message);
-      return;
-    }
-
-  ide_subprocess_communicate_utf8_async (subprocess,
-                                         NULL,
-                                         NULL,
-                                         ide_guess_shell_communicate_cb,
-                                         NULL);
+  return ide_pty_intercept_create_producer (consumer_fd, TRUE);
 }
diff --git a/src/libide/terminal/ide-terminal-util.h b/src/libide/terminal/ide-terminal-util.h
index a07b35efc..fd4d7c66f 100644
--- a/src/libide/terminal/ide-terminal-util.h
+++ b/src/libide/terminal/ide-terminal-util.h
@@ -29,9 +29,7 @@
 
 G_BEGIN_DECLS
 
-IDE_AVAILABLE_IN_3_32
-int          ide_vte_pty_create_slave (VtePty *pty);
-IDE_AVAILABLE_IN_3_32
-const gchar *ide_get_user_shell       (void);
+IDE_AVAILABLE_IN_ALL
+int ide_vte_pty_create_producer (VtePty *pty);
 
 G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal.c b/src/libide/terminal/ide-terminal.c
index f1270962b..ad5ccbd70 100644
--- a/src/libide/terminal/ide-terminal.c
+++ b/src/libide/terminal/ide-terminal.c
@@ -22,11 +22,12 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <glib/gi18n.h>
+
 #include <libide-gui.h>
 
 #include "ide-terminal.h"
+#include "ide-terminal-search.h"
 
 #define BUILDER_PCRE2_MULTILINE 0x00000400u
 #define BUILDER_PCRE2_UCP 0x00020000u
@@ -36,6 +37,8 @@ typedef struct
   GtkWidget *popup_menu;
   GSettings *settings;
   gchar     *url;
+  GdkRGBA    bg;
+  GdkRGBA    fg;
 } IdeTerminalPrivate;
 
 typedef struct
@@ -58,6 +61,7 @@ enum {
   POPULATE_POPUP,
   SELECT_ALL,
   SEARCH_REVEAL,
+  COLORS_CHANGED,
   N_SIGNALS
 };
 
@@ -92,31 +96,53 @@ static const GdkRGBA solarized_palette[] = {
   { 0.992156, 0.964705, 0.890196, 1 },
 };
 
+void
+ide_terminal_get_colors (IdeTerminal *self,
+                         GdkRGBA     *bg,
+                         GdkRGBA     *fg)
+{
+  IdeTerminalPrivate *priv = ide_terminal_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_TERMINAL (self));
+
+  if (bg)
+    *bg = priv->bg;
+
+  if (fg)
+    *fg = priv->fg;
+}
+
 static void
-style_context_changed (IdeTerminal *self,
-                       GtkStyleContext *style_context)
+ide_terminal_css_changed (GtkWidget         *widget,
+                          GtkCssStyleChange *change)
 {
-  GtkStateFlags state;
+  IdeTerminal *self = (IdeTerminal *)widget;
+  IdeTerminalPrivate *priv = ide_terminal_get_instance_private (self);
+  GtkStyleContext *style_context;
   GdkRGBA fg;
   GdkRGBA bg;
 
-  g_assert (GTK_IS_STYLE_CONTEXT (style_context));
-  g_assert (IDE_IS_TERMINAL (self));
+  g_assert (IDE_IS_TERMINAL (widget));
 
-  state = gtk_style_context_get_state (style_context);
+  style_context = gtk_widget_get_style_context (widget);
 
-  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
-  gtk_style_context_get_color (style_context, state, &fg);
-  gtk_style_context_get_background_color (style_context, state, &bg);
-  G_GNUC_END_IGNORE_DEPRECATIONS;
+  if (!gtk_style_context_lookup_color (style_context, "window_fg_color", &fg))
+    gdk_rgba_parse (&fg, "#eeeeec");
 
-  if (bg.alpha == 0.0)
-    gdk_rgba_parse (&bg, "#f6f7f8");
+  if (!gtk_style_context_lookup_color (style_context, "window_bg_color", &bg))
+    gdk_rgba_parse (&bg, "#242424");
 
-  vte_terminal_set_colors (VTE_TERMINAL (self), &fg, &bg,
+  vte_terminal_set_colors (VTE_TERMINAL (widget),
+                           &fg, &bg,
                            solarized_palette, G_N_ELEMENTS (solarized_palette));
+
+  priv->fg = fg;
+  priv->bg = bg;
+
+  g_signal_emit (self, signals [COLORS_CHANGED], 0);
 }
 
+#if 0
 static void
 popup_menu_detach (GtkWidget *attach_widget,
                    GtkMenu   *menu)
@@ -218,49 +244,68 @@ ide_terminal_popup_menu (GtkWidget *widget)
 
   return TRUE;
 }
+#endif
 
-static gboolean
-ide_terminal_button_press_event (GtkWidget      *widget,
-                                 GdkEventButton *button)
+static void
+ide_terminal_click_pressed_cb (IdeTerminal     *self,
+                               int              n_presses,
+                               double           x,
+                               double           y,
+                               GtkGestureClick *click)
 {
-  IdeTerminal *self = (IdeTerminal *)widget;
   IdeTerminalPrivate *priv = ide_terminal_get_instance_private (self);
+  int button;
+
+  IDE_ENTRY;
 
   g_assert (IDE_IS_TERMINAL (self));
-  g_assert (button != NULL);
+  g_assert (GTK_IS_GESTURE_CLICK (click));
 
-  if (button->type == GDK_BUTTON_PRESS)
+  button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (click));
+
+  if (button == 1)
     {
-      if (button->button == GDK_BUTTON_PRIMARY)
+      g_autofree gchar *pattern = NULL;
+      glong cell_width = vte_terminal_get_char_width (VTE_TERMINAL (self));
+      glong cell_height = vte_terminal_get_char_height (VTE_TERMINAL (self));
+      glong column, row;
+      int tag = 0;
+
+      /* crappy way to do this, but i dont see another option right
+       * now given we have to go through deprecated APIs in Vte
+       * until it gets things together for GTK 4.
+       */
+      column = x / cell_width;
+      row = y / cell_height;
+
+      /* no other option in VTE for GTK 4 right now */
+      G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+      pattern = vte_terminal_match_check (VTE_TERMINAL (self), column, row, &tag);
+      G_GNUC_END_IGNORE_DEPRECATIONS
+
+      if (pattern != NULL)
         {
-          g_autofree gchar *pattern = NULL;
-
-          pattern = vte_terminal_match_check_event (VTE_TERMINAL (self), (GdkEvent *)button, NULL);
-
-          if (pattern != NULL)
-            {
-              gboolean ret = GDK_EVENT_PROPAGATE;
+          gboolean ret = GDK_EVENT_PROPAGATE;
 
-              g_free (priv->url);
-              priv->url = g_steal_pointer (&pattern);
+          ide_set_string (&priv->url, pattern);
 
-              g_signal_emit (self, signals [OPEN_LINK], 0, &ret);
+          g_signal_emit (self, signals[OPEN_LINK], 0, &ret);
 
-              return ret;
-            }
+          if (ret)
+            gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED);
         }
-      else if (button->button == GDK_BUTTON_SECONDARY)
-        {
-          if (!gtk_widget_has_focus (GTK_WIDGET (self)))
-            gtk_widget_grab_focus (GTK_WIDGET (self));
-
-          ide_terminal_do_popup (self, (GdkEvent *)button);
+    }
+#if 0
+  else if (button == 3)
+    {
+      if (!gtk_widget_has_focus (GTK_WIDGET (self)))
+        gtk_widget_grab_focus (GTK_WIDGET (self));
 
-          return GDK_EVENT_STOP;
-        }
+      ide_terminal_do_popup (self, (GdkEvent *)button);
     }
+#endif
 
-  return GTK_WIDGET_CLASS (ide_terminal_parent_class)->button_press_event (widget, button);
+  IDE_EXIT;
 }
 
 static void
@@ -286,8 +331,7 @@ ide_terminal_copy_link_address (IdeTerminal *self)
   if (ide_str_empty0 (priv->url))
     return FALSE;
 
-  gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (self), GDK_SELECTION_CLIPBOARD),
-                          priv->url, strlen (priv->url));
+  gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (self)), priv->url);
 
   return TRUE;
 }
@@ -312,7 +356,7 @@ ide_terminal_open_link_resolve_cb (GObject      *object,
                                  pos->line,
                                  pos->column,
                                  IDE_BUFFER_OPEN_FLAGS_NONE,
-                                 NULL, NULL, NULL);
+                                 NULL, NULL, NULL, NULL);
 
   g_slice_free (Position, pos);
 }
@@ -366,9 +410,27 @@ ide_terminal_open_link (IdeTerminal *self)
   return FALSE;
 }
 
+static GtkWidget *
+find_child_typed (GtkWidget *parent,
+                  GType      child_type)
+{
+  for (GtkWidget *child = gtk_widget_get_first_child (parent);
+       child;
+       child = gtk_widget_get_next_sibling (child))
+    {
+      if (g_type_is_a (G_OBJECT_TYPE (child), child_type))
+        return child;
+    }
+
+  return NULL;
+}
+
 static void
-ide_terminal_real_search_reveal (IdeTerminal *self)
+ide_terminal_search_reveal (GtkWidget  *widget,
+                            const char *action_name,
+                            GVariant   *param)
 {
+  IdeTerminal *self = (IdeTerminal *)widget;
   GtkWidget *parent_overlay;
 
   g_assert (IDE_IS_TERMINAL (self));
@@ -377,10 +439,15 @@ ide_terminal_real_search_reveal (IdeTerminal *self)
 
   if (parent_overlay != NULL)
     {
-      GtkRevealer *revealer = dzl_gtk_widget_find_child_typed (parent_overlay, GTK_TYPE_REVEALER);
+      IdeTerminalSearch *search = IDE_TERMINAL_SEARCH (find_child_typed (parent_overlay, 
IDE_TYPE_TERMINAL_SEARCH));
+
+      if (search != NULL)
+        {
+          GtkRevealer *revealer = ide_terminal_search_get_revealer (search);
 
-      if (revealer != NULL && !gtk_revealer_get_child_revealed (revealer))
-        gtk_revealer_set_reveal_child (revealer, TRUE);
+          if (!gtk_revealer_get_child_revealed (revealer))
+            gtk_revealer_set_reveal_child (revealer, TRUE);
+        }
     }
 }
 
@@ -405,30 +472,28 @@ ide_terminal_font_changed (IdeTerminal *self,
 }
 
 static void
-ide_terminal_size_allocate (GtkWidget     *widget,
-                            GtkAllocation *alloc)
+ide_terminal_size_allocate (GtkWidget *widget,
+                            int        width,
+                            int        height,
+                            int        baseline)
 {
   IdeTerminal *self = (IdeTerminal *)widget;
-  glong width;
-  glong height;
-  glong columns;
-  glong rows;
+  int char_width, char_height;
+  int columns, rows;
 
-  GTK_WIDGET_CLASS (ide_terminal_parent_class)->size_allocate (widget, alloc);
+  GTK_WIDGET_CLASS (ide_terminal_parent_class)->size_allocate (widget, width, height, baseline);
 
-  if ((alloc->width == 0) || (alloc->height == 0))
+  if (width == 0 || height == 0)
     return;
 
-  width = vte_terminal_get_char_width (VTE_TERMINAL (self));
-  height = vte_terminal_get_char_height (VTE_TERMINAL (self));
-
-  if ((width == 0) || (height == 0))
+  char_width = vte_terminal_get_char_width (VTE_TERMINAL (self));
+  char_height = vte_terminal_get_char_height (VTE_TERMINAL (self));
+  if (char_width == 0 || char_height == 0)
     return;
 
-  columns = alloc->width / width;
-  rows = alloc->height / height;
-
-  if ((columns < 2) || (rows < 2))
+  columns = width / char_width;
+  rows = height / char_height;
+  if (columns < 2 || rows < 2)
     return;
 
   vte_terminal_set_size (VTE_TERMINAL (self), columns, rows);
@@ -455,38 +520,59 @@ update_scrollback_cb (IdeTerminal *self,
 }
 
 static void
-ide_terminal_destroy (GtkWidget *widget)
+copy_clipboard_action (GtkWidget  *widget,
+                       const char *action_name,
+                       GVariant   *param)
 {
-  IdeTerminal *self = (IdeTerminal *)widget;
-  IdeTerminalPrivate *priv = ide_terminal_get_instance_private (self);
+  g_signal_emit_by_name (widget, "copy-clipboard");
+}
 
-  g_assert (IDE_IS_TERMINAL (self));
+static void
+paste_clipboard_action (GtkWidget  *widget,
+                        const char *action_name,
+                        GVariant   *param)
+{
+  g_signal_emit_by_name (widget, "paste-clipboard");
+}
+
+static void
+ide_terminal_dispose (GObject *object)
+{
+  IdeTerminal *self = (IdeTerminal *)object;
+  IdeTerminalPrivate *priv = ide_terminal_get_instance_private (self);
 
   g_clear_object (&priv->settings);
   g_clear_pointer (&priv->url, g_free);
 
-  GTK_WIDGET_CLASS (ide_terminal_parent_class)->destroy (widget);
+  G_OBJECT_CLASS (ide_terminal_parent_class)->dispose (object);
 }
 
 static void
 ide_terminal_class_init (IdeTerminalClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-  GtkBindingSet *binding_set;
 
-  widget_class->destroy = ide_terminal_destroy;
-  widget_class->button_press_event = ide_terminal_button_press_event;
-  widget_class->popup_menu = ide_terminal_popup_menu;
+  object_class->dispose = ide_terminal_dispose;
+
+  widget_class->css_changed = ide_terminal_css_changed;
   widget_class->size_allocate = ide_terminal_size_allocate;
 
   klass->copy_link_address = ide_terminal_copy_link_address;
   klass->open_link = ide_terminal_open_link;
   klass->select_all = ide_terminal_real_select_all;
-  klass->search_reveal = ide_terminal_real_search_reveal;
 
   filename_regex = g_regex_new (FILENAME_PLUS_LOCATION, 0, 0, NULL);
   g_assert (filename_regex != NULL);
 
+  signals [COLORS_CHANGED] =
+    g_signal_new ("colors-changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
   signals [COPY_LINK_ADDRESS] =
     g_signal_new ("copy-link-address",
                   G_TYPE_FROM_CLASS (klass),
@@ -496,23 +582,14 @@ ide_terminal_class_init (IdeTerminalClass *klass)
                   G_TYPE_BOOLEAN,
                   0);
 
-  signals [SEARCH_REVEAL] =
-    g_signal_new ("search-reveal",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
-                  G_STRUCT_OFFSET (IdeTerminalClass, search_reveal),
-                  NULL, NULL, NULL,
-                  G_TYPE_NONE,
-                  0);
-
   signals [OPEN_LINK] =
     g_signal_new ("open-link",
                   G_TYPE_FROM_CLASS (klass),
                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                   G_STRUCT_OFFSET (IdeTerminalClass, open_link),
-                  NULL, NULL, NULL,
-                  G_TYPE_BOOLEAN,
-                  0);
+                  g_signal_accumulator_true_handled, NULL,
+                  NULL,
+                  G_TYPE_BOOLEAN, 0);
 
   signals [POPULATE_POPUP] =
     g_signal_new ("populate-popup",
@@ -534,34 +611,24 @@ ide_terminal_class_init (IdeTerminalClass *klass)
                   1,
                   G_TYPE_BOOLEAN);
 
-  binding_set = gtk_binding_set_by_class (klass);
-
-  gtk_binding_entry_add_signal (binding_set,
-                                GDK_KEY_c,
-                                GDK_SHIFT_MASK | GDK_CONTROL_MASK,
-                                "copy-clipboard",
-                                0);
-
-  gtk_binding_entry_add_signal (binding_set,
-                                GDK_KEY_v,
-                                GDK_SHIFT_MASK | GDK_CONTROL_MASK,
-                                "paste-clipboard",
-                                0);
-
-  gtk_binding_entry_add_signal (binding_set,
-                                GDK_KEY_f,
-                                GDK_SHIFT_MASK | GDK_CONTROL_MASK,
-                                "search-reveal",
-                                0);
+  gtk_widget_class_install_action (widget_class, "terminal.copy-clipboard", NULL, copy_clipboard_action);
+  gtk_widget_class_install_action (widget_class, "terminal.paste-clipboard", NULL, paste_clipboard_action);
+  gtk_widget_class_install_action (widget_class, "terminal.search-reveal", NULL, ide_terminal_search_reveal);
 }
 
 static void
 ide_terminal_init (IdeTerminal *self)
 {
   IdeTerminalPrivate *priv = ide_terminal_get_instance_private (self);
-  GtkStyleContext *style_context;
+  GtkEventController *gesture;
 
-  dzl_widget_action_group_attach (self, "terminal");
+  gesture = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+  g_signal_connect_object (gesture,
+                           "pressed",
+                           G_CALLBACK (ide_terminal_click_pressed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  gtk_widget_add_controller (GTK_WIDGET (self), g_steal_pointer (&gesture));
 
   for (guint i = 0; i < G_N_ELEMENTS (url_regexes); i++)
     {
@@ -569,7 +636,7 @@ ide_terminal_init (IdeTerminal *self)
       const gchar *pattern = url_regexes[i];
       gint tag;
 
-      regex = vte_regex_new_for_match (pattern, DZL_LITERAL_LENGTH (pattern),
+      regex = vte_regex_new_for_match (pattern, strlen (pattern),
                                        VTE_REGEX_FLAGS_DEFAULT | BUILDER_PCRE2_MULTILINE | BUILDER_PCRE2_UCP,
                                        NULL);
       tag = vte_terminal_match_add_regex (VTE_TERMINAL (self), regex, 0);
@@ -599,17 +666,6 @@ ide_terminal_init (IdeTerminal *self)
   ide_terminal_font_changed (self, NULL, priv->settings);
   update_scrollback_cb (self, "scrollback-lines", priv->settings);
 
-  style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
-  gtk_style_context_add_class (style_context, "terminal");
-  g_signal_connect_object (style_context,
-                           "changed",
-                           G_CALLBACK (style_context_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
-  style_context_changed (self, style_context);
-
-  gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
-
   vte_terminal_set_enable_fallback_scrolling (VTE_TERMINAL (self), FALSE);
   vte_terminal_set_scroll_unit_is_pixels (VTE_TERMINAL (self), TRUE);
 }
diff --git a/src/libide/terminal/ide-terminal.h b/src/libide/terminal/ide-terminal.h
index c4687e6c2..13e8e1ef9 100644
--- a/src/libide/terminal/ide-terminal.h
+++ b/src/libide/terminal/ide-terminal.h
@@ -31,7 +31,7 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_TERMINAL (ide_terminal_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_DERIVABLE_TYPE (IdeTerminal, ide_terminal, IDE, TERMINAL, VteTerminal)
 
 struct _IdeTerminalClass
@@ -42,15 +42,15 @@ struct _IdeTerminalClass
                                    GtkWidget   *widget);
   void     (*select_all)          (IdeTerminal *self,
                                    gboolean     all);
-  void     (*search_reveal)       (IdeTerminal *self);
   gboolean (*open_link)           (IdeTerminal *self);
   gboolean (*copy_link_address)   (IdeTerminal *self);
-
-  /*< private >*/
-  gpointer padding[16];
 };
 
-IDE_AVAILABLE_IN_3_32
-GtkWidget *ide_terminal_new (void);
+IDE_AVAILABLE_IN_ALL
+GtkWidget *ide_terminal_new        (void);
+IDE_AVAILABLE_IN_ALL
+void       ide_terminal_get_colors (IdeTerminal *self,
+                                    GdkRGBA     *bg,
+                                    GdkRGBA     *fg);
 
 G_END_DECLS
diff --git a/src/libide/terminal/libide-terminal.gresource.xml 
b/src/libide/terminal/libide-terminal.gresource.xml
index 25622585b..2a27dcbb6 100644
--- a/src/libide/terminal/libide-terminal.gresource.xml
+++ b/src/libide/terminal/libide-terminal.gresource.xml
@@ -8,7 +8,5 @@
     <file preprocess="xml-stripblanks">ide-terminal-popover.ui</file>
     <file preprocess="xml-stripblanks">ide-terminal-popover-row.ui</file>
     <file preprocess="xml-stripblanks">ide-terminal-search.ui</file>
-    <file preprocess="xml-stripblanks">ide-terminal-surface.ui</file>
-    <file preprocess="xml-stripblanks">ide-terminal-workspace.ui</file>
   </gresource>
 </gresources>
diff --git a/src/libide/terminal/libide-terminal.h b/src/libide/terminal/libide-terminal.h
index 3953767f1..330bcf1ed 100644
--- a/src/libide/terminal/libide-terminal.h
+++ b/src/libide/terminal/libide-terminal.h
@@ -33,8 +33,6 @@
 #include "ide-terminal-page.h"
 #include "ide-terminal-popover.h"
 #include "ide-terminal-search.h"
-#include "ide-terminal-surface.h"
 #include "ide-terminal-util.h"
-#include "ide-terminal-workspace.h"
 
 #undef IDE_TERMINAL_INSIDE
diff --git a/src/libide/terminal/meson.build b/src/libide/terminal/meson.build
index 6affcae14..1878d3ba7 100644
--- a/src/libide/terminal/meson.build
+++ b/src/libide/terminal/meson.build
@@ -15,9 +15,7 @@ libide_terminal_public_headers = [
   'ide-terminal-popover.h',
   'ide-terminal-launcher.h',
   'ide-terminal-search.h',
-  'ide-terminal-surface.h',
   'ide-terminal-util.h',
-  'ide-terminal-workspace.h',
   'ide-terminal.h',
   'libide-terminal.h',
 ]
@@ -32,8 +30,9 @@ libide_terminal_private_headers = [
   'ide-terminal-page-actions.h',
   'ide-terminal-page-private.h',
   'ide-terminal-popover-row.h',
-  'ide-terminal-private.h',
+  'ide-terminal-run-command-private.h',
   'ide-terminal-search-private.h',
+  'ide-terminal-private.h',
 ]
 
 libide_terminal_public_sources = [
@@ -41,15 +40,15 @@ libide_terminal_public_sources = [
   'ide-terminal-popover.c',
   'ide-terminal-launcher.c',
   'ide-terminal-search.c',
-  'ide-terminal-surface.c',
   'ide-terminal-util.c',
-  'ide-terminal-workspace.c',
   'ide-terminal.c',
 ]
 
 libide_terminal_private_sources = [
+  'ide-terminal-init.c',
   'ide-terminal-page-actions.c',
   'ide-terminal-popover-row.c',
+  'ide-terminal-run-command.c',
 ]
 
 #
@@ -71,7 +70,6 @@ libide_terminal_generated_headers += [libide_terminal_resources[1]]
 libide_terminal_deps = [
   libgio_dep,
   libgtk_dep,
-  libdazzle_dep,
   libvte_dep,
 
   libide_core_dep,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]