[gnome-builder: 54/139] libide-terminal: add libide-terminal static library
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder: 54/139] libide-terminal: add libide-terminal static library
- Date: Thu, 10 Jan 2019 04:21:51 +0000 (UTC)
commit 5554d8f5415d4f8cadb3262714158b09b2a6b104
Author: Christian Hergert <chergert redhat com>
Date: Wed Jan 9 16:50:07 2019 -0800
libide-terminal: add libide-terminal static library
This adds the new libide-terminal static library that provides the
reusable terminal components for plugins.
It also includes some experimental new groundwork for a terminal workspace
which may prove useful in the future for people who do not want a
traditional IDE.
src/libide/terminal/gtk/menus.ui | 12 +
src/libide/terminal/ide-terminal-page-actions.c | 335 ++++++++++
src/libide/terminal/ide-terminal-page-actions.h | 29 +
src/libide/terminal/ide-terminal-page-private.h | 66 ++
src/libide/terminal/ide-terminal-page.c | 765 ++++++++++++++++++++++
src/libide/terminal/ide-terminal-page.h | 45 ++
src/libide/terminal/ide-terminal-page.ui | 41 ++
src/libide/terminal/ide-terminal-search-private.h | 5 +-
src/libide/terminal/ide-terminal-search.c | 7 +-
src/libide/terminal/ide-terminal-search.h | 7 +-
src/libide/terminal/ide-terminal-surface.c | 84 +++
src/libide/terminal/ide-terminal-surface.h | 39 ++
src/libide/terminal/ide-terminal-surface.ui | 10 +
src/libide/terminal/ide-terminal-util.c | 20 +-
src/libide/terminal/ide-terminal-util.h | 7 +-
src/libide/terminal/ide-terminal-workspace.c | 52 ++
src/libide/terminal/ide-terminal-workspace.h | 37 ++
src/libide/terminal/ide-terminal-workspace.ui | 33 +
src/libide/terminal/ide-terminal.c | 8 +-
src/libide/terminal/ide-terminal.h | 8 +-
src/libide/terminal/libide-terminal.gresource.xml | 12 +
src/libide/terminal/libide-terminal.h | 38 ++
src/libide/terminal/meson.build | 94 ++-
23 files changed, 1720 insertions(+), 34 deletions(-)
---
diff --git a/src/libide/terminal/gtk/menus.ui b/src/libide/terminal/gtk/menus.ui
new file mode 100644
index 000000000..b70d3ad94
--- /dev/null
+++ b/src/libide/terminal/gtk/menus.ui
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <menu id="ide-terminal-workspace-menu">
+ <section id="ide-terminal-workspace-menu-close">
+ <item>
+ <attribute name="id">ide-terminal-workspace-menu-close</attribute>
+ <attribute name="label" translatable="yes">Close</attribute>
+ <attribute name="action">win.close</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/libide/terminal/ide-terminal-page-actions.c b/src/libide/terminal/ide-terminal-page-actions.c
new file mode 100644
index 000000000..b4f96b5af
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-page-actions.c
@@ -0,0 +1,335 @@
+/* gb-editor-view-actions.c
+ *
+ * Copyright 2015 Sebastien Lafargue <slafargue gnome org>
+ *
+ * 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-page"
+
+#include <glib/gi18n.h>
+#include <libide-gui.h>
+#include <libide-terminal.h>
+#include <string.h>
+
+#include "ide-terminal-page-actions.h"
+#include "ide-terminal-page-private.h"
+
+typedef struct
+{
+ VteTerminal *terminal;
+ GFile *file;
+ GOutputStream *stream;
+ gchar *buffer;
+} SaveTask;
+
+static void
+savetask_free (gpointer data)
+{
+ SaveTask *savetask = (SaveTask *)data;
+
+ if (savetask != NULL)
+ {
+ g_clear_object (&savetask->file);
+ g_clear_object (&savetask->stream);
+ g_clear_object (&savetask->terminal);
+ g_clear_pointer (&savetask->buffer, g_free);
+ g_slice_free (SaveTask, savetask);
+ }
+}
+
+static gboolean
+ide_terminal_page_actions_save_finish (IdeTerminalPage *view,
+ GAsyncResult *result,
+ GError **error)
+{
+ IdeTask *task = (IdeTask *)result;
+
+ g_return_val_if_fail (ide_task_is_valid (result, view), FALSE);
+
+ g_return_val_if_fail (IDE_IS_TERMINAL_PAGE (view), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (task), FALSE);
+
+ return ide_task_propagate_boolean (task, error);
+}
+
+static void
+save_worker (IdeTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ SaveTask *savetask = (SaveTask *)task_data;
+ g_autoptr(GError) error = NULL;
+ gboolean ret;
+
+ g_assert (IDE_IS_MAIN_THREAD ());
+ g_assert (IDE_IS_TASK (task));
+ g_assert (IDE_IS_TERMINAL_PAGE (source_object));
+ g_assert (savetask != NULL);
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ if (savetask->buffer != NULL)
+ {
+ g_autoptr(GInputStream) input_stream = NULL;
+
+ input_stream = g_memory_input_stream_new_from_data (savetask->buffer, -1, NULL);
+ ret = g_output_stream_splice (G_OUTPUT_STREAM (savetask->stream),
+ G_INPUT_STREAM (input_stream),
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable,
+ &error);
+ }
+ else
+ {
+ ret = vte_terminal_write_contents_sync (savetask->terminal,
+ G_OUTPUT_STREAM (savetask->stream),
+ VTE_WRITE_DEFAULT,
+ cancellable,
+ &error);
+ }
+
+ if (ret)
+ ide_task_return_boolean (task, TRUE);
+ else
+ ide_task_return_error (task, g_steal_pointer (&error));
+}
+
+static void
+ide_terminal_page_actions_save_async (IdeTerminalPage *view,
+ VteTerminal *terminal,
+ GFile *file,
+ GAsyncReadyCallback callback,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ g_autoptr(GFileOutputStream) output_stream = NULL;
+ g_autoptr(GError) error = NULL;
+ SaveTask *savetask;
+
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (view, cancellable, callback, user_data);
+
+ output_stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, cancellable, &error);
+
+ if (output_stream != NULL)
+ {
+ savetask = g_slice_new0 (SaveTask);
+ savetask->file = g_object_ref (file);
+ savetask->stream = g_object_ref (G_OUTPUT_STREAM (output_stream));
+ savetask->terminal = g_object_ref (terminal);
+ savetask->buffer = g_steal_pointer (&view->selection_buffer);
+
+ ide_task_set_task_data (task, savetask, savetask_free);
+ save_worker (task, view, savetask, cancellable);
+ }
+ else
+ ide_task_return_error (task, g_steal_pointer (&error));
+}
+
+static void
+save_as_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeTask *task = (IdeTask *)result;
+ IdeTerminalPage *view = user_data;
+ SaveTask *savetask;
+ GFile *file;
+ GError *error = NULL;
+
+ savetask = ide_task_get_task_data (task);
+ file = g_object_ref (savetask->file);
+
+ if (!ide_terminal_page_actions_save_finish (view, result, &error))
+ {
+ g_object_unref (file);
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ g_clear_object (&view->save_as_file_top);
+ view->save_as_file_top = file;
+ }
+}
+
+static GFile *
+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;
+
+ return file;
+}
+
+static VteTerminal *
+get_last_focused_terminal (IdeTerminalPage *view)
+{
+ return VTE_TERMINAL (view->terminal_top);
+}
+
+static gchar *
+gb_terminal_get_selected_text (IdeTerminalPage *view,
+ VteTerminal **terminal_p)
+{
+ VteTerminal *terminal;
+ gchar *buf = NULL;
+
+ terminal = get_last_focused_terminal (view);
+ if (terminal_p != NULL)
+ *terminal_p = terminal;
+
+ if (vte_terminal_get_has_selection (terminal))
+ {
+ vte_terminal_copy_primary (terminal);
+ buf = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
+ }
+
+ return buf;
+}
+
+static void
+save_as_response (GtkWidget *widget,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(IdeTerminalPage) view = user_data;
+ g_autoptr(GFile) file = NULL;
+ GtkFileChooser *chooser = (GtkFileChooser *)widget;
+ VteTerminal *terminal;
+
+ g_assert (GTK_IS_FILE_CHOOSER (chooser));
+ g_assert (IDE_IS_TERMINAL_PAGE (view));
+
+ switch (response)
+ {
+ case GTK_RESPONSE_OK:
+ file = gtk_file_chooser_get_file (chooser);
+ terminal = get_last_focused_terminal (view);
+ ide_terminal_page_actions_save_async (view, terminal, file, save_as_cb, NULL, view);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ g_free (view->selection_buffer);
+
+ default:
+ break;
+ }
+
+ gtk_widget_destroy (widget);
+}
+
+static void
+ide_terminal_page_actions_save_as (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeTerminalPage *view = user_data;
+ GtkWidget *suggested;
+ GtkWidget *toplevel;
+ GtkWidget *dialog;
+ GFile *file = NULL;
+
+ g_assert (IDE_IS_TERMINAL_PAGE (view));
+
+ /* We can't get this later because the dialog makes the terminal
+ * unfocused and thus resets the selection
+ */
+ view->selection_buffer = gb_terminal_get_selected_text (view, NULL);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "action", GTK_FILE_CHOOSER_ACTION_SAVE,
+ "do-overwrite-confirmation", TRUE,
+ "local-only", FALSE,
+ "modal", TRUE,
+ "select-multiple", FALSE,
+ "show-hidden", FALSE,
+ "transient-for", toplevel,
+ "title", _("Save Terminal Content As"),
+ NULL);
+
+ file = get_last_focused_terminal_file (view);
+ if (file != NULL)
+ gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), file, NULL);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Save"), GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ 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);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (save_as_response), g_object_ref (view));
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+ide_terminal_page_actions_reset (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeTerminalPage *self = user_data;
+ VteTerminal *terminal;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ terminal = get_last_focused_terminal (self);
+ vte_terminal_reset (terminal, TRUE, FALSE);
+}
+
+static void
+ide_terminal_page_actions_reset_and_clear (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeTerminalPage *self = user_data;
+ VteTerminal *terminal;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ terminal = get_last_focused_terminal (self);
+ vte_terminal_reset (terminal, TRUE, TRUE);
+}
+
+static GActionEntry IdeTerminalPageActions[] = {
+ { "save-as", ide_terminal_page_actions_save_as },
+ { "reset", ide_terminal_page_actions_reset },
+ { "reset-and-clear", ide_terminal_page_actions_reset_and_clear },
+};
+
+void
+ide_terminal_page_actions_init (IdeTerminalPage *self)
+{
+ g_autoptr(GSimpleActionGroup) group = NULL;
+
+ group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group), IdeTerminalPageActions,
+ G_N_ELEMENTS (IdeTerminalPageActions), self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self), "terminal-view", G_ACTION_GROUP (group));
+}
diff --git a/src/libide/terminal/ide-terminal-page-actions.h b/src/libide/terminal/ide-terminal-page-actions.h
new file mode 100644
index 000000000..925ed90d8
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-page-actions.h
@@ -0,0 +1,29 @@
+/* ide-terminal-page-actions.h
+ *
+ * opyright (C) 2015 Sebastien Lafargue <slafargue gnome org>
+ *
+ * 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
+ */
+
+#pragma once
+
+#include "ide-terminal-page.h"
+
+G_BEGIN_DECLS
+
+void ide_terminal_page_actions_init (IdeTerminalPage *self);
+
+G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-page-private.h b/src/libide/terminal/ide-terminal-page-private.h
new file mode 100644
index 000000000..8005602e7
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-page-private.h
@@ -0,0 +1,66 @@
+/* ide-terminal-page-private.h
+ *
+ * Copyright 2015 Sebastien Lafargue <slafargue gnome org>
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <libide-foundry.h>
+#include <libide-gui.h>
+#include <libide-terminal.h>
+
+G_BEGIN_DECLS
+
+struct _IdeTerminalPage
+{
+ IdePage parent_instance;
+
+ /*
+ * If we are spawning a process in a runtime instead of the
+ * host, then we will have a runtime pointer here.
+ */
+ IdeRuntime *runtime;
+
+ GtkOverlay *terminal_overlay_top;
+
+ GtkRevealer *search_revealer_top;
+
+ IdeTerminal *terminal_top;
+
+ GtkScrollbar *top_scrollbar;
+
+ IdeTerminalSearch *tsearch;
+
+ GFile *save_as_file_top;
+
+ gchar *selection_buffer;
+
+ gchar *cwd;
+
+ VtePty *pty;
+
+ gint64 last_respawn;
+
+ guint manage_spawn : 1;
+ guint top_has_spawned : 1;
+ guint top_has_needs_attention : 1;
+ guint run_on_host : 1;
+ guint use_runner : 1;
+};
+
+G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-page.c b/src/libide/terminal/ide-terminal-page.c
new file mode 100644
index 000000000..27ba33298
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-page.c
@@ -0,0 +1,765 @@
+/* ide-terminal-page.c
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-terminal-page"
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <glib/gi18n.h>
+#include <libide-foundry.h>
+#include <libide-gui.h>
+#include <libide-terminal.h>
+#include <stdlib.h>
+#include <vte/vte.h>
+#include <unistd.h>
+
+#define PCRE2_CODE_UNIT_WIDTH 0
+#include <pcre2.h>
+
+#include "ide-terminal-page.h"
+#include "ide-terminal-page-private.h"
+#include "ide-terminal-page-actions.h"
+
+G_DEFINE_TYPE (IdeTerminalPage, ide_terminal_page, IDE_TYPE_PAGE)
+
+enum {
+ PROP_0,
+ PROP_CWD,
+ PROP_MANAGE_SPAWN,
+ PROP_PTY,
+ PROP_RUNTIME,
+ PROP_RUN_ON_HOST,
+ PROP_USE_RUNNER,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void ide_terminal_page_connect_terminal (IdeTerminalPage *self,
+ VteTerminal *terminal);
+static void gbp_terminal_respawn (IdeTerminalPage *self,
+ VteTerminal *terminal);
+
+static gboolean
+shell_supports_login (const gchar *shell)
+{
+ g_autofree gchar *name = NULL;
+
+ /* Shells that support --login */
+ static const gchar *supported[] = {
+ "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
+ide_terminal_page_wait_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeSubprocess *subprocess = (IdeSubprocess *)object;
+ VteTerminal *terminal = user_data;
+ IdeTerminalPage *self;
+ g_autoptr(GError) error = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_SUBPROCESS (subprocess));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (VTE_IS_TERMINAL (terminal));
+
+ if (!ide_subprocess_wait_finish (subprocess, result, &error))
+ {
+ g_warning ("%s", error->message);
+ IDE_GOTO (failure);
+ }
+
+ self = (IdeTerminalPage *)gtk_widget_get_ancestor (GTK_WIDGET (terminal), IDE_TYPE_TERMINAL_PAGE);
+ if (self == NULL)
+ IDE_GOTO (failure);
+
+ if (!dzl_gtk_widget_action (GTK_WIDGET (self), "frame", "close-page", NULL))
+ {
+ if (!gtk_widget_in_destruction (GTK_WIDGET (terminal)))
+ gbp_terminal_respawn (self, terminal);
+ }
+
+failure:
+ g_clear_object (&terminal);
+
+ IDE_EXIT;
+}
+
+static void
+ide_terminal_page_run_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeRunner *runner = (IdeRunner *)object;
+ VteTerminal *terminal = user_data;
+ IdeTerminalPage *self;
+ g_autoptr(GError) error = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_RUNNER (runner));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (VTE_IS_TERMINAL (terminal));
+
+ if (!ide_runner_run_finish (runner, result, &error))
+ {
+ g_warning ("%s", error->message);
+ IDE_GOTO (failure);
+ }
+
+ self = (IdeTerminalPage *)gtk_widget_get_ancestor (GTK_WIDGET (terminal), IDE_TYPE_TERMINAL_PAGE);
+ if (self == NULL)
+ IDE_GOTO (failure);
+
+ if (!dzl_gtk_widget_action (GTK_WIDGET (self), "frame", "close-page", NULL))
+ {
+ if (!gtk_widget_in_destruction (GTK_WIDGET (terminal)))
+ gbp_terminal_respawn (self, terminal);
+ }
+
+failure:
+ ide_object_destroy (IDE_OBJECT (runner));
+ g_clear_object (&terminal);
+
+ IDE_EXIT;
+}
+
+static gboolean
+terminal_has_notification_signal (void)
+{
+ GQuark quark;
+ guint signal_id;
+
+ return g_signal_parse_name ("notification-received",
+ VTE_TYPE_TERMINAL,
+ &signal_id,
+ &quark,
+ FALSE);
+}
+
+static void
+gbp_terminal_respawn (IdeTerminalPage *self,
+ VteTerminal *terminal)
+{
+ g_autoptr(IdeSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+ g_autoptr(GFile) workdir = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *workpath = NULL;
+ g_autofree gchar *shell = NULL;
+ IdeBuildPipeline *pipeline = NULL;
+ IdeWorkbench *workbench;
+ IdeContext *context;
+ VtePty *pty = NULL;
+ gint64 now;
+ int tty_fd = -1;
+ gint stdout_fd = -1;
+ gint stderr_fd = -1;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ vte_terminal_reset (terminal, TRUE, TRUE);
+
+ if (!(workbench = ide_widget_get_workbench (GTK_WIDGET (self))))
+ IDE_EXIT;
+
+ /* Prevent flapping */
+ now = g_get_monotonic_time ();
+ if ((now - self->last_respawn) < (G_USEC_PER_SEC / 10))
+ IDE_EXIT;
+ self->last_respawn = now;
+
+ context = ide_widget_get_context (GTK_WIDGET (self));
+ workdir = ide_context_ref_workdir (context);
+ workpath = g_file_get_path (workdir);
+
+ if (ide_workbench_has_project (workbench))
+ {
+ IdeBuildManager *build_manager;
+
+ build_manager = ide_build_manager_from_context (context);
+ pipeline = ide_build_manager_get_pipeline (build_manager);
+ }
+
+ shell = g_strdup (ide_get_user_shell ());
+
+ pty = vte_terminal_pty_new_sync (terminal,
+ VTE_PTY_DEFAULT | VTE_PTY_NO_LASTLOG | VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP,
+ NULL,
+ &error);
+ if (pty == NULL)
+ IDE_GOTO (cleanup);
+
+ vte_terminal_set_pty (terminal, pty);
+
+ if (-1 == (tty_fd = ide_vte_pty_create_slave (pty)))
+ IDE_GOTO (cleanup);
+
+ if (self->runtime != NULL &&
+ !ide_runtime_contains_program_in_path (self->runtime, shell, NULL))
+ {
+ g_free (shell);
+ shell = g_strdup ("/bin/bash");
+ }
+
+ /* they want to use the runner API, which means we spawn in the
+ * program mount namespace, etc.
+ */
+ if (self->runtime != NULL && self->use_runner)
+ {
+ g_autoptr(IdeSimpleBuildTarget) target = NULL;
+ g_autoptr(IdeRunner) runner = NULL;
+ const gchar *argv[] = { shell, NULL };
+
+
+ target = ide_simple_build_target_new (context);
+ ide_simple_build_target_set_argv (target, argv);
+ ide_simple_build_target_set_cwd (target, self->cwd ?: workpath);
+
+ runner = ide_runtime_create_runner (self->runtime, IDE_BUILD_TARGET (target));
+
+ if (runner != NULL)
+ {
+ IdeEnvironment *env = ide_runner_get_environment (runner);
+
+ /* set_tty() will dup() the fd */
+ ide_runner_set_tty (runner, tty_fd);
+
+ ide_environment_setenv (env, "TERM", "xterm-256color");
+ ide_environment_setenv (env, "INSIDE_GNOME_BUILDER", PACKAGE_VERSION);
+ ide_environment_setenv (env, "SHELL", shell);
+
+ if (pipeline != NULL)
+ {
+ ide_environment_setenv (env, "BUILDDIR", ide_build_pipeline_get_builddir (pipeline));
+ ide_environment_setenv (env, "SRCDIR", ide_build_pipeline_get_srcdir (pipeline));
+ }
+
+ ide_runner_run_async (runner,
+ NULL,
+ ide_terminal_page_run_cb,
+ g_object_ref (terminal));
+ IDE_GOTO (cleanup);
+ }
+ }
+
+ /* dup() is safe as it will inherit O_CLOEXEC */
+ if (-1 == (stdout_fd = dup (tty_fd)) || -1 == (stderr_fd = dup (tty_fd)))
+ IDE_GOTO (cleanup);
+
+ if (self->runtime != NULL)
+ launcher = ide_runtime_create_launcher (self->runtime, NULL);
+
+ if (launcher == NULL)
+ launcher = ide_subprocess_launcher_new (0);
+
+ ide_subprocess_launcher_set_flags (launcher, 0);
+ ide_subprocess_launcher_set_run_on_host (launcher, self->run_on_host);
+ 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, tty_fd);
+ ide_subprocess_launcher_take_stdout_fd (launcher, stdout_fd);
+ ide_subprocess_launcher_take_stderr_fd (launcher, stderr_fd);
+ ide_subprocess_launcher_setenv (launcher, "TERM", "xterm-256color", TRUE);
+ ide_subprocess_launcher_setenv (launcher, "INSIDE_GNOME_BUILDER", PACKAGE_VERSION, TRUE);
+ ide_subprocess_launcher_setenv (launcher, "SHELL", shell, TRUE);
+
+ if (self->cwd != NULL)
+ ide_subprocess_launcher_set_cwd (launcher, self->cwd);
+ else
+ ide_subprocess_launcher_set_cwd (launcher, workpath);
+
+ if (pipeline != NULL)
+ {
+ ide_subprocess_launcher_setenv (launcher, "BUILDDIR", ide_build_pipeline_get_builddir (pipeline),
TRUE);
+ ide_subprocess_launcher_setenv (launcher, "SRCDIR", ide_build_pipeline_get_srcdir (pipeline), TRUE);
+ }
+
+ tty_fd = -1;
+ stdout_fd = -1;
+ stderr_fd = -1;
+
+ if (NULL == (subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error)))
+ IDE_GOTO (cleanup);
+
+ ide_subprocess_wait_async (subprocess,
+ NULL,
+ ide_terminal_page_wait_cb,
+ g_object_ref (terminal));
+
+cleanup:
+ if (tty_fd != -1)
+ close (tty_fd);
+
+ if (stdout_fd != -1)
+ close (stdout_fd);
+
+ if (stderr_fd != -1)
+ close (stderr_fd);
+
+ g_clear_object (&pty);
+
+ if (error != NULL)
+ g_warning ("%s", error->message);
+
+ IDE_EXIT;
+}
+
+static void
+gbp_terminal_realize (GtkWidget *widget)
+{
+ IdeTerminalPage *self = (IdeTerminalPage *)widget;
+
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ GTK_WIDGET_CLASS (ide_terminal_page_parent_class)->realize (widget);
+
+ if (self->manage_spawn && !self->top_has_spawned)
+ {
+ self->top_has_spawned = TRUE;
+ gbp_terminal_respawn (self, VTE_TERMINAL (self->terminal_top));
+ }
+
+ if (!self->manage_spawn && self->pty != NULL)
+ vte_terminal_set_pty (VTE_TERMINAL (self->terminal_top), self->pty);
+}
+
+static void
+gbp_terminal_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ /*
+ * Since we are placing the terminal in a GtkStack, we need
+ * to fake the size a bit. Otherwise, GtkStack tries to keep the
+ * 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
+gbp_terminal_get_preferred_height (GtkWidget *widget,
+ gint *min_height,
+ gint *nat_height)
+{
+ /*
+ * Since we are placing the terminal in a GtkStack, we need
+ * to fake the size a bit. Otherwise, GtkStack tries to keep the
+ * 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
+gbp_terminal_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->top_has_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,
+ const gchar *body,
+ IdeTerminalPage *self)
+{
+ g_assert (VTE_IS_TERMINAL (terminal));
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (terminal)))
+ gbp_terminal_set_needs_attention (self, TRUE);
+}
+
+static gboolean
+focus_in_event_cb (VteTerminal *terminal,
+ GdkEvent *event,
+ IdeTerminalPage *self)
+{
+ g_assert (VTE_IS_TERMINAL (terminal));
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ self->top_has_needs_attention = FALSE;
+ gbp_terminal_set_needs_attention (self, FALSE);
+ gtk_revealer_set_reveal_child (self->search_revealer_top, FALSE);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+window_title_changed_cb (VteTerminal *terminal,
+ IdeTerminalPage *self)
+{
+ const gchar *title;
+
+ g_assert (VTE_IS_TERMINAL (terminal));
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ title = vte_terminal_get_window_title (VTE_TERMINAL (self->terminal_top));
+
+ if (title == NULL)
+ 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);
+}
+
+static IdePage *
+gbp_terminal_create_split (IdePage *page)
+{
+ g_assert (IDE_IS_TERMINAL_PAGE (page));
+
+ return g_object_new (IDE_TYPE_TERMINAL_PAGE,
+ "visible", TRUE,
+ NULL);
+}
+
+static void
+gbp_terminal_grab_focus (GtkWidget *widget)
+{
+ IdeTerminalPage *self = (IdeTerminalPage *)widget;
+
+ g_assert (IDE_IS_TERMINAL_PAGE (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->terminal_top));
+}
+
+static void
+ide_terminal_page_connect_terminal (IdeTerminalPage *self,
+ VteTerminal *terminal)
+{
+ GtkAdjustment *vadj;
+
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (terminal));
+
+ gtk_range_set_adjustment (GTK_RANGE (self->top_scrollbar), vadj);
+
+ g_signal_connect_object (terminal,
+ "focus-in-event",
+ G_CALLBACK (focus_in_event_cb),
+ self,
+ 0);
+
+ g_signal_connect_object (terminal,
+ "window-title-changed",
+ G_CALLBACK (window_title_changed_cb),
+ self,
+ 0);
+
+ if (terminal_has_notification_signal ())
+ {
+ g_signal_connect_object (terminal,
+ "notification-received",
+ G_CALLBACK (notification_received_cb),
+ self,
+ 0);
+ }
+}
+
+static void
+ide_terminal_page_finalize (GObject *object)
+{
+ IdeTerminalPage *self = IDE_TERMINAL_PAGE (object);
+
+ g_clear_object (&self->save_as_file_top);
+ g_clear_pointer (&self->cwd, g_free);
+ g_clear_pointer (&self->selection_buffer, g_free);
+ g_clear_object (&self->pty);
+ g_clear_object (&self->runtime);
+
+ G_OBJECT_CLASS (ide_terminal_page_parent_class)->finalize (object);
+}
+
+static void
+ide_terminal_page_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTerminalPage *self = IDE_TERMINAL_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGE_SPAWN:
+ g_value_set_boolean (value, self->manage_spawn);
+ break;
+
+ case PROP_PTY:
+ g_value_set_object (value, self->pty);
+ break;
+
+ case PROP_RUNTIME:
+ g_value_set_object (value, self->runtime);
+ break;
+
+ case PROP_RUN_ON_HOST:
+ g_value_set_boolean (value, self->run_on_host);
+ break;
+
+ case PROP_USE_RUNNER:
+ g_value_set_boolean (value, self->use_runner);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_terminal_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTerminalPage *self = IDE_TERMINAL_PAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CWD:
+ self->cwd = g_value_dup_string (value);
+ break;
+
+ case PROP_MANAGE_SPAWN:
+ self->manage_spawn = g_value_get_boolean (value);
+ break;
+
+ case PROP_PTY:
+ self->pty = g_value_dup_object (value);
+ break;
+
+ case PROP_RUNTIME:
+ self->runtime = g_value_dup_object (value);
+ break;
+
+ case PROP_RUN_ON_HOST:
+ self->run_on_host = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_RUNNER:
+ self->use_runner = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_terminal_page_class_init (IdeTerminalPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ IdePageClass *page_class = IDE_PAGE_CLASS (klass);
+
+ object_class->finalize = ide_terminal_page_finalize;
+ object_class->get_property = ide_terminal_page_get_property;
+ object_class->set_property = ide_terminal_page_set_property;
+
+ widget_class->realize = gbp_terminal_realize;
+ widget_class->get_preferred_width = gbp_terminal_get_preferred_width;
+ widget_class->get_preferred_height = gbp_terminal_get_preferred_height;
+ widget_class->grab_focus = gbp_terminal_grab_focus;
+
+ page_class->create_split = gbp_terminal_create_split;
+
+ 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, top_scrollbar);
+ gtk_widget_class_bind_template_child (widget_class, IdeTerminalPage, terminal_overlay_top);
+
+ properties [PROP_CWD] =
+ g_param_spec_string ("cwd",
+ "CWD",
+ "The directory to spawn the terminal in",
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ properties [PROP_MANAGE_SPAWN] =
+ g_param_spec_boolean ("manage-spawn",
+ "Manage Spawn",
+ "Manage Spawn",
+ TRUE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_PTY] =
+ g_param_spec_object ("pty",
+ "Pty",
+ "The pseudo terminal to use",
+ VTE_TYPE_PTY,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_RUNTIME] =
+ g_param_spec_object ("runtime",
+ "Runtime",
+ "The runtime to use for spawning",
+ IDE_TYPE_RUNTIME,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_RUN_ON_HOST] =
+ g_param_spec_boolean ("run-on-host",
+ "Run on Host",
+ "If the process should be spawned on the host",
+ TRUE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_USE_RUNNER] =
+ g_param_spec_boolean ("use-runner",
+ "Use Runner",
+ "If we should use the runner interface and build target",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_terminal_page_init (IdeTerminalPage *self)
+{
+ GtkStyleContext *style_context;
+
+ self->run_on_host = TRUE;
+ self->manage_spawn = TRUE;
+
+ self->tsearch = g_object_new (IDE_TYPE_TERMINAL_SEARCH,
+ "visible", TRUE,
+ NULL);
+ self->search_revealer_top = ide_terminal_search_get_revealer (self->tsearch);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ ide_page_set_icon_name (IDE_PAGE (self), "utilities-terminal-symbolic");
+ ide_page_set_can_split (IDE_PAGE (self), TRUE);
+ ide_page_set_menu_id (IDE_PAGE (self), "terminal-page-document-menu");
+
+ gtk_overlay_add_overlay (self->terminal_overlay_top, GTK_WIDGET (self->tsearch));
+
+ ide_terminal_page_connect_terminal (self, VTE_TERMINAL (self->terminal_top));
+
+ ide_terminal_search_set_terminal (self->tsearch, VTE_TERMINAL (self->terminal_top));
+
+ 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);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (self->terminal_top), TRUE);
+}
+
+void
+ide_terminal_page_set_pty (IdeTerminalPage *self,
+ VtePty *pty)
+{
+ g_return_if_fail (IDE_IS_TERMINAL_PAGE (self));
+ g_return_if_fail (VTE_IS_PTY (pty));
+
+ if (self->manage_spawn)
+ {
+ g_warning ("Cannot set pty when IdeTerminalPage manages tty");
+ return;
+ }
+
+ if (self->terminal_top)
+ {
+ vte_terminal_reset (VTE_TERMINAL (self->terminal_top), TRUE, TRUE);
+ vte_terminal_set_pty (VTE_TERMINAL (self->terminal_top), pty);
+ }
+}
+
+void
+ide_terminal_page_feed (IdeTerminalPage *self,
+ const gchar *message)
+{
+ g_return_if_fail (IDE_IS_TERMINAL_PAGE (self));
+
+ if (self->terminal_top != NULL)
+ vte_terminal_feed (VTE_TERMINAL (self->terminal_top), message, -1);
+}
diff --git a/src/libide/terminal/ide-terminal-page.h b/src/libide/terminal/ide-terminal-page.h
new file mode 100644
index 000000000..901bc5b05
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-page.h
@@ -0,0 +1,45 @@
+/* ide-terminal-page.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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>
+#include <vte/vte.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TERMINAL_PAGE (ide_terminal_page_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeTerminalPage, ide_terminal_page, IDE, TERMINAL_PAGE, IdePage)
+
+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);
+
+G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-page.ui b/src/libide/terminal/ide-terminal-page.ui
new file mode 100644
index 000000000..708915a26
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-page.ui
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.16 -->
+ <template class="IdeTerminalPage" parent="IdePage">
+ <property name="visible">true</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>
+ <child>
+ <object class="GtkBox" id="top_container">
+ <property name="orientation">horizontal</property>
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="IdeTerminal" id="terminal_top">
+ <property name="audible-bell">false</property>
+ <property name="expand">true</property>
+ <property name="visible">true</property>
+ <property name="scrollback-lines">0xffffffff</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrollbar" id="top_scrollbar">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/terminal/ide-terminal-search-private.h
b/src/libide/terminal/ide-terminal-search-private.h
index 9880f5eb6..2ace9737d 100644
--- a/src/libide/terminal/ide-terminal-search-private.h
+++ b/src/libide/terminal/ide-terminal-search-private.h
@@ -21,8 +21,7 @@
#pragma once
#include <gtk/gtk.h>
-
-#include "search/ide-tagged-entry.h"
+#include <libide-gui.h>
G_BEGIN_DECLS
@@ -33,7 +32,7 @@ struct _IdeTerminalSearch
VteTerminal *terminal;
GtkRevealer *search_revealer;
-
+
IdeTaggedEntry *search_entry;
GtkButton *search_prev_button;
diff --git a/src/libide/terminal/ide-terminal-search.c b/src/libide/terminal/ide-terminal-search.c
index e0e31bfd7..6eb2f4629 100644
--- a/src/libide/terminal/ide-terminal-search.c
+++ b/src/libide/terminal/ide-terminal-search.c
@@ -25,14 +25,13 @@
#include <fcntl.h>
#include <glib/gi18n.h>
-#include <ide.h>
#include <pcre2.h>
#include <stdlib.h>
#include <vte/vte.h>
#include <unistd.h>
-#include "terminal/ide-terminal-search.h"
-#include "terminal/ide-terminal-search-private.h"
+#include "ide-terminal-search.h"
+#include "ide-terminal-search-private.h"
G_DEFINE_TYPE (IdeTerminalSearch, ide_terminal_search, GTK_TYPE_BIN)
@@ -300,7 +299,7 @@ ide_terminal_search_class_init (IdeTerminalSearchClass *klass)
object_class->get_property = ide_terminal_search_get_property;
- gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-terminal-search.ui");
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-terminal/ui/ide-terminal-search.ui");
gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_prev_button);
gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, search_next_button);
gtk_widget_class_bind_template_child (widget_class, IdeTerminalSearch, close_button);
diff --git a/src/libide/terminal/ide-terminal-search.h b/src/libide/terminal/ide-terminal-search.h
index e424f42d3..1081b0d05 100644
--- a/src/libide/terminal/ide-terminal-search.h
+++ b/src/libide/terminal/ide-terminal-search.h
@@ -20,9 +20,12 @@
#pragma once
-#include <vte/vte.h>
+#if !defined (IDE_TERMINAL_INSIDE) && !defined (IDE_TERMINAL_COMPILATION)
+# error "Only <libide-terminal.h> can be included directly."
+#endif
-#include "ide-version-macros.h"
+#include <vte/vte.h>
+#include <libide-core.h>
G_BEGIN_DECLS
diff --git a/src/libide/terminal/ide-terminal-surface.c b/src/libide/terminal/ide-terminal-surface.c
new file mode 100644
index 000000000..fca73db83
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-surface.c
@@ -0,0 +1,84 @@
+/* ide-terminal-surface.c
+ *
+ * Copyright 2018 Christian Hergert <unknown domain org>
+ *
+ * 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-surface"
+
+#include "config.h"
+
+#include "ide-terminal-page.h"
+#include "ide-terminal-surface.h"
+
+struct _IdeTerminalSurface
+{
+ IdeSurface parent_instance;
+
+ IdeGrid *grid;
+};
+
+G_DEFINE_TYPE (IdeTerminalSurface, ide_terminal_surface, IDE_TYPE_SURFACE)
+
+/**
+ * ide_terminal_surface_new:
+ *
+ * Create a new #IdeTerminalSurface.
+ *
+ * Returns: (transfer full): a newly created #IdeTerminalSurface
+ *
+ * Since: 3.32
+ */
+IdeTerminalSurface *
+ide_terminal_surface_new (void)
+{
+ return g_object_new (IDE_TYPE_TERMINAL_SURFACE, NULL);
+}
+
+static void
+ide_terminal_surface_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ IdeTerminalSurface *self = (IdeTerminalSurface *)container;
+
+ g_assert (IDE_IS_TERMINAL_SURFACE (self));
+
+ if (IDE_IS_TERMINAL_PAGE (child))
+ gtk_container_add (GTK_CONTAINER (self->grid), child);
+ else
+ GTK_CONTAINER_CLASS (ide_terminal_surface_parent_class)->add (container, child);
+}
+
+static void
+ide_terminal_surface_class_init (IdeTerminalSurfaceClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ container_class->add = ide_terminal_surface_add;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-terminal/ui/ide-terminal-surface.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeTerminalSurface, grid);
+}
+
+static void
+ide_terminal_surface_init (IdeTerminalSurface *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_set_name (GTK_WIDGET (self), "terminal");
+}
diff --git a/src/libide/terminal/ide-terminal-surface.h b/src/libide/terminal/ide-terminal-surface.h
new file mode 100644
index 000000000..4921aef4e
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-surface.h
@@ -0,0 +1,39 @@
+/* ide-terminal-surface.h
+ *
+ * Copyright 2018 Christian Hergert <unknown domain org>
+ *
+ * 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
+ */
+
+#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>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TERMINAL_SURFACE (ide_terminal_surface_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeTerminalSurface, ide_terminal_surface, IDE, TERMINAL_SURFACE, IdeSurface)
+
+IDE_AVAILABLE_IN_3_32
+IdeTerminalSurface *ide_terminal_surface_new (void);
+
+G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-surface.ui b/src/libide/terminal/ide-terminal-surface.ui
new file mode 100644
index 000000000..f0110988a
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-surface.ui
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeTerminalSurface" parent="IdeSurface">
+ <child>
+ <object class="IdeGrid" id="grid">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/terminal/ide-terminal-util.c b/src/libide/terminal/ide-terminal-util.c
index b775be259..8f53aad66 100644
--- a/src/libide/terminal/ide-terminal-util.c
+++ b/src/libide/terminal/ide-terminal-util.c
@@ -23,15 +23,14 @@
#include "config.h"
#include <fcntl.h>
+#include <libide-io.h>
+#include <libide-threading.h>
#include <stdlib.h>
#include <unistd.h>
#include <vte/vte.h>
-#include "subprocess/ide-subprocess.h"
-#include "subprocess/ide-subprocess-launcher.h"
-#include "terminal/ide-terminal-private.h"
-#include "terminal/ide-terminal-util.h"
-#include "util/ptyintercept.h"
+#include "ide-terminal-private.h"
+#include "ide-terminal-util.h"
static const gchar *user_shell = "/bin/sh";
@@ -40,13 +39,13 @@ ide_vte_pty_create_slave (VtePty *pty)
{
gint master_fd;
- g_return_val_if_fail (VTE_IS_PTY (pty), PTY_FD_INVALID);
+ g_return_val_if_fail (VTE_IS_PTY (pty), IDE_PTY_FD_INVALID);
master_fd = vte_pty_get_fd (pty);
- if (master_fd == PTY_FD_INVALID)
- return PTY_FD_INVALID;
+ if (master_fd == IDE_PTY_FD_INVALID)
+ return IDE_PTY_FD_INVALID;
- return pty_intercept_create_slave (master_fd, TRUE);
+ return ide_pty_intercept_create_slave (master_fd, TRUE);
}
/**
@@ -118,7 +117,8 @@ _ide_guess_shell (void)
if (!g_shell_parse_argv (command, NULL, &argv, &error))
{
- g_warning ("Failed to parse command into argv: %s", error->message);
+ g_warning ("Failed to parse command into argv: %s",
+ error ? error->message : "unknown error");
return;
}
diff --git a/src/libide/terminal/ide-terminal-util.h b/src/libide/terminal/ide-terminal-util.h
index 1f1ca4d55..a07b35efc 100644
--- a/src/libide/terminal/ide-terminal-util.h
+++ b/src/libide/terminal/ide-terminal-util.h
@@ -20,9 +20,12 @@
#pragma once
-#include <vte/vte.h>
+#if !defined (IDE_TERMINAL_INSIDE) && !defined (IDE_TERMINAL_COMPILATION)
+# error "Only <libide-terminal.h> can be included directly."
+#endif
-#include "ide-version-macros.h"
+#include <vte/vte.h>
+#include <libide-core.h>
G_BEGIN_DECLS
diff --git a/src/libide/terminal/ide-terminal-workspace.c b/src/libide/terminal/ide-terminal-workspace.c
new file mode 100644
index 000000000..ad1019e9b
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-workspace.c
@@ -0,0 +1,52 @@
+/* ide-terminal-workspace.c
+ *
+ * Copyright 2018-2019 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-workspace"
+
+#include "config.h"
+
+#include "ide-terminal-workspace.h"
+
+struct _IdeTerminalWorkspace
+{
+ IdeWorkspace parent_instance;
+
+ IdeHeaderBar *header_bar;
+};
+
+G_DEFINE_TYPE (IdeTerminalWorkspace, ide_terminal_workspace, IDE_TYPE_WORKSPACE)
+
+static void
+ide_terminal_workspace_class_init (IdeTerminalWorkspaceClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ IdeWorkspaceClass *workspace_class = IDE_WORKSPACE_CLASS (klass);
+
+ ide_workspace_class_set_kind (workspace_class, "terminal");
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/libide-terminal/ui/ide-terminal-workspace.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeTerminalWorkspace, header_bar);
+}
+
+static void
+ide_terminal_workspace_init (IdeTerminalWorkspace *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/src/libide/terminal/ide-terminal-workspace.h b/src/libide/terminal/ide-terminal-workspace.h
new file mode 100644
index 000000000..41ac6f5a1
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-workspace.h
@@ -0,0 +1,37 @@
+/* ide-terminal-workspace.h
+ *
+ * Copyright 2018-2019 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
+ */
+
+#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)
+
+G_END_DECLS
diff --git a/src/libide/terminal/ide-terminal-workspace.ui b/src/libide/terminal/ide-terminal-workspace.ui
new file mode 100644
index 000000000..fc03b508e
--- /dev/null
+++ b/src/libide/terminal/ide-terminal-workspace.ui
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="IdeTerminalWorkspace" parent="IdeWorkspace">
+ <property name="default-width">750</property>
+ <property name="default-height">450</property>
+ <child type="titlebar">
+ <object class="IdeHeaderBar" id="header_bar">
+ <property name="show-close-button">true</property>
+ <property name="show-fullscreen-button">true</property>
+ <property name="menu-id">ide-terminal-workspace-menu</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child internal-child="surfaces">
+ <object class="GtkStack" id="surfaces">
+ <property name="visible">true</property>
+ <child>
+ <object class="IdeTerminalSurface">
+ <property name="visible">true</property>
+ <child>
+ <object class="IdeTerminalPage">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">terminal</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/libide/terminal/ide-terminal.c b/src/libide/terminal/ide-terminal.c
index a8deec3a7..3410a2c4b 100644
--- a/src/libide/terminal/ide-terminal.c
+++ b/src/libide/terminal/ide-terminal.c
@@ -24,9 +24,9 @@
#include <dazzle.h>
#include <glib/gi18n.h>
-#include <ide.h>
+#include <libide-gui.h>
-#include "terminal/ide-terminal.h"
+#include "ide-terminal.h"
#define BUILDER_PCRE2_MULTILINE 0x00000400u
@@ -274,7 +274,7 @@ ide_terminal_copy_link_address (IdeTerminal *self)
g_assert (IDE_IS_TERMINAL (self));
g_assert (priv->url != NULL);
- if (dzl_str_empty0 (priv->url))
+ if (ide_str_empty0 (priv->url))
return FALSE;
gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (self), GDK_SELECTION_CLIPBOARD),
@@ -293,7 +293,7 @@ ide_terminal_open_link (IdeTerminal *self)
g_assert (IDE_IS_TERMINAL (self));
g_assert (priv->url != NULL);
- if (dzl_str_empty0 (priv->url))
+ if (ide_str_empty0 (priv->url))
return FALSE;
if (NULL != (app = GTK_APPLICATION (g_application_get_default ())) &&
diff --git a/src/libide/terminal/ide-terminal.h b/src/libide/terminal/ide-terminal.h
index 3a5400e59..c4687e6c2 100644
--- a/src/libide/terminal/ide-terminal.h
+++ b/src/libide/terminal/ide-terminal.h
@@ -20,9 +20,12 @@
#pragma once
-#include <vte/vte.h>
+#if !defined (IDE_TERMINAL_INSIDE) && !defined (IDE_TERMINAL_COMPILATION)
+# error "Only <libide-terminal.h> can be included directly."
+#endif
-#include "ide-version-macros.h"
+#include <vte/vte.h>
+#include <libide-core.h>
G_BEGIN_DECLS
@@ -43,6 +46,7 @@ struct _IdeTerminalClass
gboolean (*open_link) (IdeTerminal *self);
gboolean (*copy_link_address) (IdeTerminal *self);
+ /*< private >*/
gpointer padding[16];
};
diff --git a/src/libide/terminal/libide-terminal.gresource.xml
b/src/libide/terminal/libide-terminal.gresource.xml
new file mode 100644
index 000000000..a8623ac95
--- /dev/null
+++ b/src/libide/terminal/libide-terminal.gresource.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/libide-terminal">
+ <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+ </gresource>
+ <gresource prefix="/org/gnome/libide-terminal/ui">
+ <file preprocess="xml-stripblanks">ide-terminal-page.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
new file mode 100644
index 000000000..37838c104
--- /dev/null
+++ b/src/libide/terminal/libide-terminal.h
@@ -0,0 +1,38 @@
+/* libide-terminal.h
+ *
+ * Copyright 2018-2019 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
+ */
+
+#pragma once
+
+#include <libide-core.h>
+#include <libide-io.h>
+#include <libide-gui.h>
+#include <libide-threading.h>
+#include <vte/vte.h>
+
+#define IDE_TERMINAL_INSIDE
+
+#include "ide-terminal.h"
+#include "ide-terminal-page.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 fca092cce..4ab3a67cd 100644
--- a/src/libide/terminal/meson.build
+++ b/src/libide/terminal/meson.build
@@ -1,16 +1,96 @@
-terminal_headers = [
- 'ide-terminal.h',
+libide_terminal_header_subdir = join_paths(libide_header_subdir, 'terminal')
+libide_include_directories += include_directories('.')
+
+libide_terminal_generated_headers = []
+
+#
+# Public API Headers
+#
+
+libide_terminal_public_headers = [
+ 'ide-terminal-page.h',
'ide-terminal-search.h',
+ 'ide-terminal-surface.h',
'ide-terminal-util.h',
+ 'ide-terminal-workspace.h',
+ 'ide-terminal.h',
+ 'libide-terminal.h',
]
-terminal_sources = [
- 'ide-terminal.c',
+install_headers(libide_terminal_public_headers, subdir: libide_terminal_header_subdir)
+
+#
+# Sources
+#
+
+libide_terminal_private_headers = [
+ 'ide-terminal-page-actions.h',
+ 'ide-terminal-page-private.h',
+ 'ide-terminal-private.h',
+ 'ide-terminal-search-private.h',
+]
+
+libide_terminal_public_sources = [
+ 'ide-terminal-page.c',
'ide-terminal-search.c',
+ 'ide-terminal-surface.c',
'ide-terminal-util.c',
+ 'ide-terminal-workspace.c',
+ 'ide-terminal.c',
]
-libide_public_headers += files(terminal_headers)
-libide_public_sources += files(terminal_sources)
+libide_terminal_private_sources = [
+ 'ide-terminal-page-actions.c',
+]
+
+#
+# Generated Resource Files
+#
+
+libide_terminal_resources = gnome.compile_resources(
+ 'ide-terminal-resources',
+ 'libide-terminal.gresource.xml',
+ c_name: 'ide_terminal',
+)
+libide_terminal_generated_headers += [libide_terminal_resources[1]]
+
+
+#
+# Dependencies
+#
+
+libide_terminal_deps = [
+ libgio_dep,
+ libgtk_dep,
+ libdazzle_dep,
+ libvte_dep,
+
+ libide_core_dep,
+ libide_io_dep,
+ libide_threading_dep,
+ libide_gui_dep,
+]
+
+#
+# Library Definitions
+#
+
+libide_terminal = static_library('ide-terminal-' + libide_api_version,
+ libide_terminal_public_sources + libide_terminal_private_sources + [libide_terminal_resources[0]],
+ dependencies: libide_terminal_deps,
+ c_args: libide_args + release_args + ['-DIDE_TERMINAL_COMPILATION'],
+)
+
+libide_terminal_dep = declare_dependency(
+ sources: libide_terminal_generated_headers,
+ dependencies: libide_terminal_deps,
+ link_whole: libide_terminal,
+ include_directories: include_directories('.'),
+)
-install_headers(terminal_headers, subdir: join_paths(libide_header_subdir, 'terminal'))
+gnome_builder_public_sources += files(libide_terminal_public_sources)
+gnome_builder_public_headers += files(libide_terminal_public_headers)
+gnome_builder_private_sources += files(libide_terminal_private_sources)
+gnome_builder_private_headers += files(libide_terminal_private_headers)
+gnome_builder_include_subdirs += libide_terminal_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-terminal.h', '-DIDE_TERMINAL_COMPILATION']
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]