/* * Copyright © 2001, 2002 Havoc Pennington * Copyright © 2002 Red Hat, Inc. * Copyright © 2002 Sun Microsystems * Copyright © 2003 Mariano Suarez-Alvarez * Copyright © 2008 Christian Persch * Copyright (C) 2012-2021 MATE Developers * * 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 . */ #include #include #include #include #include "terminal-intl.h" #include "terminal-debug.h" #include "terminal-app.h" #include "terminal-accels.h" #include "terminal-screen.h" #include "terminal-screen-container.h" #include "terminal-window.h" #include "terminal-util.h" #include "profile-editor.h" #include "terminal-encoding.h" #include #include #ifdef HAVE_SMCLIENT #include "eggsmclient.h" #include "eggdesktopfile.h" #endif /* HAVE_SMCLIENT */ #define FALLBACK_PROFILE_ID "default" /* Settings storage works as follows: * /apps/mate-terminal/global/ * /apps/mate-terminal/profiles/Foo/ * * It's somewhat tricky to manage the profiles/ dir since we need to track * the list of profiles, but GSettings doesn't have a concept of notifying that * a directory has appeared or disappeared. * * Session state is stored entirely in the RestartCommand command line. * * The number one rule: all stored information is EITHER per-session, * per-profile, or set from a command line option. THERE CAN BE NO * OVERLAP. The UI and implementation totally break if you overlap * these categories. See mate-terminal 1.x for why. * * Don't use this code as an example of how to use GSettings - it's hugely * overcomplicated due to the profiles stuff. Most apps should not * have to do scary things of this nature, and should not have * a profiles feature. * */ GSettings *settings_global; struct _TerminalAppClass { GObjectClass parent_class; void (* quit) (TerminalApp *app); void (* profile_list_changed) (TerminalApp *app); void (* encoding_list_changed) (TerminalApp *app); }; struct _TerminalApp { GObject parent_instance; GList *windows; GtkWidget *new_profile_dialog; GtkWidget *manage_profiles_dialog; GtkWidget *manage_profiles_list; GtkWidget *manage_profiles_new_button; GtkWidget *manage_profiles_edit_button; GtkWidget *manage_profiles_delete_button; GtkWidget *manage_profiles_default_menu; GSettings *settings_font; GHashTable *profiles; char* default_profile_id; TerminalProfile *default_profile; gboolean default_profile_locked; GHashTable *encodings; gboolean encodings_locked; PangoFontDescription *system_font_desc; gboolean enable_mnemonics; gboolean enable_menu_accels; }; enum { PROP_0, PROP_DEFAULT_PROFILE, PROP_ENABLE_MENU_BAR_ACCEL, PROP_ENABLE_MNEMONICS, PROP_SYSTEM_FONT, }; enum { QUIT, PROFILE_LIST_CHANGED, ENCODING_LIST_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; enum { COL_PROFILE, NUM_COLUMNS }; enum { SOURCE_DEFAULT = 0, SOURCE_SESSION = 1 }; static TerminalApp *global_app = NULL; #define MONOSPACE_FONT_SCHEMA "org.mate.interface" #define MONOSPACE_FONT_KEY "monospace-font-name" #define DEFAULT_MONOSPACE_FONT ("Monospace 10") #define ENABLE_MNEMONICS_KEY "use-mnemonics" #define DEFAULT_ENABLE_MNEMONICS (TRUE) #define ENABLE_MENU_BAR_ACCEL_KEY "use-menu-accelerators" #define DEFAULT_ENABLE_MENU_BAR_ACCEL (TRUE) #define PROFILE_LIST_KEY "profile-list" #define DEFAULT_PROFILE_KEY "default-profile" #define ENCODING_LIST_KEY "active-encodings" /* two following functions were copied from libmate-desktop to get rid * of dependency on it * * FIXME: I suspect there's no need for excessive copies, we might use * existing profile list to form GVariant's and write them to GSettings */ static gboolean gsettings_append_strv (GSettings *settings, const gchar *key, const gchar *value) { gchar **old; gchar **new; gint size; gboolean retval; old = g_settings_get_strv (settings, key); for (size = 0; old[size] != NULL; size++); size += 1; /* appended value */ size += 1; /* NULL */ new = g_realloc_n (old, size, sizeof (gchar *)); new[size - 2] = g_strdup (value); new[size - 1] = NULL; retval = g_settings_set_strv (settings, key, (const gchar **) new); g_strfreev (new); return retval; } static gboolean gsettings_remove_all_from_strv (GSettings *settings, const gchar *key, const gchar *value) { GArray *array; gchar **old; gint i; gboolean retval; old = g_settings_get_strv (settings, key); array = g_array_new (TRUE, TRUE, sizeof (gchar *)); for (i = 0; old[i] != NULL; i++) { if (g_strcmp0 (old[i], value) != 0) array = g_array_append_val (array, old[i]); } retval = g_settings_set_strv (settings, key, (const gchar **) array->data); g_strfreev (old); g_array_free (array, TRUE); return retval; } /* Helper functions */ static GdkScreen* terminal_app_get_screen_by_display_name (const char *display_name) { GdkDisplay *display = NULL; GdkScreen *screen = NULL; if (display_name == NULL) display = gdk_display_get_default (); else { GSList *displays, *l; const char *period; period = strrchr (display_name, '.'); displays = gdk_display_manager_list_displays (gdk_display_manager_get ()); for (l = displays; l != NULL; l = l->next) { GdkDisplay *disp = l->data; /* compare without the screen number part, if present */ if ((period && strncmp (gdk_display_get_name (disp), display_name, period - display_name) == 0) || (period == NULL && strcmp (gdk_display_get_name (disp), display_name) == 0)) { display = disp; break; } } g_slist_free (displays); if (display == NULL) display = gdk_display_open (display_name); /* FIXME we never close displays */ } if (display == NULL) return NULL; else screen = gdk_display_get_default_screen (display); return screen; } static int terminal_app_get_workspace_for_window (TerminalWindow *window) { int ret = -1; guchar *data = NULL; GdkAtom atom; GdkAtom cardinal_atom; atom = gdk_atom_intern_static_string ("_NET_WM_DESKTOP"); cardinal_atom = gdk_atom_intern_static_string ("CARDINAL"); gdk_property_get (gtk_widget_get_window(GTK_WIDGET(window)), atom, cardinal_atom, 0, 8, FALSE, NULL, NULL, NULL, &data); if (data) ret = *(int *)data; g_free (data); return ret; } /* Menubar mnemonics settings handling */ static int profiles_alphabetic_cmp (gconstpointer pa, gconstpointer pb) { TerminalProfile *a = (TerminalProfile *) pa; TerminalProfile *b = (TerminalProfile *) pb; int result; result = g_utf8_collate (terminal_profile_get_property_string (a, TERMINAL_PROFILE_VISIBLE_NAME), terminal_profile_get_property_string (b, TERMINAL_PROFILE_VISIBLE_NAME)); if (result == 0) result = strcmp (terminal_profile_get_property_string (a, TERMINAL_PROFILE_NAME), terminal_profile_get_property_string (b, TERMINAL_PROFILE_NAME)); return result; } typedef struct { TerminalProfile *result; const char *target; } LookupInfo; static void profiles_lookup_by_visible_name_foreach (gpointer key, gpointer value, gpointer data) { LookupInfo *info = data; const char *name; name = terminal_profile_get_property_string (value, TERMINAL_PROFILE_VISIBLE_NAME); if (name && strcmp (info->target, name) == 0) info->result = value; } static void terminal_window_destroyed (TerminalWindow *window, TerminalApp *app) { app->windows = g_list_remove (app->windows, window); if (app->windows == NULL) g_signal_emit (app, signals[QUIT], 0); } static TerminalProfile * terminal_app_create_profile (TerminalApp *app, const char *name) { TerminalProfile *profile; g_assert (terminal_app_get_profile_by_name (app, name) == NULL); profile = _terminal_profile_new (name); g_hash_table_insert (app->profiles, g_strdup (terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME)), profile /* adopts the refcount */); if (app->default_profile == NULL && app->default_profile_id != NULL && strcmp (app->default_profile_id, terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME)) == 0) { /* We are the default profile */ app->default_profile = profile; g_object_notify (G_OBJECT (app), TERMINAL_APP_DEFAULT_PROFILE); } return profile; } static void terminal_app_delete_profile (TerminalProfile *profile) { const char *profile_name; char *profile_dir; GError *error = NULL; profile_name = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME); profile_dir = g_strconcat (CONF_PROFILE_PREFIX, profile_name, "/", NULL); gsettings_remove_all_from_strv (settings_global, PROFILE_LIST_KEY, profile_name); /* And remove the profile directory */ DConfClient *client = dconf_client_new (); if (!dconf_client_write_sync (client, profile_dir, NULL, NULL, NULL, &error)) { g_warning ("Failed to recursively unset %s: %s\n", profile_dir, error->message); g_error_free (error); } g_object_unref (client); g_free (profile_dir); } static void terminal_app_profile_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { TerminalProfile *profile; GValue value = { 0, }; gtk_tree_model_get (tree_model, iter, (int) COL_PROFILE, &profile, (int) -1); g_value_init (&value, G_TYPE_STRING); g_object_get_property (G_OBJECT (profile), "visible-name", &value); g_object_set_property (G_OBJECT (cell), "text", &value); g_value_unset (&value); } static int terminal_app_profile_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { TerminalProfile *profile_a, *profile_b; int retval; gtk_tree_model_get (model, a, (int) COL_PROFILE, &profile_a, (int) -1); gtk_tree_model_get (model, b, (int) COL_PROFILE, &profile_b, (int) -1); retval = profiles_alphabetic_cmp (profile_a, profile_b); g_object_unref (profile_a); g_object_unref (profile_b); return retval; } static /* ref */ GtkTreeModel * terminal_app_get_profile_liststore (TerminalApp *app, TerminalProfile *selected_profile, GtkTreeIter *selected_profile_iter, gboolean *selected_profile_iter_set) { GtkListStore *store; GtkTreeIter iter; GList *profiles, *l; store = gtk_list_store_new (NUM_COLUMNS, TERMINAL_TYPE_PROFILE); *selected_profile_iter_set = FALSE; if (selected_profile && _terminal_profile_get_forgotten (selected_profile)) selected_profile = NULL; profiles = terminal_app_get_profile_list (app); for (l = profiles; l != NULL; l = l->next) { TerminalProfile *profile = TERMINAL_PROFILE (l->data); gtk_list_store_insert_with_values (store, &iter, 0, (int) COL_PROFILE, profile, (int) -1); if (selected_profile_iter && profile == selected_profile) { *selected_profile_iter = iter; *selected_profile_iter_set = TRUE; } } g_list_free (profiles); /* Now turn on sorting */ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), COL_PROFILE, terminal_app_profile_sort_func, NULL, NULL); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), COL_PROFILE, GTK_SORT_ASCENDING); return GTK_TREE_MODEL (store); } static /* ref */ TerminalProfile* profile_combo_box_get_selected (GtkWidget *widget) { GtkComboBox *combo = GTK_COMBO_BOX (widget); TerminalProfile *profile = NULL; GtkTreeIter iter; if (gtk_combo_box_get_active_iter (combo, &iter)) gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter, (int) COL_PROFILE, &profile, (int) -1); return profile; } static void profile_combo_box_refill (TerminalApp *app, GtkWidget *widget) { GtkComboBox *combo = GTK_COMBO_BOX (widget); GtkTreeIter iter; gboolean iter_set; TerminalProfile *selected_profile; GtkTreeModel *model; selected_profile = profile_combo_box_get_selected (widget); if (!selected_profile) { selected_profile = terminal_app_get_default_profile (app); if (selected_profile) g_object_ref (selected_profile); } model = terminal_app_get_profile_liststore (app, selected_profile, &iter, &iter_set); gtk_combo_box_set_model (combo, model); g_object_unref (model); if (iter_set) gtk_combo_box_set_active_iter (combo, &iter); if (selected_profile) g_object_unref (selected_profile); } static GtkWidget* profile_combo_box_new (TerminalApp *app) { GtkWidget *combo; GtkCellRenderer *renderer; combo = gtk_combo_box_new (); terminal_util_set_atk_name_description (combo, NULL, _("Click button to choose profile")); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo), renderer, (GtkCellLayoutDataFunc) terminal_app_profile_cell_data_func, NULL, NULL); profile_combo_box_refill (app, combo); g_signal_connect (app, "profile-list-changed", G_CALLBACK (profile_combo_box_refill), combo); gtk_widget_show (combo); return combo; } static void profile_combo_box_changed_cb (GtkWidget *widget, TerminalApp *app) { TerminalProfile *profile; profile = profile_combo_box_get_selected (widget); if (!profile) return; g_settings_set_string (settings_global, DEFAULT_PROFILE_KEY, terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME)); /* Even though the GSettings change notification does this, it happens too late. * In some cases, the default profile changes twice in quick succession, * and update_default_profile must be called in sync with those changes. */ app->default_profile = profile; g_object_notify (G_OBJECT (app), TERMINAL_APP_DEFAULT_PROFILE); g_object_unref (profile); } static void profile_list_treeview_refill (TerminalApp *app, GtkWidget *widget) { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); GtkTreeIter iter; gboolean iter_set; GtkTreeSelection *selection; GtkTreeModel *model; TerminalProfile *selected_profile = NULL; model = gtk_tree_view_get_model (tree_view); selection = gtk_tree_view_get_selection (tree_view); if (gtk_tree_selection_get_selected (selection, NULL, &iter)) gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); model = terminal_app_get_profile_liststore (terminal_app_get (), selected_profile, &iter, &iter_set); gtk_tree_view_set_model (tree_view, model); g_object_unref (model); if (!iter_set) iter_set = gtk_tree_model_get_iter_first (model, &iter); if (iter_set) gtk_tree_selection_select_iter (selection, &iter); if (selected_profile) g_object_unref (selected_profile); } static GtkWidget* profile_list_treeview_create (TerminalApp *app) { GtkWidget *tree_view; GtkTreeSelection *selection; GtkCellRenderer *renderer; GtkTreeViewColumn *column; tree_view = gtk_tree_view_new (); terminal_util_set_atk_name_description (tree_view, _("Profile list"), NULL); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection), GTK_SELECTION_BROWSE); column = gtk_tree_view_column_new (); renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), renderer, TRUE); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer, (GtkCellLayoutDataFunc) terminal_app_profile_cell_data_func, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), GTK_TREE_VIEW_COLUMN (column)); return tree_view; } static void profile_list_delete_confirm_response_cb (GtkWidget *dialog, int response) { TerminalProfile *profile; profile = TERMINAL_PROFILE (g_object_get_data (G_OBJECT (dialog), "profile")); g_assert (profile != NULL); if (response == GTK_RESPONSE_ACCEPT) terminal_app_delete_profile (profile); gtk_widget_destroy (dialog); } static void mate_dialog_add_button (GtkDialog *dialog, const gchar *button_text, const gchar *icon_name, gint response_id) { GtkWidget *button; button = gtk_button_new_with_mnemonic (button_text); gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON)); gtk_button_set_use_underline (GTK_BUTTON (button), TRUE); gtk_style_context_add_class (gtk_widget_get_style_context (button), "text-button"); gtk_widget_set_can_default (button, TRUE); gtk_widget_show (button); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response_id); } static void profile_list_delete_button_clicked_cb (GtkWidget *button, GtkWidget *widget) { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); GtkTreeSelection *selection; GtkWidget *dialog; GtkTreeIter iter; GtkTreeModel *model; TerminalProfile *selected_profile; GtkWidget *transient_parent; model = gtk_tree_view_get_model (tree_view); selection = gtk_tree_view_get_selection (tree_view); if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) return; gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); transient_parent = gtk_widget_get_toplevel (widget); dialog = gtk_message_dialog_new (GTK_WINDOW (transient_parent), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Delete profile “%s”?"), terminal_profile_get_property_string (selected_profile, TERMINAL_PROFILE_VISIBLE_NAME)); mate_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), "process-stop", GTK_RESPONSE_REJECT); mate_dialog_add_button (GTK_DIALOG (dialog), _("_Delete"), "edit-delete", GTK_RESPONSE_ACCEPT); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); gtk_window_set_title (GTK_WINDOW (dialog), _("Delete Profile")); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); /* Transfer refcount of |selected_profile|, so no unref below */ g_object_set_data_full (G_OBJECT (dialog), "profile", selected_profile, g_object_unref); g_signal_connect (dialog, "response", G_CALLBACK (profile_list_delete_confirm_response_cb), NULL); gtk_window_present (GTK_WINDOW (dialog)); } static void profile_list_new_button_clicked_cb (GtkWidget *button, gpointer data) { TerminalApp *app; app = terminal_app_get (); terminal_app_new_profile (app, NULL, GTK_WINDOW (app->manage_profiles_dialog)); } static void profile_list_edit_button_clicked_cb (GtkWidget *button, GtkWidget *widget) { GtkTreeView *tree_view = GTK_TREE_VIEW (widget); GtkTreeSelection *selection; GtkTreeIter iter; GtkTreeModel *model; TerminalProfile *selected_profile; TerminalApp *app; app = terminal_app_get (); model = gtk_tree_view_get_model (tree_view); selection = gtk_tree_view_get_selection (tree_view); if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) return; gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); terminal_app_edit_profile (app, selected_profile, GTK_WINDOW (app->manage_profiles_dialog), NULL); g_object_unref (selected_profile); } static void profile_list_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) { GtkTreeIter iter; GtkTreeModel *model; TerminalProfile *selected_profile; TerminalApp *app; app = terminal_app_get (); model = gtk_tree_view_get_model (tree_view); if (!gtk_tree_model_get_iter (model, &iter, path)) return; gtk_tree_model_get (model, &iter, (int) COL_PROFILE, &selected_profile, (int) -1); terminal_app_edit_profile (app, selected_profile, GTK_WINDOW (app->manage_profiles_dialog), NULL); g_object_unref (selected_profile); } static GList* find_profile_link (GList *profiles, const char *name) { GList *l; for (l = profiles; l != NULL; l = l->next) { const char *profile_name; profile_name = terminal_profile_get_property_string (TERMINAL_PROFILE (l->data), TERMINAL_PROFILE_NAME); if (profile_name && strcmp (profile_name, name) == 0) break; } return l; } static void terminal_app_profile_list_notify_cb (GSettings *settings, const gchar *key, gpointer user_data) { TerminalApp *app = TERMINAL_APP (user_data); GObject *object = G_OBJECT (app); GVariant *val; const gchar **value_list; int i; GList *profiles_to_delete, *l; gboolean need_new_default; TerminalProfile *fallback; guint count; g_object_freeze_notify (object); profiles_to_delete = terminal_app_get_profile_list (app); val = g_settings_get_value (settings, key); if (val == NULL || (!g_variant_is_of_type (val, G_VARIANT_TYPE_STRING_ARRAY) && !g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))) goto ensure_one_profile; value_list = g_variant_get_strv (val, NULL); if (value_list == NULL) goto ensure_one_profile; /* Add any new ones */ for (i = 0; value_list[i] != NULL; ++i) { const char *profile_name = value_list[i]; GList *link; if (!profile_name) continue; link = find_profile_link (profiles_to_delete, profile_name); if (link) /* make profiles_to_delete point to profiles we didn't find in the list */ profiles_to_delete = g_list_delete_link (profiles_to_delete, link); else terminal_app_create_profile (app, profile_name); } g_free (value_list); ensure_one_profile: if (val != NULL) g_variant_unref (val); fallback = NULL; count = g_hash_table_size (app->profiles); if (count == 0 || count <= g_list_length (profiles_to_delete)) { /* We are going to run out, so create the fallback * to be sure we always have one. Must be done * here before we emit "forgotten" signals so that * screens have a profile to fall back to. * * If the profile with the FALLBACK_ID already exists, * we aren't allowed to delete it, unless at least one * other profile will still exist. And if you delete * all profiles, the FALLBACK_ID profile returns as * the living dead. */ fallback = terminal_app_get_profile_by_name (app, FALLBACK_PROFILE_ID); if (fallback == NULL) fallback = terminal_app_create_profile (app, FALLBACK_PROFILE_ID); g_assert (fallback != NULL); } /* Forget no-longer-existing profiles */ need_new_default = FALSE; for (l = profiles_to_delete; l != NULL; l = l->next) { TerminalProfile *profile = TERMINAL_PROFILE (l->data); const char *name; name = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_NAME); if (strcmp (name, FALLBACK_PROFILE_ID) == 0) continue; if (profile == app->default_profile) { app->default_profile = NULL; need_new_default = TRUE; } _terminal_profile_forget (profile); g_hash_table_remove (app->profiles, name); /* |profile| possibly isn't dead yet since the profiles dialogue's tree model holds a ref too... */ } g_list_free (profiles_to_delete); if (need_new_default) { TerminalProfile *new_default; TerminalProfile **new_default_ptr = &new_default; new_default = terminal_app_get_profile_by_name (app, FALLBACK_PROFILE_ID); if (new_default == NULL) { GHashTableIter iter; g_hash_table_iter_init (&iter, app->profiles); if (!g_hash_table_iter_next (&iter, NULL, (gpointer *) new_default_ptr)) /* shouldn't really happen ever, but just to be safe */ new_default = terminal_app_create_profile (app, FALLBACK_PROFILE_ID); } g_assert (new_default != NULL); app->default_profile = new_default; g_object_notify (object, TERMINAL_APP_DEFAULT_PROFILE); } g_assert (g_hash_table_size (app->profiles) > 0); g_signal_emit (app, signals[PROFILE_LIST_CHANGED], 0); g_object_thaw_notify (object); } static void terminal_app_default_profile_notify_cb (GSettings *settings, const gchar *key, gpointer user_data) { TerminalApp *app = TERMINAL_APP (user_data); GVariant *val; const char *name = NULL; app->default_profile_locked = !g_settings_is_writable (settings, key); val = g_settings_get_value (settings, key); if (val != NULL && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) name = g_variant_get_string (val, NULL); if (!name || !name[0]) name = FALLBACK_PROFILE_ID; g_assert (name != NULL); g_free (app->default_profile_id); app->default_profile_id = g_strdup (name); app->default_profile = terminal_app_get_profile_by_name (app, name); g_object_notify (G_OBJECT (app), TERMINAL_APP_DEFAULT_PROFILE); g_variant_unref (val); } static int compare_encodings (TerminalEncoding *a, TerminalEncoding *b) { return g_utf8_collate (a->name, b->name); } static void encoding_mark_active (gpointer key, gpointer value, gpointer data) { TerminalEncoding *encoding = (TerminalEncoding *) value; guint active = GPOINTER_TO_UINT (data); encoding->is_active = active; } static void terminal_app_encoding_list_notify_cb (GSettings *settings, const gchar *key, gpointer user_data) { TerminalApp *app = TERMINAL_APP (user_data); GVariant *val; const gchar **strings; int i; TerminalEncoding *encoding; const char *charset; app->encodings_locked = !g_settings_is_writable (settings, key); /* Mark all as non-active, then re-enable the active ones */ g_hash_table_foreach (app->encodings, (GHFunc) encoding_mark_active, GUINT_TO_POINTER (FALSE)); /* First add the locale's charset */ encoding = g_hash_table_lookup (app->encodings, "current"); g_assert (encoding); if (terminal_encoding_is_valid (encoding)) encoding->is_active = TRUE; /* Also always make UTF-8 available */ encoding = g_hash_table_lookup (app->encodings, "UTF-8"); g_assert (encoding); if (terminal_encoding_is_valid (encoding)) encoding->is_active = TRUE; val = g_settings_get_value (settings, key); if (val != NULL && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING_ARRAY)) strings = g_variant_get_strv (val, NULL); else strings = NULL; if (strings != NULL) { for (i = 0; strings[i] != NULL; ++i) { charset = strings[i]; if (!charset) continue; encoding = terminal_app_ensure_encoding (app, charset); if (!terminal_encoding_is_valid (encoding)) continue; encoding->is_active = TRUE; } g_free (strings); } g_signal_emit (app, signals[ENCODING_LIST_CHANGED], 0); if (val != NULL) g_variant_unref (val); } static void terminal_app_system_font_notify_cb (GSettings *settings, const gchar *key, gpointer user_data) { TerminalApp *app = TERMINAL_APP (user_data); GVariant *val; const char *font = NULL; PangoFontDescription *font_desc; if (strcmp (key, MONOSPACE_FONT_KEY) != 0) return; val = g_settings_get_value (settings, key); if (val && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING)) font = g_variant_get_string (val, NULL); if (!font) font = DEFAULT_MONOSPACE_FONT; g_assert (font != NULL); if (font && (strlen (font) == 0)) /* empty string */ font = DEFAULT_MONOSPACE_FONT; font_desc = pango_font_description_from_string (font); if (app->system_font_desc && pango_font_description_equal (app->system_font_desc, font_desc)) { pango_font_description_free (font_desc); return; } if (app->system_font_desc) pango_font_description_free (app->system_font_desc); app->system_font_desc = font_desc; g_object_notify (G_OBJECT (app), TERMINAL_APP_SYSTEM_FONT); g_variant_unref (val); } static void terminal_app_enable_mnemonics_notify_cb (GSettings *settings, const gchar *key, gpointer user_data) { TerminalApp *app = TERMINAL_APP (user_data); gboolean enable; enable = g_settings_get_boolean (settings, key); if (enable == app->enable_mnemonics) return; app->enable_mnemonics = enable; g_object_notify (G_OBJECT (app), TERMINAL_APP_ENABLE_MNEMONICS); } static void terminal_app_enable_menu_accels_notify_cb (GSettings *settings, const gchar *key, gpointer user_data) { TerminalApp *app = TERMINAL_APP (user_data); gboolean enable; enable = g_settings_get_boolean (settings, key); if (enable == app->enable_menu_accels) return; app->enable_menu_accels = enable; g_object_notify (G_OBJECT (app), TERMINAL_APP_ENABLE_MENU_BAR_ACCEL); } static void new_profile_response_cb (GtkWidget *new_profile_dialog, int response_id, TerminalApp *app) { if (response_id == GTK_RESPONSE_ACCEPT) { GtkWidget *name_entry; char *name; const char *new_profile_name; GtkWidget *base_option_menu; TerminalProfile *base_profile = NULL; TerminalProfile *new_profile; GList *profiles; GList *tmp; GtkWindow *transient_parent; GtkWidget *confirm_dialog; gint retval; base_option_menu = g_object_get_data (G_OBJECT (new_profile_dialog), "base_option_menu"); base_profile = profile_combo_box_get_selected (base_option_menu); if (!base_profile) base_profile = terminal_app_get_default_profile (app); if (!base_profile) return; /* shouldn't happen ever though */ name_entry = g_object_get_data (G_OBJECT (new_profile_dialog), "name_entry"); name = gtk_editable_get_chars (GTK_EDITABLE (name_entry), 0, -1); g_strstrip (name); /* name will be non empty after stripping */ profiles = terminal_app_get_profile_list (app); for (tmp = profiles; tmp != NULL; tmp = tmp->next) { TerminalProfile *profile = tmp->data; const char *visible_name; visible_name = terminal_profile_get_property_string (profile, TERMINAL_PROFILE_VISIBLE_NAME); if (visible_name && strcmp (name, visible_name) == 0) break; } if (tmp) { confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (new_profile_dialog), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("You already have a profile called “%s”. Do you want to create another profile with the same name?"), name); /* Alternative button order was set automatically by GtkMessageDialog */ retval = gtk_dialog_run (GTK_DIALOG (confirm_dialog)); gtk_widget_destroy (confirm_dialog); if (retval == GTK_RESPONSE_NO) goto cleanup; } g_list_free (profiles); transient_parent = gtk_window_get_transient_for (GTK_WINDOW (new_profile_dialog)); new_profile = _terminal_profile_clone (base_profile, name); new_profile_name = terminal_profile_get_property_string (new_profile, TERMINAL_PROFILE_NAME); g_hash_table_insert (app->profiles, g_strdup (new_profile_name), new_profile /* adopts the refcount */); /* And now save the new profile name to GSettings */ gsettings_append_strv (settings_global, PROFILE_LIST_KEY, new_profile_name); terminal_profile_edit (new_profile, transient_parent, NULL); cleanup: g_free (name); } gtk_widget_destroy (new_profile_dialog); } static void new_profile_dialog_destroy_cb (GtkWidget *new_profile_dialog, TerminalApp *app) { GtkWidget *combo; combo = g_object_get_data (G_OBJECT (new_profile_dialog), "base_option_menu"); g_signal_handlers_disconnect_by_func (app, G_CALLBACK (profile_combo_box_refill), combo); app->new_profile_dialog = NULL; } static void new_profile_name_entry_changed_cb (GtkEntry *entry, GtkDialog *dialog) { const char *name; name = gtk_entry_get_text (entry); /* make the create button sensitive only if something other than space has been set */ while (*name != '\0' && g_ascii_isspace (*name)) ++name; gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_ACCEPT, name[0] != '\0'); } void terminal_app_new_profile (TerminalApp *app, TerminalProfile *default_base_profile, GtkWindow *transient_parent) { if (app->new_profile_dialog == NULL) { GtkWidget *create_button, *grid, *name_label, *name_entry, *base_label, *combo; if (!terminal_util_load_builder_resource (TERMINAL_RESOURCES_PATH_PREFIX G_DIR_SEPARATOR_S "ui/profile-new-dialog.ui", "new-profile-dialog", &app->new_profile_dialog, "new-profile-create-button", &create_button, "new-profile-grid", &grid, "new-profile-name-label", &name_label, "new-profile-name-entry", &name_entry, "new-profile-base-label", &base_label, NULL)) return; g_signal_connect (G_OBJECT (app->new_profile_dialog), "response", G_CALLBACK (new_profile_response_cb), app); g_signal_connect (app->new_profile_dialog, "destroy", G_CALLBACK (new_profile_dialog_destroy_cb), app); g_object_set_data (G_OBJECT (app->new_profile_dialog), "create_button", create_button); gtk_widget_set_sensitive (create_button, FALSE); /* the name entry */ g_object_set_data (G_OBJECT (app->new_profile_dialog), "name_entry", name_entry); g_signal_connect (name_entry, "changed", G_CALLBACK (new_profile_name_entry_changed_cb), app->new_profile_dialog); gtk_entry_set_activates_default (GTK_ENTRY (name_entry), TRUE); gtk_widget_grab_focus (name_entry); gtk_label_set_mnemonic_widget (GTK_LABEL (name_label), name_entry); /* the base profile option menu */ combo = profile_combo_box_new (app); gtk_grid_attach (GTK_GRID (grid), combo, 2, 1, 1, 1); g_object_set_data (G_OBJECT (app->new_profile_dialog), "base_option_menu", combo); terminal_util_set_atk_name_description (combo, NULL, _("Choose base profile")); gtk_label_set_mnemonic_widget (GTK_LABEL (base_label), combo); gtk_dialog_set_default_response (GTK_DIALOG (app->new_profile_dialog), GTK_RESPONSE_ACCEPT); gtk_dialog_set_response_sensitive (GTK_DIALOG (app->new_profile_dialog), GTK_RESPONSE_ACCEPT, FALSE); } gtk_window_set_transient_for (GTK_WINDOW (app->new_profile_dialog), transient_parent); gtk_window_present (GTK_WINDOW (app->new_profile_dialog)); } static void profile_list_selection_changed_cb (GtkTreeSelection *selection, TerminalApp *app) { gboolean selected; selected = gtk_tree_selection_get_selected (selection, NULL, NULL); gtk_widget_set_sensitive (app->manage_profiles_edit_button, selected); gtk_widget_set_sensitive (app->manage_profiles_delete_button, selected && g_hash_table_size (app->profiles) > 1); } static void profile_list_response_cb (GtkWidget *dialog, int id, TerminalApp *app) { g_assert (app->manage_profiles_dialog == dialog); if (id == GTK_RESPONSE_HELP) { terminal_util_show_help ("mate-terminal-manage-profiles", GTK_WINDOW (dialog)); return; } gtk_widget_destroy (dialog); } static void profile_list_destroyed_cb (GtkWidget *manage_profiles_dialog, TerminalApp *app) { g_signal_handlers_disconnect_by_func (app, G_CALLBACK (profile_list_treeview_refill), app->manage_profiles_list); g_signal_handlers_disconnect_by_func (app, G_CALLBACK (profile_combo_box_refill), app->manage_profiles_default_menu); app->manage_profiles_dialog = NULL; app->manage_profiles_list = NULL; app->manage_profiles_new_button = NULL; app->manage_profiles_edit_button = NULL; app->manage_profiles_delete_button = NULL; app->manage_profiles_default_menu = NULL; } void terminal_app_manage_profiles (TerminalApp *app, GtkWindow *transient_parent) { GObject *dialog; GObject *tree_view_container, *new_button, *edit_button, *remove_button; GObject *default_hbox, *default_label; GtkTreeSelection *selection; if (app->manage_profiles_dialog) { gtk_window_set_transient_for (GTK_WINDOW (app->manage_profiles_dialog), transient_parent); gtk_window_present (GTK_WINDOW (app->manage_profiles_dialog)); return; } if (!terminal_util_load_builder_resource (TERMINAL_RESOURCES_PATH_PREFIX G_DIR_SEPARATOR_S "ui/profile-manager.ui", "profile-manager", &dialog, "profiles-treeview-container", &tree_view_container, "new-profile-button", &new_button, "edit-profile-button", &edit_button, "delete-profile-button", &remove_button, "default-profile-hbox", &default_hbox, "default-profile-label", &default_label, NULL)) return; app->manage_profiles_dialog = GTK_WIDGET (dialog); app->manage_profiles_new_button = GTK_WIDGET (new_button); app->manage_profiles_edit_button = GTK_WIDGET (edit_button); app->manage_profiles_delete_button = GTK_WIDGET (remove_button); g_signal_connect (dialog, "response", G_CALLBACK (profile_list_response_cb), app); g_signal_connect (dialog, "destroy", G_CALLBACK (profile_list_destroyed_cb), app); app->manage_profiles_list = profile_list_treeview_create (app); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (app->manage_profiles_list)); g_signal_connect (selection, "changed", G_CALLBACK (profile_list_selection_changed_cb), app); profile_list_treeview_refill (app, app->manage_profiles_list); g_signal_connect (app, "profile-list-changed", G_CALLBACK (profile_list_treeview_refill), app->manage_profiles_list); g_signal_connect (app->manage_profiles_list, "row-activated", G_CALLBACK (profile_list_row_activated_cb), app); gtk_container_add (GTK_CONTAINER (tree_view_container), app->manage_profiles_list); gtk_widget_show (app->manage_profiles_list); g_signal_connect (new_button, "clicked", G_CALLBACK (profile_list_new_button_clicked_cb), app->manage_profiles_list); g_signal_connect (edit_button, "clicked", G_CALLBACK (profile_list_edit_button_clicked_cb), app->manage_profiles_list); g_signal_connect (remove_button, "clicked", G_CALLBACK (profile_list_delete_button_clicked_cb), app->manage_profiles_list); app->manage_profiles_default_menu = profile_combo_box_new (app); g_signal_connect (app->manage_profiles_default_menu, "changed", G_CALLBACK (profile_combo_box_changed_cb), app); gtk_box_pack_start (GTK_BOX (default_hbox), app->manage_profiles_default_menu, FALSE, FALSE, 0); gtk_widget_show (app->manage_profiles_default_menu); gtk_label_set_mnemonic_widget (GTK_LABEL (default_label), app->manage_profiles_default_menu); gtk_widget_grab_focus (app->manage_profiles_list); gtk_window_set_transient_for (GTK_WINDOW (app->manage_profiles_dialog), transient_parent); gtk_window_present (GTK_WINDOW (app->manage_profiles_dialog)); } #ifdef HAVE_SMCLIENT static void terminal_app_save_state_cb (EggSMClient *client, GKeyFile *key_file, TerminalApp *app) { terminal_app_save_config (app, key_file); } static void terminal_app_client_quit_cb (EggSMClient *client, TerminalApp *app) { g_signal_emit (app, signals[QUIT], 0); } #endif /* HAVE_SMCLIENT */ /* Class implementation */ G_DEFINE_TYPE (TerminalApp, terminal_app, G_TYPE_OBJECT) static void terminal_app_init (TerminalApp *app) { global_app = app; gtk_window_set_default_icon_name (MATE_TERMINAL_ICON_NAME); /* Initialise defaults */ app->enable_mnemonics = DEFAULT_ENABLE_MNEMONICS; app->enable_menu_accels = DEFAULT_ENABLE_MENU_BAR_ACCEL; app->profiles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); app->encodings = terminal_encodings_get_builtins (); settings_global = g_settings_new (CONF_GLOBAL_SCHEMA); app->settings_font = g_settings_new (MONOSPACE_FONT_SCHEMA); g_signal_connect (settings_global, "changed::" PROFILE_LIST_KEY, G_CALLBACK(terminal_app_profile_list_notify_cb), app); g_signal_connect (settings_global, "changed::" DEFAULT_PROFILE_KEY, G_CALLBACK(terminal_app_default_profile_notify_cb), app); g_signal_connect (settings_global, "changed::" ENCODING_LIST_KEY, G_CALLBACK(terminal_app_encoding_list_notify_cb), app); g_signal_connect (app->settings_font, "changed::" MONOSPACE_FONT_KEY, G_CALLBACK(terminal_app_system_font_notify_cb), app); g_signal_connect (settings_global, "changed::" ENABLE_MNEMONICS_KEY, G_CALLBACK(terminal_app_enable_mnemonics_notify_cb), app); g_signal_connect (settings_global, "changed::" ENABLE_MENU_BAR_ACCEL_KEY, G_CALLBACK(terminal_app_enable_menu_accels_notify_cb), app); /* Load the settings */ terminal_app_profile_list_notify_cb (settings_global, PROFILE_LIST_KEY, app); terminal_app_default_profile_notify_cb (settings_global, DEFAULT_PROFILE_KEY, app); terminal_app_encoding_list_notify_cb (settings_global, ENCODING_LIST_KEY, app); terminal_app_system_font_notify_cb (app->settings_font, MONOSPACE_FONT_KEY, app); terminal_app_enable_menu_accels_notify_cb (settings_global, ENABLE_MENU_BAR_ACCEL_KEY, app); terminal_app_enable_mnemonics_notify_cb (settings_global, ENABLE_MNEMONICS_KEY, app); /* Ensure we have valid settings */ g_assert (app->default_profile_id != NULL); g_assert (app->system_font_desc != NULL); terminal_accels_init (); #ifdef HAVE_SMCLIENT EggSMClient *sm_client; char *desktop_file; desktop_file = g_build_filename (TERM_DATADIR, "applications", PACKAGE ".desktop", NULL); egg_set_desktop_file_without_defaults (desktop_file); g_free (desktop_file); sm_client = egg_sm_client_get (); g_signal_connect (sm_client, "save-state", G_CALLBACK (terminal_app_save_state_cb), app); g_signal_connect (sm_client, "quit", G_CALLBACK (terminal_app_client_quit_cb), app); #endif /* HAVE_SMCLIENT */ } static void terminal_app_finalize (GObject *object) { TerminalApp *app = TERMINAL_APP (object); #ifdef HAVE_SMCLIENT EggSMClient *sm_client; sm_client = egg_sm_client_get (); g_signal_handlers_disconnect_matched (sm_client, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, app); #endif /* HAVE_SMCLIENT */ g_signal_handlers_disconnect_by_func (settings_global, G_CALLBACK(terminal_app_profile_list_notify_cb), app); g_signal_handlers_disconnect_by_func (settings_global, G_CALLBACK(terminal_app_default_profile_notify_cb), app); g_signal_handlers_disconnect_by_func (settings_global, G_CALLBACK(terminal_app_encoding_list_notify_cb), app); g_signal_handlers_disconnect_by_func (app->settings_font, G_CALLBACK(terminal_app_system_font_notify_cb), app); g_signal_handlers_disconnect_by_func (settings_global, G_CALLBACK(terminal_app_enable_menu_accels_notify_cb), app); g_signal_handlers_disconnect_by_func (settings_global, G_CALLBACK(terminal_app_enable_mnemonics_notify_cb), app); g_object_unref (settings_global); g_object_unref (app->settings_font); g_free (app->default_profile_id); g_hash_table_destroy (app->profiles); g_hash_table_destroy (app->encodings); pango_font_description_free (app->system_font_desc); terminal_accels_shutdown (); G_OBJECT_CLASS (terminal_app_parent_class)->finalize (object); global_app = NULL; } static void terminal_app_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { TerminalApp *app = TERMINAL_APP (object); switch (prop_id) { case PROP_SYSTEM_FONT: if (app->system_font_desc) g_value_set_boxed (value, app->system_font_desc); else g_value_take_boxed (value, pango_font_description_from_string (DEFAULT_MONOSPACE_FONT)); break; case PROP_ENABLE_MENU_BAR_ACCEL: g_value_set_boolean (value, app->enable_menu_accels); break; case PROP_ENABLE_MNEMONICS: g_value_set_boolean (value, app->enable_mnemonics); break; case PROP_DEFAULT_PROFILE: g_value_set_object (value, app->default_profile); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void terminal_app_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { TerminalApp *app = TERMINAL_APP (object); switch (prop_id) { case PROP_ENABLE_MENU_BAR_ACCEL: app->enable_menu_accels = g_value_get_boolean (value); g_settings_set_boolean (settings_global, ENABLE_MENU_BAR_ACCEL_KEY, app->enable_menu_accels); break; case PROP_ENABLE_MNEMONICS: app->enable_mnemonics = g_value_get_boolean (value); g_settings_set_boolean (settings_global, ENABLE_MNEMONICS_KEY, app->enable_mnemonics); break; case PROP_DEFAULT_PROFILE: case PROP_SYSTEM_FONT: /* not writable */ default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void terminal_app_real_quit (TerminalApp *app) { gtk_main_quit(); } static void terminal_app_class_init (TerminalAppClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = terminal_app_finalize; object_class->get_property = terminal_app_get_property; object_class->set_property = terminal_app_set_property; klass->quit = terminal_app_real_quit; signals[QUIT] = g_signal_new (I_("quit"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (TerminalAppClass, quit), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[PROFILE_LIST_CHANGED] = g_signal_new (I_("profile-list-changed"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (TerminalAppClass, profile_list_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[ENCODING_LIST_CHANGED] = g_signal_new (I_("encoding-list-changed"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (TerminalAppClass, profile_list_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_ENABLE_MENU_BAR_ACCEL, g_param_spec_boolean (TERMINAL_APP_ENABLE_MENU_BAR_ACCEL, NULL, NULL, DEFAULT_ENABLE_MENU_BAR_ACCEL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_object_class_install_property (object_class, PROP_ENABLE_MNEMONICS, g_param_spec_boolean (TERMINAL_APP_ENABLE_MNEMONICS, NULL, NULL, DEFAULT_ENABLE_MNEMONICS, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_object_class_install_property (object_class, PROP_SYSTEM_FONT, g_param_spec_boxed (TERMINAL_APP_SYSTEM_FONT, NULL, NULL, PANGO_TYPE_FONT_DESCRIPTION, G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); g_object_class_install_property (object_class, PROP_DEFAULT_PROFILE, g_param_spec_object (TERMINAL_APP_DEFAULT_PROFILE, NULL, NULL, TERMINAL_TYPE_PROFILE, G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); } /* Public API */ TerminalApp* terminal_app_get (void) { if (global_app == NULL) { g_object_new (TERMINAL_TYPE_APP, NULL); g_assert (global_app != NULL); } return global_app; } void terminal_app_shutdown (void) { if (global_app == NULL) return; g_object_unref (global_app); g_assert (global_app == NULL); } /** * terminal_app_handle_options: * @app: * @options: a #TerminalOptions * @allow_resume: whether to merge the terminal configuration from the * saved session on resume * @error: a #GError to fill in * * Processes @options. It loads or saves the terminal configuration, or * opens the specified windows and tabs. * * Returns: %TRUE if @options could be successfully handled, or %FALSE on * error */ gboolean terminal_app_handle_options (TerminalApp *app, TerminalOptions *options, gboolean allow_resume, GError **error) { GList *lw; GdkScreen *gdk_screen; gdk_screen = terminal_app_get_screen_by_display_name (options->display_name); if (options->save_config) { if (options->remote_arguments) return terminal_app_save_config_file (app, options->config_file, error); g_set_error_literal (error, TERMINAL_OPTION_ERROR, TERMINAL_OPTION_ERROR_NOT_IN_FACTORY, "Cannot use \"--save-config\" when starting the factory process"); return FALSE; } if (options->load_config) { GKeyFile *key_file; gboolean result; key_file = g_key_file_new (); result = g_key_file_load_from_file (key_file, options->config_file, 0, error) && terminal_options_merge_config (options, key_file, SOURCE_DEFAULT, error); g_key_file_free (key_file); if (!result) return FALSE; /* fall-through on success */ } #ifdef HAVE_SMCLIENT EggSMClient *sm_client; sm_client = egg_sm_client_get (); if (allow_resume && egg_sm_client_is_resumed (sm_client)) { GKeyFile *key_file; key_file = egg_sm_client_get_state_file (sm_client); if (key_file != NULL && !terminal_options_merge_config (options, key_file, SOURCE_SESSION, error)) return FALSE; } #endif /* HAVE_SMCLIENT */ /* Make sure we open at least one window */ terminal_options_ensure_window (options); if (options->startup_id != NULL) _terminal_debug_print (TERMINAL_DEBUG_FACTORY, "Startup ID is '%s'\n", options->startup_id); for (lw = options->initial_windows; lw != NULL; lw = lw->next) { InitialWindow *iw = lw->data; TerminalWindow *window = NULL; GList *lt; g_assert (iw->tabs); if ( lw == options->initial_windows && ((InitialTab *)iw->tabs->data)->attach_window ) window = terminal_app_get_current_window(app, gdk_screen, options->initial_workspace); if (!window) { /* Create & setup new window */ window = terminal_app_new_window (app, gdk_screen); /* Restored windows shouldn't demand attention; see bug #586308. */ if (iw->source_tag == SOURCE_SESSION) terminal_window_set_is_restored (window); if (options->startup_id != NULL) gtk_window_set_startup_id (GTK_WINDOW (window), options->startup_id); /* Overwrite the default, unique window role set in terminal_window_init */ if (iw->role) gtk_window_set_role (GTK_WINDOW (window), iw->role); if (iw->icon) { if (g_path_is_absolute (iw->icon)) gtk_window_set_icon_from_file (GTK_WINDOW (window), iw->icon, NULL); else gtk_window_set_icon_name (GTK_WINDOW (window), iw->icon); } if (iw->force_menubar_state) terminal_window_set_menubar_visible (window, iw->menubar_state); if (iw->start_fullscreen) gtk_window_fullscreen (GTK_WINDOW (window)); if (iw->start_maximized) gtk_window_maximize (GTK_WINDOW (window)); } /* Now add the tabs */ for (lt = iw->tabs; lt != NULL; lt = lt->next) { InitialTab *it = lt->data; TerminalProfile *profile = NULL; TerminalScreen *screen; const char *profile_name; gboolean profile_is_id; if (it->profile) { profile_name = it->profile; profile_is_id = it->profile_is_id; } else { profile_name = options->default_profile; profile_is_id = options->default_profile_is_id; } if (profile_name) { if (profile_is_id) profile = terminal_app_get_profile_by_name (app, profile_name); else profile = terminal_app_get_profile_by_visible_name (app, profile_name); if (profile == NULL) g_printerr (_("No such profile \"%s\", using default profile\n"), it->profile); } if (profile == NULL) profile = terminal_app_get_profile_for_new_term (app); g_assert (profile); screen = terminal_app_new_terminal (app, window, profile, it->exec_argv ? it->exec_argv : options->exec_argv, it->title ? it->title : options->default_title, it->working_dir ? it->working_dir : options->default_working_dir, options->env, it->zoom_set ? it->zoom : options->zoom); if (it->active) terminal_window_switch_screen (window, screen); } if (iw->geometry) { _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "[window %p] applying geometry %s\n", window, iw->geometry); if (!terminal_window_update_size_set_geometry (window, terminal_window_get_active (window), FALSE, iw->geometry)) g_printerr (_("Invalid geometry string \"%s\"\n"), iw->geometry); } gtk_window_present (GTK_WINDOW (window)); } return TRUE; } TerminalWindow * terminal_app_new_window (TerminalApp *app, GdkScreen *screen) { TerminalWindow *window; window = terminal_window_new (); app->windows = g_list_append (app->windows, window); g_signal_connect (window, "destroy", G_CALLBACK (terminal_window_destroyed), app); if (screen) gtk_window_set_screen (GTK_WINDOW (window), screen); return window; } TerminalScreen * terminal_app_new_terminal (TerminalApp *app, TerminalWindow *window, TerminalProfile *profile, char **override_command, const char *title, const char *working_dir, char **child_env, double zoom) { TerminalScreen *screen; g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); g_return_val_if_fail (TERMINAL_IS_WINDOW (window), NULL); screen = terminal_screen_new (profile, override_command, title, working_dir, child_env, zoom); terminal_window_add_screen (window, screen, -1); terminal_window_switch_screen (window, screen); gtk_widget_grab_focus (GTK_WIDGET (screen)); return screen; } void terminal_app_edit_profile (TerminalApp *app, TerminalProfile *profile, GtkWindow *transient_parent, const char *widget_name) { terminal_profile_edit (profile, transient_parent, widget_name); } void terminal_app_edit_keybindings (TerminalApp *app, GtkWindow *transient_parent) { terminal_edit_keys_dialog_show (transient_parent); } void terminal_app_edit_encodings (TerminalApp *app, GtkWindow *transient_parent) { terminal_encoding_dialog_show (transient_parent); } /* * Get the window in the given screen and workspace. If nothing is found, * a NULL is returned. */ TerminalWindow * terminal_app_get_current_window (TerminalApp *app, GdkScreen *from_screen, int workspace) { GList *res = NULL; TerminalWindow *ret = NULL; if (app->windows == NULL) return NULL; res = g_list_last (app->windows); g_assert (from_screen != NULL); while (res) { int win_workspace; if (gtk_window_get_screen(GTK_WINDOW(res->data)) != from_screen) continue; win_workspace = terminal_app_get_workspace_for_window(res->data); /* Same workspace or if the window is set to show up on all workspaces */ if (win_workspace == workspace || win_workspace == -1) ret = terminal_window_get_latest_focused (ret, TERMINAL_WINDOW(res->data)); res = g_list_previous (res); } return ret; } /** * terminal_profile_get_list: * * Returns: a #GList containing all #TerminalProfile objects. * The content of the list is owned by the backend and * should not be modified or freed. Use g_list_free() when done * using the list. */ GList* terminal_app_get_profile_list (TerminalApp *app) { g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); return g_list_sort (g_hash_table_get_values (app->profiles), profiles_alphabetic_cmp); } TerminalProfile* terminal_app_get_profile_by_name (TerminalApp *app, const char *name) { g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); g_return_val_if_fail (name != NULL, NULL); return g_hash_table_lookup (app->profiles, name); } TerminalProfile* terminal_app_get_profile_by_visible_name (TerminalApp *app, const char *name) { LookupInfo info; g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); g_return_val_if_fail (name != NULL, NULL); info.result = NULL; info.target = name; g_hash_table_foreach (app->profiles, profiles_lookup_by_visible_name_foreach, &info); return info.result; } TerminalProfile* terminal_app_get_default_profile (TerminalApp *app) { g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); return app->default_profile; } TerminalProfile* terminal_app_get_profile_for_new_term (TerminalApp *app) { GHashTableIter iter; TerminalProfile *profile = NULL; TerminalProfile **profileptr = &profile; g_return_val_if_fail (TERMINAL_IS_APP (app), NULL); if (app->default_profile) return app->default_profile; g_hash_table_iter_init (&iter, app->profiles); if (g_hash_table_iter_next (&iter, NULL, (gpointer *) profileptr)) return profile; return NULL; } GHashTable * terminal_app_get_encodings (TerminalApp *app) { return app->encodings; } /** * terminal_app_ensure_encoding: * @app: * @charset: * * Ensures there's a #TerminalEncoding for @charset available. */ TerminalEncoding * terminal_app_ensure_encoding (TerminalApp *app, const char *charset) { TerminalEncoding *encoding; encoding = g_hash_table_lookup (app->encodings, charset); if (encoding == NULL) { encoding = terminal_encoding_new (charset, _("User Defined"), TRUE, TRUE /* scary! */); g_hash_table_insert (app->encodings, (gpointer) terminal_encoding_get_id (encoding), encoding); } return encoding; } /** * terminal_app_get_active_encodings: * * Returns: a newly allocated list of newly referenced #TerminalEncoding objects. */ GSList* terminal_app_get_active_encodings (TerminalApp *app) { GSList *list = NULL; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, app->encodings); while (g_hash_table_iter_next (&iter, &key, &value)) { TerminalEncoding *encoding = (TerminalEncoding *) value; if (!encoding->is_active) continue; list = g_slist_prepend (list, terminal_encoding_ref (encoding)); } return g_slist_sort (list, (GCompareFunc) compare_encodings); } void terminal_app_save_config (TerminalApp *app, GKeyFile *key_file) { GList *lw; guint n = 0; GPtrArray *window_names_array; char **window_names; gsize len; g_key_file_set_comment (key_file, NULL, NULL, "Written by " PACKAGE_STRING, NULL); g_key_file_set_integer (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_VERSION, TERMINAL_CONFIG_VERSION); g_key_file_set_integer (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_COMPAT_VERSION, TERMINAL_CONFIG_COMPAT_VERSION); window_names_array = g_ptr_array_sized_new (g_list_length (app->windows) + 1); for (lw = app->windows; lw != NULL; lw = lw->next) { TerminalWindow *window = TERMINAL_WINDOW (lw->data); char *group; group = g_strdup_printf ("Window%u", n++); g_ptr_array_add (window_names_array, group); terminal_window_save_state (window, key_file, group); } len = window_names_array->len; g_ptr_array_add (window_names_array, NULL); window_names = (char **) g_ptr_array_free (window_names_array, FALSE); g_key_file_set_string_list (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_WINDOWS, (const char * const *) window_names, len); g_strfreev (window_names); } gboolean terminal_app_save_config_file (TerminalApp *app, const char *file_name, GError **error) { GKeyFile *key_file; char *data; gsize len; gboolean result; key_file = g_key_file_new (); terminal_app_save_config (app, key_file); data = g_key_file_to_data (key_file, &len, NULL); result = g_file_set_contents (file_name, data, len, error); g_free (data); return result; }