diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:02:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:02:09 +0000 |
commit | 0f7ab3dc0ed5cfddfc3002992f0525756b6b670e (patch) | |
tree | 4f520e757f99f136e01abe1dd2e0d1fad5c3cd11 /src/terminal-options.cc | |
parent | Initial commit. (diff) | |
download | gnome-terminal-0f7ab3dc0ed5cfddfc3002992f0525756b6b670e.tar.xz gnome-terminal-0f7ab3dc0ed5cfddfc3002992f0525756b6b670e.zip |
Adding upstream version 3.46.8.upstream/3.46.8upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/terminal-options.cc')
-rw-r--r-- | src/terminal-options.cc | 1743 |
1 files changed, 1743 insertions, 0 deletions
diff --git a/src/terminal-options.cc b/src/terminal-options.cc new file mode 100644 index 0000000..6c13955 --- /dev/null +++ b/src/terminal-options.cc @@ -0,0 +1,1743 @@ +/* + * Copyright © 2001, 2002 Havoc Pennington + * Copyright © 2002 Red Hat, Inc. + * Copyright © 2002 Sun Microsystems + * Copyright © 2003 Mariano Suarez-Alvarez + * Copyright © 2008, 2017 Christian Persch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gprintf.h> + +#include "terminal-options.hh" +#include "terminal-client-utils.hh" +#include "terminal-defines.hh" +#include "terminal-schemas.hh" +#include "terminal-screen.hh" +#include "terminal-app.hh" +#include "terminal-util.hh" +#include "terminal-version.hh" +#include "terminal-libgsystem.hh" +#include "terminal-settings-utils.hh" + +static int verbosity = 1; + +void +terminal_fprintf (FILE* fp, + int verbosity_level, + const char* format, + ...) +{ + if (verbosity < verbosity_level) + return; + + va_list args; + va_start(args, format); + gs_free char *str = g_strdup_vprintf(format, args); + va_end(args); + + gs_strfreev char **lines = g_strsplit_set(str, "\n\r", -1); + for (gsize i = 0; lines[i]; ++i) { + if (lines[i][0] != '\0') + g_fprintf(fp, "# %s\n", lines[i]); + } +} + +static TerminalVerbosity +verbosity_from_log_level (GLogLevelFlags log_level) +{ + guint level = log_level & G_LOG_LEVEL_MASK; + TerminalVerbosity res; + level = level & ~(level - 1); /* extract the highest bit */ + switch (level) { + case G_LOG_LEVEL_DEBUG: + res = TERMINAL_VERBOSITY_DEBUG; + break; + case G_LOG_LEVEL_INFO: + res = TERMINAL_VERBOSITY_DETAIL; + break; + default: + /* better display than lose important messages */ + res = TERMINAL_VERBOSITY_NORMAL; + } + return res; +} + +/* Need to install a special log writer so we never output + * anything without the '# ' prepended, in case --print-environment + * is used. + * + * FIXME: Until issue glib#2087 is fixed, apply a simple log level filter + * to prevent spamming dconf (and other) debug messages to stderr, + * see issue gnome-terminal#42. + */ +GLogWriterOutput +terminal_log_writer (GLogLevelFlags log_level, + const GLogField *fields, + gsize n_fields, + gpointer user_data) +{ +#if GLIB_CHECK_VERSION(2, 68, 0) + char const* domain = nullptr; + for (auto i = gsize{0}; i < n_fields; i++) { + if (g_str_equal(fields[i].key, "GLIB_DOMAIN")) { + domain = (char const*)fields[i].value; + break; + } + } + if (g_log_writer_default_would_drop(log_level, domain)) + return G_LOG_WRITER_HANDLED; +#endif /* glib 2.68 */ + + TerminalVerbosity level = verbosity_from_log_level(log_level); + for (gsize i = 0; i < n_fields; i++) { + if (g_str_equal (fields[i].key, "MESSAGE")) + terminal_fprintf (stderr, level, "%s\n", (const char*)fields[i].value); + } + + return G_LOG_WRITER_HANDLED; +} + +static GOptionContext *get_goption_context (TerminalOptions *options); + +static void +terminal_options_ensure_schema_source(TerminalOptions* options) +{ + if (options->schema_source) + return; + + options->schema_source = terminal_g_settings_schema_source_get_default(); +} + +static TerminalSettingsList * +terminal_options_ensure_profiles_list (TerminalOptions *options) +{ + if (options->profiles_list == nullptr) { + terminal_options_ensure_schema_source(options); + options->profiles_list = terminal_profiles_list_new(nullptr /* default backend */, + options->schema_source); + } + + return options->profiles_list; +} + +static char * +terminal_util_key_file_get_string_unescape (GKeyFile *key_file, + const char *group, + const char *key, + GError **error) +{ + char *escaped, *unescaped; + + escaped = g_key_file_get_string (key_file, group, key, error); + if (!escaped) + return nullptr; + + unescaped = g_strcompress (escaped); + g_free (escaped); + + return unescaped; +} + +static char ** +terminal_util_key_file_get_argv (GKeyFile *key_file, + const char *group, + const char *key, + int *argc, + GError **error) +{ + char **argv; + char *flat; + gboolean retval; + + flat = terminal_util_key_file_get_string_unescape (key_file, group, key, error); + if (!flat) + return nullptr; + + retval = g_shell_parse_argv (flat, argc, &argv, error); + g_free (flat); + + if (retval) + return argv; + + return nullptr; +} + +static InitialTab* +initial_tab_new (char *profile /* adopts */) +{ + InitialTab *it; + + it = g_slice_new (InitialTab); + + it->profile = profile; + it->exec_argv = nullptr; + it->title = nullptr; + it->working_dir = nullptr; + it->zoom = 1.0; + it->zoom_set = FALSE; + it->active = FALSE; + it->fd_list = nullptr; + it->fd_array = nullptr; + + return it; +} + +static void +initial_tab_free (InitialTab *it) +{ + g_free (it->profile); + g_strfreev (it->exec_argv); + g_free (it->title); + g_free (it->working_dir); + g_clear_object (&it->fd_list); + if (it->fd_array) + g_array_unref (it->fd_array); + g_slice_free (InitialTab, it); +} + +static InitialWindow* +initial_window_new (guint source_tag) +{ + InitialWindow *iw; + + iw = g_slice_new0 (InitialWindow); + iw->source_tag = source_tag; + + return iw; +} + +static void +initial_window_free (InitialWindow *iw) +{ + g_list_free_full (iw->tabs, (GDestroyNotify) initial_tab_free); + g_free (iw->geometry); + g_free (iw->role); + g_slice_free (InitialWindow, iw); +} + +static void +apply_window_defaults (TerminalOptions *options, + InitialWindow *iw) +{ + if (options->default_role) + { + iw->role = options->default_role; + options->default_role = nullptr; + } + + if (iw->geometry == nullptr) + iw->geometry = g_strdup (options->default_geometry); + + if (options->default_window_menubar_forced) + { + iw->force_menubar_state = TRUE; + iw->menubar_state = options->default_window_menubar_state; + + options->default_window_menubar_forced = FALSE; + } + + iw->start_fullscreen |= options->default_fullscreen; + iw->start_maximized |= options->default_maximize; +} + +static void +apply_tab_defaults (TerminalOptions *options, + InitialTab *it) +{ + it->wait = options->default_wait; +} + +static InitialWindow* +add_new_window (TerminalOptions *options, + char *profile /* adopts */, + gboolean implicit_if_first_window) +{ + InitialWindow *iw; + InitialTab *it; + + iw = initial_window_new (0); + iw->implicit_first_window = (options->initial_windows == nullptr) && implicit_if_first_window; + apply_window_defaults (options, iw); + + it = initial_tab_new (profile); + + /* If this is an implicit first window, the new tab should be active */ + if (iw->implicit_first_window) + it->active = TRUE; + + iw->tabs = g_list_prepend (nullptr, it); + apply_tab_defaults (options, it); + + options->initial_windows = g_list_append (options->initial_windows, iw); + return iw; +} + +static InitialWindow* +ensure_top_window (TerminalOptions *options, + gboolean implicit_if_first_window) +{ + InitialWindow *iw; + + if (options->initial_windows == nullptr) + iw = add_new_window (options, nullptr /* profile */, implicit_if_first_window); + else + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + + g_assert_nonnull (iw->tabs); + + return iw; +} + +static InitialTab* +ensure_top_tab (TerminalOptions *options) +{ + InitialWindow *iw; + InitialTab *it; + + iw = ensure_top_window (options, TRUE); + + g_assert_nonnull (iw->tabs); + + it = (InitialTab*)g_list_last (iw->tabs)->data; + + return it; +} + +/* handle deprecated command line options */ + +static void +deprecated_option_warning (const gchar *option_name) +{ + terminal_printerr (_("Option “%s” is deprecated and might be removed in a later version of gnome-terminal."), + option_name); + terminal_printerr ("\n"); +} + +static void +deprecated_command_option_warning (const char *option_name) +{ + deprecated_option_warning (option_name); + + /* %s is being replaced with "-- " (without quotes), which must be used literally, not translatable */ + terminal_printerr (_("Use “%s” to terminate the options and put the command line to execute after it."), "-- "); + terminal_printerr ("\n"); +} + +static gboolean +unsupported_option_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + terminal_printerr (_("Option “%s” is no longer supported in this version of gnome-terminal."), + option_name); + terminal_printerr ("\n"); + return TRUE; /* we do not want to bail out here but continue */ +} + +static gboolean +unsupported_option_fatal_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_UNKNOWN_OPTION, + _("Option “%s” is no longer supported in this version of gnome-terminal."), + option_name); + return FALSE; +} + + +static gboolean G_GNUC_NORETURN +option_version_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + terminal_print ("GNOME Terminal %s using VTE %u.%u.%u %s\n", + VERSION, + vte_get_major_version (), + vte_get_minor_version (), + vte_get_micro_version (), + vte_get_features ()); + exit (EXIT_SUCCESS); +} + +static gboolean +option_verbosity_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + if (g_str_equal (option_name, "--quiet") || g_str_equal (option_name, "-q")) + verbosity = 0; + else + verbosity++; + + return TRUE; +} + +static gboolean +option_app_id_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + if (!g_application_id_is_valid (value)) { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "\"%s\" is not a valid application ID", value); + return FALSE; + } + + g_free (options->server_app_id); + options->server_app_id = g_strdup (value); + + return TRUE; +} + +static gboolean +option_command_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + GError *err = nullptr; + char **exec_argv; + + deprecated_command_option_warning (option_name); + + if (!g_shell_parse_argv (value, nullptr, &exec_argv, &err)) + { + g_set_error(error, + G_OPTION_ERROR, + G_OPTION_ERROR_BAD_VALUE, + _("Argument to “%s” is not a valid command: %s"), + "--command/-e", + err->message); + g_error_free (err); + return FALSE; + } + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + + g_strfreev (it->exec_argv); + it->exec_argv = exec_argv; + } + else + { + g_strfreev (options->exec_argv); + options->exec_argv = exec_argv; + } + + return TRUE; +} + +static gboolean +option_profile_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + char *profile; + + profile = terminal_profiles_list_dup_uuid_or_name (terminal_options_ensure_profiles_list (options), + value, error); + if (profile == nullptr) + { + terminal_printerr ("Profile '%s' specified but not found. Attempting to fall back " + "to the default profile.\n", value); + g_clear_error (error); + profile = terminal_profiles_list_dup_uuid_or_name (terminal_options_ensure_profiles_list (options), + nullptr, error); + } + + if (profile == nullptr) + return FALSE; + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + + g_free (it->profile); + it->profile = profile; + } + else + { + g_free (options->default_profile); + options->default_profile = profile; + } + + return TRUE; +} + +static gboolean +option_profile_id_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + char *profile; + + profile = terminal_profiles_list_dup_uuid (terminal_options_ensure_profiles_list (options), + value, error); + if (profile == nullptr) + return FALSE; + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + + g_free (it->profile); + it->profile = profile; + } + else + { + g_free (options->default_profile); + options->default_profile = profile; + } + + return TRUE; +} + + +static gboolean +option_window_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + char *profile; + + if (value != nullptr) { + profile = terminal_profiles_list_dup_uuid_or_name (terminal_options_ensure_profiles_list (options), + value, error); + + if (value && profile == nullptr) { + terminal_printerr ("Profile '%s' specified but not found. Attempting to fall back " + "to the default profile.\n", value); + g_clear_error (error); + profile = terminal_profiles_list_dup_uuid_or_name (terminal_options_ensure_profiles_list (options), + nullptr, error); + } + + if (profile == nullptr) + return FALSE; + } else + profile = nullptr; + + add_new_window (options, profile /* adopts */, FALSE); + + return TRUE; +} + +static gboolean +option_tab_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + char *profile; + + if (value != nullptr) { + profile = terminal_profiles_list_dup_uuid_or_name (terminal_options_ensure_profiles_list (options), + value, error); + if (profile == nullptr) + return FALSE; + } else + profile = nullptr; + + if (options->initial_windows) + { + InitialWindow *iw; + + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + iw->tabs = g_list_append (iw->tabs, initial_tab_new (profile /* adopts */)); + } + else + add_new_window (options, profile /* adopts */, TRUE); + + return TRUE; +} + +static gboolean +option_role_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + InitialWindow *iw; + + if (options->initial_windows) + { + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + iw->role = g_strdup (value); + } + else if (!options->default_role) + options->default_role = g_strdup (value); + else + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "%s", _("Two roles given for one window")); + return FALSE; + } + + return TRUE; +} + +static gboolean +option_show_menubar_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + InitialWindow *iw; + + if (options->initial_windows) + { + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + if (iw->force_menubar_state && iw->menubar_state == TRUE) + { + terminal_printerr_detail (_("“%s” option given twice for the same window\n"), + "--show-menubar"); + + return TRUE; + } + + iw->force_menubar_state = TRUE; + iw->menubar_state = TRUE; + } + else + { + options->default_window_menubar_forced = TRUE; + options->default_window_menubar_state = TRUE; + } + + return TRUE; +} + +static gboolean +option_hide_menubar_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + InitialWindow *iw; + + if (options->initial_windows) + { + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + + if (iw->force_menubar_state && iw->menubar_state == FALSE) + { + terminal_printerr_detail (_("“%s” option given twice for the same window\n"), + "--hide-menubar"); + return TRUE; + } + + iw->force_menubar_state = TRUE; + iw->menubar_state = FALSE; + } + else + { + options->default_window_menubar_forced = TRUE; + options->default_window_menubar_state = FALSE; + } + + return TRUE; +} + +static gboolean +option_maximize_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + InitialWindow *iw; + + if (options->initial_windows) + { + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + iw->start_maximized = TRUE; + } + else + options->default_maximize = TRUE; + + return TRUE; +} + +static gboolean +option_fullscreen_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + if (options->initial_windows) + { + InitialWindow *iw; + + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + iw->start_fullscreen = TRUE; + } + else + options->default_fullscreen = TRUE; + + return TRUE; +} + +static gboolean +option_geometry_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + if (options->initial_windows) + { + InitialWindow *iw; + + iw = (InitialWindow*)g_list_last (options->initial_windows)->data; + iw->geometry = g_strdup (value); + } + else + options->default_geometry = g_strdup (value); + + return TRUE; +} + +static gboolean +option_load_config_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + GFile *file; + char *config_file; + GKeyFile *key_file; + gboolean result; + + file = g_file_new_for_commandline_arg (value); + config_file = g_file_get_path (file); + g_object_unref (file); + + key_file = g_key_file_new (); + result = g_key_file_load_from_file (key_file, config_file, GKeyFileFlags(0), error) && + terminal_options_merge_config (options, key_file, + strcmp (option_name, "load-config") == 0 ? SOURCE_DEFAULT : SOURCE_SESSION, + error); + g_key_file_free (key_file); + g_free (config_file); + + return result; +} + +static gboolean +option_title_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + + g_free (it->title); + it->title = g_strdup (value); + } + else + { + g_free (options->default_title); + options->default_title = g_strdup (value); + } + + return TRUE; +} + +static gboolean +option_working_directory_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + + g_free (it->working_dir); + it->working_dir = g_strdup (value); + } + else + { + g_free (options->default_working_dir); + options->default_working_dir = g_strdup (value); + } + + return TRUE; +} + +static gboolean +option_wait_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + + g_free (it->working_dir); + it->wait = TRUE; + } + else + { + options->default_wait = TRUE; + } + + return TRUE; +} + +static gboolean +option_pass_fd_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + + errno = 0; + char *end; + gint64 v = g_ascii_strtoll (value, &end, 10); + if (errno || end == value || v == -1 || v < G_MININT || v > G_MAXINT) { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Failed to parse \"%s\" as file descriptor number", + value); + return FALSE; + } + + int fd = v; + if (fd == STDIN_FILENO || + fd == STDOUT_FILENO || + fd == STDERR_FILENO) { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "FD passing of %s is not supported", + fd == STDIN_FILENO ? "stdin" : fd == STDOUT_FILENO ? "stdout" : "stderr"); + return FALSE; + } + + InitialTab *it = ensure_top_tab (options); + if (it->fd_list == nullptr) + it->fd_list = g_unix_fd_list_new (); + if (it->fd_array == nullptr) + it->fd_array = g_array_sized_new (FALSE /* zero terminate */, + TRUE /* clear */, + sizeof (PassFdElement), + 8 /* that should be plenty */); + + + for (guint i = 0; i < it->fd_array->len; i++) { + PassFdElement *e = &g_array_index (it->fd_array, PassFdElement, i); + if (e->fd == fd) { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + _("Cannot pass FD %d twice"), fd); + return FALSE; + } + } + + int idx = g_unix_fd_list_append (it->fd_list, fd, error); + if (idx == -1) { + g_prefix_error (error, "%d: ", fd); + return FALSE; + } + + PassFdElement e = { idx, fd }; + g_array_append_val (it->fd_array, e); + +#if 0 + if (fd == STDOUT_FILENO || + fd == STDERR_FILENO) + verbosity = 0; + if (fd == STDIN_FILENO) + it->wait = TRUE; +#endif + + return TRUE; +} + +static gboolean +option_active_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + InitialTab *it; + + it = ensure_top_tab (options); + it->active = TRUE; + + return TRUE; +} + +static gboolean +option_zoom_callback (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + double zoom; + char *end; + + /* Try reading a locale-style double first, in case it was + * typed by a person, then fall back to ascii_strtod (we + * always save session in C locale format) + */ + end = nullptr; + errno = 0; + zoom = g_strtod (value, &end); + if (end == nullptr || *end != '\0') + { + g_set_error (error, + G_OPTION_ERROR, + G_OPTION_ERROR_BAD_VALUE, + _("“%s” is not a valid zoom factor"), + value); + return FALSE; + } + + if (zoom < (TERMINAL_SCALE_MINIMUM + 1e-6)) + { + terminal_printerr (_("Zoom factor “%g” is too small, using %g\n"), + zoom, + TERMINAL_SCALE_MINIMUM); + zoom = TERMINAL_SCALE_MINIMUM; + } + + if (zoom > (TERMINAL_SCALE_MAXIMUM - 1e-6)) + { + terminal_printerr (_("Zoom factor “%g” is too large, using %g\n"), + zoom, + TERMINAL_SCALE_MAXIMUM); + zoom = TERMINAL_SCALE_MAXIMUM; + } + + if (options->initial_windows) + { + InitialTab *it = ensure_top_tab (options); + it->zoom = zoom; + it->zoom_set = TRUE; + } + else + { + options->zoom = zoom; + options->zoom_set = TRUE; + } + + return TRUE; +} + +/* Evaluation of the arguments given to the command line options */ +static gboolean +digest_options_callback (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + TerminalOptions *options = (TerminalOptions*)data; + InitialTab *it; + + if (options->execute) + { + if (options->exec_argv == nullptr) + { + g_set_error (error, + G_OPTION_ERROR, + G_OPTION_ERROR_BAD_VALUE, + _("Option “%s” requires specifying the command to run" + " on the rest of the command line"), + "--execute/-x"); + return FALSE; + } + + /* Apply -x/--execute command only to the first tab */ + it = ensure_top_tab (options); + it->exec_argv = options->exec_argv; + options->exec_argv = nullptr; + } + + return TRUE; +} + +static char* +getenv_utf8(char const* env) +{ + auto const value = g_getenv(env); + if (!value || + !value[0] || + !g_utf8_validate(value, -1, nullptr)) + return nullptr; + + return g_strdup(value); +} + +/** + * terminal_options_parse: + * @argcp: (inout) address of the argument count. Changed if any arguments were handled + * @argvp: (inout) address of the argument vector. Any parameters understood by + * the terminal #GOptionContext are removed + * @error: a #GError to fill in + * + * Parses the argument vector *@argvp. + * + * Returns: a new #TerminalOptions containing the windows and tabs to open, + * or %nullptr on error. + */ +TerminalOptions * +terminal_options_parse (int *argcp, + char ***argvp, + GError **error) +{ + TerminalOptions *options; + GOptionContext *context; + gboolean retval; + int i; + char **argv = *argvp; + + options = g_new0 (TerminalOptions, 1); + + options->print_environment = FALSE; + options->default_window_menubar_forced = FALSE; + options->default_window_menubar_state = TRUE; + options->default_fullscreen = FALSE; + options->default_maximize = FALSE; + options->execute = FALSE; + + options->startup_id = getenv_utf8("DESKTOP_STARTUP_ID"); + options->activation_token = getenv_utf8("XDG_ACTIVATION_TOKEN"); + options->display_name = nullptr; + options->initial_windows = nullptr; + options->default_role = nullptr; + options->default_geometry = nullptr; + options->default_title = nullptr; + options->zoom = 1.0; + options->zoom_set = FALSE; + + options->default_working_dir = g_get_current_dir (); + + /* Collect info from gnome-terminal private env vars */ + const char *server_unique_name = g_getenv (TERMINAL_ENV_SERVICE_NAME); + if (server_unique_name != nullptr) { + if (g_dbus_is_unique_name (server_unique_name)) + options->server_unique_name = g_strdup (server_unique_name); + else + terminal_printerr ("Warning: %s set but \"%s\" is not a unique D-Bus name.\n", + TERMINAL_ENV_SERVICE_NAME, + server_unique_name); + } + + const char *parent_screen_object_path = g_getenv (TERMINAL_ENV_SCREEN); + if (parent_screen_object_path != nullptr) { + if (g_variant_is_object_path (parent_screen_object_path)) + options->parent_screen_object_path = g_strdup (parent_screen_object_path); + else + terminal_printerr ("Warning: %s set but \"%s\" is not a valid D-Bus object path.\n", + TERMINAL_ENV_SCREEN, + parent_screen_object_path); + } + + /* The old -x/--execute option is broken, so we need to pre-scan for it. */ + /* We now also support passing the command after the -- switch. */ + options->exec_argv = nullptr; + for (i = 1 ; i < *argcp; ++i) + { + gboolean is_execute; + gboolean is_dashdash; + int j, last; + + is_execute = strcmp (argv[i], "-x") == 0 || strcmp (argv[i], "--execute") == 0; + is_dashdash = strcmp (argv[i], "--") == 0; + + if (!is_execute && !is_dashdash) + continue; + + if (is_execute) + deprecated_command_option_warning (argv[i]); + + options->execute = is_execute; + + /* Skip the switch */ + last = i; + ++i; + if (i == *argcp) + break; /* we'll complain about this later for -x/--execute; it's fine for -- */ + + /* Collect the args, and remove them from argv */ + options->exec_argv = g_new0 (char*, *argcp - i + 1); + for (j = 0; i < *argcp; ++i, ++j) + options->exec_argv[j] = g_strdup (argv[i]); + options->exec_argv[j] = nullptr; + + *argcp = last; + break; + } + + context = get_goption_context (options); + retval = g_option_context_parse (context, argcp, argvp, error); + g_option_context_free (context); + + if (!retval) { + terminal_options_free (options); + return nullptr; + } + +#ifdef GDK_WINDOWING_X11 + /* Do this here so that gdk_display is initialized */ + if (options->startup_id == nullptr) { + options->startup_id = terminal_client_get_fallback_startup_id (); + } +#endif /* X11 */ + + GdkDisplay *display = gdk_display_get_default (); + if (display != nullptr) + options->display_name = g_strdup (gdk_display_get_name (display)); + + /* Sanity check */ + guint wait = 0; + for (GList *lw = options->initial_windows; lw != nullptr; lw = lw->next) { + InitialWindow *iw = (InitialWindow*)lw->data; + for (GList *lt = iw->tabs; lt != nullptr; lt = lt->next) { + InitialTab *it = (InitialTab*)lt->data; + if (it->wait) + wait++; + } + } + + if (wait > 1) { + g_set_error_literal (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + _("Can only use --wait once")); + return FALSE; + } + + options->wait = wait != 0; + return options; +} + +/** + * terminal_options_merge_config: + * @options: + * @key_file: a #GKeyFile containing to merge the options from + * @source_tag: a source_tag to use in new #InitialWindow<!-- -->s + * @error: a #GError to fill in + * + * Merges the saved options from @key_file into @options. + * + * Returns: %TRUE if @key_file was a valid key file containing a stored + * terminal configuration, or %FALSE on error + */ +gboolean +terminal_options_merge_config (TerminalOptions *options, + GKeyFile *key_file, + guint source_tag, + GError **error) +{ + int version, compat_version; + char **groups; + guint i; + gboolean have_error = FALSE; + GList *initial_windows = nullptr; + + if (!g_key_file_has_group (key_file, TERMINAL_CONFIG_GROUP)) + { + g_set_error_literal (error, TERMINAL_OPTION_ERROR, + TERMINAL_OPTION_ERROR_INVALID_CONFIG_FILE, + _("Not a valid terminal config file.")); + return FALSE; + } + + version = g_key_file_get_integer (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_VERSION, nullptr); + compat_version = g_key_file_get_integer (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_COMPAT_VERSION, nullptr); + + if (version <= 0 || + compat_version <= 0 || + compat_version > TERMINAL_CONFIG_COMPAT_VERSION) + { + g_set_error_literal (error, TERMINAL_OPTION_ERROR, + TERMINAL_OPTION_ERROR_INCOMPATIBLE_CONFIG_FILE, + _("Incompatible terminal config file version.")); + return FALSE; + } + + groups = g_key_file_get_string_list (key_file, TERMINAL_CONFIG_GROUP, TERMINAL_CONFIG_PROP_WINDOWS, nullptr, error); + if (!groups) + return FALSE; + + for (i = 0; groups[i]; ++i) + { + const char *window_group = groups[i]; + char *active_terminal; + char **tab_groups; + InitialWindow *iw; + guint j; + + tab_groups = g_key_file_get_string_list (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_TABS, nullptr, error); + if (!tab_groups) + continue; /* no tabs in this window, skip it */ + + iw = initial_window_new (source_tag); + initial_windows = g_list_append (initial_windows, iw); + apply_window_defaults (options, iw); + + active_terminal = g_key_file_get_string (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_ACTIVE_TAB, nullptr); + iw->role = g_key_file_get_string (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_ROLE, nullptr); + iw->geometry = g_key_file_get_string (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_GEOMETRY, nullptr); + iw->start_fullscreen = g_key_file_get_boolean (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_FULLSCREEN, nullptr); + iw->start_maximized = g_key_file_get_boolean (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_MAXIMIZED, nullptr); + if (g_key_file_has_key (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_MENUBAR_VISIBLE, nullptr)) + { + iw->force_menubar_state = TRUE; + iw->menubar_state = g_key_file_get_boolean (key_file, window_group, TERMINAL_CONFIG_WINDOW_PROP_MENUBAR_VISIBLE, nullptr); + } + + for (j = 0; tab_groups[j]; ++j) + { + const char *tab_group = tab_groups[j]; + InitialTab *it; + char *profile; + + profile = g_key_file_get_string (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_PROFILE_ID, nullptr); + it = initial_tab_new (profile /* adopts */); + + iw->tabs = g_list_append (iw->tabs, it); + + if (g_strcmp0 (active_terminal, tab_group) == 0) + it->active = TRUE; + +/* it->width = g_key_file_get_integer (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_WIDTH, nullptr); + it->height = g_key_file_get_integer (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_HEIGHT, nullptr);*/ + it->working_dir = terminal_util_key_file_get_string_unescape (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_WORKING_DIRECTORY, nullptr); + it->title = g_key_file_get_string (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_TITLE, nullptr); + + if (g_key_file_has_key (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_COMMAND, nullptr) && + !(it->exec_argv = terminal_util_key_file_get_argv (key_file, tab_group, TERMINAL_CONFIG_TERMINAL_PROP_COMMAND, nullptr, error))) + { + have_error = TRUE; + break; + } + } + + g_free (active_terminal); + g_strfreev (tab_groups); + + if (have_error) + break; + } + + g_strfreev (groups); + + if (have_error) + { + g_list_free_full (initial_windows, (GDestroyNotify) initial_window_free); + return FALSE; + } + + options->initial_windows = g_list_concat (options->initial_windows, initial_windows); + + return TRUE; +} + +/** + * terminal_options_ensure_window: + * @options: + * + * Ensure that @options will contain at least one window to open. + */ +void +terminal_options_ensure_window (TerminalOptions *options) +{ + terminal_options_ensure_schema_source(options); + gs_unref_object auto global_settings = + terminal_g_settings_new(nullptr, // default backend + options->schema_source, + TERMINAL_SETTING_SCHEMA); + + gs_free char *mode_str = g_settings_get_string (global_settings, + TERMINAL_SETTING_NEW_TERMINAL_MODE_KEY); + + gboolean implicit_if_first_window = g_str_equal (mode_str, "tab"); + ensure_top_window (options, implicit_if_first_window); +} + +/** + * terminal_options_free: + * @options: + * + * Frees @options. + */ +void +terminal_options_free (TerminalOptions *options) +{ + g_list_free_full (options->initial_windows, (GDestroyNotify) initial_window_free); + + g_free (options->default_role); + g_free (options->default_geometry); + g_free (options->default_working_dir); + g_free (options->default_title); + g_free (options->default_profile); + + g_strfreev (options->exec_argv); + + g_free (options->server_unique_name); + g_free (options->parent_screen_object_path); + + g_free (options->display_name); + g_free (options->startup_id); + g_free (options->activation_token); + g_free (options->server_app_id); + + g_free (options->sm_client_id); + g_free (options->sm_config_prefix); + + g_clear_object (&options->profiles_list); + g_clear_pointer (&options->schema_source, g_settings_schema_source_unref); + + g_free (options); +} + +static GOptionContext * +get_goption_context (TerminalOptions *options) +{ + const GOptionEntry global_unique_goptions[] = { + { + "app-id", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_app_id_callback, + "Server application ID", + "ID" + }, + { + "disable-factory", + 0, + G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)unsupported_option_fatal_callback, + N_("Do not register with the activation nameserver, do not re-use an active terminal"), + nullptr + }, + { + "load-config", + 0, + G_OPTION_FLAG_FILENAME, + G_OPTION_ARG_CALLBACK, + (void*)option_load_config_cb, + N_("Load a terminal configuration file"), + N_("FILE") + }, + { + "save-config", + 0, + G_OPTION_FLAG_FILENAME | G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)unsupported_option_callback, + nullptr, nullptr + }, + { + "no-environment", + 0, + 0, + G_OPTION_ARG_NONE, + &options->no_environment, + N_("Do not pass the environment"), + nullptr + }, + { + "preferences", + 0, + 0, + G_OPTION_ARG_NONE, + &options->show_preferences, + N_("Show preferences window"), + nullptr + }, + { + "print-environment", + 'p', + 0, + G_OPTION_ARG_NONE, + &options->print_environment, + N_("Print environment variables to interact with the terminal"), + nullptr + }, + { + "version", + 0, + G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_version_cb, + nullptr, + nullptr + }, + { + "verbose", + 'v', + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_verbosity_cb, + N_("Increase diagnostic verbosity"), + nullptr + }, + { + "quiet", + 'q', + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_verbosity_cb, + N_("Suppress output"), + nullptr + }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + const GOptionEntry global_multiple_goptions[] = { + { + "window", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_window_callback, + N_("Open a new window containing a tab with the default profile"), + nullptr + }, + { + "tab", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_tab_callback, + N_("Open a new tab in the last-opened window with the default profile"), + nullptr + }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + const GOptionEntry window_goptions[] = { + { + "show-menubar", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_show_menubar_callback, + N_("Turn on the menubar"), + nullptr + }, + { + "hide-menubar", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_hide_menubar_callback, + N_("Turn off the menubar"), + nullptr + }, + { + "maximize", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_maximize_callback, + N_("Maximize the window"), + nullptr + }, + { + "full-screen", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_fullscreen_callback, + N_("Full-screen the window"), + nullptr + }, + { + "geometry", + 0, + 0, + G_OPTION_ARG_CALLBACK, + (void*)option_geometry_callback, + N_("Set the window size; for example: 80x24, or 80x24+200+200 (COLSxROWS+X+Y)"), + N_("GEOMETRY") + }, + { + "role", + 0, + 0, + G_OPTION_ARG_CALLBACK, + (void*)option_role_callback, + N_("Set the window role"), + N_("ROLE") + }, + { + "active", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_active_callback, + N_("Set the last specified tab as the active one in its window"), + nullptr + }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + const GOptionEntry terminal_goptions[] = { + { + "command", + 'e', + G_OPTION_FLAG_FILENAME, + G_OPTION_ARG_CALLBACK, + (void*)option_command_callback, + N_("Execute the argument to this option inside the terminal"), + nullptr + }, + { + "profile", + 0, + 0, + G_OPTION_ARG_CALLBACK, + (void*)option_profile_cb, + N_("Use the given profile instead of the default profile"), + N_("PROFILE-NAME") + }, + { + "title", + 't', + 0, + G_OPTION_ARG_CALLBACK, + (void*)option_title_callback, + N_("Set the initial terminal title"), + N_("TITLE") + }, + { + "working-directory", + 0, + G_OPTION_FLAG_FILENAME, + G_OPTION_ARG_CALLBACK, + (void*)option_working_directory_callback, + N_("Set the working directory"), + N_("DIRNAME") + }, + { + "wait", + 0, + G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, + (void*)option_wait_cb, + N_("Wait until the child exits"), + nullptr + }, + { + "fd", + 0, + 0, + G_OPTION_ARG_CALLBACK, + (void*)option_pass_fd_cb, + N_("Forward file descriptor"), + /* FD = file descriptor */ + N_("FD") + }, + { + "zoom", + 0, + 0, + G_OPTION_ARG_CALLBACK, + (void*)option_zoom_callback, + N_("Set the terminal’s zoom factor (1.0 = normal size)"), + N_("ZOOM") + }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + const GOptionEntry internal_goptions[] = { + { + "profile-id", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_profile_id_cb, + nullptr, nullptr + }, + { + "window-with-profile", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_window_callback, + nullptr, nullptr + }, + { + "tab-with-profile", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_tab_callback, + nullptr, nullptr + }, + { + "window-with-profile-internal-id", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_window_callback, + nullptr, nullptr + }, + { + "tab-with-profile-internal-id", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)option_tab_callback, + nullptr, nullptr + }, + { + "default-working-directory", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_FILENAME, + &options->default_working_dir, + nullptr, nullptr, + }, + { + "use-factory", + 0, + G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_CALLBACK, + (void*)unsupported_option_callback, + nullptr, nullptr + }, + { + "startup-id", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, + &options->startup_id, + nullptr, + nullptr + }, + { + "activation-token", + 0, + G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, + &options->activation_token, + nullptr, + nullptr + }, + { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } + }; + + const GOptionEntry smclient_goptions[] = { + { "sm-client-disable", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options->sm_client_disable, nullptr, nullptr }, + { "sm-client-state-file", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, (void*)option_load_config_cb, nullptr, nullptr }, + { "sm-client-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options->sm_client_id, nullptr, nullptr }, + { "sm-disable", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options->sm_client_disable, nullptr, nullptr }, + { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options->sm_config_prefix, nullptr, nullptr }, + { nullptr } + }; + + GOptionContext *context; + GOptionGroup *group; + gs_free char *parameter; + + parameter = g_strdup_printf ("[-- %s …]", _("COMMAND")); + context = g_option_context_new (parameter); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_set_ignore_unknown_options (context, FALSE); + + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + + group = g_option_group_new ("gnome-terminal", + N_("GNOME Terminal Emulator"), + N_("Show GNOME Terminal options"), + options, + nullptr); + g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); + g_option_group_add_entries (group, global_unique_goptions); + g_option_group_add_entries (group, internal_goptions); + g_option_group_set_parse_hooks (group, nullptr, digest_options_callback); + g_option_context_set_main_group (context, group); + + group = g_option_group_new ("terminal", + N_("Options to open new windows or terminal tabs; more than one of these may be specified:"), + N_("Show terminal options"), + options, + nullptr); + g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); + g_option_group_add_entries (group, global_multiple_goptions); + g_option_context_add_group (context, group); + + group = g_option_group_new ("window-options", + N_("Window options; if used before the first --window or --tab argument, sets the default for all windows:"), + N_("Show per-window options"), + options, + nullptr); + g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); + g_option_group_add_entries (group, window_goptions); + g_option_context_add_group (context, group); + + group = g_option_group_new ("terminal-options", + N_("Terminal options; if used before the first --window or --tab argument, sets the default for all terminals:"), + N_("Show per-terminal options"), + options, + nullptr); + g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); + g_option_group_add_entries (group, terminal_goptions); + g_option_context_add_group (context, group); + + group = g_option_group_new ("sm-client", "", "", options, nullptr); + g_option_group_add_entries (group, smclient_goptions); + g_option_context_add_group (context, group); + + return context; +} |