gatuno-terminal/src/terminal-screen.c

2310 lines
66 KiB
C

/*
* Copyright © 2001 Havoc Pennington
* Copyright © 2007, 2008, 2010 Christian Persch
*
* Mate-terminal 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.
*
* Mate-terminal 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 <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include "terminal-accels.h"
#include "terminal-app.h"
#include "terminal-debug.h"
#include "terminal-intl.h"
#include "terminal-marshal.h"
#include "terminal-profile.h"
#include "terminal-screen-container.h"
#include "terminal-util.h"
#include "terminal-window.h"
#include "terminal-info-bar.h"
#include "eggshell.h"
#define URL_MATCH_CURSOR (GDK_HAND2)
#define SKEY_MATCH_CURSOR (GDK_HAND2)
typedef struct
{
int tag;
TerminalURLFlavour flavor;
} TagData;
struct _TerminalScreenPrivate
{
TerminalProfile *profile; /* may be NULL at times */
guint profile_changed_id;
guint profile_forgotten_id;
char *raw_title, *raw_icon_title;
char *cooked_title, *cooked_icon_title;
char *override_title;
gboolean icon_title_set;
char *initial_working_directory;
char **initial_env;
char **override_command;
int child_pid;
int pty_fd;
double font_scale;
gboolean user_title; /* title was manually set */
GSList *match_tags;
guint launch_child_source_id;
};
enum
{
PROFILE_SET,
SHOW_POPUP_MENU,
MATCH_CLICKED,
CLOSE_SCREEN,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_PROFILE,
PROP_ICON_TITLE,
PROP_ICON_TITLE_SET,
PROP_OVERRIDE_COMMAND,
PROP_TITLE,
PROP_INITIAL_ENVIRONMENT
};
enum
{
TARGET_COLOR,
TARGET_BGIMAGE,
TARGET_RESET_BG,
TARGET_MOZ_URL,
TARGET_NETSCAPE_URL,
TARGET_TAB
};
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_system_font_notify_cb (TerminalApp *app,
GParamSpec *pspec,
TerminalScreen *screen);
static void terminal_screen_change_font (TerminalScreen *screen);
static gboolean terminal_screen_popup_menu (GtkWidget *widget);
static gboolean terminal_screen_button_press (GtkWidget *widget,
GdkEventButton *event);
static void terminal_screen_launch_child_on_idle (TerminalScreen *screen);
static void terminal_screen_child_exited (VteTerminal *terminal, int status);
static void terminal_screen_window_title_changed (VteTerminal *vte_terminal,
TerminalScreen *screen);
static void terminal_screen_icon_title_changed (VteTerminal *vte_terminal,
TerminalScreen *screen);
static void update_color_scheme (TerminalScreen *screen);
static gboolean terminal_screen_format_title (TerminalScreen *screen, const char *raw_title, char **old_cooked_title);
static void terminal_screen_cook_title (TerminalScreen *screen);
static void terminal_screen_cook_icon_title (TerminalScreen *screen);
static char* terminal_screen_check_match (TerminalScreen *screen,
GdkEvent *event,
int *flavor);
static guint signals[LAST_SIGNAL] = { 0 };
#define USERCHARS "-[:alnum:]"
#define USERCHARS_CLASS "[" USERCHARS "]"
#define PASSCHARS_CLASS "[-[:alnum:]\\Q,?;.:/!%$^*&~\"#'\\E]"
#define HOSTCHARS_CLASS "[-[:alnum:]]"
#define HOST HOSTCHARS_CLASS "+(\\." HOSTCHARS_CLASS "+)*"
#define PORT "(?:\\:[[:digit:]]{1,5})?"
#define PATHCHARS_CLASS "[-[:alnum:]\\Q_$.+!*,:;@&=?/~#%\\E]"
#define PATHTERM_CLASS "[^\\Q]'.:}>) \t\r\n,\"\\E]"
#define SCHEME "(?:news:|telnet:|nntp:|file:\\/|https?:|ftps?:|sftp:|webcal:)"
#define USERPASS USERCHARS_CLASS "+(?:" PASSCHARS_CLASS "+)?"
#define URLPATH "(?:(/"PATHCHARS_CLASS"+(?:[(]"PATHCHARS_CLASS"*[)])*"PATHCHARS_CLASS"*)*"PATHTERM_CLASS")?"
typedef struct
{
const char *pattern;
TerminalURLFlavour flavor;
GRegexCompileFlags flags;
} TerminalRegexPattern;
static const TerminalRegexPattern url_regex_patterns[] =
{
{ SCHEME "//(?:" USERPASS "\\@)?" HOST PORT URLPATH, FLAVOR_AS_IS, G_REGEX_CASELESS },
{ "(?:www|ftp)" HOSTCHARS_CLASS "*\\." HOST PORT URLPATH , FLAVOR_DEFAULT_TO_HTTP, G_REGEX_CASELESS },
{ "(?:callto:|h323:|sip:)" USERCHARS_CLASS "[" USERCHARS ".]*(?:" PORT "/[a-z0-9]+)?\\@" HOST, FLAVOR_VOIP_CALL, G_REGEX_CASELESS },
{ "(?:mailto:)?" USERCHARS_CLASS "[" USERCHARS ".]*\\@" HOSTCHARS_CLASS "+\\." HOST, FLAVOR_EMAIL, G_REGEX_CASELESS },
{ "news:[[:alnum:]\\Q^_{|}~!\"#$%&'()*+,./;:=?`\\E]+", FLAVOR_AS_IS, G_REGEX_CASELESS },
};
static GRegex **url_regexes;
static TerminalURLFlavour *url_regex_flavors;
static guint n_url_regexes;
#ifdef ENABLE_SKEY
static const TerminalRegexPattern skey_regex_patterns[] =
{
{ "s/key [[:digit:]]* [-[:alnum:]]*", FLAVOR_AS_IS },
{ "otp-[a-z0-9]* [[:digit:]]* [-[:alnum:]]*", FLAVOR_AS_IS },
};
static GRegex **skey_regexes;
static guint n_skey_regexes;
static void terminal_screen_skey_match_remove (TerminalScreen *screen);
#endif /* ENABLE_SKEY */
G_DEFINE_TYPE (TerminalScreen, terminal_screen, VTE_TYPE_TERMINAL)
static char *
cwd_of_pid (int pid)
{
static const char patterns[][18] =
{
"/proc/%d/cwd", /* Linux */
"/proc/%d/path/cwd", /* Solaris >= 10 */
};
guint i;
if (pid == -1)
return NULL;
/* Try to get the working directory using various OS-specific mechanisms */
for (i = 0; i < G_N_ELEMENTS (patterns); ++i)
{
char cwd_file[64];
char buf[PATH_MAX + 1];
int len;
g_snprintf (cwd_file, sizeof (cwd_file), patterns[i], pid);
len = readlink (cwd_file, buf, sizeof (buf) - 1);
if (len > 0 && buf[0] == '/')
return g_strndup (buf, len);
/* If that didn't do it, try this hack */
if (len <= 0)
{
char *cwd, *working_dir = NULL;
cwd = g_get_current_dir ();
if (cwd != NULL)
{
/* On Solaris, readlink returns an empty string, but the
* link can be used as a directory, including as a target
* of chdir().
*/
if (chdir (cwd_file) == 0)
{
working_dir = g_get_current_dir ();
(void) chdir (cwd);
}
g_free (cwd);
}
if (working_dir)
return working_dir;
}
}
return NULL;
}
static void
free_tag_data (TagData *tagdata)
{
g_slice_free (TagData, tagdata);
}
static void
terminal_screen_class_enable_menu_bar_accel_notify_cb (TerminalApp *app,
GParamSpec *pspec,
TerminalScreenClass *klass)
{
static gboolean is_enabled = TRUE; /* the binding is enabled by default since GtkWidgetClass installs it */
gboolean enable;
GtkBindingSet *binding_set;
g_object_get (app, TERMINAL_APP_ENABLE_MENU_BAR_ACCEL, &enable, NULL);
/* 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 NULL;
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);
}
static void
terminal_screen_style_updated (GtkWidget *widget)
{
TerminalScreen *screen = TERMINAL_SCREEN (widget);
GTK_WIDGET_CLASS (terminal_screen_parent_class)->style_updated (widget);
update_color_scheme (screen);
if (gtk_widget_get_realized (widget))
terminal_screen_change_font (screen);
}
#ifdef MATE_ENABLE_DEBUG
static void
size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
_terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
"[screen %p] size-alloc %d : %d at (%d, %d)\n",
widget, allocation->width, allocation->height, allocation->x, allocation->y);
}
#endif
static void
terminal_screen_init (TerminalScreen *screen)
{
const GtkTargetEntry target_table[] =
{
{ "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, TARGET_TAB },
{ "application/x-color", 0, TARGET_COLOR },
{ "property/bgimage", 0, TARGET_BGIMAGE },
{ "x-special/mate-reset-background", 0, TARGET_RESET_BG },
{ "text/x-moz-url", 0, TARGET_MOZ_URL },
{ "_NETSCAPE_URL", 0, TARGET_NETSCAPE_URL }
};
VteTerminal *terminal = VTE_TERMINAL (screen);
TerminalScreenPrivate *priv;
GtkTargetList *target_list;
GtkTargetEntry *targets;
int n_targets;
guint i;
priv = screen->priv = G_TYPE_INSTANCE_GET_PRIVATE (screen, TERMINAL_TYPE_SCREEN, TerminalScreenPrivate);
vte_terminal_set_mouse_autohide (VTE_TERMINAL (screen), TRUE);
priv->child_pid = -1;
priv->pty_fd = -1;
priv->font_scale = PANGO_SCALE_MEDIUM;
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_gregex (terminal, url_regexes[i], 0);
vte_terminal_match_set_cursor_type (terminal, tag_data->tag, URL_MATCH_CURSOR);
priv->match_tags = g_slist_prepend (priv->match_tags, tag_data);
}
/* Setup DND */
target_list = gtk_target_list_new (NULL, 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),
GTK_DEST_DEFAULT_MOTION |
GTK_DEST_DEFAULT_HIGHLIGHT |
GTK_DEST_DEFAULT_DROP,
targets, n_targets,
GDK_ACTION_COPY | GDK_ACTION_MOVE);
gtk_target_table_free (targets, n_targets);
gtk_target_list_unref (target_list);
priv->override_title = NULL;
priv->user_title = FALSE;
g_signal_connect (screen, "window-title-changed",
G_CALLBACK (terminal_screen_window_title_changed),
screen);
g_signal_connect (screen, "icon-title-changed",
G_CALLBACK (terminal_screen_icon_title_changed),
screen);
g_signal_connect (terminal_app_get (), "notify::system-font",
G_CALLBACK (terminal_screen_system_font_notify_cb), screen);
#ifdef MATE_ENABLE_DEBUG
_TERMINAL_DEBUG_IF (TERMINAL_DEBUG_GEOMETRY)
{
g_signal_connect_after (screen, "size-allocate", G_CALLBACK (size_allocate), NULL);
}
#endif
}
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_ICON_TITLE:
g_value_set_string (value, terminal_screen_get_icon_title (screen));
break;
case PROP_ICON_TITLE_SET:
g_value_set_boolean (value, terminal_screen_get_icon_title_set (screen));
break;
case PROP_OVERRIDE_COMMAND:
g_value_set_boxed (value, terminal_screen_get_override_command (screen));
break;
case PROP_INITIAL_ENVIRONMENT:
g_value_set_boxed (value, terminal_screen_get_initial_environment (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:
{
TerminalProfile *profile;
profile = g_value_get_object (value);
g_assert (profile != NULL);
terminal_screen_set_profile (screen, profile);
break;
}
case PROP_OVERRIDE_COMMAND:
terminal_screen_set_override_command (screen, g_value_get_boxed (value));
break;
case PROP_INITIAL_ENVIRONMENT:
terminal_screen_set_initial_environment (screen, g_value_get_boxed (value));
break;
case PROP_ICON_TITLE:
case PROP_ICON_TITLE_SET:
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);
TerminalApp *app;
guint i;
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),
NULL, NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1, TERMINAL_TYPE_PROFILE);
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),
NULL, NULL,
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, NULL,
_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),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
g_object_class_install_property
(object_class,
PROP_PROFILE,
g_param_spec_string ("profile", NULL, NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
g_object_class_install_property
(object_class,
PROP_ICON_TITLE,
g_param_spec_string ("icon-title", NULL, NULL,
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
g_object_class_install_property
(object_class,
PROP_ICON_TITLE_SET,
g_param_spec_boolean ("icon-title-set", NULL, NULL,
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
g_object_class_install_property
(object_class,
PROP_OVERRIDE_COMMAND,
g_param_spec_boxed ("override-command", NULL, NULL,
G_TYPE_STRV,
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", NULL, NULL,
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
g_object_class_install_property
(object_class,
PROP_INITIAL_ENVIRONMENT,
g_param_spec_boxed ("initial-environment", NULL, NULL,
G_TYPE_STRV,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
g_type_class_add_private (object_class, sizeof (TerminalScreenPrivate));
/* Precompile the regexes */
n_url_regexes = G_N_ELEMENTS (url_regex_patterns);
url_regexes = g_new0 (GRegex*, n_url_regexes);
url_regex_flavors = g_new0 (TerminalURLFlavour, n_url_regexes);
for (i = 0; i < n_url_regexes; ++i)
{
GError *error = NULL;
url_regexes[i] = g_regex_new (url_regex_patterns[i].pattern,
url_regex_patterns[i].flags | G_REGEX_OPTIMIZE | G_REGEX_MULTILINE,
0, &error);
if (error)
{
g_message ("%s", error->message);
g_error_free (error);
}
url_regex_flavors[i] = url_regex_patterns[i].flavor;
}
#ifdef ENABLE_SKEY
n_skey_regexes = G_N_ELEMENTS (skey_regex_patterns);
skey_regexes = g_new0 (GRegex*, n_skey_regexes);
for (i = 0; i < n_skey_regexes; ++i)
{
GError *error = NULL;
skey_regexes[i] = g_regex_new (skey_regex_patterns[i].pattern,
G_REGEX_OPTIMIZE | G_REGEX_MULTILINE,
0, &error);
if (error)
{
g_message ("%s", error->message);
g_error_free (error);
}
}
#endif /* ENABLE_SKEY */
/* This fixes bug #329827 */
app = terminal_app_get ();
terminal_screen_class_enable_menu_bar_accel_notify_cb (app, NULL, klass);
g_signal_connect (app, "notify::" TERMINAL_APP_ENABLE_MENU_BAR_ACCEL,
G_CALLBACK (terminal_screen_class_enable_menu_bar_accel_notify_cb), klass);
}
static void
terminal_screen_dispose (GObject *object)
{
TerminalScreen *screen = TERMINAL_SCREEN (object);
TerminalScreenPrivate *priv = screen->priv;
GtkSettings *settings;
settings = gtk_widget_get_settings (GTK_WIDGET (screen));
g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL,
screen);
if (priv->launch_child_source_id != 0)
{
g_source_remove (priv->launch_child_source_id);
priv->launch_child_source_id = 0;
}
G_OBJECT_CLASS (terminal_screen_parent_class)->dispose (object);
}
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 (),
G_CALLBACK (terminal_screen_system_font_notify_cb),
screen);
terminal_screen_set_profile (screen, NULL);
g_free (priv->raw_title);
g_free (priv->cooked_title);
g_free (priv->override_title);
g_free (priv->raw_icon_title);
g_free (priv->cooked_icon_title);
g_free (priv->initial_working_directory);
g_strfreev (priv->override_command);
g_strfreev (priv->initial_env);
g_slist_foreach (priv->match_tags, (GFunc) free_tag_data, NULL);
g_slist_free (priv->match_tags);
G_OBJECT_CLASS (terminal_screen_parent_class)->finalize (object);
}
TerminalScreen *
terminal_screen_new (TerminalProfile *profile,
char **override_command,
const char *title,
const char *working_dir,
char **child_env,
double zoom)
{
TerminalScreen *screen;
TerminalScreenPrivate *priv;
g_return_val_if_fail (TERMINAL_IS_PROFILE (profile), NULL);
screen = g_object_new (TERMINAL_TYPE_SCREEN, NULL);
priv = screen->priv;
terminal_screen_set_profile (screen, profile);
if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_DEFAULT_SIZE))
{
vte_terminal_set_size (VTE_TERMINAL (screen),
terminal_profile_get_property_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS),
terminal_profile_get_property_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_ROWS));
}
if (title)
terminal_screen_set_override_title (screen, title);
priv->initial_working_directory = g_strdup (working_dir);
if (override_command)
terminal_screen_set_override_command (screen, override_command);
if (child_env)
terminal_screen_set_initial_environment (screen, child_env);
terminal_screen_set_font_scale (screen, zoom);
terminal_screen_set_font (screen);
/* Launch the child on idle */
terminal_screen_launch_child_on_idle (screen);
return screen;
}
const char*
terminal_screen_get_raw_title (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (priv->raw_title)
return priv->raw_title;
return "";
}
const char*
terminal_screen_get_title (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (priv->cooked_title == NULL)
terminal_screen_cook_title (screen);
/* cooked_title may still be NULL */
if (priv->cooked_title != NULL)
return priv->cooked_title;
else
return "";
}
const char*
terminal_screen_get_icon_title (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (priv->cooked_icon_title == NULL)
terminal_screen_cook_icon_title (screen);
/* cooked_icon_title may still be NULL */
if (priv->cooked_icon_title != NULL)
return priv->cooked_icon_title;
else
return "";
}
gboolean
terminal_screen_get_icon_title_set (TerminalScreen *screen)
{
return screen->priv->icon_title_set;
}
/* Supported format specifiers:
* %S = static title
* %D = dynamic title
* %A = dynamic title, falling back to static title if empty
* %- = separator, if not at start or end of string (excluding whitespace)
*/
static const char *
terminal_screen_get_title_format (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
static const char *formats[] =
{
"%A" /* TERMINAL_TITLE_REPLACE */,
"%D%-%S" /* TERMINAL_TITLE_BEFORE */,
"%S%-%D" /* TERMINAL_TITLE_AFTER */,
"%S" /* TERMINAL_TITLE_IGNORE */
};
return formats[terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_TITLE_MODE)];
}
/**
* terminal_screen_format_title::
* @screen:
* @raw_title: main ingredient
* @titleptr <inout>: pointer of the current title string
*
* Format title according @format, and stores it in <literal>*titleptr</literal>.
* Always ensures that *titleptr will be non-NULL.
*
* Returns: %TRUE iff the title changed
*/
static gboolean
terminal_screen_format_title (TerminalScreen *screen,
const char *raw_title,
char **titleptr)
{
TerminalScreenPrivate *priv = screen->priv;
const char *format, *arg;
const char *static_title = NULL;
GString *title;
gboolean add_sep = FALSE;
g_assert (titleptr);
/* use --title argument if one was supplied, otherwise ask the profile */
if (priv->override_title)
static_title = priv->override_title;
else
static_title = terminal_profile_get_property_string (priv->profile, TERMINAL_PROFILE_TITLE);
//title = g_string_sized_new (strlen (static_title) + strlen (raw_title) + 3 + 1);
title = g_string_sized_new (128);
format = terminal_screen_get_title_format (screen);
for (arg = format; *arg; arg += 2)
{
const char *text_to_append = NULL;
g_assert (arg[0] == '%');
switch (arg[1])
{
case 'A':
text_to_append = raw_title ? raw_title : static_title;
break;
case 'D':
text_to_append = raw_title;
break;
case 'S':
text_to_append = static_title;
break;
case '-':
text_to_append = NULL;
add_sep = TRUE;
break;
default:
g_assert_not_reached ();
}
if (!text_to_append || !text_to_append[0])
continue;
if (add_sep && title->len > 0)
g_string_append (title, " - ");
g_string_append (title, text_to_append);
add_sep = FALSE;
}
if (*titleptr == NULL || strcmp (title->str, *titleptr) != 0)
{
g_free (*titleptr);
*titleptr = g_string_free (title, FALSE);
return TRUE;
}
g_string_free (title, TRUE);
return FALSE;
}
static void
terminal_screen_cook_title (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (terminal_screen_format_title (screen, priv->raw_title, &priv->cooked_title))
g_object_notify (G_OBJECT (screen), "title");
}
static void
terminal_screen_cook_icon_title (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (terminal_screen_format_title (screen, priv->raw_icon_title, &priv->cooked_icon_title))
g_object_notify (G_OBJECT (screen), "icon-title");
}
static void
terminal_screen_profile_notify_cb (TerminalProfile *profile,
GParamSpec *pspec,
TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
GObject *object = G_OBJECT (screen);
VteTerminal *vte_terminal = VTE_TERMINAL (screen);
const char *prop_name;
TerminalWindow *window;
if (pspec)
prop_name = pspec->name;
else
prop_name = NULL;
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);
/* madars.vitolins@gmail.com 24/07/2014 -
* update terminal window config
* with the flag of copy selection to clipboard or not. */
terminal_window_update_copy_selection(screen, window);
}
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLLBAR_POSITION))
_terminal_screen_update_scrollbar (screen);
if (!prop_name ||
prop_name == I_(TERMINAL_PROFILE_TITLE_MODE) ||
prop_name == I_(TERMINAL_PROFILE_TITLE))
{
terminal_screen_cook_title (screen);
terminal_screen_cook_icon_title (screen);
}
if (gtk_widget_get_realized (GTK_WIDGET (screen)) &&
(!prop_name ||
prop_name == I_(TERMINAL_PROFILE_USE_SYSTEM_FONT) ||
prop_name == I_(TERMINAL_PROFILE_FONT)))
terminal_screen_change_font (screen);
if (!prop_name ||
prop_name == I_(TERMINAL_PROFILE_USE_THEME_COLORS) ||
prop_name == I_(TERMINAL_PROFILE_FOREGROUND_COLOR) ||
prop_name == I_(TERMINAL_PROFILE_BACKGROUND_COLOR) ||
prop_name == I_(TERMINAL_PROFILE_BACKGROUND_TYPE) ||
prop_name == I_(TERMINAL_PROFILE_BACKGROUND_DARKNESS) ||
prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG) ||
prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR) ||
prop_name == I_(TERMINAL_PROFILE_PALETTE))
update_color_scheme (screen);
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SILENT_BELL))
vte_terminal_set_audible_bell (vte_terminal, !terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SILENT_BELL));
#if VTE_CHECK_VERSION (0, 40, 0)
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_WORD_CHARS))
vte_terminal_set_word_char_exceptions (vte_terminal,
terminal_profile_get_property_string (profile, TERMINAL_PROFILE_WORD_CHARS));
#endif
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE))
vte_terminal_set_scroll_on_keystroke (vte_terminal,
terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE));
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_OUTPUT))
vte_terminal_set_scroll_on_output (vte_terminal,
terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_OUTPUT));
if (!prop_name ||
prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_LINES) ||
prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_UNLIMITED))
{
glong lines = terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_SCROLLBACK_UNLIMITED) ?
-1 : terminal_profile_get_property_int (profile, TERMINAL_PROFILE_SCROLLBACK_LINES);
vte_terminal_set_scrollback_lines (vte_terminal, lines);
}
#ifdef ENABLE_SKEY
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_USE_SKEY))
{
if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_SKEY))
{
guint i;
for (i = 0; i < n_skey_regexes; ++i)
{
TagData *tag_data;
tag_data = g_slice_new (TagData);
tag_data->flavor = FLAVOR_SKEY;
tag_data->tag = vte_terminal_match_add_gregex (vte_terminal, skey_regexes[i], 0);
vte_terminal_match_set_cursor_type (vte_terminal, tag_data->tag, SKEY_MATCH_CURSOR);
priv->match_tags = g_slist_prepend (priv->match_tags, tag_data);
}
}
else
{
terminal_screen_skey_match_remove (screen);
}
}
#endif /* ENABLE_SKEY */
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_BACKSPACE_BINDING))
vte_terminal_set_backspace_binding (vte_terminal,
terminal_profile_get_property_enum (profile, TERMINAL_PROFILE_BACKSPACE_BINDING));
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_DELETE_BINDING))
vte_terminal_set_delete_binding (vte_terminal,
terminal_profile_get_property_enum (profile, TERMINAL_PROFILE_DELETE_BINDING));
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ALLOW_BOLD))
vte_terminal_set_allow_bold (vte_terminal,
terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_ALLOW_BOLD));
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_BLINK_MODE))
vte_terminal_set_cursor_blink_mode (vte_terminal,
terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_CURSOR_BLINK_MODE));
if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_SHAPE))
vte_terminal_set_cursor_shape (vte_terminal,
terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_CURSOR_SHAPE));
g_object_thaw_notify (object);
}
static void
update_color_scheme (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
TerminalProfile *profile = priv->profile;
GdkRGBA colors[TERMINAL_PALETTE_SIZE];
const GdkRGBA *fg_rgba, *bg_rgba, *bold_rgba;
double bg_alpha = 1.0;
GdkRGBA fg, bg;
guint n_colors;
GtkStyleContext *context;
context = gtk_widget_get_style_context (GTK_WIDGET (screen));
gtk_style_context_save (context);
gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &fg);
gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
gtk_style_context_restore (context);
bold_rgba = NULL;
if (!terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_THEME_COLORS))
{
fg_rgba = terminal_profile_get_property_boxed (profile, TERMINAL_PROFILE_FOREGROUND_COLOR);
bg_rgba = terminal_profile_get_property_boxed (profile, TERMINAL_PROFILE_BACKGROUND_COLOR);
if (!terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG))
bold_rgba = terminal_profile_get_property_boxed (profile, TERMINAL_PROFILE_BOLD_COLOR);
if (fg_rgba)
fg = *fg_rgba;
if (bg_rgba)
bg = *bg_rgba;
}
n_colors = G_N_ELEMENTS (colors);
terminal_profile_get_palette (priv->profile, colors, &n_colors);
if (terminal_profile_get_property_enum (profile, TERMINAL_PROFILE_BACKGROUND_TYPE) == TERMINAL_BACKGROUND_TRANSPARENT)
bg_alpha = terminal_profile_get_property_double (profile, TERMINAL_PROFILE_BACKGROUND_DARKNESS);
bg.alpha = bg_alpha;
vte_terminal_set_colors (VTE_TERMINAL (screen),
&fg, &bg,
colors, n_colors);
if (bold_rgba)
vte_terminal_set_color_bold (VTE_TERMINAL (screen),
bold_rgba);
}
void
terminal_screen_set_font (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
TerminalProfile *profile;
PangoFontDescription *desc;
profile = priv->profile;
if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_SYSTEM_FONT))
g_object_get (terminal_app_get (), "system-font", &desc, NULL);
else
g_object_get (profile, TERMINAL_PROFILE_FONT, &desc, NULL);
g_assert (desc);
if (pango_font_description_get_size_is_absolute (desc))
pango_font_description_set_absolute_size (desc,
priv->font_scale *
pango_font_description_get_size (desc));
else
pango_font_description_set_size (desc,
priv->font_scale *
pango_font_description_get_size (desc));
vte_terminal_set_font (VTE_TERMINAL (screen), desc);
pango_font_description_free (desc);
}
static void
terminal_screen_system_font_notify_cb (TerminalApp *app,
GParamSpec *pspec,
TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (!gtk_widget_get_realized (GTK_WIDGET (screen)))
return;
if (!terminal_profile_get_property_boolean (priv->profile, TERMINAL_PROFILE_USE_SYSTEM_FONT))
return;
terminal_screen_change_font (screen);
}
static void
terminal_screen_change_font (TerminalScreen *screen)
{
TerminalWindow *window;
terminal_screen_set_font (screen);
window = terminal_screen_get_window (screen);
terminal_window_set_size (window, screen, TRUE);
}
static void
profile_forgotten_callback (TerminalProfile *profile,
TerminalScreen *screen)
{
TerminalProfile *new_profile;
new_profile = terminal_app_get_profile_for_new_term (terminal_app_get ());
g_assert (new_profile != NULL);
terminal_screen_set_profile (screen, new_profile);
}
void
terminal_screen_set_profile (TerminalScreen *screen,
TerminalProfile *profile)
{
TerminalScreenPrivate *priv = screen->priv;
TerminalProfile *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;
}
if (priv->profile_forgotten_id)
{
g_signal_handler_disconnect (G_OBJECT (priv->profile),
priv->profile_forgotten_id);
priv->profile_forgotten_id = 0;
}
priv->profile = profile;
if (profile)
{
g_object_ref (profile);
priv->profile_changed_id =
g_signal_connect (profile, "notify",
G_CALLBACK (terminal_screen_profile_notify_cb),
screen);
priv->profile_forgotten_id =
g_signal_connect (G_OBJECT (profile),
"forgotten",
G_CALLBACK (profile_forgotten_callback),
screen);
terminal_screen_profile_notify_cb (profile, NULL, 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");
}
TerminalProfile*
terminal_screen_get_profile (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
g_assert (priv->profile != NULL);
return priv->profile;
}
void
terminal_screen_set_override_command (TerminalScreen *screen,
char **argv)
{
TerminalScreenPrivate *priv;
g_return_if_fail (TERMINAL_IS_SCREEN (screen));
priv = screen->priv;
g_strfreev (priv->override_command);
priv->override_command = g_strdupv (argv);
}
const char**
terminal_screen_get_override_command (TerminalScreen *screen)
{
g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
return (const char**) screen->priv->override_command;
}
void
terminal_screen_set_initial_environment (TerminalScreen *screen,
char **argv)
{
TerminalScreenPrivate *priv;
g_return_if_fail (TERMINAL_IS_SCREEN (screen));
priv = screen->priv;
g_assert (priv->initial_env == NULL);
priv->initial_env = g_strdupv (argv);
}
char**
terminal_screen_get_initial_environment (TerminalScreen *screen)
{
g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
return screen->priv->initial_env;
}
static gboolean
get_child_command (TerminalScreen *screen,
const char *shell_env,
GSpawnFlags *spawn_flags_p,
char ***argv_p,
GError **err)
{
TerminalScreenPrivate *priv = screen->priv;
TerminalProfile *profile;
char **argv;
g_assert (spawn_flags_p != NULL && argv_p != NULL);
profile = priv->profile;
*argv_p = argv = NULL;
if (priv->override_command)
{
argv = g_strdupv (priv->override_command);
*spawn_flags_p |= G_SPAWN_SEARCH_PATH;
}
else if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND))
{
if (!g_shell_parse_argv (terminal_profile_get_property_string (profile, TERMINAL_PROFILE_CUSTOM_COMMAND),
NULL, &argv,
err))
return FALSE;
*spawn_flags_p |= G_SPAWN_SEARCH_PATH;
}
else
{
const char *only_name;
char *shell;
int argc = 0;
shell = egg_shell (shell_env);
only_name = strrchr (shell, '/');
if (only_name != NULL)
only_name++;
else
only_name = shell;
argv = g_new (char*, 3);
argv[argc++] = shell;
if (terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_LOGIN_SHELL))
argv[argc++] = g_strconcat ("-", only_name, NULL);
else
argv[argc++] = g_strdup (only_name);
argv[argc++] = NULL;
*spawn_flags_p |= G_SPAWN_FILE_AND_ARGV_ZERO;
}
*argv_p = argv;
return TRUE;
}
static char**
get_child_environment (TerminalScreen *screen,
char **shell)
{
TerminalScreenPrivate *priv = screen->priv;
GtkWidget *term = GTK_WIDGET (screen);
GtkWidget *window;
GdkDisplay *display;
char **env;
char *e, *v;
GHashTable *env_table;
GHashTableIter iter;
GPtrArray *retval;
guint i;
const char * const *list_schemas;
gboolean schema_exists;
window = gtk_widget_get_toplevel (term);
g_assert (window != NULL);
g_assert (gtk_widget_is_toplevel (window));
display = gdk_window_get_display (gtk_widget_get_window (window));
env_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
/* First take the factory's environment */
env = g_listenv ();
for (i = 0; env[i]; ++i)
g_hash_table_insert (env_table, env[i], g_strdup (g_getenv (env[i])));
g_free (env); /* the strings themselves are now owned by the hash table */
/* and then merge the child environment, if any */
env = priv->initial_env;
if (env)
{
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]), NULL);
}
}
g_hash_table_remove (env_table, "COLUMNS");
g_hash_table_remove (env_table, "LINES");
g_hash_table_remove (env_table, "MATE_DESKTOP_ICON");
g_hash_table_replace (env_table, g_strdup ("COLORTERM"), g_strdup (EXECUTABLE_NAME));
g_hash_table_replace (env_table, g_strdup ("TERM"), g_strdup ("xterm")); /* FIXME configurable later? */
/* FIXME: moving the tab between windows, or the window between displays will make the next two invalid... */
g_hash_table_replace (env_table, g_strdup ("WINDOWID"), g_strdup_printf ("%ld", GDK_WINDOW_XID (gtk_widget_get_window (window))));
g_hash_table_replace (env_table, g_strdup ("DISPLAY"), g_strdup (gdk_display_get_name (display)));
list_schemas = g_settings_list_schemas();
schema_exists = FALSE;
for (i = 0; list_schemas[i] != NULL; i++) {
if (g_strcmp0 (list_schemas[i], CONF_PROXY_SCHEMA) == 0)
{
schema_exists = TRUE;
break;
}
}
if (schema_exists == TRUE) {
terminal_util_add_proxy_env (env_table);
}
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, NULL);
*shell = g_strdup (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_PROFILE
};
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_launch_child_on_idle (screen);
break;
case RESPONSE_EDIT_PROFILE:
terminal_app_edit_profile (terminal_app_get (),
terminal_screen_get_profile (screen),
GTK_WINDOW (terminal_screen_get_window (screen)),
"custom-command-entry");
break;
default:
gtk_widget_destroy (info_bar);
break;
}
}
static gboolean
terminal_screen_launch_child_cb (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
VteTerminal *terminal = VTE_TERMINAL (screen);
TerminalProfile *profile;
char **env, **argv;
char *shell = NULL;
GError *err = NULL;
const char *working_dir;
VtePtyFlags pty_flags = VTE_PTY_DEFAULT;
GSpawnFlags spawn_flags = 0;
GPid pid;
priv->launch_child_source_id = 0;
_terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
"[screen %p] now launching the child process\n",
screen);
profile = priv->profile;
env = get_child_environment (screen, &shell);
if (priv->initial_working_directory)
working_dir = priv->initial_working_directory;
else
working_dir = g_get_home_dir ();
if (!terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_LOGIN_SHELL))
pty_flags |= VTE_PTY_NO_LASTLOG;
if (!terminal_profile_get_property_boolean (profile, TERMINAL_PROFILE_UPDATE_RECORDS))
pty_flags |= VTE_PTY_NO_UTMP | VTE_PTY_NO_WTMP;
if (!get_child_command (screen, shell, &spawn_flags, &argv, &err) ||
!vte_terminal_spawn_sync (
terminal,
pty_flags,
working_dir,
argv,
env,
spawn_flags,
NULL, NULL,
&pid,
NULL,
&err))
{
GtkWidget *info_bar;
info_bar = terminal_info_bar_new (GTK_MESSAGE_ERROR,
_("_Profile Preferences"), RESPONSE_EDIT_PROFILE,
_("_Relaunch"), RESPONSE_RELAUNCH,
NULL);
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", err->message);
g_signal_connect (info_bar, "response",
G_CALLBACK (info_bar_response_cb), screen);
gtk_box_pack_start (GTK_BOX (terminal_screen_container_get_from_screen (screen)),
info_bar, FALSE, FALSE, 0);
gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL);
gtk_widget_show (info_bar);
g_error_free (err);
g_strfreev (env);
g_free (shell);
return FALSE;
}
priv->child_pid = pid;
priv->pty_fd = vte_terminal_get_pty (terminal);
g_free (shell);
g_strfreev (argv);
g_strfreev (env);
return FALSE; /* don't run again */
}
static void
terminal_screen_launch_child_on_idle (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (priv->launch_child_source_id != 0)
return;
_terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
"[screen %p] scheduling launching the child process on idle\n",
screen);
priv->launch_child_source_id = g_idle_add ((GSourceFunc) terminal_screen_launch_child_cb, screen);
}
static TerminalScreenPopupInfo *
terminal_screen_popup_info_new (TerminalScreen *screen)
{
TerminalScreenPopupInfo *info;
info = g_slice_new0 (TerminalScreenPopupInfo);
info->ref_count = 1;
info->screen = g_object_ref (screen);
info->window = terminal_screen_get_window (screen);
return info;
}
TerminalScreenPopupInfo *
terminal_screen_popup_info_ref (TerminalScreenPopupInfo *info)
{
g_return_val_if_fail (info != NULL, NULL);
info->ref_count++;
return info;
}
void
terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info)
{
g_return_if_fail (info != NULL);
if (--info->ref_count > 0)
return;
g_object_unref (info->screen);
g_free (info->string);
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 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;
char *matched_string;
int matched_flavor = 0;
guint state;
state = event->state & gtk_accelerator_get_default_mod_mask ();
matched_string = terminal_screen_check_match (screen, (GdkEvent*)event, &matched_flavor);
if (matched_string != NULL &&
(event->button == 1 || event->button == 2) &&
(state & GDK_CONTROL_MASK))
{
gboolean handled = FALSE;
#ifdef ENABLE_SKEY
if (matched_flavor != FLAVOR_SKEY ||
terminal_profile_get_property_boolean (screen->priv->profile, TERMINAL_PROFILE_USE_SKEY))
#endif
{
g_signal_emit (screen, signals[MATCH_CLICKED], 0,
matched_string,
matched_flavor,
state,
&handled);
}
g_free (matched_string);
if (handled)
return TRUE; /* don't do anything else such as select with the click */
}
if (event->button == 3 &&
(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) == 0)
{
TerminalScreenPopupInfo *info;
info = terminal_screen_popup_info_new (screen);
info->button = event->button;
info->state = state;
info->timestamp = event->time;
info->string = matched_string; /* adopted */
info->flavour = matched_flavor;
g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
terminal_screen_popup_info_unref (info);
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;
}
static void
terminal_screen_set_dynamic_title (TerminalScreen *screen,
const char *title,
gboolean userset)
{
TerminalScreenPrivate *priv = screen->priv;
g_assert (TERMINAL_IS_SCREEN (screen));
if ((priv->user_title && !userset) ||
(priv->raw_title && title &&
strcmp (priv->raw_title, title) == 0))
return;
g_free (priv->raw_title);
priv->raw_title = g_strdup (title);
terminal_screen_cook_title (screen);
}
static void
terminal_screen_set_dynamic_icon_title (TerminalScreen *screen,
const char *icon_title,
gboolean userset)
{
TerminalScreenPrivate *priv = screen->priv;
GObject *object = G_OBJECT (screen);
g_assert (TERMINAL_IS_SCREEN (screen));
if ((priv->user_title && !userset) ||
(priv->icon_title_set &&
priv->raw_icon_title &&
icon_title &&
strcmp (priv->raw_icon_title, icon_title) == 0))
return;
g_object_freeze_notify (object);
g_free (priv->raw_icon_title);
priv->raw_icon_title = g_strdup (icon_title);
priv->icon_title_set = TRUE;
g_object_notify (object, "icon-title-set");
terminal_screen_cook_icon_title (screen);
g_object_thaw_notify (object);
}
void
terminal_screen_set_override_title (TerminalScreen *screen,
const char *title)
{
TerminalScreenPrivate *priv = screen->priv;
char *old_title;
old_title = priv->override_title;
priv->override_title = g_strdup (title);
g_free (old_title);
terminal_screen_set_dynamic_title (screen, title, FALSE);
terminal_screen_set_dynamic_icon_title (screen, title, FALSE);
}
const char*
terminal_screen_get_dynamic_title (TerminalScreen *screen)
{
g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
return screen->priv->raw_title;
}
const char*
terminal_screen_get_dynamic_icon_title (TerminalScreen *screen)
{
g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
return screen->priv->raw_icon_title;
}
/**
* terminal_screen_get_current_dir:
* @screen:
*
* Tries to determine the current working directory of the foreground process
* in @screen's PTY, falling back to the current working directory of the
* primary child.
*
* Returns: a newly allocated string containing the current working directory,
* or %NULL on failure
*/
char*
terminal_screen_get_current_dir (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
char *cwd;
if (priv->pty_fd != -1)
{
#if 0
/* Get the foreground process ID */
cwd = cwd_of_pid (tcgetpgrp (priv->pty_fd));
if (cwd != NULL)
return cwd;
#endif
/* If that didn't work, try falling back to the primary child. See bug #575184. */
cwd = cwd_of_pid (priv->child_pid);
if (cwd != NULL)
return cwd;
}
return NULL;
}
/**
* terminal_screen_get_current_dir_with_fallback:
* @screen:
*
* Like terminal_screen_get_current_dir(), but falls back to returning
* @screen's initial working directory, with a further fallback to the
* user's home directory.
*
* Returns: a newly allocated string containing the current working directory,
* or %NULL on failure
*/
char*
terminal_screen_get_current_dir_with_fallback (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
if (priv->pty_fd == -1)
return g_strdup (priv->initial_working_directory);
return terminal_screen_get_current_dir (screen);
}
void
terminal_screen_set_font_scale (TerminalScreen *screen,
double factor)
{
TerminalScreenPrivate *priv = screen->priv;
g_return_if_fail (TERMINAL_IS_SCREEN (screen));
if (factor < TERMINAL_SCALE_MINIMUM)
factor = TERMINAL_SCALE_MINIMUM;
if (factor > TERMINAL_SCALE_MAXIMUM)
factor = TERMINAL_SCALE_MAXIMUM;
priv->font_scale = factor;
if (gtk_widget_get_realized (GTK_WIDGET (screen)))
{
/* Update the font */
terminal_screen_change_font (screen);
}
}
double
terminal_screen_get_font_scale (TerminalScreen *screen)
{
g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), 1.0);
return screen->priv->font_scale;
}
static void
terminal_screen_window_title_changed (VteTerminal *vte_terminal,
TerminalScreen *screen)
{
terminal_screen_set_dynamic_title (screen,
vte_terminal_get_window_title (vte_terminal),
FALSE);
}
static void
terminal_screen_icon_title_changed (VteTerminal *vte_terminal,
TerminalScreen *screen)
{
terminal_screen_set_dynamic_icon_title (screen,
vte_terminal_get_icon_title (vte_terminal),
FALSE);
}
static void
terminal_screen_child_exited (VteTerminal *terminal, int status)
{
TerminalScreen *screen = TERMINAL_SCREEN (terminal);
TerminalScreenPrivate *priv = screen->priv;
TerminalExitAction action;
/* No need to chain up to VteTerminalClass::child_exited since it's NULL */
_terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
"[screen %p] child process exited\n",
screen);
priv->child_pid = -1;
priv->pty_fd = -1;
action = terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_EXIT_ACTION);
switch (action)
{
case TERMINAL_EXIT_CLOSE:
g_signal_emit (screen, signals[CLOSE_SCREEN], 0);
break;
case TERMINAL_EXIT_RESTART:
terminal_screen_launch_child_on_idle (screen);
break;
case TERMINAL_EXIT_HOLD:
{
GtkWidget *info_bar;
info_bar = terminal_info_bar_new (GTK_MESSAGE_INFO,
_("_Relaunch"), RESPONSE_RELAUNCH,
NULL);
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 terminated by signal %d."), WTERMSIG (status));
}
else
{
terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
_("The child process was terminated."));
}
g_signal_connect (info_bar, "response",
G_CALLBACK (info_bar_response_cb), screen);
gtk_box_pack_start (GTK_BOX (terminal_screen_container_get_from_screen (screen)),
info_bar, FALSE, FALSE, 0);
gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), RESPONSE_RELAUNCH);
gtk_widget_show (info_bar);
break;
}
default:
break;
}
}
void
terminal_screen_set_user_title (TerminalScreen *screen,
const char *text)
{
TerminalScreenPrivate *priv = screen->priv;
/* The user set the title to nothing, let's understand that as a
request to revert to dynamically setting the title again. */
if (!text || !text[0])
priv->user_title = FALSE;
else
{
priv->user_title = TRUE;
terminal_screen_set_dynamic_title (screen, text, TRUE);
terminal_screen_set_dynamic_icon_title (screen, text, TRUE);
}
}
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 != NULL)
{
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))
{
char **uris;
char *text;
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);
vte_terminal_feed_child (VTE_TERMINAL (screen), text, len);
g_free (text);
g_strfreev (uris);
}
else if (gtk_targets_include_text (&selection_data_target, 1))
{
char *text;
text = (char *) gtk_selection_data_get_text (selection_data);
if (text && text[0])
vte_terminal_feed_child (VTE_TERMINAL (screen), text, strlen (text));
g_free (text);
}
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] */
g_object_set (priv->profile,
TERMINAL_PROFILE_BACKGROUND_TYPE, TERMINAL_BACKGROUND_SOLID,
TERMINAL_PROFILE_USE_THEME_COLORS, FALSE,
TERMINAL_PROFILE_BACKGROUND_COLOR, &color,
NULL);
}
break;
case TARGET_MOZ_URL:
{
char *utf8_data, *newline, *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.
*/
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,
NULL, NULL, NULL);
if (!utf8_data)
return;
newline = strchr (utf8_data, '\n');
if (newline)
*newline = '\0';
uris[0] = utf8_data;
uris[1] = NULL;
terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */
text = terminal_util_concat_uris (uris, &len);
vte_terminal_feed_child (VTE_TERMINAL (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] = NULL;
terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */
text = terminal_util_concat_uris (uris, &len);
vte_terminal_feed_child (VTE_TERMINAL (screen), text, len);
g_free (text);
g_free (uris[0]);
}
break;
case TARGET_BGIMAGE:
{
char *utf8_data;
char **uris;
if (selection_data_length < 0 || selection_data_format != 8)
return;
utf8_data = g_strndup ((char *) selection_data_data, selection_data_length);
uris = g_uri_list_extract_uris (utf8_data);
g_free (utf8_data);
/* FIXME: use terminal_util_transform_uris_to_quoted_fuse_paths? */
if (uris && uris[0])
{
char *filename;
filename = g_filename_from_uri (uris[0], NULL, NULL);
if (filename)
{
g_object_set (priv->profile,
TERMINAL_PROFILE_BACKGROUND_TYPE, TERMINAL_BACKGROUND_IMAGE,
TERMINAL_PROFILE_BACKGROUND_IMAGE_FILE, filename,
NULL);
}
g_free (filename);
}
g_strfreev (uris);
}
break;
case TARGET_RESET_BG:
g_object_set (priv->profile,
TERMINAL_PROFILE_BACKGROUND_TYPE, TERMINAL_BACKGROUND_SOLID,
NULL);
break;
case TARGET_TAB:
{
GtkWidget *container;
TerminalScreen *moving_screen;
TerminalWindow *source_window;
TerminalWindow *dest_window;
GtkWidget *dest_notebook;
int page_num;
container = *(GtkWidget**) selection_data_data;
if (!GTK_IS_WIDGET (container))
return;
moving_screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (container));
g_return_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);
dest_notebook = terminal_window_get_notebook (dest_window);
page_num = gtk_notebook_page_num (GTK_NOTEBOOK (dest_notebook),
GTK_WIDGET (screen));
terminal_window_move_screen (source_window, dest_window, moving_screen, page_num + 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 policy = GTK_POLICY_ALWAYS;
GtkCornerType corner = GTK_CORNER_TOP_LEFT;
container = terminal_screen_container_get_from_screen (screen);
if (container == NULL)
return;
switch (terminal_profile_get_property_enum (priv->profile, TERMINAL_PROFILE_SCROLLBAR_POSITION))
{
case TERMINAL_SCROLLBAR_HIDDEN:
policy = GTK_POLICY_NEVER;
break;
case TERMINAL_SCROLLBAR_RIGHT:
policy = GTK_POLICY_ALWAYS;
corner = GTK_CORNER_TOP_LEFT;
break;
case TERMINAL_SCROLLBAR_LEFT:
policy = GTK_POLICY_ALWAYS;
corner = GTK_CORNER_TOP_RIGHT;
break;
default:
g_assert_not_reached ();
break;
}
terminal_screen_container_set_placement (container, corner);
terminal_screen_container_set_policy (container, GTK_POLICY_NEVER, policy);
}
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);
}
#ifdef ENABLE_SKEY
static void
terminal_screen_skey_match_remove (TerminalScreen *screen)
{
TerminalScreenPrivate *priv = screen->priv;
GSList *l, *next;
l = priv->match_tags;
while (l != NULL)
{
TagData *tag_data = (TagData *) l->data;
next = l->next;
if (tag_data->flavor == FLAVOR_SKEY)
{
vte_terminal_match_remove (VTE_TERMINAL (screen), tag_data->tag);
priv->match_tags = g_slist_delete_link (priv->match_tags, l);
}
l = next;
}
}
#endif /* ENABLE_SKEY */
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 != NULL; 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 NULL;
}
void
terminal_screen_save_config (TerminalScreen *screen,
GKeyFile *key_file,
const char *group)
{
TerminalScreenPrivate *priv = screen->priv;
VteTerminal *terminal = VTE_TERMINAL (screen);
TerminalProfile *profile = priv->profile;
const char *profile_id;
char *working_directory;
profile_id = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME);
g_key_file_set_string (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_PROFILE_ID, profile_id);
if (priv->override_command)
terminal_util_key_file_set_argv (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_COMMAND,
-1, priv->override_command);
if (priv->override_title)
g_key_file_set_string (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_TITLE, priv->override_title);
/* FIXMEchpe: use the initial_working_directory instead?? */
working_directory = terminal_screen_get_current_dir (screen);
if (working_directory)
terminal_util_key_file_set_string_escape (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_WORKING_DIRECTORY, working_directory);
g_free (working_directory);
g_key_file_set_double (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_ZOOM, priv->font_scale);
g_key_file_set_integer (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_WIDTH,
vte_terminal_get_column_count (terminal));
g_key_file_set_integer (key_file, group, TERMINAL_CONFIG_TERMINAL_PROP_HEIGHT,
vte_terminal_get_row_count (terminal));
}
/**
* terminal_screen_has_foreground_process:
* @screen:
*
* 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)
{
TerminalScreenPrivate *priv = screen->priv;
int fgpid;
if (priv->pty_fd == -1)
return FALSE;
fgpid = tcgetpgrp (priv->pty_fd);
if (fgpid == -1 || fgpid == priv->child_pid)
return FALSE;
return TRUE;
#if 0
char *cmdline, *basename, *name;
gsize len;
char filename[64];
g_snprintf (filename, sizeof (filename), "/proc/%d/cmdline", fgpid);
if (!g_file_get_contents (filename, &cmdline, &len, NULL))
return TRUE;
basename = g_path_get_basename (cmdline);
g_free (cmdline);
if (!basename)
return TRUE;
name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
g_free (basename);
if (!name)
return TRUE;
if (process_name)
*process_name = name;
return TRUE;
#endif
}