From f1814cb7ecfe6a5cb4f629dcea56db0dec10a242 Mon Sep 17 00:00:00 2001 From: Christian Persch Date: Tue, 1 Nov 2022 20:35:28 +0100 Subject: [PATCH] all: Implement xdg-terminal-exec support Add support to read and write the default terminal as determined by xdg-terminal-exec, as well as UI to make gnome-terminal the default terminal. Fixes: https://gitlab.gnome.org/GNOME/gnome-terminal/-/issues/7942 --- data/meson.build | 8 + data/org.gnome.Terminal.desktop.in | 1 + src/meson.build | 1 + src/org.gnome.Terminal.gschema.xml | 13 +- src/preferences.ui | 33 +++ src/terminal-app.cc | 120 ++++++++-- src/terminal-app.hh | 8 + src/terminal-debug.cc | 1 + src/terminal-debug.hh | 1 + src/terminal-libgsystem.hh | 29 +++ src/terminal-prefs.cc | 25 ++ src/terminal-schemas.hh | 1 + src/terminal-util.cc | 358 +++++++++++++++++++++++++++++ src/terminal-util.hh | 6 + src/terminal-window.cc | 74 ++++++ 15 files changed, 663 insertions(+), 16 deletions(-) diff --git a/data/meson.build b/data/meson.build index a9db7e2f8..cb4c4f64c 100644 --- a/data/meson.build +++ b/data/meson.build @@ -71,6 +71,14 @@ meson.add_install_script( gt_dns_name + '.desktop', ) +# Install a symlink for xdg-terminal-exec +install_symlink( + gt_dns_name + '.desktop', + install_dir: gt_datadir / 'xdg-terminals', + install_tag: 'runtime', + pointing_to: '..' / 'applications' / (gt_dns_name + '.desktop'), +) + # Subdirs subdir('icons') diff --git a/data/org.gnome.Terminal.desktop.in b/data/org.gnome.Terminal.desktop.in index 7533764f2..841deb42a 100644 --- a/data/org.gnome.Terminal.desktop.in +++ b/data/org.gnome.Terminal.desktop.in @@ -11,6 +11,7 @@ StartupNotify=true StartupWMClass=Gnome-terminal SingleMainWindow=false Actions=new-window;preferences; +X-ExecArg=-- [Desktop Action new-window] Name=New Window diff --git a/src/meson.build b/src/meson.build index 7347f3c05..86fa265a6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -164,6 +164,7 @@ util_sources = files( common_cxxflags = version_cxxflags + [ '-DTERMINAL_COMPILATION', + '-DTERM_PREFIX="@0@"'.format(gt_prefix), '-DTERM_BINDIR="@0@"'.format(gt_prefix / gt_bindir), '-DTERM_DATADIR="@0@"'.format(gt_prefix / gt_datadir), '-DTERM_LIBEXECDIR="@0@"'.format(gt_prefix / gt_libexecdir), diff --git a/src/org.gnome.Terminal.gschema.xml b/src/org.gnome.Terminal.gschema.xml index 36a0230e1..882b251a8 100644 --- a/src/org.gnome.Terminal.gschema.xml +++ b/src/org.gnome.Terminal.gschema.xml @@ -704,6 +704,13 @@ Whether new tabs should open next to the current one or at the last position + + + + true + Always check whether GNOME Terminal is the default terminal + + @@ -720,9 +727,9 @@ - - 3 - + + 3 + diff --git a/src/preferences.ui b/src/preferences.ui index d1aca7b2c..ca58c6566 100644 --- a/src/preferences.ui +++ b/src/preferences.ui @@ -494,6 +494,39 @@ 3 + + + _Always check if default terminal + False + True + True + False + True + 0 + True + + + False + True + 4 + + + + + _Set as default terminal + True + True + False + True + False + start + + + False + True + 5 + + diff --git a/src/terminal-app.cc b/src/terminal-app.cc index f672e4ab5..70933a681 100644 --- a/src/terminal-app.cc +++ b/src/terminal-app.cc @@ -90,6 +90,8 @@ enum { PROP_SETTINGS_BACKEND = 1, + PROP_IS_DEFAULT_TERMINAL, + PROP_ASK_DEFAULT_TERMINAL, }; /* @@ -145,8 +147,11 @@ struct _TerminalApp int n_clipboard_targets; GWeakRef prefs_process_ref; + #endif /* TERMINAL_SERVER */ + gboolean ask_default; + gboolean xte_is_default; gboolean unified_menu; gboolean use_headerbar; }; @@ -271,15 +276,8 @@ terminal_app_should_use_headerbar (TerminalApp *app) if (set) return use; - const char *desktop = g_getenv ("XDG_CURRENT_DESKTOP"); - if (desktop == nullptr) - return FALSE; - - char **desktops = g_strsplit (desktop, G_SEARCHPATH_SEPARATOR_S, -1); - use = strv_contains_gnome (desktops); - g_strfreev (desktops); - - return use; + gs_strfreev auto desktops = terminal_util_get_desktops(); + return strv_contains_gnome(desktops); } static gboolean @@ -402,11 +400,29 @@ terminal_app_theme_variant_changed_cb (GSettings *settings, /* Submenus for New Terminal per profile, and to change profiles */ +static void +terminal_app_check_default(TerminalApp* app) +{ + // Only do this for the default app ID + gs_free char* app_id = nullptr; + g_object_get(app, "application-id", &app_id, nullptr); + if (!_terminal_debug_on(TERMINAL_DEBUG_DEFAULT) && + !g_str_equal(app_id, TERMINAL_APPLICATION_ID)) + return; + + // Check whether gnome-terminal is the default terminal + // as per XDG-Terminal-Exec. + app->xte_is_default = terminal_util_is_default_terminal(); + + gboolean ask = false; + g_settings_get(app->global_settings, TERMINAL_SETTING_ALWAYS_CHECK_DEFAULT_KEY, "b", &ask); + app->ask_default = (ask != false) && !app->xte_is_default; +} + #ifdef TERMINAL_SERVER static void terminal_app_update_profile_menus (TerminalApp *app); - typedef struct { char *uuid; char *label; @@ -849,6 +865,8 @@ terminal_app_activate (GApplication *application) static void terminal_app_startup (GApplication *application) { + auto const app = TERMINAL_APP(application); + g_application_set_resource_base_path (application, TERMINAL_RESOURCES_PATH_PREFIX); G_APPLICATION_CLASS (terminal_app_parent_class)->startup (application); @@ -876,8 +894,6 @@ terminal_app_startup (GApplication *application) action_entries, G_N_ELEMENTS (action_entries), application); - auto const app = TERMINAL_APP(application); - /* Figure out whether the shell shows the menubar */ gboolean shell_shows_menubar; g_object_get (gtk_settings_get_default (), @@ -898,6 +914,8 @@ terminal_app_startup (GApplication *application) #endif /* TERMINAL_SERVER */ + terminal_app_check_default(app); + _terminal_debug_print (TERMINAL_DEBUG_SERVER, "Startup complete\n"); } @@ -1069,6 +1087,30 @@ terminal_app_finalize (GObject *object) G_OBJECT_CLASS (terminal_app_parent_class)->finalize (object); } +static void +terminal_app_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + auto app = TERMINAL_APP(object); + + switch (prop_id) { + case PROP_SETTINGS_BACKEND: + g_value_set_object(value, app->settings_backend); + break; + case PROP_IS_DEFAULT_TERMINAL: + g_value_set_boolean(value, app->xte_is_default); + break; + case PROP_ASK_DEFAULT_TERMINAL: + g_value_set_boolean(value, app->ask_default); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + static void terminal_app_set_property(GObject* object, guint prop_id, @@ -1081,6 +1123,10 @@ terminal_app_set_property(GObject* object, case PROP_SETTINGS_BACKEND: app->settings_backend = G_SETTINGS_BACKEND(g_value_dup_object(value)); break; + case PROP_ASK_DEFAULT_TERMINAL: + app->ask_default = g_value_get_boolean(value); + break; + case PROP_IS_DEFAULT_TERMINAL: // not writable default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1169,6 +1215,7 @@ terminal_app_class_init (TerminalAppClass *klass) object_class->constructed = terminal_app_constructed; object_class->finalize = terminal_app_finalize; + object_class->get_property = terminal_app_get_property; object_class->set_property = terminal_app_set_property; g_object_class_install_property @@ -1176,10 +1223,26 @@ terminal_app_class_init (TerminalAppClass *klass) PROP_SETTINGS_BACKEND, g_param_spec_object("settings-backend", nullptr, nullptr, G_TYPE_SETTINGS_BACKEND, - GParamFlags(G_PARAM_WRITABLE | + GParamFlags(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property + (object_class, + PROP_IS_DEFAULT_TERMINAL, + g_param_spec_boolean("is-default-terminal", nullptr, nullptr, + false, + GParamFlags(G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property + (object_class, + PROP_ASK_DEFAULT_TERMINAL, + g_param_spec_boolean("ask-default-terminal", nullptr, nullptr, + false, + GParamFlags(G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS))); + g_application_class->activate = terminal_app_activate; g_application_class->startup = terminal_app_startup; #ifdef TERMINAL_SERVER @@ -1573,3 +1636,34 @@ terminal_app_get_dialog_use_headerbar (TerminalApp *app) return dialog_use_header && app->use_headerbar; } + +gboolean +terminal_app_is_default_terminal(TerminalApp* app) +{ + g_return_val_if_fail(TERMINAL_IS_APP(app), false); + return app->xte_is_default; +} + +gboolean +terminal_app_get_ask_default_terminal(TerminalApp* app) +{ + g_return_val_if_fail(TERMINAL_IS_APP(app), false); + return app->ask_default; +} + +void +terminal_app_unset_ask_default_terminal(TerminalApp* app) +{ + g_return_if_fail(TERMINAL_IS_APP(app)); + app->ask_default = false; + g_object_notify(G_OBJECT(app), "ask-default-terminal"); +} + +void +terminal_app_make_default_terminal(TerminalApp* app) +{ + g_return_if_fail(TERMINAL_IS_APP(app)); + terminal_util_make_default_terminal(); + app->xte_is_default = terminal_util_is_default_terminal(); + g_object_notify(G_OBJECT(app), "is-default-terminal"); +} diff --git a/src/terminal-app.hh b/src/terminal-app.hh index 0437a8822..940986497 100644 --- a/src/terminal-app.hh +++ b/src/terminal-app.hh @@ -129,6 +129,14 @@ GSettings *terminal_app_get_gtk_debug_settings (TerminalApp *app); PangoFontDescription *terminal_app_get_system_font (TerminalApp *app); +gboolean terminal_app_is_default_terminal(TerminalApp* app); + +gboolean terminal_app_get_ask_default_terminal(TerminalApp* app); + +void terminal_app_unset_ask_default_terminal(TerminalApp* app); + +void terminal_app_make_default_terminal(TerminalApp* app); + G_END_DECLS #endif /* !TERMINAL_APP_H */ diff --git a/src/terminal-debug.cc b/src/terminal-debug.cc index 61814819d..bf2db7e93 100644 --- a/src/terminal-debug.cc +++ b/src/terminal-debug.cc @@ -39,6 +39,7 @@ _terminal_debug_init(void) { "settings-list", TERMINAL_DEBUG_SETTINGS_LIST }, { "search", TERMINAL_DEBUG_SEARCH }, { "bridge", TERMINAL_DEBUG_BRIDGE }, + { "default", TERMINAL_DEBUG_DEFAULT }, }; _terminal_debug_flags = TerminalDebugFlags(g_parse_debug_string (g_getenv ("GNOME_TERMINAL_DEBUG"), diff --git a/src/terminal-debug.hh b/src/terminal-debug.hh index c1a4113c1..fedc9e4d1 100644 --- a/src/terminal-debug.hh +++ b/src/terminal-debug.hh @@ -36,6 +36,7 @@ typedef enum { TERMINAL_DEBUG_SETTINGS_LIST = 1 << 8, TERMINAL_DEBUG_SEARCH = 1 << 9, TERMINAL_DEBUG_BRIDGE = 1 << 10, + TERMINAL_DEBUG_DEFAULT = 1 << 11, } TerminalDebugFlags; void _terminal_debug_init(void); diff --git a/src/terminal-libgsystem.hh b/src/terminal-libgsystem.hh index e13c5a9cc..86e3c1fd4 100644 --- a/src/terminal-libgsystem.hh +++ b/src/terminal-libgsystem.hh @@ -52,6 +52,7 @@ GS_DEFINE_CLEANUP_FUNCTION0(GArray*, gs_local_array_unref, g_array_unref) GS_DEFINE_CLEANUP_FUNCTION0(GBytes*, gs_local_bytes_unref, g_bytes_unref) GS_DEFINE_CLEANUP_FUNCTION0(GChecksum*, gs_local_checksum_free, g_checksum_free) GS_DEFINE_CLEANUP_FUNCTION0(GDateTime*, gs_local_date_time_unref, g_date_time_unref) +GS_DEFINE_CLEANUP_FUNCTION0(GDir*, gs_local_dir_close, g_dir_close) GS_DEFINE_CLEANUP_FUNCTION0(GError*, gs_local_free_error, g_error_free) GS_DEFINE_CLEANUP_FUNCTION0(GHashTable*, gs_local_hashtable_unref, g_hash_table_unref) GS_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, gs_local_key_file_unref, g_key_file_unref) @@ -274,6 +275,34 @@ static inline void gs_local_gstring_free (void *v) \ */ #define gs_free_option_context __attribute__ ((cleanup(gs_local_option_context_free))) +/** + * gs_close_dir: + * + * Call g_dir_close() on a variable location when it goes out of + * scope. + + */ +#define gs_close_dir __attribute__ ((cleanup(gs_local_dir_close))) + +static inline void gs_local_fd_close (void *v) +{ + auto fd = *reinterpret_cast(v); + if (fd != -1) { + auto const errsv = errno; + close(fd); + errno = errsv; + } +} + +/** + * gs_free_close: + * + * Call close() on a variable location when it goes out of + * scope. + + */ +#define gs_close_fd __attribute__ ((cleanup(gs_local_fd_close))) + G_END_DECLS #endif diff --git a/src/terminal-prefs.cc b/src/terminal-prefs.cc index 1163f739c..437c58af8 100644 --- a/src/terminal-prefs.cc +++ b/src/terminal-prefs.cc @@ -714,6 +714,13 @@ prefs_dialog_destroy_cb (GtkWidget *widget, g_free (data); } +static void +make_default_button_clicked_cb(GtkWidget* button, + PrefData* data) +{ + terminal_app_make_default_terminal(terminal_app_get()); +} + void terminal_prefs_show_preferences(GSettings* profile, char const* widget_name, @@ -729,6 +736,7 @@ terminal_prefs_show_preferences(GSettings* profile, GtkWidget *new_tab_position_combo; GtkWidget *close_button, *help_button; GtkWidget *content_box, *general_frame, *keybindings_frame; + GtkWidget *always_check_default_button, *make_default_button; GSettings *settings; const GActionEntry action_entries[] = { @@ -764,6 +772,8 @@ terminal_prefs_show_preferences(GSettings* profile, "disable-shortcuts-checkbutton", &disable_shortcuts_button, "disable-menu-accel-checkbutton", &disable_menu_accel_button, "new-tab-position-combobox", &new_tab_position_combo, + "always-check-default-checkbutton", &always_check_default_button, + "make-default-button", &make_default_button, "accelerators-treeview", &tree_view, "the-stack", &data->stack, "the-listbox", &data->listbox, @@ -856,6 +866,21 @@ terminal_prefs_show_preferences(GSettings* profile, "active", GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET)); + g_settings_bind(settings, + TERMINAL_SETTING_ALWAYS_CHECK_DEFAULT_KEY, + always_check_default_button, + "active", + GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET)); + + g_signal_connect(make_default_button, "clicked", + G_CALLBACK(make_default_button_clicked_cb), data); + + g_object_bind_property(app, "is-default-terminal", + make_default_button, "sensitive", + GBindingFlags(G_BINDING_DEFAULT | + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN)); + /* Shortcuts page */ g_settings_bind (settings, diff --git a/src/terminal-schemas.hh b/src/terminal-schemas.hh index b146ba82e..61f1305e4 100644 --- a/src/terminal-schemas.hh +++ b/src/terminal-schemas.hh @@ -93,6 +93,7 @@ G_BEGIN_DECLS #define TERMINAL_SETTING_TAB_POSITION_KEY "tab-position" #define TERMINAL_SETTING_THEME_VARIANT_KEY "theme-variant" #define TERMINAL_SETTING_UNIFIED_MENU_KEY "unified-menu" +#define TERMINAL_SETTING_ALWAYS_CHECK_DEFAULT_KEY "always-check-default-terminal" #define TERMINAL_SETTINGS_LIST_LIST_KEY "list" #define TERMINAL_SETTINGS_LIST_DEFAULT_KEY "default" diff --git a/src/terminal-util.cc b/src/terminal-util.cc index b430efca4..fcdd939a5 100644 --- a/src/terminal-util.cc +++ b/src/terminal-util.cc @@ -21,10 +21,12 @@ #include "config.h" +#include #include #include #include #include +#include #include #include #include @@ -40,6 +42,8 @@ #include "terminal-accels.hh" #include "terminal-app.hh" #include "terminal-client-utils.hh" +#include "terminal-debug.hh" +#include "terminal-defines.hh" #include "terminal-intl.hh" #include "terminal-util.hh" #include "terminal-version.hh" @@ -1570,3 +1574,357 @@ terminal_util_check_envv(char const* const* strv) return TRUE; } + +char** +terminal_util_get_desktops(void) +{ + auto const desktop = g_getenv("XDG_CURRENT_DESKTOP"); + if (!desktop) + return nullptr; + + return g_strsplit(desktop, G_SEARCHPATH_SEPARATOR_S, -1); +} + +#define XTE_CONFIG_DIRNAME "xdg-terminals" +#define XTE_CONFIG_FILENAME "xdg-terminals.list" + +#define NEWLINE '\n' +#define DOT_DESKTOP ".desktop" +#define TERMINAL_DESKTOP_FILENAME TERMINAL_APPLICATION_ID DOT_DESKTOP + +static bool +xte_data_check_one(char const* file, + bool full) +{ + if (!g_file_test(file, G_FILE_TEST_EXISTS)) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Desktop file \"%s\" does not exist.\n", + file); + return false; + } + + if (!full) + return true; + + gs_free_error GError* error = nullptr; + gs_unref_key_file auto kf = g_key_file_new(); + if (!g_key_file_load_from_file(kf, + file, + GKeyFileFlags(G_KEY_FILE_NONE), + &error)) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Failed to load \"%s\" as keyfile: %s\n", + file, error->message); + + return false; + } + + if (!g_key_file_has_group(kf, G_KEY_FILE_DESKTOP_GROUP)) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Keyfile file \"%s\" is not a desktop file.\n", + file); + return false; + } + + // As per the XDG desktop entry spec, the TryExec key contains the name + // of an executable that can be used to determine if the programme is + // actually present. + gs_free auto try_exec = g_key_file_get_string(kf, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, + nullptr); + if (!try_exec) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Desktop file \"%s\" has no TryExec field.\n", + file); + + return false; + } + + gs_free auto exec_path = g_find_program_in_path(try_exec); + auto const exists = exec_path != nullptr; + + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Desktop file \"%s\" is %sinstalled.\n", + file, exists ? "" : "not "); + + return exists; +} + +static bool +xte_data_check(char const* name, + bool full) +{ + gs_free auto user_path = g_build_filename(g_get_user_data_dir(), + XTE_CONFIG_DIRNAME, + name, + nullptr); + if (xte_data_check_one(user_path, full)) + return true; + + gs_free auto local_path = g_build_filename(TERM_PREFIX, "local", "share", + XTE_CONFIG_DIRNAME, + name, + nullptr); + if (xte_data_check_one(local_path, full)) + return true; + + gs_free auto sys_path = g_build_filename(TERM_DATADIR, + XTE_CONFIG_DIRNAME, + name, + nullptr); + if (xte_data_check_one(sys_path, full)) + return true; + + return false; +} + +static bool +xte_data_ensure(void) +{ + if (xte_data_check(TERMINAL_DESKTOP_FILENAME, false)) + return true; + + // If we get here, there wasn't a desktop file in any of the paths. Install + // a symlink to the system-installed desktop file into the user path. + + gs_free auto user_dir = g_build_filename(g_get_user_data_dir(), + XTE_CONFIG_DIRNAME, + nullptr); + if (g_mkdir_with_parents(user_dir, 0700) != 0 && + errno != EEXIST) { + auto const errsv = errno; + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Failed to create directory %s: %s\n", + user_dir, g_strerror(errsv)); + return false; + } + + gs_free auto link_path = g_build_filename(user_dir, + TERMINAL_DESKTOP_FILENAME, + nullptr); + gs_free auto target_path = g_build_filename(TERM_DATADIR, + "applications", + TERMINAL_DESKTOP_FILENAME, + nullptr); + + auto const r = symlink(target_path, link_path); + if (r != -1) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Installed symlink %s -> %s\n", + link_path, target_path); + + } else { + auto const errsv = errno; + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Failed to create symlink %s: %s\n", + link_path, g_strerror(errsv)); + } + + return r != -1; +} + +static char** +xte_config_read(char const* path, + GError** error) +{ + gs_close_fd auto fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd == -1) + return nullptr; + + // This is a small config file, so shouldn't be any bigger than this. + // If it is bigger, we'll discard the rest. That's why we're not using + // g_file_get_contents() here. + char buf[8192]; + auto r = ssize_t{}; + do { + r = read(fd, buf, sizeof(buf) - 1); // reserve one byte in buf + } while (r == -1 && errno == EINTR); + if (r < 0) + return nullptr; + + buf[r] = '\0'; // NUL terminator; note that r < sizeof(buf) + + auto lines = g_strsplit_set(buf, "\r\n", -1); + if (!lines) + return nullptr; + + for (auto i = 0; lines[i]; ++i) + lines[i] = g_strstrip(lines[i]); + + return lines; +} + +static bool +xte_config_rewrite(char const* path) +{ + gs_free_gstring auto str = g_string_sized_new(1024); + g_string_append(str, TERMINAL_DESKTOP_FILENAME); + g_string_append_c(str, NEWLINE); + + gs_strfreev auto lines = xte_config_read(path, nullptr); + if (lines) { + for (auto i = 0; lines[i]; ++i) { + if (lines[i][0] == '\0') + continue; + if (strcmp(lines[i], TERMINAL_DESKTOP_FILENAME) == 0) + continue; + + g_string_append(str, lines[i]); + g_string_append_c(str, NEWLINE); + } + } + + gs_free_error GError* error = nullptr; + auto const r = g_file_set_contents(path, str->str, str->len, &error); + if (!r) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Failed to rewrite XTE config %s: %s\n", + path, error->message); + } + + return r; +} + +static void +xte_config_rewrite(void) +{ + auto const user_dir = g_get_user_config_dir(); + if (g_mkdir_with_parents(user_dir, 0700) != 0 && + errno != EEXIST) { + auto const errsv = errno; + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Failed to create directory %s: %s\n", + user_dir, g_strerror(errsv)); + // Nothing to do if we can't even create the directory + return; + } + + // Install as default for all current desktops + gs_strfreev auto desktops = terminal_util_get_desktops(); + if (desktops) { + for (auto i = 0; desktops[i]; ++i) { + gs_free auto name = g_strdup_printf("%s-" XTE_CONFIG_FILENAME, + desktops[i]); + gs_free auto path = g_build_filename(user_dir, name, nullptr); + + xte_config_rewrite(path); + } + } + + // Install as non-desktop specific default too + gs_free auto path = g_build_filename(user_dir, XTE_CONFIG_FILENAME, nullptr); + xte_config_rewrite(path); +} + +static bool +xte_config_is_foreign(char const* name) +{ + return !g_str_equal(name, TERMINAL_DESKTOP_FILENAME); +} + +static char* +xte_config_get_default(char const* path) +{ + gs_strfreev auto lines = xte_config_read(path, nullptr); + if (!lines) + return nullptr; + + // A terminal is the default if it's the first non-comment line in the file + for (auto i = 0; lines[i]; ++i) { + auto const line = lines[i]; + if (!line[0] || line[0] == '#') + continue; + + // If a foreign terminal is default, check whether it is actually installed. + // (We always ensure our own desktop file exists.) + if (xte_config_is_foreign(line) && + !xte_data_check(line, true)) { + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "Default entry \"%s\" from config \"%s\" is not installed, skipping.\n", + line, path); + return nullptr; + } + + return g_strdup(line); + } + + return nullptr; +} + +static char* +xte_config_get_default(void) +{ + auto const user_dir = g_get_user_config_dir(); + gs_strfreev auto desktops = terminal_util_get_desktops(); + if (desktops) { + for (auto i = 0; desktops[i]; ++i) { + gs_free auto name = g_strdup_printf("%s-" XTE_CONFIG_FILENAME, + desktops[i]); + gs_free auto path = g_build_filename(user_dir, name, nullptr); + if (auto term = xte_config_get_default(path)) + return term; + } + } + + gs_free auto user_path = g_build_filename(user_dir, XTE_CONFIG_FILENAME, nullptr); + if (auto term = xte_config_get_default(user_path)) + return term; + + if (desktops) { + for (auto i = 0; desktops[i]; ++i) { + gs_free auto name = g_strdup_printf("%s-" XTE_CONFIG_FILENAME, + desktops[i]); + gs_free auto path = g_build_filename("/etc/xdg", name, nullptr); + if (auto term = xte_config_get_default(path)) + return term; + } + } + + gs_free auto sys_path = g_build_filename("/etc/xdg", XTE_CONFIG_FILENAME, nullptr); + if (auto term = xte_config_get_default(sys_path)) + return term; + + return nullptr; +} + +static bool +xte_config_is_default(bool* set = nullptr) +{ + gs_free auto term = xte_config_get_default(); + + auto const is_default = term && g_str_equal(term, TERMINAL_DESKTOP_FILENAME); + if (set) + *set = term != nullptr; + return is_default; +} + +gboolean +terminal_util_is_default_terminal(void) +{ + auto set = false; + auto const is_default = xte_config_is_default(&set); + if (!set) { + // No terminal is default yet, so we claim the default. + _terminal_debug_print(TERMINAL_DEBUG_DEFAULT, + "No default terminal, claiming default.\n"); + return terminal_util_make_default_terminal(); + } + + if (is_default) { + // If we're the default terminal, ensure our desktop file is installed + // in the right location. + xte_data_ensure(); + } + + return is_default; +} + +gboolean +terminal_util_make_default_terminal(void) +{ + xte_config_rewrite(); + xte_data_ensure(); + + return xte_config_is_default(); +} diff --git a/src/terminal-util.hh b/src/terminal-util.hh index d980725e3..3770c8540 100644 --- a/src/terminal-util.hh +++ b/src/terminal-util.hh @@ -109,6 +109,12 @@ char *terminal_util_find_program_in_path (const char *path, gboolean terminal_util_check_envv(char const* const* strv); +char** terminal_util_get_desktops(void); + +gboolean terminal_util_is_default_terminal(void); + +gboolean terminal_util_make_default_terminal(void); + G_END_DECLS #endif /* TERMINAL_UTIL_H */ diff --git a/src/terminal-window.cc b/src/terminal-window.cc index 78fcbc1a3..36eb7ce79 100644 --- a/src/terminal-window.cc +++ b/src/terminal-window.cc @@ -56,6 +56,7 @@ struct _TerminalWindowPrivate GtkWidget *menubar; TerminalMdiContainer *mdi_container; GtkWidget *main_vbox; + GtkWidget* ask_default_infobar; TerminalScreen *active_screen; /* Size of a character cell in pixels */ @@ -1938,6 +1939,44 @@ terminal_window_fill_notebook_action_box (TerminalWindow *window, gtk_widget_show (tabs_menu_button); } +static void +window_hide_ask_default_terminal(TerminalWindow* window) +{ + auto const priv = window->priv; + + if (!priv->ask_default_infobar || + !gtk_widget_get_visible(priv->ask_default_infobar)) + return; + + gtk_widget_hide(priv->ask_default_infobar); + terminal_window_update_size(window); +} + +static void +window_sync_ask_default_terminal_cb(TerminalApp* app, + GParamSpec* pspect, + TerminalWindow* window) +{ + window_hide_ask_default_terminal(window); +} + +static void +default_infobar_response_cb(GtkInfoBar* infobar, + int response, + TerminalWindow* window) +{ + auto const app = terminal_app_get(); + + if (response == GTK_RESPONSE_YES) { + terminal_app_make_default_terminal(app); + terminal_app_unset_ask_default_terminal(app); + } else if (response == GTK_RESPONSE_NO) { + terminal_app_unset_ask_default_terminal(app); + } else { // GTK_RESPONSE_CLOSE + window_hide_ask_default_terminal(window); + } +} + /*****************************************/ static void @@ -2243,6 +2282,31 @@ terminal_window_init (TerminalWindow *window) priv->use_default_menubar_visibility = !use_headerbar; } + /* Add "Set as default terminal" infobar */ + if (terminal_app_get_ask_default_terminal(app)) { + auto const infobar = priv->ask_default_infobar = gtk_info_bar_new(); + gtk_info_bar_set_show_close_button(GTK_INFO_BAR(infobar), true); + gtk_info_bar_set_message_type(GTK_INFO_BAR(infobar), GTK_MESSAGE_QUESTION); + + auto const question = gtk_label_new (_("Set GNOME Terminal as your default terminal?")); + gtk_label_set_line_wrap(GTK_LABEL(question), true); + auto const box = gtk_info_bar_get_content_area(GTK_INFO_BAR(infobar)); + gtk_container_add(GTK_CONTAINER(box), question); + gtk_widget_show(question); + + gtk_info_bar_add_button(GTK_INFO_BAR(infobar), _("_Yes"), GTK_RESPONSE_YES); + gtk_info_bar_add_button(GTK_INFO_BAR(infobar), _("_No"), GTK_RESPONSE_NO); + + g_signal_connect (infobar, "response", + G_CALLBACK(default_infobar_response_cb), window); + + gtk_box_pack_start(GTK_BOX(priv->main_vbox), infobar, false, true, 0); + + gtk_widget_show(infobar); + g_signal_connect(app, "notify::ask-default-terminal", + G_CALLBACK(window_sync_ask_default_terminal_cb), window); + } + /* Maybe make Inspector available */ action = lookup_action (window, "inspector"); gtk_debug_settings = terminal_app_get_gtk_debug_settings (app); @@ -2340,6 +2404,13 @@ terminal_window_dispose (GObject *object) priv->clipboard = nullptr; } + if (priv->ask_default_infobar) { + g_signal_handlers_disconnect_by_func(app, + (void*)window_sync_ask_default_terminal_cb, + window); + priv->ask_default_infobar = nullptr; + } + remove_popup_info (window); if (priv->search_popover != nullptr) @@ -2648,6 +2719,9 @@ terminal_window_update_size (TerminalWindow *window) return; } + if (!priv->active_screen) + return; + /* be sure our geometry is up-to-date */ terminal_window_update_geometry (window);