diff options
Diffstat (limited to '')
-rw-r--r-- | src/terminal-screen.cc | 2358 |
1 files changed, 2358 insertions, 0 deletions
diff --git a/src/terminal-screen.cc b/src/terminal-screen.cc new file mode 100644 index 0000000..a559f7f --- /dev/null +++ b/src/terminal-screen.cc @@ -0,0 +1,2358 @@ +/* + * Copyright © 2001 Havoc Pennington + * Copyright © 2007, 2008, 2010, 2011 Christian Persch + * + * 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/>. + */ + +#include "config.h" + +#include "terminal-pcre2.hh" +#include "terminal-regex.hh" +#include "terminal-screen.hh" +#include "terminal-client-utils.hh" + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <uuid.h> + +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) +#include <sys/sysctl.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gio/gunixfdlist.h> + +#include <gtk/gtk.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + +#include "terminal-accels.hh" +#include "terminal-app.hh" +#include "terminal-debug.hh" +#include "terminal-defines.hh" +#include "terminal-enums.hh" +#include "terminal-intl.hh" +#include "terminal-marshal.h" +#include "terminal-schemas.hh" +#include "terminal-screen-container.hh" +#include "terminal-util.hh" +#include "terminal-window.hh" +#include "terminal-info-bar.hh" +#include "terminal-libgsystem.hh" + +#include "eggshell.hh" + +#define URL_MATCH_CURSOR_NAME "pointer" + +namespace { + +typedef struct { + volatile int refcount; + char **argv; /* as passed */ + char **exec_argv; /* as processed */ + char **envv; + char *cwd; + gboolean as_shell; + + VtePtyFlags pty_flags; + GSpawnFlags spawn_flags; + + /* FD passing */ + GUnixFDList *fd_list; + int n_fd_map; + int* fd_map; + + /* async exec callback */ + TerminalScreenExecCallback callback; + gpointer callback_data; + GDestroyNotify callback_data_destroy_notify; + + /* Cancellable */ + GCancellable *cancellable; +} ExecData; + +} // anon namespace + +typedef struct +{ + int tag; + TerminalURLFlavor flavor; +} TagData; + +struct _TerminalScreenPrivate +{ + char *uuid; + gboolean registered; /* D-Bus interface is registered */ + + GSettings *profile; /* never nullptr */ + guint profile_changed_id; + guint profile_forgotten_id; + int child_pid; + GSList *match_tags; + gboolean exec_on_realize; + guint idle_exec_source; + ExecData *exec_data; +}; + +enum +{ + PROFILE_SET, + SHOW_POPUP_MENU, + MATCH_CLICKED, + CLOSE_SCREEN, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_PROFILE, + PROP_TITLE, +}; + +enum +{ + TARGET_COLOR, + TARGET_BGIMAGE, + TARGET_RESET_BG, + TARGET_MOZ_URL, + TARGET_NETSCAPE_URL, + TARGET_TAB +}; + +static void terminal_screen_constructed (GObject *object); +static void terminal_screen_dispose (GObject *object); +static void terminal_screen_finalize (GObject *object); +static void terminal_screen_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time); +static void terminal_screen_set_font (TerminalScreen *screen); +static void terminal_screen_system_font_changed_cb (GSettings *, + const char*, + TerminalScreen *screen); +static gboolean terminal_screen_popup_menu (GtkWidget *widget); +static gboolean terminal_screen_button_press (GtkWidget *widget, + GdkEventButton *event); +static void terminal_screen_child_exited (VteTerminal *terminal, + int status); + +static void terminal_screen_window_title_changed (VteTerminal *vte_terminal, + TerminalScreen *screen); + +static void update_color_scheme (TerminalScreen *screen); + +static char* terminal_screen_check_hyperlink (TerminalScreen *screen, + GdkEvent *event); +static void terminal_screen_check_extra (TerminalScreen *screen, + GdkEvent *event, + char **number_info, + char **timestamp_info); +static char* terminal_screen_check_match (TerminalScreen *screen, + GdkEvent *event, + int *flavor); + +static void terminal_screen_show_info_bar (TerminalScreen *screen, + GError *error, + gboolean show_relaunch); + + +static char**terminal_screen_get_child_environment (TerminalScreen *screen, + char **initial_envv, + char **path, + char **shell); + +static gboolean terminal_screen_get_child_command (TerminalScreen *screen, + char **exec_argv, + const char *path_env, + const char *shell_env, + gboolean shell, + gboolean *preserve_cwd_p, + GSpawnFlags *spawn_flags_p, + char ***argv_p, + GError **err); + +static void terminal_screen_queue_idle_exec (TerminalScreen *screen); + +static guint signals[LAST_SIGNAL]; + +typedef struct { + const char *pattern; + TerminalURLFlavor flavor; +} TerminalRegexPattern; + +static const TerminalRegexPattern url_regex_patterns[] = { + { REGEX_URL_AS_IS, FLAVOR_AS_IS }, + { REGEX_URL_HTTP, FLAVOR_DEFAULT_TO_HTTP }, + { REGEX_URL_FILE, FLAVOR_AS_IS }, + { REGEX_URL_VOIP, FLAVOR_VOIP_CALL }, + { REGEX_EMAIL, FLAVOR_EMAIL }, + { REGEX_NEWS_MAN, FLAVOR_AS_IS }, +}; + +static const TerminalRegexPattern extra_regex_patterns[] = { + { "(0[Xx][[:xdigit:]]+|[[:digit:]]+)", FLAVOR_NUMBER }, +}; + +static VteRegex **url_regexes; +static VteRegex **extra_regexes; +static TerminalURLFlavor *url_regex_flavors; +static TerminalURLFlavor *extra_regex_flavors; +static guint n_url_regexes; +static guint n_extra_regexes; + +/* See bug #697024 */ +#ifndef __linux__ + +#undef dup3 +#define dup3 fake_dup3 + +static int +fake_dup3 (int fd, int fd2, int flags) +{ + if (dup2 (fd, fd2) == -1) + return -1; + + return fcntl (fd2, F_SETFD, flags); +} +#endif /* !__linux__ */ + +static char* +strv_to_string (char **strv) +{ + return strv ? g_strjoinv (" ", strv) : g_strdup ("(null)"); +} + +static char* +exec_data_to_string (ExecData *data) +{ + gs_free char *str1 = nullptr; + gs_free char *str2 = nullptr; + return data ? g_strdup_printf ("data %p argv:[%s] exec-argv:[%s] envv:%p(%u) as-shell:%s cwd:%s", + data, + (str1 = strv_to_string (data->argv)), + (str2 = strv_to_string (data->exec_argv)), + data->envv, data->envv ? g_strv_length (data->envv) : 0, + data->as_shell ? "true" : "false", + data->cwd) + : g_strdup ("(null)"); +} + +static ExecData* +exec_data_new (void) +{ + ExecData *data = g_new0 (ExecData, 1); + data->refcount = 1; + + return data; +} + +static ExecData * +exec_data_clone (ExecData *data, + gboolean preserve_argv) +{ + if (data == nullptr) + return nullptr; + + ExecData *clone = exec_data_new (); + clone->envv = g_strdupv (data->envv); + clone->cwd = g_strdup (data->cwd); + + /* If FDs were passed, cannot repeat argv. Return data only for env and cwd */ + if (!preserve_argv || + data->fd_list != nullptr) { + clone->as_shell = TRUE; + return clone; + } + + clone->argv = g_strdupv (data->argv); + clone->as_shell = data->as_shell; + + return clone; +} + +static void +exec_data_callback (ExecData *data, + GError *error, + TerminalScreen *screen) +{ + if (data->callback) + data->callback (screen, error, data->callback_data); +} + +static ExecData* +exec_data_ref (ExecData *data) +{ + data->refcount++; + return data; +} + +static void +exec_data_unref (ExecData *data) +{ + if (data == nullptr) + return; + + if (--data->refcount > 0) + return; + + g_strfreev (data->argv); + g_strfreev (data->exec_argv); + g_strfreev (data->envv); + g_free (data->cwd); + g_clear_object (&data->fd_list); + g_free (data->fd_map); + + if (data->callback_data_destroy_notify && data->callback_data) + data->callback_data_destroy_notify (data->callback_data); + + g_clear_object (&data->cancellable); + + g_free (data); +} + +GS_DEFINE_CLEANUP_FUNCTION0(ExecData*, _terminal_local_unref_exec_data, exec_data_unref) +#define terminal_unref_exec_data __attribute__((__cleanup__(_terminal_local_unref_exec_data))) + +static void +terminal_screen_clear_exec_data (TerminalScreen *screen, + gboolean cancelled) +{ + TerminalScreenPrivate *priv = screen->priv; + + if (priv->exec_data == nullptr) + return; + + if (cancelled) { + gs_free_error GError *err = nullptr; + g_set_error_literal (&err, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Spawning was cancelled"); + exec_data_callback (priv->exec_data, err, screen); + } + + exec_data_unref (priv->exec_data); + priv->exec_data = nullptr; +} + +G_DEFINE_TYPE (TerminalScreen, terminal_screen, VTE_TYPE_TERMINAL) + +static void +free_tag_data (TagData *tagdata) +{ + g_slice_free (TagData, tagdata); +} + +static void +precompile_regexes (const TerminalRegexPattern *regex_patterns, + guint n_regexes, + VteRegex ***regexes, + TerminalURLFlavor **regex_flavors) +{ + guint i; + + *regexes = g_new0 (VteRegex*, n_regexes); + *regex_flavors = g_new0 (TerminalURLFlavor, n_regexes); + + for (i = 0; i < n_regexes; ++i) + { + GError *error = nullptr; + + (*regexes)[i] = vte_regex_new_for_match (regex_patterns[i].pattern, -1, + PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_UCP | PCRE2_MULTILINE, + &error); + g_assert_no_error (error); + + if (!vte_regex_jit ((*regexes)[i], PCRE2_JIT_COMPLETE, &error) || + !vte_regex_jit ((*regexes)[i], PCRE2_JIT_PARTIAL_SOFT, &error)) { + g_printerr ("Failed to JIT regex '%s': %s\n", regex_patterns[i].pattern, error->message); + g_clear_error (&error); + } + + (*regex_flavors)[i] = regex_patterns[i].flavor; + } +} + +static void +terminal_screen_class_enable_menu_bar_accel_notify_cb (GSettings *settings, + const char *key, + TerminalScreenClass *klass) +{ + static gboolean is_enabled = TRUE; /* the binding is enabled by default since GtkWidgetClass installs it */ + gboolean enable; + GtkBindingSet *binding_set; + + enable = g_settings_get_boolean (settings, key); + + /* Only remove the 'skip' entry when we have added it previously! */ + if (enable == is_enabled) + return; + + is_enabled = enable; + + binding_set = gtk_binding_set_by_class (klass); + if (enable) + gtk_binding_entry_remove (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK); + else + gtk_binding_entry_skip (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK); +} + +static TerminalWindow * +terminal_screen_get_window (TerminalScreen *screen) +{ + GtkWidget *widget = GTK_WIDGET (screen); + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + if (!gtk_widget_is_toplevel (toplevel)) + return nullptr; + + return TERMINAL_WINDOW (toplevel); +} + +static void +terminal_screen_realize (GtkWidget *widget) +{ + TerminalScreen *screen = TERMINAL_SCREEN (widget); + + GTK_WIDGET_CLASS (terminal_screen_parent_class)->realize (widget); + + terminal_screen_set_font (screen); + + TerminalScreenPrivate *priv = screen->priv; + if (priv->exec_on_realize) + terminal_screen_queue_idle_exec (screen); + + priv->exec_on_realize = FALSE; + +} + +static void +terminal_screen_update_style (TerminalScreen *screen) +{ + update_color_scheme (screen); + terminal_screen_set_font (screen); +} + +static void +terminal_screen_style_updated (GtkWidget *widget) +{ + TerminalScreen *screen = TERMINAL_SCREEN (widget); + + GTK_WIDGET_CLASS (terminal_screen_parent_class)->style_updated (widget); + + terminal_screen_update_style (screen); +} + +static void +terminal_screen_init (TerminalScreen *screen) +{ + const GtkTargetEntry target_table[] = { + { (char *) "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, TARGET_TAB }, + { (char *) "application/x-color", 0, TARGET_COLOR }, + { (char *) "x-special/gnome-reset-background", 0, TARGET_RESET_BG }, + { (char *) "text/x-moz-url", 0, TARGET_MOZ_URL }, + { (char *) "_NETSCAPE_URL", 0, TARGET_NETSCAPE_URL } + }; + VteTerminal *terminal = VTE_TERMINAL (screen); + TerminalScreenPrivate *priv; + TerminalApp *app; + GtkTargetList *target_list; + GtkTargetEntry *targets; + int n_targets; + guint i; + uuid_t u; + char uuidstr[37]; + + priv = screen->priv = G_TYPE_INSTANCE_GET_PRIVATE (screen, TERMINAL_TYPE_SCREEN, TerminalScreenPrivate); + + uuid_generate (u); + uuid_unparse (u, uuidstr); + priv->uuid = g_strdup (uuidstr); + + vte_terminal_set_mouse_autohide (terminal, TRUE); + + priv->child_pid = -1; + + vte_terminal_set_allow_hyperlink (terminal, TRUE); + + for (i = 0; i < n_url_regexes; ++i) + { + TagData *tag_data; + + tag_data = g_slice_new (TagData); + tag_data->flavor = url_regex_flavors[i]; + tag_data->tag = vte_terminal_match_add_regex (terminal, url_regexes[i], 0); + vte_terminal_match_set_cursor_name (terminal, tag_data->tag, URL_MATCH_CURSOR_NAME); + + priv->match_tags = g_slist_prepend (priv->match_tags, tag_data); + } + + /* Setup DND */ + target_list = gtk_target_list_new (nullptr, 0); + gtk_target_list_add_uri_targets (target_list, 0); + gtk_target_list_add_text_targets (target_list, 0); + gtk_target_list_add_table (target_list, target_table, G_N_ELEMENTS (target_table)); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + + gtk_drag_dest_set (GTK_WIDGET (screen), + GtkDestDefaults(GTK_DEST_DEFAULT_MOTION | + GTK_DEST_DEFAULT_HIGHLIGHT | + GTK_DEST_DEFAULT_DROP), + targets, n_targets, + GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE)); + + gtk_target_table_free (targets, n_targets); + gtk_target_list_unref (target_list); + + g_signal_connect (screen, "window-title-changed", + G_CALLBACK (terminal_screen_window_title_changed), + screen); + + app = terminal_app_get (); + g_signal_connect (terminal_app_get_desktop_interface_settings (app), "changed::" MONOSPACE_FONT_KEY_NAME, + G_CALLBACK (terminal_screen_system_font_changed_cb), screen); + +} + +static void +terminal_screen_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TerminalScreen *screen = TERMINAL_SCREEN (object); + + switch (prop_id) + { + case PROP_PROFILE: + g_value_set_object (value, terminal_screen_get_profile (screen)); + break; + case PROP_TITLE: + g_value_set_string (value, terminal_screen_get_title (screen)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +terminal_screen_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TerminalScreen *screen = TERMINAL_SCREEN (object); + + switch (prop_id) + { + case PROP_PROFILE: + terminal_screen_set_profile (screen, (GSettings*)g_value_get_object (value)); + break; + case PROP_TITLE: + /* not writable */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +terminal_screen_class_init (TerminalScreenClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + VteTerminalClass *terminal_class = VTE_TERMINAL_CLASS (klass); + GSettings *settings; + + object_class->constructed = terminal_screen_constructed; + object_class->dispose = terminal_screen_dispose; + object_class->finalize = terminal_screen_finalize; + object_class->get_property = terminal_screen_get_property; + object_class->set_property = terminal_screen_set_property; + + widget_class->realize = terminal_screen_realize; + widget_class->style_updated = terminal_screen_style_updated; + widget_class->drag_data_received = terminal_screen_drag_data_received; + widget_class->button_press_event = terminal_screen_button_press; + widget_class->popup_menu = terminal_screen_popup_menu; + + terminal_class->child_exited = terminal_screen_child_exited; + + signals[PROFILE_SET] = + g_signal_new (I_("profile-set"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalScreenClass, profile_set), + nullptr, nullptr, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, G_TYPE_SETTINGS); + + signals[SHOW_POPUP_MENU] = + g_signal_new (I_("show-popup-menu"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalScreenClass, show_popup_menu), + nullptr, nullptr, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + signals[MATCH_CLICKED] = + g_signal_new (I_("match-clicked"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalScreenClass, match_clicked), + g_signal_accumulator_true_handled, nullptr, + _terminal_marshal_BOOLEAN__STRING_INT_UINT, + G_TYPE_BOOLEAN, + 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT); + + signals[CLOSE_SCREEN] = + g_signal_new (I_("close-screen"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (TerminalScreenClass, close_screen), + nullptr, nullptr, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_object_class_install_property + (object_class, + PROP_PROFILE, + g_param_spec_object ("profile", nullptr, nullptr, + G_TYPE_SETTINGS, + GParamFlags(G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB))); + + g_object_class_install_property + (object_class, + PROP_TITLE, + g_param_spec_string ("title", nullptr, nullptr, + nullptr, + GParamFlags(G_PARAM_READABLE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB))); + + g_type_class_add_private (object_class, sizeof (TerminalScreenPrivate)); + + n_url_regexes = G_N_ELEMENTS (url_regex_patterns); + precompile_regexes (url_regex_patterns, n_url_regexes, &url_regexes, &url_regex_flavors); + n_extra_regexes = G_N_ELEMENTS (extra_regex_patterns); + precompile_regexes (extra_regex_patterns, n_extra_regexes, &extra_regexes, &extra_regex_flavors); + + /* This fixes bug #329827 */ + settings = terminal_app_get_global_settings (terminal_app_get ()); + terminal_screen_class_enable_menu_bar_accel_notify_cb (settings, TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY, klass); + g_signal_connect (settings, "changed::" TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY, + G_CALLBACK (terminal_screen_class_enable_menu_bar_accel_notify_cb), klass); +} + +static void +terminal_screen_constructed (GObject *object) +{ + TerminalScreen *screen = TERMINAL_SCREEN (object); + TerminalScreenPrivate *priv = screen->priv; + + G_OBJECT_CLASS (terminal_screen_parent_class)->constructed (object); + + terminal_app_register_screen (terminal_app_get (), screen); + priv->registered = TRUE; +} + +static void +terminal_screen_dispose (GObject *object) +{ + TerminalScreen *screen = TERMINAL_SCREEN (object); + TerminalScreenPrivate *priv = screen->priv; + GtkSettings *settings; + + /* Unset child PID so that when an eventual child-exited signal arrives, + * we don't emit "close". + */ + priv->child_pid = -1; + + settings = gtk_widget_get_settings (GTK_WIDGET (screen)); + g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA, + 0, 0, nullptr, nullptr, + screen); + + if (priv->idle_exec_source != 0) + { + g_source_remove (priv->idle_exec_source); + priv->idle_exec_source = 0; + } + + terminal_screen_clear_exec_data (screen, TRUE); + + G_OBJECT_CLASS (terminal_screen_parent_class)->dispose (object); + + /* Unregister *after* chaining up to the parent's dispose, + * since that will terminate the child process if there still + * is any, and we need to get the dbus signal out + * from the TerminalReceiver. + */ + if (priv->registered) { + terminal_app_unregister_screen (terminal_app_get (), screen); + priv->registered = FALSE; + } +} + +static void +terminal_screen_finalize (GObject *object) +{ + TerminalScreen *screen = TERMINAL_SCREEN (object); + TerminalScreenPrivate *priv = screen->priv; + + g_signal_handlers_disconnect_by_func (terminal_app_get_desktop_interface_settings (terminal_app_get ()), + (void*)terminal_screen_system_font_changed_cb, + screen); + + terminal_screen_set_profile (screen, nullptr); + + g_slist_free_full (priv->match_tags, (GDestroyNotify) free_tag_data); + + g_free (priv->uuid); + + G_OBJECT_CLASS (terminal_screen_parent_class)->finalize (object); +} + +TerminalScreen * +terminal_screen_new (GSettings *profile, + const char *title, + double zoom) +{ + g_return_val_if_fail (G_IS_SETTINGS (profile), nullptr); + + TerminalScreen *screen = (TerminalScreen*)g_object_new (TERMINAL_TYPE_SCREEN, nullptr); + + terminal_screen_set_profile (screen, profile); + + vte_terminal_set_size (VTE_TERMINAL (screen), + g_settings_get_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS_KEY), + g_settings_get_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_ROWS_KEY)); + + /* If given an initial title, strip it of control characters and + * feed it to the terminal. + */ + if (title != nullptr) { + GString *seq; + const char *p; + + seq = g_string_new ("\033]0;"); + for (p = title; *p; p = g_utf8_next_char (p)) { + gunichar c = g_utf8_get_char (p); + if (c < 0x20 || (c >= 0x7f && c <= 0x9f)) + continue; + else if (c == ';') + break; + + g_string_append_unichar (seq, c); + } + g_string_append (seq, "\033\\"); + + vte_terminal_feed (VTE_TERMINAL (screen), seq->str, seq->len); + g_string_free (seq, TRUE); + } + + vte_terminal_set_font_scale (VTE_TERMINAL (screen), zoom); + terminal_screen_set_font (screen); + + return screen; +} + +static gboolean +terminal_screen_reexec_from_exec_data (TerminalScreen *screen, + ExecData *data, + char **envv, + const char *cwd, + GCancellable *cancellable, + GError **error) +{ + _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) { + gs_free char *str = exec_data_to_string (data); + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] reexec_from_data: envv:%p(%u) cwd:%s data:[%s]\n", + screen, + envv, envv ? g_strv_length (envv) : 0, + cwd, + str); + } + + return terminal_screen_exec (screen, + data ? data->argv : nullptr, + envv ? envv : data ? data->envv : nullptr, + data ? data->as_shell : TRUE, + /* If we have command line args, must always pass the cwd from the command line, too */ + data && data->argv ? data->cwd : cwd ? cwd : data ? data->cwd : nullptr, + nullptr /* fd list */, nullptr /* fd array */, + nullptr, nullptr, nullptr, /* callback + data + destroy notify */ + cancellable, + error); +} + +gboolean +terminal_screen_reexec_from_screen (TerminalScreen *screen, + TerminalScreen *parent_screen, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE); + + if (parent_screen == nullptr) + return TRUE; + + g_return_val_if_fail (TERMINAL_IS_SCREEN (parent_screen), FALSE); + + terminal_unref_exec_data ExecData* data = exec_data_clone (parent_screen->priv->exec_data, FALSE); + gs_free char* cwd = terminal_screen_get_current_dir (parent_screen); + + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] reexec_from_screen: parent:%p cwd:%s\n", + screen, + parent_screen, + cwd); + + return terminal_screen_reexec_from_exec_data (screen, + data, + nullptr /* envv */, + cwd, + cancellable, + error); +} + +gboolean +terminal_screen_reexec (TerminalScreen *screen, + char **envv, + const char *cwd, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE); + + + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] reexec: envv:%p(%u) cwd:%s\n", + screen, + envv, envv ? g_strv_length (envv) : 0, + cwd); + + return terminal_screen_reexec_from_exec_data (screen, + screen->priv->exec_data, + envv, + cwd, + cancellable, + error); +} + +gboolean +terminal_screen_exec (TerminalScreen *screen, + char **argv, + char **initial_envv, + gboolean as_shell, + const char *cwd, + GUnixFDList *fd_list, + GVariant *fd_array, + TerminalScreenExecCallback callback, + gpointer user_data, + GDestroyNotify destroy_notify, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE); + g_return_val_if_fail (cancellable == nullptr || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == nullptr || *error == nullptr, FALSE); + g_return_val_if_fail (gtk_widget_get_parent (GTK_WIDGET (screen)) != nullptr, FALSE); + + _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) { + gs_free char *argv_str = nullptr; + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] exec: argv:[%s] envv:%p(%u) as-shell:%s cwd:%s\n", + screen, + (argv_str = strv_to_string(argv)), + initial_envv, initial_envv ? g_strv_length (initial_envv) : 0, + as_shell ? "true":"false", + cwd); + } + + TerminalScreenPrivate *priv = screen->priv; + + ExecData *data = exec_data_new (); + data->callback = callback; + data->callback_data = user_data; + data->callback_data_destroy_notify = destroy_notify; + + GError *err = nullptr; + if (priv->child_pid != -1) { + g_set_error_literal (&err, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, + "Cannot launch a new child process while the terminal is still running another child process"); + + terminal_screen_show_info_bar (screen, err, FALSE); + g_propagate_error (error, err); + exec_data_unref (data); /* frees the callback data */ + return FALSE; + } + + gs_free char *path = nullptr; + gs_free char *shell = nullptr; + gs_strfreev char **envv = terminal_screen_get_child_environment (screen, + initial_envv, + &path, + &shell); + + gboolean preserve_cwd = FALSE; + GSpawnFlags spawn_flags = GSpawnFlags(G_SPAWN_SEARCH_PATH_FROM_ENVP | + VTE_SPAWN_NO_PARENT_ENVV); + gs_strfreev char **exec_argv = nullptr; + if (!terminal_screen_get_child_command (screen, + argv, + path, + shell, + as_shell, + &preserve_cwd, + &spawn_flags, + &exec_argv, + &err)) { + terminal_screen_show_info_bar (screen, err, FALSE); + g_propagate_error (error, err); + exec_data_unref (data); /* frees the callback data */ + return FALSE; + } + + if (!preserve_cwd) { + cwd = g_get_home_dir (); + envv = g_environ_unsetenv (envv, "PWD"); + } + + data->fd_list = (GUnixFDList*)(fd_list ? g_object_ref(fd_list) : nullptr); + + if (fd_array) { + g_assert_nonnull(fd_list); + int n_fds = g_unix_fd_list_get_length(fd_list); + + gsize fd_array_data_len; + const int *fd_array_data = (int const*)g_variant_get_fixed_array (fd_array, &fd_array_data_len, 2 * sizeof (int)); + + data->n_fd_map = fd_array_data_len; + data->fd_map = g_new (int, data->n_fd_map); + for (gsize i = 0; i < fd_array_data_len; i++) { + const int fd = fd_array_data[2 * i]; + const int idx = fd_array_data[2 * i + 1]; + g_assert_cmpint(idx, >=, 0); + g_assert_cmpuint(idx, <, n_fds); + + data->fd_map[idx] = fd; + } + } else { + data->n_fd_map = 0; + data->fd_map = nullptr; + } + + data->argv = g_strdupv (argv); + data->exec_argv = g_strdupv (exec_argv); + data->cwd = g_strdup (cwd); + data->envv = g_strdupv (envv); + data->as_shell = as_shell; + data->pty_flags = VTE_PTY_DEFAULT; + data->spawn_flags = spawn_flags; + data->cancellable = (GCancellable*)(cancellable ? g_object_ref (cancellable) : nullptr); + + terminal_screen_clear_exec_data (screen, TRUE); + priv->exec_data = data; + + terminal_screen_queue_idle_exec (screen); + + return TRUE; +} + +const char* +terminal_screen_get_title (TerminalScreen *screen) +{ + return vte_terminal_get_window_title (VTE_TERMINAL (screen)); +} + +static void +terminal_screen_profile_changed_cb (GSettings *profile, + const char *prop_name, + TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + GObject *object = G_OBJECT (screen); + VteTerminal *vte_terminal = VTE_TERMINAL (screen); + TerminalWindow *window; + + g_object_freeze_notify (object); + + if ((window = terminal_screen_get_window (screen))) + { + /* We need these in line for the set_size in + * update_on_realize + */ + terminal_window_update_geometry (window); + } + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLLBAR_POLICY_KEY)) + _terminal_screen_update_scrollbar (screen); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENCODING_KEY)) + { + gs_free char *charset = g_settings_get_string (profile, TERMINAL_PROFILE_ENCODING_KEY); + const char *encoding = terminal_util_translate_encoding (charset); + if (encoding != nullptr) + vte_terminal_set_encoding (vte_terminal, encoding, nullptr); + } + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY)) + { + int width; + + width = g_settings_get_enum (profile, TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY); + vte_terminal_set_cjk_ambiguous_width (vte_terminal, width); + } + + if (gtk_widget_get_realized (GTK_WIDGET (screen)) && + (!prop_name || + prop_name == I_(TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY) || + prop_name == I_(TERMINAL_PROFILE_FONT_KEY) || + prop_name == I_(TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY) || + prop_name == I_(TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY))) + terminal_screen_set_font (screen); + + if (!prop_name || + prop_name == I_(TERMINAL_PROFILE_USE_THEME_COLORS_KEY) || + prop_name == I_(TERMINAL_PROFILE_FOREGROUND_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_BACKGROUND_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY) || + prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY) || + prop_name == I_(TERMINAL_PROFILE_CURSOR_BACKGROUND_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_CURSOR_FOREGROUND_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY) || + prop_name == I_(TERMINAL_PROFILE_HIGHLIGHT_BACKGROUND_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_HIGHLIGHT_FOREGROUND_COLOR_KEY) || + prop_name == I_(TERMINAL_PROFILE_PALETTE_KEY)) + update_color_scheme (screen); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_AUDIBLE_BELL_KEY)) + vte_terminal_set_audible_bell (vte_terminal, g_settings_get_boolean (profile, TERMINAL_PROFILE_AUDIBLE_BELL_KEY)); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE_KEY)) + vte_terminal_set_scroll_on_keystroke (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE_KEY)); + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_OUTPUT_KEY)) + vte_terminal_set_scroll_on_output (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_OUTPUT_KEY)); + if (!prop_name || + prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_LINES_KEY) || + prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY)) + { + glong lines = g_settings_get_boolean (profile, TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY) ? + -1 : g_settings_get_int (profile, TERMINAL_PROFILE_SCROLLBACK_LINES_KEY); + vte_terminal_set_scrollback_lines (vte_terminal, lines); + } + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_BACKSPACE_BINDING_KEY)) + vte_terminal_set_backspace_binding (vte_terminal, + VteEraseBinding(g_settings_get_enum (profile, TERMINAL_PROFILE_BACKSPACE_BINDING_KEY))); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_DELETE_BINDING_KEY)) + vte_terminal_set_delete_binding (vte_terminal, + VteEraseBinding(g_settings_get_enum (profile, TERMINAL_PROFILE_DELETE_BINDING_KEY))); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENABLE_BIDI_KEY)) + vte_terminal_set_enable_bidi (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_ENABLE_BIDI_KEY)); + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENABLE_SHAPING_KEY)) + vte_terminal_set_enable_shaping (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_ENABLE_SHAPING_KEY)); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENABLE_SIXEL_KEY)) + vte_terminal_set_enable_sixel (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_ENABLE_SIXEL_KEY)); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_BOLD_IS_BRIGHT_KEY)) + vte_terminal_set_bold_is_bright (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_BOLD_IS_BRIGHT_KEY)); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_BLINK_MODE_KEY)) + vte_terminal_set_cursor_blink_mode (vte_terminal, + VteCursorBlinkMode(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_CURSOR_BLINK_MODE_KEY))); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_SHAPE_KEY)) + vte_terminal_set_cursor_shape (vte_terminal, + VteCursorShape(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_CURSOR_SHAPE_KEY))); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_REWRAP_ON_RESIZE_KEY)) + vte_terminal_set_rewrap_on_resize (vte_terminal, + g_settings_get_boolean (profile, TERMINAL_PROFILE_REWRAP_ON_RESIZE_KEY)); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_TEXT_BLINK_MODE_KEY)) + vte_terminal_set_text_blink_mode (vte_terminal, + VteTextBlinkMode(g_settings_get_enum (profile, TERMINAL_PROFILE_TEXT_BLINK_MODE_KEY))); + + if (!prop_name || prop_name == I_(TERMINAL_PROFILE_WORD_CHAR_EXCEPTIONS_KEY)) + { + gs_free char *word_char_exceptions; + g_settings_get (profile, TERMINAL_PROFILE_WORD_CHAR_EXCEPTIONS_KEY, "ms", &word_char_exceptions); + vte_terminal_set_word_char_exceptions (vte_terminal, word_char_exceptions); + } + + g_object_thaw_notify (object); +} + +static void +update_color_scheme (TerminalScreen *screen) +{ + GtkWidget *widget = GTK_WIDGET (screen); + TerminalScreenPrivate *priv = screen->priv; + GSettings *profile = priv->profile; + gs_free GdkRGBA *colors; + gsize n_colors; + GdkRGBA fg, bg, bold, theme_fg, theme_bg; + GdkRGBA cursor_bg, cursor_fg; + GdkRGBA highlight_bg, highlight_fg; + GdkRGBA *boldp; + GdkRGBA *cursor_bgp = nullptr, *cursor_fgp = nullptr; + GdkRGBA *highlight_bgp = nullptr, *highlight_fgp = nullptr; + GtkStyleContext *context; + gboolean use_theme_colors; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_get_color (context, gtk_style_context_get_state (context), &theme_fg); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &theme_bg); + G_GNUC_END_IGNORE_DEPRECATIONS + + use_theme_colors = g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_THEME_COLORS_KEY); + if (use_theme_colors || + (!terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_FOREGROUND_COLOR_KEY, &fg) || + !terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_BACKGROUND_COLOR_KEY, &bg))) + { + fg = theme_fg; + bg = theme_bg; + } + + if (!g_settings_get_boolean (profile, TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY) && + !use_theme_colors && + terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_BOLD_COLOR_KEY, &bold)) + boldp = &bold; + else + boldp = nullptr; + + if (g_settings_get_boolean (profile, TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY) && + !use_theme_colors) + { + if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_CURSOR_BACKGROUND_COLOR_KEY, &cursor_bg)) + cursor_bgp = &cursor_bg; + if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_CURSOR_FOREGROUND_COLOR_KEY, &cursor_fg)) + cursor_fgp = &cursor_fg; + } + + if (g_settings_get_boolean (profile, TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY) && + !use_theme_colors) + { + if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_HIGHLIGHT_BACKGROUND_COLOR_KEY, &highlight_bg)) + highlight_bgp = &highlight_bg; + if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_HIGHLIGHT_FOREGROUND_COLOR_KEY, &highlight_fg)) + highlight_fgp = &highlight_fg; + } + + colors = terminal_g_settings_get_rgba_palette (priv->profile, TERMINAL_PROFILE_PALETTE_KEY, &n_colors); + vte_terminal_set_colors (VTE_TERMINAL (screen), &fg, &bg, + colors, n_colors); + vte_terminal_set_color_bold (VTE_TERMINAL (screen), boldp); + vte_terminal_set_color_cursor (VTE_TERMINAL (screen), cursor_bgp); + vte_terminal_set_color_cursor_foreground (VTE_TERMINAL (screen), cursor_fgp); + vte_terminal_set_color_highlight (VTE_TERMINAL (screen), highlight_bgp); + vte_terminal_set_color_highlight_foreground (VTE_TERMINAL (screen), highlight_fgp); +} + +static void +terminal_screen_set_font (TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + GSettings *profile = priv->profile; + PangoFontDescription *desc; + int size; + + if (g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY)) + { + desc = terminal_app_get_system_font (terminal_app_get ()); + } + else + { + gs_free char *font; + font = g_settings_get_string (profile, TERMINAL_PROFILE_FONT_KEY); + desc = pango_font_description_from_string (font); + } + + size = pango_font_description_get_size (desc); + /* Sanity check */ + if (size == 0) { + if (pango_font_description_get_size_is_absolute (desc)) + pango_font_description_set_absolute_size (desc, 10); + else + pango_font_description_set_size (desc, 10); + } + + vte_terminal_set_font (VTE_TERMINAL (screen), desc); + + pango_font_description_free (desc); + + vte_terminal_set_cell_width_scale (VTE_TERMINAL (screen), + g_settings_get_double (profile, TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY)); + vte_terminal_set_cell_height_scale (VTE_TERMINAL (screen), + g_settings_get_double (profile, TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY)); +} + +static void +terminal_screen_system_font_changed_cb (GSettings *settings, + const char *key, + TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + + if (!gtk_widget_get_realized (GTK_WIDGET (screen))) + return; + + if (!g_settings_get_boolean (priv->profile, TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY)) + return; + + terminal_screen_set_font (screen); +} + +void +terminal_screen_set_profile (TerminalScreen *screen, + GSettings *profile) +{ + TerminalScreenPrivate *priv = screen->priv; + GSettings*old_profile; + + old_profile = priv->profile; + if (profile == old_profile) + return; + + if (priv->profile_changed_id) + { + g_signal_handler_disconnect (G_OBJECT (priv->profile), + priv->profile_changed_id); + priv->profile_changed_id = 0; + } + + priv->profile = profile; + if (profile) + { + g_object_ref (profile); + priv->profile_changed_id = + g_signal_connect (profile, "changed", + G_CALLBACK (terminal_screen_profile_changed_cb), + screen); + terminal_screen_profile_changed_cb (profile, nullptr, screen); + + g_signal_emit (G_OBJECT (screen), signals[PROFILE_SET], 0, old_profile); + } + + if (old_profile) + g_object_unref (old_profile); + + g_object_notify (G_OBJECT (screen), "profile"); +} + +GSettings* +terminal_screen_get_profile (TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + + return priv->profile; +} + +GSettings* +terminal_screen_ref_profile (TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + + if (priv->profile != nullptr) + return (GSettings*)g_object_ref (priv->profile); + return nullptr; +} + +static gboolean +should_preserve_cwd (TerminalPreserveWorkingDirectory preserve_cwd, + const char *path, + const char *arg0) +{ + switch (preserve_cwd) { + case TERMINAL_PRESERVE_WORKING_DIRECTORY_SAFE: { + gs_free char *resolved_arg0 = terminal_util_find_program_in_path (path, arg0); + return resolved_arg0 != nullptr && + terminal_util_get_is_shell (resolved_arg0); + } + + case TERMINAL_PRESERVE_WORKING_DIRECTORY_ALWAYS: + return TRUE; + + case TERMINAL_PRESERVE_WORKING_DIRECTORY_NEVER: + default: + return FALSE; + } +} + +static gboolean +terminal_screen_get_child_command (TerminalScreen *screen, + char **argv, + const char *path_env, + const char *shell_env, + gboolean as_shell, + gboolean *preserve_cwd_p, + GSpawnFlags *spawn_flags_p, + char ***exec_argv_p, + GError **err) +{ + TerminalScreenPrivate *priv = screen->priv; + GSettings *profile = priv->profile; + TerminalPreserveWorkingDirectory preserve_cwd; + char **exec_argv; + + g_assert (spawn_flags_p != nullptr && exec_argv_p != nullptr && preserve_cwd_p != nullptr); + + *exec_argv_p = exec_argv = nullptr; + + preserve_cwd = TerminalPreserveWorkingDirectory + (g_settings_get_enum (profile, TERMINAL_PROFILE_PRESERVE_WORKING_DIRECTORY_KEY)); + + if (argv) + { + exec_argv = g_strdupv (argv); + + /* argv and cwd come from the command line client, so it must always be used */ + *preserve_cwd_p = TRUE; + *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_SEARCH_PATH_FROM_ENVP); + } + else if (g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY)) + { + gs_free char *exec_argv_str; + + exec_argv_str = g_settings_get_string (profile, TERMINAL_PROFILE_CUSTOM_COMMAND_KEY); + if (!g_shell_parse_argv (exec_argv_str, nullptr, &exec_argv, err)) + return FALSE; + + *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, exec_argv[0]); + *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_SEARCH_PATH_FROM_ENVP); + } + else if (as_shell) + { + const char *only_name; + char *shell; + int argc = 0; + + shell = egg_shell (shell_env); + + only_name = strrchr (shell, '/'); + if (only_name != nullptr) + only_name++; + else { + only_name = shell; + *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_SEARCH_PATH_FROM_ENVP); + } + + exec_argv = g_new (char*, 3); + + exec_argv[argc++] = shell; + + if (g_settings_get_boolean (profile, TERMINAL_PROFILE_LOGIN_SHELL_KEY)) + exec_argv[argc++] = g_strconcat ("-", only_name, nullptr); + else + exec_argv[argc++] = g_strdup (only_name); + + exec_argv[argc++] = nullptr; + + *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, shell); + *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_FILE_AND_ARGV_ZERO); + } + + else + { + g_set_error_literal (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + _("No command supplied nor shell requested")); + return FALSE; + } + + *exec_argv_p = exec_argv; + + return TRUE; +} + +static gboolean +remove_prefixed_cb(void* key, + void* value, + void* user_data) +{ + auto const env = reinterpret_cast<char const*>(key); + auto const prefix = reinterpret_cast<char const*>(user_data); + + if (terminal_client_get_environment_prefix_filters_is_excluded(env)) + return false; + + return g_str_has_prefix(env, prefix); +} + +static char** +terminal_screen_get_child_environment (TerminalScreen *screen, + char **initial_envv, + char **path, + char **shell) +{ + TerminalApp *app = terminal_app_get (); + char **env; + gs_strfreev char** current_environ = nullptr; + char *e, *v; + GHashTable *env_table; + GHashTableIter iter; + GPtrArray *retval; + guint i; + + env_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (initial_envv) + env = initial_envv; + else { + env = current_environ = g_get_environ (); + /* Remove this variable which we set in server.c:main() */ + env = g_environ_unsetenv (env, "G_ENABLE_DIAGNOSTIC"); + } + + for (i = 0; env[i]; ++i) + { + v = strchr (env[i], '='); + if (v) + g_hash_table_replace (env_table, g_strndup (env[i], v - env[i]), g_strdup (v + 1)); + else + g_hash_table_replace (env_table, g_strdup (env[i]), nullptr); + } + + /* Remove unwanted env variables */ + auto const filters = terminal_client_get_environment_filters (); + for (i = 0; filters[i]; ++i) + g_hash_table_remove (env_table, filters[i]); + + auto const pfilters = terminal_client_get_environment_prefix_filters (); + for (i = 0; pfilters[i]; ++i) { + g_hash_table_foreach_remove (env_table, + GHRFunc(remove_prefixed_cb), + (void*)pfilters[i]); + } + + terminal_util_add_proxy_env (env_table); + + /* Add gnome-terminal private env vars used to communicate back to g-t-server */ + GDBusConnection *connection = g_application_get_dbus_connection (G_APPLICATION (app)); + g_hash_table_replace (env_table, g_strdup (TERMINAL_ENV_SERVICE_NAME), + g_strdup (g_dbus_connection_get_unique_name (connection))); + + g_hash_table_replace (env_table, g_strdup (TERMINAL_ENV_SCREEN), + terminal_app_dup_screen_object_path (app, screen)); + + /* Convert to strv */ + retval = g_ptr_array_sized_new (g_hash_table_size (env_table)); + g_hash_table_iter_init (&iter, env_table); + while (g_hash_table_iter_next (&iter, (gpointer *) &e, (gpointer *) &v)) + g_ptr_array_add (retval, g_strdup_printf ("%s=%s", e, v ? v : "")); + g_ptr_array_add (retval, nullptr); + + *path = g_strdup ((char const*)g_hash_table_lookup (env_table, "PATH")); + *shell = g_strdup ((char const*)g_hash_table_lookup (env_table, "SHELL")); + + g_hash_table_destroy (env_table); + return (char **) g_ptr_array_free (retval, FALSE); +} + +enum { + RESPONSE_RELAUNCH, + RESPONSE_EDIT_PREFERENCES +}; + +static void +info_bar_response_cb (GtkWidget *info_bar, + int response, + TerminalScreen *screen) +{ + gtk_widget_grab_focus (GTK_WIDGET (screen)); + + switch (response) { + case GTK_RESPONSE_CANCEL: + gtk_widget_destroy (info_bar); + g_signal_emit (screen, signals[CLOSE_SCREEN], 0); + break; + case RESPONSE_RELAUNCH: + gtk_widget_destroy (info_bar); + terminal_screen_reexec (screen, nullptr, nullptr, nullptr, nullptr); + break; + case RESPONSE_EDIT_PREFERENCES: + terminal_app_edit_preferences (terminal_app_get (), + terminal_screen_get_profile (screen), + "custom-command-entry", + gtk_get_current_event_time()); + break; + default: + gtk_widget_destroy (info_bar); + break; + } +} + +static void +terminal_screen_show_info_bar (TerminalScreen *screen, + GError *error, + gboolean show_relaunch) +{ + GtkWidget *info_bar; + + if (!gtk_widget_get_parent (GTK_WIDGET (screen))) + return; + + info_bar = terminal_info_bar_new (GTK_MESSAGE_ERROR, + _("_Preferences"), RESPONSE_EDIT_PREFERENCES, + !show_relaunch ? nullptr : _("_Relaunch"), RESPONSE_RELAUNCH, + nullptr); + terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar), + _("There was an error creating the child process for this terminal")); + terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar), + "%s", error->message); + g_signal_connect (info_bar, "response", + G_CALLBACK (info_bar_response_cb), screen); + + gtk_widget_set_halign (info_bar, GTK_ALIGN_FILL); + gtk_widget_set_valign (info_bar, GTK_ALIGN_START); + gtk_overlay_add_overlay (GTK_OVERLAY (terminal_screen_container_get_from_screen (screen)), + info_bar); + gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL); + gtk_widget_show (info_bar); +} + +static void +spawn_result_cb (VteTerminal *terminal, + GPid pid, + GError *error, + gpointer user_data) +{ + TerminalScreen *screen = TERMINAL_SCREEN (terminal); + ExecData *exec_data = (ExecData*)user_data; + + /* Terminal was destroyed while the spawn operation was in progress; nothing to do. */ + if (terminal == nullptr) + goto out; + + { + TerminalScreenPrivate *priv = screen->priv; + + priv->child_pid = pid; + + if (error) { + // FIXMEchpe should be unnecessary, vte already does this internally + vte_terminal_set_pty (terminal, nullptr); + + gboolean can_reexec = TRUE; /* FIXME */ + terminal_screen_show_info_bar (screen, error, can_reexec); + } + + /* Retain info for reexec, if possible */ + ExecData *new_exec_data = exec_data_clone (exec_data, TRUE); + terminal_screen_clear_exec_data (screen, FALSE); + priv->exec_data = new_exec_data; + } + +out: + + /* Must do this even if the terminal was destroyed */ + exec_data_callback (exec_data, error, screen); + + exec_data_unref (exec_data); +} + +static gboolean +idle_exec_cb (TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + + priv->idle_exec_source = 0; + + ExecData *data = priv->exec_data; + _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) { + gs_free char *str = exec_data_to_string (data); + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] now launching the child process: %s\n", + screen, str); + } + + int n_fds; + int *fds; + if (data->fd_list) { + fds = g_unix_fd_list_steal_fds(data->fd_list, &n_fds); + } else { + fds = nullptr; + n_fds = 0; + } + + VteTerminal *terminal = VTE_TERMINAL (screen); + vte_terminal_spawn_with_fds_async (terminal, + data->pty_flags, + data->cwd, + (char const* const*)data->exec_argv, + (char const* const*)data->envv, + fds, n_fds, + data->fd_map, data->n_fd_map, + data->spawn_flags, + nullptr, nullptr, nullptr, /* child setup, data, destroy */ + -1, + data->cancellable, + spawn_result_cb, + exec_data_ref (data)); + + return FALSE; /* don't run again */ +} + +static void +terminal_screen_queue_idle_exec (TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + + if (priv->idle_exec_source != 0) + return; + + if (!gtk_widget_get_realized (GTK_WIDGET (screen))) { + priv->exec_on_realize = TRUE; + return; + } + + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] scheduling launching the child process on idle\n", + screen); + + priv->idle_exec_source = g_idle_add ((GSourceFunc) idle_exec_cb, screen); +} + +static TerminalScreenPopupInfo * +terminal_screen_popup_info_new (TerminalScreen *screen) +{ + TerminalScreenPopupInfo *info; + + info = g_slice_new0 (TerminalScreenPopupInfo); + info->ref_count = 1; + + return info; +} + +TerminalScreenPopupInfo * +terminal_screen_popup_info_ref (TerminalScreenPopupInfo *info) +{ + g_return_val_if_fail (info != nullptr, nullptr); + + info->ref_count++; + return info; +} + +void +terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info) +{ + g_return_if_fail (info != nullptr); + + if (--info->ref_count > 0) + return; + + g_free (info->hyperlink); + g_free (info->url); + g_free (info->number_info); + g_free (info->timestamp_info); + g_slice_free (TerminalScreenPopupInfo, info); +} + +static gboolean +terminal_screen_popup_menu (GtkWidget *widget) +{ + TerminalScreen *screen = TERMINAL_SCREEN (widget); + TerminalScreenPopupInfo *info; + + info = terminal_screen_popup_info_new (screen); + info->button = 0; + info->timestamp = gtk_get_current_event_time (); + + g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info); + terminal_screen_popup_info_unref (info); + + return TRUE; +} + +static void +terminal_screen_do_popup (TerminalScreen *screen, + GdkEventButton *event, + char *hyperlink, + char *url, + int url_flavor, + char *number_info, + char *timestamp_info) +{ + TerminalScreenPopupInfo *info; + + info = terminal_screen_popup_info_new (screen); + info->button = event->button; + info->state = event->state & gtk_accelerator_get_default_mod_mask (); + info->timestamp = event->time; + info->hyperlink = hyperlink; /* adopted */ + info->url = url; /* adopted */ + info->url_flavor = TerminalURLFlavor(url_flavor); + info->number_info = number_info; /* adopted */ + info->timestamp_info = timestamp_info; /* adopted */ + + g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info); + terminal_screen_popup_info_unref (info); +} + +static gboolean +terminal_screen_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + TerminalScreen *screen = TERMINAL_SCREEN (widget); + gboolean (* button_press_event) (GtkWidget*, GdkEventButton*) = + GTK_WIDGET_CLASS (terminal_screen_parent_class)->button_press_event; + gs_free char *hyperlink = nullptr; + gs_free char *url = nullptr; + int url_flavor = 0; + gs_free char *number_info = nullptr; + gs_free char *timestamp_info = nullptr; + guint state; + + state = event->state & gtk_accelerator_get_default_mod_mask (); + + hyperlink = terminal_screen_check_hyperlink (screen, (GdkEvent*)event); + url = terminal_screen_check_match (screen, (GdkEvent*)event, &url_flavor); + terminal_screen_check_extra (screen, (GdkEvent*)event, &number_info, ×tamp_info); + + if (hyperlink != nullptr && + (event->button == 1 || event->button == 2) && + (state & GDK_CONTROL_MASK)) + { + gboolean handled = FALSE; + + g_signal_emit (screen, signals[MATCH_CLICKED], 0, + hyperlink, + FLAVOR_AS_IS, + state, + &handled); + if (handled) + return TRUE; /* don't do anything else such as select with the click */ + } + + if (url != nullptr && + (event->button == 1 || event->button == 2) && + (state & GDK_CONTROL_MASK)) + { + gboolean handled = FALSE; + + g_signal_emit (screen, signals[MATCH_CLICKED], 0, + url, + url_flavor, + state, + &handled); + if (handled) + return TRUE; /* don't do anything else such as select with the click */ + } + + if (event->type == GDK_BUTTON_PRESS && event->button == 3) + { + if (!(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK))) + { + /* on right-click, we should first try to send the mouse event to + * the client, and popup only if that's not handled. */ + if (button_press_event && button_press_event (widget, event)) + return TRUE; + + terminal_screen_do_popup (screen, event, hyperlink, url, url_flavor, number_info, timestamp_info); + hyperlink = nullptr; /* adopted to the popup info */ + url = nullptr; /* ditto */ + number_info = nullptr; /* ditto */ + timestamp_info = nullptr; /* ditto */ + return TRUE; + } + else if (!(event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))) + { + /* do popup on shift+right-click */ + terminal_screen_do_popup (screen, event, hyperlink, url, url_flavor, number_info, timestamp_info); + hyperlink = nullptr; /* adopted to the popup info */ + url = nullptr; /* ditto */ + number_info = nullptr; /* ditto */ + timestamp_info = nullptr; /* ditto */ + return TRUE; + } + } + + /* default behavior is to let the terminal widget deal with it */ + if (button_press_event) + return button_press_event (widget, event); + + return FALSE; +} + +/** + * terminal_screen_get_current_dir: + * @screen: + * + * Tries to determine the current working directory of the foreground process + * in @screen's PTY. + * + * Returns: a newly allocated string containing the current working directory, + * or %nullptr on failure + */ +char * +terminal_screen_get_current_dir (TerminalScreen *screen) +{ + const char *uri; + + uri = vte_terminal_get_current_directory_uri (VTE_TERMINAL (screen)); + if (uri != nullptr) + return g_filename_from_uri (uri, nullptr, nullptr); + + ExecData *data = screen->priv->exec_data; + if (data && data->cwd) + return g_strdup (data->cwd); + + return nullptr; +} + +static void +terminal_screen_window_title_changed (VteTerminal *vte_terminal, + TerminalScreen *screen) +{ + g_object_notify (G_OBJECT (screen), "title"); +} + +static void +terminal_screen_child_exited (VteTerminal *terminal, + int status) +{ + TerminalScreen *screen = TERMINAL_SCREEN (terminal); + TerminalScreenPrivate *priv = screen->priv; + TerminalExitAction action; + + /* Don't do anything if we don't have a child */ + if (priv->child_pid == -1) + return; + + /* No need to chain up to VteTerminalClass::child_exited since it's nullptr */ + + _terminal_debug_print (TERMINAL_DEBUG_PROCESSES, + "[screen %p] child process exited\n", + screen); + + priv->child_pid = -1; + + action = TerminalExitAction(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_EXIT_ACTION_KEY)); + + switch (action) + { + case TERMINAL_EXIT_CLOSE: + g_signal_emit (screen, signals[CLOSE_SCREEN], 0); + break; + case TERMINAL_EXIT_RESTART: + terminal_screen_reexec (screen, nullptr, nullptr, nullptr, nullptr); + break; + case TERMINAL_EXIT_HOLD: { + GtkWidget *info_bar; + + info_bar = terminal_info_bar_new (GTK_MESSAGE_INFO, + _("_Relaunch"), RESPONSE_RELAUNCH, + nullptr); + if (WIFEXITED (status)) { + terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar), + _("The child process exited normally with status %d."), WEXITSTATUS (status)); + } else if (WIFSIGNALED (status)) { + terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar), + _("The child process was aborted by signal %d."), WTERMSIG (status)); + } else { + terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar), + _("The child process was aborted.")); + } + g_signal_connect (info_bar, "response", + G_CALLBACK (info_bar_response_cb), screen); + + gtk_widget_set_halign (info_bar, GTK_ALIGN_FILL); + gtk_widget_set_valign (info_bar, GTK_ALIGN_START); + gtk_overlay_add_overlay (GTK_OVERLAY (terminal_screen_container_get_from_screen (screen)), + info_bar); + gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), RESPONSE_RELAUNCH); + gtk_widget_show (info_bar); + break; + } + + default: + break; + } +} + +static void +terminal_screen_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint timestamp) +{ + TerminalScreen *screen = TERMINAL_SCREEN (widget); + TerminalScreenPrivate *priv = screen->priv; + const guchar *selection_data_data; + GdkAtom selection_data_target; + gint selection_data_length, selection_data_format; + + selection_data_data = gtk_selection_data_get_data (selection_data); + selection_data_target = gtk_selection_data_get_target (selection_data); + selection_data_length = gtk_selection_data_get_length (selection_data); + selection_data_format = gtk_selection_data_get_format (selection_data); + +#if 0 + { + GList *tmp; + + g_print ("info: %d\n", info); + tmp = context->targets; + while (tmp != nullptr) + { + GdkAtom atom = GDK_POINTER_TO_ATOM (tmp->data); + + g_print ("Target: %s\n", gdk_atom_name (atom)); + + tmp = tmp->next; + } + + g_print ("Chosen target: %s\n", gdk_atom_name (selection_data->target)); + } +#endif + + if (gtk_targets_include_uri (&selection_data_target, 1)) + { + gs_strfreev char **uris; + gs_free char *text = nullptr; + gsize len; + + uris = gtk_selection_data_get_uris (selection_data); + if (!uris) + return; + + terminal_util_transform_uris_to_quoted_fuse_paths (uris); + + text = terminal_util_concat_uris (uris, &len); + terminal_screen_paste_text (screen, text, len); + } + else if (gtk_targets_include_text (&selection_data_target, 1)) + { + gs_free char *text; + + text = (char *) gtk_selection_data_get_text (selection_data); + if (text && text[0]) + terminal_screen_paste_text (screen, text, -1); + } + else switch (info) + { + case TARGET_COLOR: + { + guint16 *data = (guint16 *)selection_data_data; + GdkRGBA color; + + /* We accept drops with the wrong format, since the KDE color + * chooser incorrectly drops application/x-color with format 8. + * So just check for the data length. + */ + if (selection_data_length != 8) + return; + + color.red = (double) data[0] / 65535.; + color.green = (double) data[1] / 65535.; + color.blue = (double) data[2] / 65535.; + color.alpha = 1.; + /* FIXME: use opacity from data[3] */ + + terminal_g_settings_set_rgba (priv->profile, + TERMINAL_PROFILE_BACKGROUND_COLOR_KEY, + &color); + g_settings_set_boolean (priv->profile, TERMINAL_PROFILE_USE_THEME_COLORS_KEY, FALSE); + } + break; + + case TARGET_MOZ_URL: + { + char *utf8_data, *text; + char *uris[2]; + gsize len; + + /* MOZ_URL is in UCS-2 but in format 8. BROKEN! + * + * The data contains the URL, a \n, then the + * title of the web page. + * + * Note that some producers (e.g. dolphin) delimit with a \r\n + * (see issue#293), so we need to handle that, too. + */ + if (selection_data_format != 8 || + selection_data_length == 0 || + (selection_data_length % 2) != 0) + return; + + utf8_data = g_utf16_to_utf8 ((const gunichar2*) selection_data_data, + selection_data_length / 2, + nullptr, nullptr, nullptr); + if (!utf8_data) + return; + + uris[0] = g_strdelimit(utf8_data, "\r\n", 0); + uris[1] = nullptr; + terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */ + + text = terminal_util_concat_uris (uris, &len); + terminal_screen_paste_text (screen, text, len); + g_free (text); + g_free (uris[0]); + } + break; + + case TARGET_NETSCAPE_URL: + { + char *utf8_data, *newline, *text; + char *uris[2]; + gsize len; + + /* The data contains the URL, a \n, then the + * title of the web page. + */ + if (selection_data_length < 0 || selection_data_format != 8) + return; + + utf8_data = g_strndup ((char *) selection_data_data, selection_data_length); + newline = strchr (utf8_data, '\n'); + if (newline) + *newline = '\0'; + + uris[0] = utf8_data; + uris[1] = nullptr; + terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */ + + text = terminal_util_concat_uris (uris, &len); + terminal_screen_paste_text (screen, text, len); + g_free (text); + g_free (uris[0]); + } + break; + + case TARGET_RESET_BG: + g_settings_reset (priv->profile, TERMINAL_PROFILE_BACKGROUND_COLOR_KEY); + break; + + case TARGET_TAB: + { + GtkWidget *container; + TerminalScreen *moving_screen; + TerminalWindow *source_window; + TerminalWindow *dest_window; + + container = *(GtkWidget**) selection_data_data; + if (!GTK_IS_WIDGET (container)) + return; + + moving_screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (container)); + g_warn_if_fail (TERMINAL_IS_SCREEN (moving_screen)); + if (!TERMINAL_IS_SCREEN (moving_screen)) + return; + + source_window = terminal_screen_get_window (moving_screen); + dest_window = terminal_screen_get_window (screen); + terminal_window_move_screen (source_window, dest_window, moving_screen, -1); + + gtk_drag_finish (context, TRUE, TRUE, timestamp); + } + break; + + default: + g_assert_not_reached (); + } +} + +void +_terminal_screen_update_scrollbar (TerminalScreen *screen) +{ + TerminalScreenPrivate *priv = screen->priv; + TerminalScreenContainer *container; + GtkPolicyType vpolicy; + + container = terminal_screen_container_get_from_screen (screen); + if (container == nullptr) + return; + + vpolicy = GtkPolicyType(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_SCROLLBAR_POLICY_KEY)); + + terminal_screen_container_set_policy (container, GTK_POLICY_NEVER, vpolicy); +} + +void +terminal_screen_get_size (TerminalScreen *screen, + int *width_chars, + int *height_chars) +{ + VteTerminal *terminal = VTE_TERMINAL (screen); + + *width_chars = vte_terminal_get_column_count (terminal); + *height_chars = vte_terminal_get_row_count (terminal); +} + +void +terminal_screen_get_cell_size (TerminalScreen *screen, + int *cell_width_pixels, + int *cell_height_pixels) +{ + VteTerminal *terminal = VTE_TERMINAL (screen); + + *cell_width_pixels = vte_terminal_get_char_width (terminal); + *cell_height_pixels = vte_terminal_get_char_height (terminal); +} + +static char* +terminal_screen_check_hyperlink (TerminalScreen *screen, + GdkEvent *event) +{ + return vte_terminal_hyperlink_check_event (VTE_TERMINAL (screen), event); +} + +static char* +terminal_screen_check_match (TerminalScreen *screen, + GdkEvent *event, + int *flavor) +{ + TerminalScreenPrivate *priv = screen->priv; + GSList *tags; + int tag; + char *match; + + match = vte_terminal_match_check_event (VTE_TERMINAL (screen), event, &tag); + for (tags = priv->match_tags; tags != nullptr; tags = tags->next) + { + TagData *tag_data = (TagData*) tags->data; + if (tag_data->tag == tag) + { + if (flavor) + *flavor = tag_data->flavor; + return match; + } + } + + g_free (match); + return nullptr; +} + +static void +terminal_screen_check_extra (TerminalScreen *screen, + GdkEvent *event, + char **number_info, + char **timestamp_info) +{ + guint i; + char **matches; + gboolean flavor_number_found = FALSE; + + matches = g_newa (char *, n_extra_regexes); + memset(matches, 0, sizeof(char*) * n_extra_regexes); + + if ( + vte_terminal_event_check_regex_simple (VTE_TERMINAL (screen), + event, + extra_regexes, + n_extra_regexes, + 0, + matches)) + { + for (i = 0; i < n_extra_regexes; i++) + { + if (matches[i] != nullptr) + { + /* Store the first match for each flavor, free all the others */ + switch (extra_regex_flavors[i]) + { + case FLAVOR_NUMBER: + if (!flavor_number_found) + { + *number_info = terminal_util_number_info (matches[i]); + *timestamp_info = terminal_util_timestamp_info (matches[i]); + flavor_number_found = TRUE; + } + g_free (matches[i]); + break; + default: + g_free (matches[i]); + } + } + } + } +} + +/** + * terminal_screen_has_foreground_process: + * @screen: + * @process_name: (out) (allow-none): the basename of the program, or %nullptr + * @cmdline: (out) (allow-none): the full command line, or %nullptr + * + * Checks whether there's a foreground process running in + * this terminal. + * + * Returns: %TRUE iff there's a foreground process running in @screen + */ +gboolean +terminal_screen_has_foreground_process (TerminalScreen *screen, + char **process_name, + char **cmdline) +{ + TerminalScreenPrivate *priv = screen->priv; + gs_free char *command = nullptr; + gs_free char *data_buf = nullptr; + gs_free char *basename = nullptr; + gs_free char *name = nullptr; + VtePty *pty; + int fd; +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) + int mib[4]; +#else + char filename[64]; +#endif + char *data; + gsize i; + gsize len; + int fgpid; + + if (priv->child_pid == -1) + return FALSE; + + pty = vte_terminal_get_pty (VTE_TERMINAL (screen)); + if (pty == nullptr) + return FALSE; + + fd = vte_pty_get_fd (pty); + if (fd == -1) + return FALSE; + + fgpid = tcgetpgrp (fd); + if (fgpid == -1 || fgpid == priv->child_pid) + return FALSE; + +#if defined(__FreeBSD__) || defined(__DragonFly__) + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ARGS; + mib[3] = fgpid; + if (sysctl (mib, G_N_ELEMENTS (mib), nullptr, &len, nullptr, 0) == -1) + return TRUE; + + data_buf = (char*)g_malloc0 (len); + if (sysctl (mib, G_N_ELEMENTS (mib), data_buf, &len, nullptr, 0) == -1) + return TRUE; + data = data_buf; +#elif defined(__OpenBSD__) + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = fgpid; + mib[3] = KERN_PROC_ARGV; + if (sysctl (mib, G_N_ELEMENTS (mib), nullptr, &len, nullptr, 0) == -1) + return TRUE; + + data_buf = (char*)g_malloc0 (len); + if (sysctl (mib, G_N_ELEMENTS (mib), data_buf, &len, nullptr, 0) == -1) + return TRUE; + data = ((char**)data_buf)[0]; +#else + g_snprintf (filename, sizeof (filename), "/proc/%d/cmdline", fgpid); + if (!g_file_get_contents (filename, &data_buf, &len, nullptr)) + return TRUE; + data = data_buf; +#endif + + basename = g_path_get_basename (data); + if (!basename) + return TRUE; + + name = g_filename_to_utf8 (basename, -1, nullptr, nullptr, nullptr); + if (!name) + return TRUE; + + if (!process_name && !cmdline) + return TRUE; + + gs_transfer_out_value (process_name, &name); + + if (len > 0 && data[len - 1] == '\0') + len--; + for (i = 0; i < len; i++) + { + if (data[i] == '\0') + data[i] = ' '; + } + + command = g_filename_to_utf8 (data, -1, nullptr, nullptr, nullptr); + if (!command) + return TRUE; + + gs_transfer_out_value (cmdline, &command); + + return TRUE; +} + +const char * +terminal_screen_get_uuid (TerminalScreen *screen) +{ + g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), nullptr); + + return screen->priv->uuid; +} + +/** + * terminal_screen_paste_text: + * @screen: + * @text: a NUL-terminated string + * @len: length of @text, or -1 + * + * Inserts @text to @terminal as if pasted. + */ +void +terminal_screen_paste_text (TerminalScreen* screen, + char const* text, + gssize len) +{ + g_return_if_fail (text != nullptr); + g_return_if_fail (len >= -1); + + /* This is just an API hack until vte 0.69 adds vte_terminal_paste_text_len() */ + /* Note that @text MUST be NUL-terminated */ + + vte_terminal_paste_text (VTE_TERMINAL (screen), text); +} |