summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eggshell.cc112
-rw-r--r--src/eggshell.hh38
-rw-r--r--src/external.gschema.xml143
-rw-r--r--src/gnome-terminal-search-provider.ini20
-rw-r--r--src/gnome-terminal-server.service.in10
-rw-r--r--src/meson.build492
-rwxr-xr-xsrc/meson_compileschemas.py28
-rw-r--r--src/nautilus.map9
-rw-r--r--src/org.gnome.Terminal.SettingsBridge.xml83
-rw-r--r--src/org.gnome.Terminal.gschema.xml736
-rw-r--r--src/org.gnome.Terminal.service.in4
-rw-r--r--src/org.gnome.Terminal.xml43
-rw-r--r--src/preferences.ui2486
-rw-r--r--src/prefs-main.cc274
-rw-r--r--src/prefs.gresource.xml23
-rw-r--r--src/profile-editor.cc1504
-rw-r--r--src/profile-editor.hh37
-rw-r--r--src/search-popover.ui251
-rw-r--r--src/server.cc195
-rw-r--r--src/terminal-accels.cc615
-rw-r--r--src/terminal-accels.hh37
-rw-r--r--src/terminal-app.cc1671
-rw-r--r--src/terminal-app.hh142
-rw-r--r--src/terminal-client-utils.cc484
-rw-r--r--src/terminal-client-utils.hh73
-rw-r--r--src/terminal-debug.cc50
-rw-r--r--src/terminal-debug.hh78
-rw-r--r--src/terminal-defines.hh56
-rw-r--r--src/terminal-enums.hh69
-rw-r--r--src/terminal-gdbus.cc573
-rw-r--r--src/terminal-gdbus.hh90
-rw-r--r--src/terminal-headerbar.cc170
-rw-r--r--src/terminal-headerbar.hh40
-rw-r--r--src/terminal-headerbar.ui112
-rw-r--r--src/terminal-headermenu.ui119
-rw-r--r--src/terminal-i18n.cc31
-rw-r--r--src/terminal-i18n.hh29
-rw-r--r--src/terminal-icon-button.cc50
-rw-r--r--src/terminal-icon-button.hh33
-rw-r--r--src/terminal-info-bar.cc121
-rw-r--r--src/terminal-info-bar.hh61
-rw-r--r--src/terminal-intl.hh29
-rw-r--r--src/terminal-libgsystem.hh317
-rw-r--r--src/terminal-marshal.list1
-rw-r--r--src/terminal-mdi-container.cc208
-rw-r--r--src/terminal-mdi-container.hh104
-rw-r--r--src/terminal-menu-button.cc149
-rw-r--r--src/terminal-menu-button.hh57
-rw-r--r--src/terminal-menubar.ui.in248
-rw-r--r--src/terminal-nautilus.cc776
-rw-r--r--src/terminal-notebook-menu.ui49
-rw-r--r--src/terminal-notebook.cc596
-rw-r--r--src/terminal-notebook.hh62
-rw-r--r--src/terminal-options.cc1743
-rw-r--r--src/terminal-options.hh196
-rw-r--r--src/terminal-pcre2.hh25
-rw-r--r--src/terminal-prefs-process.cc513
-rw-r--r--src/terminal-prefs-process.hh53
-rw-r--r--src/terminal-prefs.cc950
-rw-r--r--src/terminal-prefs.hh57
-rw-r--r--src/terminal-profiles-list.cc272
-rw-r--r--src/terminal-profiles-list.hh54
-rw-r--r--src/terminal-regex.cc390
-rw-r--r--src/terminal-regex.hh158
-rw-r--r--src/terminal-schemas.hh105
-rw-r--r--src/terminal-screen-container.cc392
-rw-r--r--src/terminal-screen-container.hh64
-rw-r--r--src/terminal-screen.cc2358
-rw-r--r--src/terminal-screen.hh171
-rw-r--r--src/terminal-search-popover.cc587
-rw-r--r--src/terminal-search-popover.hh49
-rw-r--r--src/terminal-search-provider.cc380
-rw-r--r--src/terminal-search-provider.hh51
-rw-r--r--src/terminal-settings-bridge-backend.cc563
-rw-r--r--src/terminal-settings-bridge-backend.hh38
-rw-r--r--src/terminal-settings-bridge-impl.cc402
-rw-r--r--src/terminal-settings-bridge-impl.hh38
-rw-r--r--src/terminal-settings-list.cc930
-rw-r--r--src/terminal-settings-list.hh91
-rw-r--r--src/terminal-settings-utils.cc992
-rw-r--r--src/terminal-settings-utils.hh114
-rw-r--r--src/terminal-tab-label.cc395
-rw-r--r--src/terminal-tab-label.hh66
-rw-r--r--src/terminal-type-builtins.cc.template45
-rw-r--r--src/terminal-type-builtins.hh.template25
-rw-r--r--src/terminal-util.cc1945
-rw-r--r--src/terminal-util.hh120
-rw-r--r--src/terminal-version.hh.in34
-rw-r--r--src/terminal-window.cc3369
-rw-r--r--src/terminal-window.hh97
-rw-r--r--src/terminal-window.ui17
-rw-r--r--src/terminal.about5
-rw-r--r--src/terminal.cc629
-rw-r--r--src/terminal.common.css22
-rw-r--r--src/terminal.gresource.xml30
95 files changed, 32323 insertions, 0 deletions
diff --git a/src/eggshell.cc b/src/eggshell.cc
new file mode 100644
index 0000000..b0926eb
--- /dev/null
+++ b/src/eggshell.cc
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * 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/>.
+ */
+/*
+ @NOTATION@
+ */
+
+/*
+ *
+ * Gnome utility routines.
+ * (C) 1997, 1998, 1999 the Free Software Foundation.
+ *
+ * Author: Miguel de Icaza,
+ */
+
+#include <config.h>
+
+#include "eggshell.hh"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#ifndef G_OS_WIN32
+#include <pwd.h>
+#endif
+
+#include <glib.h>
+
+/**
+ * egg_shell:
+ * @shell: the value of the SHELL env variable
+ *
+ * Retrieves the user's preferred shell.
+ *
+ * Returns: A newly allocated string that is the path to the shell.
+ */
+char *
+egg_shell (const char *shell)
+{
+#ifndef G_OS_WIN32
+ struct passwd *pw;
+ int i;
+ static const char shells [][14] = {
+ /* Note that on some systems shells can also
+ * be installed in /usr/bin */
+ "/bin/bash", "/usr/bin/bash",
+ "/bin/zsh", "/usr/bin/zsh",
+ "/bin/tcsh", "/usr/bin/tcsh",
+ "/bin/ksh", "/usr/bin/ksh",
+ "/bin/csh", "/bin/sh"
+ };
+
+ if (geteuid () == getuid () &&
+ getegid () == getgid ()) {
+ /* only in non-setuid */
+ if (shell != nullptr) {
+ if (access (shell, X_OK) == 0) {
+ return g_strdup (shell);
+ }
+ }
+ }
+ pw = getpwuid(getuid());
+ if (pw && pw->pw_shell) {
+ if (access (pw->pw_shell, X_OK) == 0) {
+ return g_strdup (pw->pw_shell);
+ }
+ }
+
+ for (i = 0; i != G_N_ELEMENTS (shells); i++) {
+ if (access (shells [i], X_OK) == 0) {
+ return g_strdup (shells[i]);
+ }
+ }
+
+ /* If /bin/sh doesn't exist, your system is truly broken. */
+ g_assert_not_reached ();
+
+ /* Placate compiler. */
+ return nullptr;
+#else
+ /* g_find_program_in_path() always looks also in the Windows
+ * and System32 directories, so it should always find either cmd.exe
+ * or command.com.
+ */
+ char *retval = g_find_program_in_path ("cmd.exe");
+
+ if (retval == nullptr)
+ retval = g_find_program_in_path ("command.com");
+
+ g_assert (retval != nullptr);
+
+ return retval;
+#endif
+}
diff --git a/src/eggshell.hh b/src/eggshell.hh
new file mode 100644
index 0000000..2312777
--- /dev/null
+++ b/src/eggshell.hh
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * Copyright (C) 1999, 2000 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * 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/>.
+ */
+/*
+ @NOTATION@
+ */
+
+#ifndef __EGG_USER_SHELL_H__
+#define __EGG_USER_SHELL_H__
+
+#include <stdlib.h>
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* Find the name of the user's shell. */
+char *egg_shell (const char *shell);
+
+G_END_DECLS
+
+#endif
diff --git a/src/external.gschema.xml b/src/external.gschema.xml
new file mode 100644
index 0000000..79c3166
--- /dev/null
+++ b/src/external.gschema.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2021 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/>.
+-->
+<schemalist>
+
+ <!-- From gsettings-desktop-schemas -->
+
+ <schema id="org.gnome.desktop.interface"
+ gettext-domain="gsettings-desktop-schemas"
+ path="/org/gnome/desktop/interface/">
+ <key name="monospace-font-name"
+ type="s">
+ <default>'DejaVu Sans Mono 10'</default>
+ </key>
+ </schema>
+
+ <enum id="org.gnome.desktop.GDesktopProxyMode">
+ <value nick="none"
+ value="0"/>
+ <value nick="manual"
+ value="1"/>
+ <value nick="auto"
+ value="2"/>
+ </enum>
+
+ <schema id="org.gnome.system.proxy.http"
+ gettext-domain="gsettings-desktop-schemas"
+ path="/system/proxy/http/">
+ <key name="host"
+ type="s">
+ <default>''</default>
+ </key>
+ <key name="port"
+ type="i">
+ <range min="0"
+ max="65535" />
+ <default>8080</default>
+ </key>
+ <key name="use-authentication"
+ type="b">
+ <default>false</default>
+ </key>
+ <key name="authentication-user"
+ type="s">
+ <default>''</default>
+ </key>
+ <key name="authentication-password"
+ type="s">
+ <default>''</default>
+ </key>
+ </schema>
+
+ <schema id="org.gnome.system.proxy.https"
+ gettext-domain="gsettings-desktop-schemas"
+ path="/system/proxy/https/">
+ <key name="host"
+ type="s">
+ <default>''</default>
+ </key>
+ <key name="port"
+ type="i">
+ <range min="0"
+ max="65535" />
+ <default>0</default>
+ </key>
+ </schema>
+
+ <schema id="org.gnome.system.proxy.ftp"
+ gettext-domain="gsettings-desktop-schemas"
+ path="/system/proxy/ftp/">
+ <key name="host"
+ type="s">
+ <default>''</default>
+ </key>
+ <key name="port"
+ type="i">
+ <range min="0"
+ max="65535" />
+ <default>0</default>
+ </key>
+ </schema>
+
+ <schema id="org.gnome.system.proxy.socks"
+ gettext-domain="gsettings-desktop-schemas"
+ path="/system/proxy/socks/">
+ <key name="host"
+ type="s">
+ <default>''</default>
+ </key>
+ <key name="port"
+ type="i">
+ <range min="0"
+ max="65535" />
+ <default>0</default>
+ </key>
+ </schema>
+
+ <schema id="org.gnome.system.proxy"
+ gettext-domain="gsettings-desktop-schemas"
+ path="/system/proxy/">
+ <child name="http"
+ schema="org.gnome.system.proxy.http" />
+ <child name="https"
+ schema="org.gnome.system.proxy.https" />
+ <child name="ftp"
+ schema="org.gnome.system.proxy.ftp" />
+ <child name="socks"
+ schema="org.gnome.system.proxy.socks" />
+ <key name="mode"
+ enum="org.gnome.desktop.GDesktopProxyMode">
+ <default>'none'</default>
+ </key>
+ <key name="ignore-hosts"
+ type="as">
+ <default>['localhost', '127.0.0.0/8', '::1']</default>
+ </key>
+ </schema>
+
+ <!-- From gtk+ -->
+
+ <schema id="org.gtk.Settings.Debug"
+ path="/org/gtk/settings/debug/">
+ <key name="enable-inspector-keybinding"
+ type="b">
+ <default>false</default>
+ </key>
+ </schema>
+
+</schemalist>
diff --git a/src/gnome-terminal-search-provider.ini b/src/gnome-terminal-search-provider.ini
new file mode 100644
index 0000000..1b9f81c
--- /dev/null
+++ b/src/gnome-terminal-search-provider.ini
@@ -0,0 +1,20 @@
+# Copyright © 2013 Red Hat, Inc.
+#
+# 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/>.
+
+[Shell Search Provider]
+DesktopId=org.gnome.Terminal.desktop
+BusName=org.gnome.Terminal
+ObjectPath=/org/gnome/Terminal/SearchProvider
+Version=2
diff --git a/src/gnome-terminal-server.service.in b/src/gnome-terminal-server.service.in
new file mode 100644
index 0000000..24a1f4b
--- /dev/null
+++ b/src/gnome-terminal-server.service.in
@@ -0,0 +1,10 @@
+[Unit]
+Description=GNOME Terminal Server
+PartOf=graphical-session.target
+[Service]
+Slice=app-@gt_dns_name@.slice
+Type=dbus
+BusName=@gt_dns_name@
+ExecStart=@libexecdir@/@gt_name@-server
+TimeoutStopSec=5s
+KillMode=process
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..8a45547
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,492 @@
+# Copyright © 2019, 2020, 2021, 2022 Christian Persch
+#
+# This programme 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 programme 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 programme. If not, see <https://www.gnu.org/licenses/>.
+
+src_inc = include_directories('.')
+
+# UI files
+
+ui_xsltproc_command = [
+ xsltproc,
+ '--nonet',
+ '-o', '@OUTPUT@',
+ '@INPUT@',
+]
+
+menubar_ui_mnemonics = custom_target(
+ 'terminal-menubar-with-mnemonics.ui',
+ command: ui_xsltproc_command,
+ input: files(
+ '..' / 'data' / 'ui-filter-mnemonics.xslt',
+ 'terminal-menubar.ui.in',
+ ),
+ install: false,
+ output: 'terminal-menubar-with-mnemonics.ui',
+)
+
+menubar_ui_nomnemonics = custom_target(
+ 'terminal-menubar-without-mnemonics.ui',
+ command: ui_xsltproc_command,
+ input: files(
+ '..' / 'data' / 'ui-filter-no-mnemonics.xslt',
+ 'terminal-menubar.ui.in',
+ ),
+ install: false,
+ output: 'terminal-menubar-without-mnemonics.ui',
+)
+
+# Common sources
+
+app_sources = files(
+ 'terminal-accels.cc',
+ 'terminal-accels.hh',
+ 'terminal-app.cc',
+ 'terminal-app.hh',
+)
+
+client_util_sources = files(
+ 'terminal-client-utils.cc',
+ 'terminal-client-utils.hh',
+)
+
+debug_sources = files(
+ 'terminal-debug.cc',
+ 'terminal-debug.hh',
+)
+
+dbus_sources = gnome.gdbus_codegen(
+ 'terminal-gdbus-generated',
+ gt_dns_name + '.xml',
+ autocleanup: 'none',
+ install_header: false,
+ interface_prefix: gt_dns_name,
+ namespace: 'Terminal',
+ object_manager: true,
+)
+
+egg_sources = files(
+ 'eggshell.cc',
+ 'eggshell.hh',
+)
+
+i18n_sources = files(
+ 'terminal-i18n.cc',
+ 'terminal-i18n.hh',
+ 'terminal-intl.hh',
+)
+
+marshal_sources = gnome.genmarshal(
+ 'terminal-marshal',
+ internal: true,
+ install_header: false,
+ prefix: '_terminal_marshal',
+ sources: files(
+ 'terminal-marshal.list',
+ ),
+ stdinc: true,
+ valist_marshallers: true,
+)
+
+misc_sources = files(
+ 'terminal-defines.hh',
+ 'terminal-libgsystem.hh',
+)
+
+profiles_sources = files(
+ 'terminal-profiles-list.cc',
+ 'terminal-profiles-list.hh',
+ 'terminal-schemas.hh',
+ 'terminal-settings-list.cc',
+ 'terminal-settings-list.hh',
+)
+
+settings_dbus_sources = gnome.gdbus_codegen(
+ 'terminal-settings-bridge-generated',
+ 'org.gnome.Terminal.SettingsBridge.xml',
+ autocleanup: 'all',
+ install_header: false,
+ interface_prefix: gt_dns_name,
+ namespace: 'Terminal',
+ object_manager: false,
+)
+
+settings_utils_sources = files(
+ 'terminal-settings-utils.cc',
+ 'terminal-settings-utils.hh',
+)
+
+regex_sources = files(
+ 'terminal-regex.hh',
+)
+
+types_sources = files(
+ 'terminal-enums.hh',
+)
+
+types_sources += gnome.mkenums(
+ 'terminal-type-builtins',
+ c_template: 'terminal-type-builtins.hh.template',
+ h_template: 'terminal-type-builtins.cc.template',
+ install_header: false,
+ sources: types_sources,
+)
+
+version_conf = {
+ 'TERMINAL_MAJOR_VERSION': gt_major_version.to_string(),
+ 'TERMINAL_MICRO_VERSION': gt_micro_version.to_string(),
+ 'TERMINAL_MINOR_VERSION': gt_minor_version.to_string(),
+}
+
+version_sources = [configure_file(
+ configuration: version_conf,
+ input: 'terminal-version.hh.in',
+ install: false,
+ output: '@BASENAME@',
+)]
+
+util_sources = files(
+ 'terminal-util.cc',
+ 'terminal-util.hh',
+)
+
+# Flags
+
+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),
+ '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
+ '-DTERM_PKGDATADIR="@0@"'.format(gt_prefix / gt_pkgdatadir),
+ '-DTERM_PKGLIBDIR="@0@"'.format(gt_prefix / gt_pkglibdir),
+ '-DVTE_DISABLE_DEPRECATION_WARNINGS',
+]
+
+# Server
+
+server_resources_sources = gnome.compile_resources(
+ 'terminal-server-resources',
+ 'terminal.gresource.xml',
+ c_name: 'terminal',
+ dependencies: [
+ menubar_ui_mnemonics,
+ menubar_ui_nomnemonics,
+ ],
+ export: false,
+)
+
+server_sources = app_sources + client_util_sources + debug_sources + dbus_sources + egg_sources + i18n_sources + marshal_sources + misc_sources + profiles_sources + regex_sources + server_resources_sources + settings_dbus_sources + settings_utils_sources + types_sources + util_sources + version_sources + files(
+ 'server.cc',
+ 'terminal-enums.hh',
+ 'terminal-gdbus.cc',
+ 'terminal-gdbus.hh',
+ 'terminal-headerbar.cc',
+ 'terminal-headerbar.hh',
+ 'terminal-icon-button.cc',
+ 'terminal-icon-button.hh',
+ 'terminal-info-bar.cc',
+ 'terminal-info-bar.hh',
+ 'terminal-mdi-container.cc',
+ 'terminal-mdi-container.hh',
+ 'terminal-menu-button.cc',
+ 'terminal-menu-button.hh',
+ 'terminal-notebook.cc',
+ 'terminal-notebook.hh',
+ 'terminal-pcre2.hh',
+ 'terminal-prefs-process.cc',
+ 'terminal-prefs-process.hh',
+ 'terminal-screen-container.cc',
+ 'terminal-screen-container.hh',
+ 'terminal-screen.cc',
+ 'terminal-screen.hh',
+ 'terminal-search-popover.cc',
+ 'terminal-search-popover.hh',
+ 'terminal-settings-bridge-impl.cc',
+ 'terminal-settings-bridge-impl.hh',
+ 'terminal-tab-label.cc',
+ 'terminal-tab-label.hh',
+ 'terminal-window.cc',
+ 'terminal-window.hh',
+)
+
+if get_option('search_provider')
+
+ server_sources += files(
+ 'terminal-search-provider.cc',
+ 'terminal-search-provider.hh',
+ )
+
+ server_sources += gnome.gdbus_codegen(
+ 'terminal-search-provider-gdbus-generated',
+ gt_prefix / gt_dbusinterfacedir / 'org.gnome.ShellSearchProvider2.xml',
+ autocleanup: 'none',
+ install_header: false,
+ interface_prefix: 'org.gnome.Shell',
+ namespace: 'Terminal',
+ )
+
+endif
+
+server_incs = [
+ top_inc,
+ src_inc,
+]
+
+server_cxxflags = common_cxxflags + [
+ '-DTERMINAL_SERVER',
+]
+
+server_deps = [
+ gio_dep,
+ gio_unix_dep,
+ glib_dep,
+ gtk_dep,
+ pcre2_dep,
+ pthreads_dep,
+ schemas_dep,
+ uuid_dep,
+ vte_dep,
+ x11_dep,
+]
+
+server = executable(
+ gt_name + '-server',
+ cpp_args: server_cxxflags,
+ dependencies: server_deps,
+ include_directories: server_incs,
+ install: true,
+ install_dir: gt_prefix / gt_libexecdir,
+ sources: server_sources,
+)
+
+# systemd and D-Bus glue
+
+server_conf = configuration_data()
+server_conf.set('gt_name', gt_name)
+server_conf.set('gt_dns_name', gt_dns_name)
+server_conf.set('libexecdir', gt_prefix / gt_libexecdir)
+
+configure_file(
+ input: gt_dns_name + '.service.in',
+ output: gt_dns_name + '.service',
+ configuration: server_conf,
+ install: true,
+ install_dir: gt_prefix / gt_dbusservicedir,
+)
+
+configure_file(
+ input: gt_name + '-server.service.in',
+ output: gt_name + '-server.service',
+ configuration: server_conf,
+ install: true,
+ install_dir: gt_prefix / gt_systemduserdir
+)
+
+# Search provider
+
+if get_option('search_provider')
+
+ provider_ini = files(gt_name + '-search-provider.ini')
+
+ install_data(
+ provider_ini,
+ install_dir: gt_prefix / gt_searchproviderdir,
+ )
+endif # option 'search_provider'
+
+# Preferences
+
+prefs_resources_sources = gnome.compile_resources(
+ 'terminal-prefs-resources',
+ 'prefs.gresource.xml',
+ c_name: 'terminal',
+ export: false,
+)
+
+prefs_main_sources = app_sources + client_util_sources + debug_sources + i18n_sources + marshal_sources + misc_sources + prefs_resources_sources + profiles_sources + settings_dbus_sources + settings_utils_sources + types_sources + util_sources + version_sources + files(
+ 'prefs-main.cc',
+ 'profile-editor.cc',
+ 'profile-editor.hh',
+ 'terminal-prefs.cc',
+ 'terminal-prefs.hh',
+ 'terminal-settings-bridge-backend.cc',
+ 'terminal-settings-bridge-backend.hh',
+)
+
+prefs_main_cxxflags = common_cxxflags + [
+ '-DTERMINAL_PREFERENCES',
+]
+
+prefs_main_incs = server_incs
+prefs_main_deps = server_deps
+
+prefs_main = executable(
+ gt_name + '-preferences',
+ cpp_args: prefs_main_cxxflags,
+ include_directories: prefs_main_incs,
+ dependencies: prefs_main_deps,
+ install: true,
+ install_dir: gt_prefix / gt_libexecdir,
+ sources: prefs_main_sources,
+)
+
+# Legacy client
+
+client_sources = client_util_sources + debug_sources + dbus_sources + i18n_sources + profiles_sources + settings_utils_sources + types_sources + files(
+ 'terminal-options.cc',
+ 'terminal-options.hh',
+ 'terminal.cc',
+)
+
+client_incs = [
+ top_inc,
+ src_inc,
+]
+
+client_incs = [
+ top_inc,
+ src_inc,
+]
+
+client_cxxflags = common_cxxflags + [
+ '-DTERMINAL_CLIENT',
+]
+
+client_deps = [
+ gio_dep,
+ gio_unix_dep,
+ glib_dep,
+ gtk_dep,
+ x11_dep,
+ uuid_dep,
+ vte_dep,
+]
+
+client = executable(
+ gt_name,
+ cpp_args: client_cxxflags,
+ include_directories: client_incs,
+ dependencies: client_deps,
+ install: true,
+ sources: client_sources,
+)
+
+# Settings schemas
+
+install_data(
+ gt_dns_name + '.gschema.xml',
+ install_dir: gt_prefix / gt_schemadir,
+)
+
+meson.add_install_script(
+ 'meson_compileschemas.py',
+ gt_prefix / gt_schemadir,
+)
+
+reference_schemas = custom_target(
+ 'gschemas.compiled',
+ command: [
+ glib_compile_schemas,
+ '--targetdir', meson.current_build_dir(),
+ '--schema-file', files(gt_dns_name + '.gschema.xml'),
+ '--schema-file', files('external.gschema.xml'),
+ ],
+ install: true,
+ install_dir: gt_prefix / gt_pkglibdir,
+ output: 'gschemas.compiled',
+)
+
+# Nautilus extension
+
+if get_option('nautilus_extension')
+
+ nautilus_sources = client_util_sources + dbus_sources + i18n_sources + misc_sources + types_sources + files(
+ 'terminal-nautilus.cc',
+ )
+
+ nautilus_map = meson.current_source_dir() / 'nautilus.map'
+
+ nautilus_incs = [
+ top_inc,
+ src_inc,
+ ]
+
+ nautilus_cxxflags = glib_version_cxxflags + [
+ '-DTERMINAL_NAUTILUS',
+ '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
+ ]
+
+ nautilus_deps = [
+ gio_dep,
+ gio_unix_dep,
+ glib_dep,
+ libnautilus_extension_dep,
+ ]
+
+ nautilus_ldflags = [
+ '-Wl,--version-script,@0@'.format(nautilus_map),
+ ]
+
+ nautilus_extension = shared_library(
+ 'terminal-nautilus',
+ cpp_args: nautilus_cxxflags,
+ dependencies: nautilus_deps,
+ include_directories: nautilus_incs,
+ install: true,
+ install_dir: gt_prefix / gt_nautilusextensiondir,
+ link_args: nautilus_ldflags,
+ link_depends: nautilus_map,
+ sources: nautilus_sources,
+ soversion: '',
+ )
+
+endif # option 'nautilus_extension'
+
+# Unit tests
+
+test_regex_sources = regex_sources + files(
+ 'terminal-regex.cc',
+)
+
+test_regex = executable(
+ 'test-regex',
+ cpp_args: [
+ '-DTERMINAL_REGEX_MAIN',
+ ],
+ dependencies: [
+ glib_dep,
+ pcre2_dep,
+ ],
+ include_directories: [top_inc, src_inc,],
+ sources: test_regex_sources,
+ install: false,
+)
+
+test_env = [
+ 'GNOME_TERMINAL_DEBUG=0',
+ 'VTE_DEBUG=0',
+]
+
+test_units = [
+ ['regex', test_regex],
+]
+
+foreach test: test_units
+ test(
+ test[0],
+ test[1],
+ env: test_env,
+ )
+endforeach
diff --git a/src/meson_compileschemas.py b/src/meson_compileschemas.py
new file mode 100755
index 0000000..3268ddc
--- /dev/null
+++ b/src/meson_compileschemas.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# Copyright © 2019 Christian Persch
+#
+# This programme 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 programme 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 programme. If not, see <https://www.gnu.org/licenses/>.
+
+import os
+import subprocess
+import sys
+
+if os.environ.get('DESTDIR'):
+ sys.exit(0)
+
+prefix = os.environ['MESON_INSTALL_PREFIX']
+schemasdir = os.path.join(prefix, sys.argv[1])
+
+rv = subprocess.call(['glib-compile-schemas', schemasdir])
+sys.exit(rv)
diff --git a/src/nautilus.map b/src/nautilus.map
new file mode 100644
index 0000000..7cd7993
--- /dev/null
+++ b/src/nautilus.map
@@ -0,0 +1,9 @@
+{
+ global:
+ nautilus_module_initialize;
+ nautilus_module_list_types;
+ nautilus_module_shutdown;
+
+ local:
+ *;
+};
diff --git a/src/org.gnome.Terminal.SettingsBridge.xml b/src/org.gnome.Terminal.SettingsBridge.xml
new file mode 100644
index 0000000..247ebbe
--- /dev/null
+++ b/src/org.gnome.Terminal.SettingsBridge.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Introspection 0.1//EN"
+ "http://www.freedesktop.org/software/dbus/introspection.dtd">
+<!--
+ Copyright © 2022 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, or (at your option)
+ any later version.
+
+ This program is distributed in the hope conf 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/>.
+-->
+<node>
+ <interface name="org.gnome.Terminal.SettingsBridge">
+ <annotation name="org.gtk.GDBus.C.Name" value="SettingsBridge" />
+
+ <method name="get_permission">
+ <arg type="s" name="path" direction="in" />
+ <arg type="v" name="permission" direction="out" />
+ </method>
+
+ <method name="get_writable">
+ <arg type="s" name="key" direction="in" />
+ <arg type="b" name="writable" direction="out" />
+ </method>
+
+ <method name="read">
+ <arg type="s" name="key" direction="in" />
+ <arg type="s" name="type" direction="in" />
+ <arg type="b" name="default" direction="in" />
+ <arg type="ay" name="value" direction="out">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+ </arg>
+ </method>
+
+ <method name="read_user_value">
+ <arg type="s" name="key" direction="in" />
+ <arg type="s" name="type" direction="in" />
+ <arg type="ay" name="value" direction="out">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+ </arg>
+ </method>
+
+ <method name="reset">
+ <arg type="s" name="key" direction="in" />
+ </method>
+
+ <method name="subscribe">
+ <arg type="s" name="name" direction="in" />
+ </method>
+
+ <method name="sync">
+ </method>
+
+ <method name="unsubscribe">
+ <arg type="s" name="name" direction="in" />
+ </method>
+
+ <method name="write">
+ <arg type="s" name="key" direction="in" />
+ <arg type="ay" name="value" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+ </arg>
+ <arg type="b" name="success" direction="out" />
+ </method>
+
+ <method name="write_tree">
+ <arg type="s" name="path_prefix" direction="in" />
+ <arg type="ay" name="tree" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+ </arg>
+ <arg type="b" name="success" direction="out" />
+ </method>
+
+ </interface>
+</node>
diff --git a/src/org.gnome.Terminal.gschema.xml b/src/org.gnome.Terminal.gschema.xml
new file mode 100644
index 0000000..a860b73
--- /dev/null
+++ b/src/org.gnome.Terminal.gschema.xml
@@ -0,0 +1,736 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2002 Havoc Pennington
+ Copyright © 2002 Jonathan Blandford
+ Copyright © 2003, 2004 Mariano Suárez-Alvarez
+ Copyright © 2005 Kjartan Maraas
+ Copyright © 2005 Tony Tsui
+ Copyright © 2006 Guilherme de S. Pastore
+ Copyright © 2009, 2010 Behdad Esfahbod
+ Copyright © 2008, 2010 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/>.
+-->
+<schemalist gettext-domain="gnome-terminal">
+
+ <enum id='org.gnome.Terminal.NewTerminalMode'>
+ <value nick='window' value='0'/>
+ <value nick='tab' value='1'/>
+ </enum>
+
+ <enum id='org.gnome.Terminal.NewTabPosition'>
+ <value nick='last' value='0'/>
+ <value nick='next' value='1'/>
+ </enum>
+
+ <enum id='org.gnome.Terminal.ScrollbarPolicy'>
+ <value nick='always' value='0'/>
+ <!-- <value nick='automatic' value='1'/> -->
+ <value nick='never' value='2'/>
+ </enum>
+
+ <enum id='org.gnome.Terminal.TabsbarPolicy'>
+ <value nick='always' value='0'/>
+ <value nick='automatic' value='1'/>
+ <!-- <value nick='never' value='2'/> -->
+ </enum>
+
+ <enum id='org.gnome.Terminal.ThemeVariant'>
+ <value nick='system' value='0'/>
+ <value nick='light' value='1'/>
+ <value nick='dark' value='2'/>
+ </enum>
+
+ <enum id='org.gnome.Terminal.ExitAction'>
+ <value nick='close' value='0'/>
+ <value nick='restart' value='1'/>
+ <value nick='hold' value='2'/>
+ </enum>
+
+ <enum id='org.gnome.Terminal.CJKWidth'>
+ <value nick='narrow' value='1'/>
+ <value nick='wide' value='2'/>
+ </enum>
+
+ <enum id="org.gnome.Terminal.PreserveWorkingDirectory">
+ <value nick="never" value='0'/>
+ <value nick="safe" value='1'/>
+ <value nick="always" value='2'/>
+ </enum>
+
+ <!-- From gtk+ -->
+ <enum id="org.gnome.Terminal.TabPosition">
+ <!-- <value nick="left" value="0" /> -->
+ <!-- <value nick="right" value="1" /> -->
+ <value nick="top" value="2" />
+ <value nick="bottom" value="3" />
+ </enum>
+
+ <!-- These really belong into some vte-built enums file, but
+ using enums from other modules still has some
+ problems. Just include a copy here for now.
+ -->
+ <enum id='org.gnome.Terminal.EraseBinding'>
+ <value nick='auto' value='0'/>
+ <value nick='ascii-backspace' value='1'/>
+ <value nick='ascii-delete' value='2'/>
+ <value nick='delete-sequence' value='3'/>
+ <value nick='tty' value='4'/>
+ </enum>
+ <enum id='org.gnome.Terminal.Cursor.BlinkMode'>
+ <value nick='system' value='0'/>
+ <value nick='on' value='1'/>
+ <value nick='off' value='2'/>
+ </enum>
+ <enum id='org.gnome.Terminal.Cursor.Shape'>
+ <value nick='block' value='0'/>
+ <value nick='ibeam' value='1'/>
+ <value nick='underline' value='2'/>
+ </enum>
+ <enum id='org.gnome.Terminal.TextBlinkMode'>
+ <value nick='never' value='0'/>
+ <value nick='focused' value='1'/>
+ <value nick='unfocused' value='2'/>
+ <value nick='always' value='3'/>
+ </enum>
+
+ <!-- SettingsList base schema -->
+
+ <schema id="org.gnome.Terminal.SettingsList">
+ <key name="list" type="as">
+ <default>[]</default>
+ </key>
+ <key name="default" type="s">
+ <default>''</default>
+ </key>
+ </schema>
+
+ <!-- Profiles list schema -->
+
+ <schema id="org.gnome.Terminal.ProfilesList"
+ extends="org.gnome.Terminal.SettingsList"
+ path="/org/gnome/terminal/legacy/profiles:/">
+ <override name="list">['b1dcc9dd-5262-4d8d-a863-c897e6d979b9']</override>
+ <override name="default">'b1dcc9dd-5262-4d8d-a863-c897e6d979b9'</override>
+ </schema>
+
+ <!-- A terminal profile -->
+
+ <schema id="org.gnome.Terminal.Legacy.Profile">
+ <key name="visible-name" type="s">
+ <!-- Translators: This needs to be parsed as a GVariant string, so keep the regular single quotes around the string as-is, and do not add extra quotes. -->
+ <default l10n="messages" context="visible-name">'Unnamed'</default>
+ <summary>Human-readable name of the profile</summary>
+ <description>Human-readable name of the profile.</description>
+ </key>
+ <key name="foreground-color" type="s">
+ <default>'#171421'</default>
+ <summary>Default color of text in the terminal</summary>
+ <description>Default color of text in the terminal, as a color specification (can be HTML-style hex digits, or a color name such as “red”).</description>
+ </key>
+ <key name="background-color" type="s">
+ <default>'#ffffff'</default>
+ <summary>Default color of terminal background</summary>
+ <description>Default color of terminal background, as a color specification (can be HTML-style hex digits, or a color name such as “red”).</description>
+ </key>
+ <key name="bold-color" type="s">
+ <default>'#000000'</default>
+ <summary>Default color of bold text in the terminal</summary>
+ <description>Default color of bold text in the terminal, as a color specification (can be HTML-style hex digits, or a color name such as “red”). This is ignored if bold-color-same-as-fg is true.</description>
+ </key>
+ <key name="bold-color-same-as-fg" type="b">
+ <default>true</default>
+ <summary>Whether bold text should use the same color as normal text</summary>
+ <description>If true, boldface text will be rendered using the same color as normal text.</description>
+ </key>
+ <key name="cell-height-scale" type="d">
+ <range min="1.0" max="2.0" />
+ <default>1.0</default>
+ <summary>Scale factor for the cell height to increase line spacing. (Does not increase the font’s height.)</summary>
+ </key>
+ <key name="cell-width-scale" type="d">
+ <range min="1.0" max="2.0" />
+ <default>1.0</default>
+ <summary>Scale factor for the cell width to increase letter spacing. (Does not increase the font’s width.)</summary>
+ </key>
+ <key name="cursor-colors-set" type="b">
+ <default>false</default>
+ <summary>Whether to use custom cursor colors</summary>
+ <description>If true, use the cursor colors from the profile.</description>
+ </key>
+ <key name="cursor-background-color" type="s">
+ <default>'#000000'</default>
+ <summary>Cursor background color</summary>
+ <description>Custom color of the background of the terminal’s cursor, as a color specification (can be HTML-style hex digits, or a color name such as “red”). This is ignored if cursor-colors-set is false.</description>
+ </key>
+ <key name="cursor-foreground-color" type="s">
+ <default>'#ffffff'</default>
+ <summary>Cursor foreground colour</summary>
+ <description>Custom color for the foreground of the text character at the terminal’s cursor position, as a color specification (can be HTML-style hex digits, or a color name such as “red”). This is ignored if cursor-colors-set is false.</description>
+ </key>
+ <key name="highlight-colors-set" type="b">
+ <default>false</default>
+ <summary>Whether to use custom highlight colors</summary>
+ <description>If true, use the highlight colors from the profile.</description>
+ </key>
+ <key name="highlight-background-color" type="s">
+ <default>'#000000'</default>
+ <summary>Highlight background color</summary>
+ <description>Custom color of the background of the terminal’s highlight, as a color specification (can be HTML-style hex digits, or a color name such as “red”). This is ignored if highlight-colors-set is false.</description>
+ </key>
+ <key name="highlight-foreground-color" type="s">
+ <default>'#ffffff'</default>
+ <summary>Highlight foreground colour</summary>
+ <description>Custom color for the foreground of the text character at the terminal’s highlight position, as a color specification (can be HTML-style hex digits, or a color name such as “red”). This is ignored if highlight-colors-set is false.</description>
+ </key>
+ <key name="enable-bidi" type="b">
+ <default>true</default>
+ <summary>Whether to perform bidirectional text rendering</summary>
+ <description>If true, perform bidirectional text rendering (“BiDi”).</description>
+ </key>
+ <key name="enable-shaping" type="b">
+ <default>true</default>
+ <summary>Whether to perform Arabic shaping</summary>
+ <description>If true, shape Arabic text.</description>
+ </key>
+ <key name="enable-sixel" type="b">
+ <default>false</default>
+ <summary>Whether to enable SIXEL images</summary>
+ <description>If true, SIXEL sequences are parsed and images are rendered.</description>
+ </key>
+ <key name="bold-is-bright" type="b">
+ <default>false</default>
+ <summary>Whether bold is also bright</summary>
+ <description>If true, setting bold on the first 8 colors also switches to their bright variants.</description>
+ </key>
+ <key name="audible-bell" type="b">
+ <default>true</default>
+ <summary>Whether to ring the terminal bell</summary>
+ </key>
+ <key name="word-char-exceptions" type="ms">
+ <default>nothing</default>
+ <summary>List of ASCII punctuation characters that are not to be treated as part of a word when doing word-wise selection</summary>
+ </key>
+ <key name="default-size-columns" type="i">
+ <range min="16" max="511" />
+ <default>80</default>
+ <summary>Default number of columns</summary>
+ <description>Number of columns in newly created terminal windows. Has no effect if use_custom_default_size is not enabled.</description>
+ </key>
+ <key name="default-size-rows" type="i">
+ <range min="4" max="511" />
+ <default>24</default>
+ <summary>Default number of rows</summary>
+ <description>Number of rows in newly created terminal windows. Has no effect if use_custom_default_size is not enabled.</description>
+ </key>
+ <key name="scrollbar-policy" enum="org.gnome.Terminal.ScrollbarPolicy">
+ <default>'always'</default>
+ <summary>When to show the scrollbar</summary>
+ </key>
+ <key name="scrollback-lines" type="i">
+ <default>10000</default>
+ <summary>Number of lines to keep in scrollback</summary>
+ <description>Number of scrollback lines to keep around. You can scroll back in the terminal by this number of lines; lines that don’t fit in the scrollback are discarded. If scrollback_unlimited is true, this value is ignored.</description>
+ </key>
+ <key name="scrollback-unlimited" type="b">
+ <default>false</default>
+ <summary>Whether an unlimited number of lines should be kept in scrollback</summary>
+ <description>If true, scrollback lines will never be discarded. The scrollback history is stored on disk temporarily, so this may cause the system to run out of disk space if there is a lot of output to the terminal.</description>
+ </key>
+ <key name="scroll-on-keystroke" type="b">
+ <default>true</default>
+ <summary>Whether to scroll to the bottom when a key is pressed</summary>
+ <description>If true, pressing a key jumps the scrollbar to the bottom.</description>
+ </key>
+ <key name="scroll-on-output" type="b">
+ <default>false</default>
+ <summary>Whether to scroll to the bottom when there’s new output</summary>
+ <description>If true, whenever there’s new output the terminal will scroll to the bottom.</description>
+ </key>
+ <key name="exit-action" enum="org.gnome.Terminal.ExitAction">
+ <default>'close'</default>
+ <summary>What to do with the terminal when the child command exits</summary>
+ <description>Possible values are “close” to close the terminal, “restart” to restart the command, and “hold” to keep the terminal open with no command running inside.</description>
+ </key>
+ <key name="login-shell" type="b">
+ <default>false</default>
+ <summary>Whether to launch the command in the terminal as a login shell</summary>
+ <description>If true, the command inside the terminal will be launched as a login shell (argv[0] will have a hyphen in front of it).</description>
+ </key>
+ <key name="preserve-working-directory" enum="org.gnome.Terminal.PreserveWorkingDirectory">
+ <default>'safe'</default>
+ <summary>Whether to preserve the working directory when opening a new terminal</summary>
+ <description>
+ Controls when opening a new terminal from a previous one carries over the working directory of the opening terminal to the new one.
+ </description>
+ </key>
+ <key name="use-custom-command" type="b">
+ <default>false</default>
+ <summary>Whether to run a custom command instead of the shell</summary>
+ <description>If true, the value of the custom_command setting will be used in place of running a shell.</description>
+ </key>
+ <key name="cursor-blink-mode" enum="org.gnome.Terminal.Cursor.BlinkMode">
+ <default>'system'</default>
+ <summary>Whether to blink the cursor</summary>
+ <description>The possible values are “system” to use the global cursor blinking settings, or “on” or “off” to set the mode explicitly.</description>
+ </key>
+ <key name="cursor-shape" enum="org.gnome.Terminal.Cursor.Shape">
+ <default>'block'</default>
+ <summary>The cursor appearance</summary>
+ </key>
+ <key name="text-blink-mode" enum="org.gnome.Terminal.TextBlinkMode">
+ <default>'always'</default>
+ <summary>Possible values are “always” or “never” allow blinking text, or only when the terminal is “focused” or “unfocused”.</summary>
+ </key>
+ <key name="custom-command" type="s">
+ <default>''</default>
+ <summary>Custom command to use instead of the shell</summary>
+ <description>Run this command in place of the shell, if use_custom_command is true.</description>
+ </key>
+ <key name="palette" type="as">
+ <default>['#171421',
+ '#c01c28',
+ '#26a269',
+ '#a2734c',
+ '#12488b',
+ '#a347ba',
+ '#2aa1b3',
+ '#d0cfcc',
+ '#5e5c64',
+ '#f66151',
+ '#33da7a',
+ '#e9ad0c',
+ '#2a7bde',
+ '#c061cb',
+ '#33c7de',
+ '#ffffff']</default>
+ <summary>Palette for terminal applications</summary>
+ </key>
+ <key name="font" type="s">
+ <default>'Monospace 12'</default>
+ <summary>A Pango font name and size</summary>
+ </key>
+ <key name="backspace-binding" enum="org.gnome.Terminal.EraseBinding">
+ <default>'ascii-delete'</default>
+ <summary>The code sequence the Backspace key generates</summary>
+ </key>
+ <key name="delete-binding" enum="org.gnome.Terminal.EraseBinding">
+ <default>'delete-sequence'</default>
+ <summary>The code sequence the Delete key generates</summary>
+ </key>
+ <key name="use-theme-colors" type="b">
+ <default>true</default>
+ <summary>Whether to use the colors from the theme for the terminal widget</summary>
+ </key>
+ <key name="use-system-font" type="b">
+ <default>true</default>
+ <summary>Whether to use the system monospace font</summary>
+ </key>
+ <key name="rewrap-on-resize" type="b">
+ <default>true</default>
+ <summary>Whether to rewrap the terminal contents on window resize</summary>
+ </key>
+ <key name="encoding" type="s">
+ <default>'UTF-8'</default>
+ <summary>Which encoding to use</summary>
+ </key>
+ <key name="cjk-utf8-ambiguous-width" enum="org.gnome.Terminal.CJKWidth">
+ <default>'narrow'</default>
+ <summary>Whether ambiguous-width characters are narrow or wide when using UTF-8 encoding</summary>
+ </key>
+ </schema>
+
+ <!-- Keybinding settings -->
+
+ <schema id="org.gnome.Terminal.Legacy.Keybindings">
+ <key name="new-tab" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;t'</default>
+ <summary>Keyboard shortcut to open a new tab</summary>
+ </key>
+ <key name="new-window" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;n'</default>
+ <summary>Keyboard shortcut to open a new window</summary>
+ </key>
+ <key name="save-contents" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to save the current tab contents to file</summary>
+ </key>
+ <key name="export" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to export the current tab contents to file in various formats</summary>
+ </key>
+ <key name="print" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to print the current tab contents to printer or file</summary>
+ </key>
+ <key name="close-tab" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;w'</default>
+ <summary>Keyboard shortcut to close a tab</summary>
+ </key>
+ <key name="close-window" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;q'</default>
+ <summary>Keyboard shortcut to close a window</summary>
+ </key>
+ <key name="copy" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;c'</default>
+ <summary>Keyboard shortcut to copy text</summary>
+ </key>
+ <key name="copy-html" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to copy text as HTML</summary>
+ </key>
+ <key name="paste" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;v'</default>
+ <summary>Keyboard shortcut to paste text</summary>
+ </key>
+ <key name="select-all" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to select all text</summary>
+ </key>
+ <key name="preferences" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to open the Preferences dialog</summary>
+ </key>
+ <key name="full-screen" type="s">
+ <default>'F11'</default>
+ <summary>Keyboard shortcut to toggle full screen mode</summary>
+ </key>
+ <key name="toggle-menubar" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to toggle the visibility of the menubar</summary>
+ </key>
+ <key name="read-only" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to toggle the read-only state</summary>
+ </key>
+ <key name="reset" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to reset the terminal</summary>
+ </key>
+ <key name="reset-and-clear" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to reset and clear the terminal</summary>
+ </key>
+ <key name="find" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;F'</default>
+ <summary>Keyboard shortcut to open the search dialog</summary>
+ </key>
+ <key name="find-next" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;G'</default>
+ <summary>Keyboard shortcut to find the next occurrence of the search term</summary>
+ </key>
+ <key name="find-previous" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;H'</default>
+ <summary>Keyboard shortcut to find the previous occurrence of the search term</summary>
+ </key>
+ <key name="find-clear" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;J'</default>
+ <summary>Keyboard shortcut to clear the find highlighting</summary>
+ </key>
+ <key name="prev-tab" type="s">
+ <default>'&lt;Control&gt;Page_Up'</default>
+ <summary>Keyboard shortcut to switch to the previous tab</summary>
+ </key>
+ <key name="next-tab" type="s">
+ <default>'&lt;Control&gt;Page_Down'</default>
+ <summary>Keyboard shortcut to switch to the next tab</summary>
+ </key>
+ <key name="move-tab-left" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;Page_Up'</default>
+ <summary>Keyboard shortcut to move the current tab to the left</summary>
+ </key>
+ <key name="move-tab-right" type="s">
+ <default>'&lt;Control&gt;&lt;Shift&gt;Page_Down'</default>
+ <summary>Keyboard shortcut to move the current tab to the right</summary>
+ </key>
+ <key name="detach-tab" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to detach current tab</summary>
+ </key>
+ <key name="switch-to-tab-1" type="s">
+ <default>'&lt;Alt&gt;1'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-2" type="s">
+ <default>'&lt;Alt&gt;2'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-3" type="s">
+ <default>'&lt;Alt&gt;3'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-4" type="s">
+ <default>'&lt;Alt&gt;4'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-5" type="s">
+ <default>'&lt;Alt&gt;5'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-6" type="s">
+ <default>'&lt;Alt&gt;6'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-7" type="s">
+ <default>'&lt;Alt&gt;7'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-8" type="s">
+ <default>'&lt;Alt&gt;8'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-9" type="s">
+ <default>'&lt;Alt&gt;9'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-10" type="s">
+ <default>'&lt;Alt&gt;0'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-11" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-12" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-13" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-14" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-15" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-16" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-17" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-18" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-19" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-20" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-21" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-22" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-23" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-24" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-25" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-26" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-27" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-28" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-29" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-30" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-31" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-32" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-33" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-34" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-35" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the numbered tab</summary>
+ </key>
+ <key name="switch-to-tab-last" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to switch to the last tab</summary>
+ </key>
+ <key name="help" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to launch help</summary>
+ </key>
+ <key name="zoom-in" type="s">
+ <default>'&lt;Control&gt;plus'</default>
+ <summary>Keyboard shortcut to make font larger</summary>
+ </key>
+ <key name="zoom-out" type="s">
+ <default>'&lt;Control&gt;minus'</default>
+ <summary>Keyboard shortcut to make font smaller</summary>
+ </key>
+ <key name="zoom-normal" type="s">
+ <default>'&lt;Control&gt;0'</default>
+ <summary>Keyboard shortcut to make font normal-size</summary>
+ </key>
+ <key name="header-menu" type="s">
+ <default>'disabled'</default>
+ <summary>Keyboard shortcut to show the primary menu</summary>
+ </key>
+ </schema>
+
+ <!-- Global settings -->
+
+ <schema id="org.gnome.Terminal.Legacy.Settings" path="/org/gnome/terminal/legacy/">
+
+ <key name="mnemonics-enabled" type="b">
+ <default>false</default>
+ <summary>Whether the menubar has access keys</summary>
+ <description>
+ Whether to have Alt+letter access keys for the menubar.
+ They may interfere with some applications run inside the terminal
+ so it’s possible to turn them off.
+ </description>
+ </key>
+
+ <key name="shortcuts-enabled" type="b">
+ <default>true</default>
+ <summary>Whether shortcuts are enabled</summary>
+ <description>
+ Whether shortcuts are enabled.
+ They may interfere with some applications run inside the terminal
+ so it’s possible to turn them off.
+ </description>
+ </key>
+
+ <key name="menu-accelerator-enabled" type="b">
+ <default>true</default>
+ <summary>Whether the standard GTK shortcut for menubar access is enabled</summary>
+ <description>
+ Normally you can access the menubar with F10. This can also
+ be customized via gtkrc (gtk-menu-bar-accel =
+ "whatever"). This option allows the standard menubar
+ accelerator to be disabled.
+ </description>
+ </key>
+
+ <key name="shell-integration-enabled" type="b">
+ <default>false</default>
+ <summary>Whether the shell integration is enabled</summary>
+ </key>
+
+ <key name="confirm-close" type="b">
+ <default>true</default>
+ <summary>Whether to ask for confirmation before closing a terminal</summary>
+ </key>
+
+ <key name="context-info" type="as">
+ <default>['numbers']</default>
+ <summary>Additional info section items to appear in the context menu</summary>
+ </key>
+
+ <key name="default-show-menubar" type="b">
+ <default>true</default>
+ <summary>Whether to show the menubar in new windows</summary>
+ </key>
+
+ <key name="new-terminal-mode" enum="org.gnome.Terminal.NewTerminalMode">
+ <default>'window'</default>
+ <summary>Whether to open new terminals as windows or tabs</summary>
+ </key>
+
+ <key name="tab-policy" enum="org.gnome.Terminal.TabsbarPolicy">
+ <default>'automatic'</default>
+ <summary>When to show the tabs bar</summary>
+ </key>
+
+ <key name="tab-position" enum="org.gnome.Terminal.TabPosition">
+ <default>'top'</default>
+ <summary>The position of the tab bar</summary>
+ </key>
+
+ <key name="theme-variant" enum="org.gnome.Terminal.ThemeVariant">
+ <default>'system'</default>
+ <summary>Which theme variant to use</summary>
+ </key>
+
+ <key name="new-tab-position" enum="org.gnome.Terminal.NewTabPosition">
+ <default>'last'</default>
+ <summary>Whether new tabs should open next to the current one or at the last position</summary>
+ </key>
+
+ <!-- Default terminal -->
+
+ <key name="always-check-default-terminal" type="b">
+ <default>true</default>
+ <summary>Always check whether GNOME Terminal is the default terminal</summary>
+ </key>
+
+ <!-- Note that changing the following settings will only take effect
+ when gnome-terminal-server is restarted.
+ -->
+
+ <key name="headerbar" type="mb">
+ <default>nothing</default>
+ </key>
+
+ <key name="unified-menu" type="b">
+ <default>true</default>
+ </key>
+
+ <!-- <child name="profiles" schema="org.gnome.Terminal.ProfilesList" /> -->
+
+ <child name="keybindings" schema="org.gnome.Terminal.Legacy.Keybindings" />
+
+ <key name="schema-version" type="u">
+ <default>3</default>
+ </key>
+
+ </schema>
+
+</schemalist>
diff --git a/src/org.gnome.Terminal.service.in b/src/org.gnome.Terminal.service.in
new file mode 100644
index 0000000..70c0930
--- /dev/null
+++ b/src/org.gnome.Terminal.service.in
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=@gt_dns_name@
+SystemdService=@gt_name@-server.service
+Exec=@libexecdir@/gnome-terminal-server
diff --git a/src/org.gnome.Terminal.xml b/src/org.gnome.Terminal.xml
new file mode 100644
index 0000000..a6ada0e
--- /dev/null
+++ b/src/org.gnome.Terminal.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Introspection 0.1//EN"
+ "http://www.freedesktop.org/software/dbus/introspection.dtd">
+<!--
+ Copyright © 2011 Christian Persch
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope conf 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/>.
+-->
+<node>
+ <interface name="org.gnome.Terminal.Factory0">
+ <annotation name="org.gtk.GDBus.C.Name" value="Factory" />
+ <method name="CreateInstance">
+ <arg type="a{sv}" name="options" direction="in" />
+ <arg type="o" name="receiver" direction="out" />
+ </method>
+ </interface>
+
+ <interface name="org.gnome.Terminal.Terminal0">
+ <annotation name="org.gtk.GDBus.C.Name" value="Receiver" />
+ <method name="Exec">
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true" />
+ <arg type="a{sv}" name="options" direction="in" />
+ <arg type="aay" name="arguments" direction="in">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true" />
+ </arg>
+ </method>
+
+ <signal name="ChildExited">
+ <arg type="i" name="exit_code" direction="in" />
+ </signal>
+ </interface>
+</node>
diff --git a/src/preferences.ui b/src/preferences.ui
new file mode 100644
index 0000000..ca58c65
--- /dev/null
+++ b/src/preferences.ui
@@ -0,0 +1,2486 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.19.0 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkListStore" id="new-terminal-mode-liststore">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="Open new terminal in new window">Window</col>
+ <col id="1">window</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Open new terminal in new tab">Tab</col>
+ <col id="1">tab</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="new-tab-position-liststore">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="New tab opens at the last position">Last</col>
+ <col id="1">last</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="New tab opens next to current tab">Next</col>
+ <col id="1">next</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="theme-variant-liststore">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="theme variant">Default</col>
+ <col id="1">system</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="theme variant">Light</col>
+ <col id="1">light</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" context="theme variant">Dark</col>
+ <col id="1">dark</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">16</property>
+ <property name="upper">511</property>
+ <property name="value">80</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="lower">4</property>
+ <property name="upper">511</property>
+ <property name="value">24</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="value">10000</property>
+ <property name="step_increment">1000</property>
+ <property name="page_increment">10000</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="lower">1.0</property>
+ <property name="upper">2.0</property>
+ <property name="value">1.0</property>
+ <property name="step_increment">0.05</property>
+ <property name="page_increment">0.25</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment6">
+ <property name="lower">1.0</property>
+ <property name="upper">2.0</property>
+ <property name="value">1.0</property>
+ <property name="step_increment">0.05</property>
+ <property name="page_increment">0.25</property>
+ </object>
+ <object class="GtkListStore" id="cjk-ambiguous-width-model">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="ambiguous-width characers are">Narrow</col>
+ <col id="1">narrow</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="ambiguous-width characers are">Wide</col>
+ <col id="1">wide</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="Cursor shape">Block</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Cursor shape">I-Beam</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Cursor shape">Underline</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model7">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="Cursor blink mode">Default</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Cursor blink mode">Enabled</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Cursor blink mode">Disabled</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model5">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="Text blink mode">Never</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Text blink mode">When focused</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Text blink mode">When unfocused</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Text blink mode">Always</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model2">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="When terminal commands set their own titles">Replace initial title</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="When terminal commands set their own titles">Append initial title</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="When terminal commands set their own titles">Prepend initial title</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="When terminal commands set their own titles">Keep initial title</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model3">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="When command exits">Exit the terminal</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="When command exits">Restart the command</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="When command exits">Hold the terminal open</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model4">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">GNOME</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">Tango</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">Linux console</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">XTerm</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">Rxvt</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">Solarized</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This is the name of a colour scheme">Custom</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model6">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="This refers to the Delete keybinding option">Automatic</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This refers to the Delete keybinding option">Control-H</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This refers to the Delete keybinding option">ASCII DEL</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This refers to the Delete keybinding option">Escape sequence</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="This refers to the Delete keybinding option">TTY Erase</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="preserve-working-directory-model">
+ <columns>
+ <!-- column-name label -->
+ <column type="gchararray"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" comments="Preserve working directory">Never</col>
+ <col id="1">never</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Preserve working directory">Shell only</col>
+ <col id="1">safe</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes" comments="Preserve working directory">Always</col>
+ <col id="1">always</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkApplicationWindow" id="preferences-dialog">
+ <property name="can_focus">False</property>
+ <property name="show_menubar">False</property>
+ <property name="role">gnome-terminal-preferences</property>
+ <property name="resizable">False</property>
+ <child>
+ <object class="GtkBox" id="dialogue-content-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkHBox" id="main-hbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkListBox" id="the-listbox">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="the-stack">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkEventBox" id="bug722114-comment25-1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="general-frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="default-show-menubar-checkbutton">
+ <property name="label" translatable="yes">_Show menubar by default in new terminals</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="disable-mnemonics-checkbutton">
+ <property name="label" translatable="yes">_Enable mnemonics (such as Alt+F to open the File menu)</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="disable-menu-accel-checkbutton">
+ <property name="label" translatable="yes">Enable the _menu accelerator key (F10 by default)</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="theme-variant-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Theme _variant:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">theme-variant-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="theme-variant-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">theme-variant-liststore</property>
+ <property name="id_column">1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1a"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="new-terminal-mode-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Open _new terminals in:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">new-terminal-mode-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="new-terminal-mode-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">new-terminal-mode-liststore</property>
+ <property name="id_column">1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer2a"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="new-tab-position-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">New tab _position:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">new-tab-position-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="new-tab-position-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">new-tab-position-liststore</property>
+ <property name="id_column">1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer3a"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="always-check-default-checkbutton">
+ <property name="label" translatable="yes">_Always check if default terminal</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="make-default-button">
+ <property name="label" translatable="yes">_Set as default terminal</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">general-prefs</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="bug722114-comment25-2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="keybindings-frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="disable-shortcuts-checkbutton">
+ <property name="label" translatable="yes">_Enable shortcuts</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkTreeView" id="accelerators-treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="rules_hint">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">shortcut-prefs</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="bug722114-comment25-3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkNotebook" id="profile-editor-notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkGrid" id="general-table">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Text Appearance</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="default-size-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="label" translatable="yes">Initial terminal si_ze:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">default-size-columns-spinbutton</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="default-size-columns-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSpinButton" id="default-size-columns-spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="numeric">True</property>
+ <property name="input_hints">GTK_INPUT_HINT_NO_EMOJI</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="default-size-columns-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">columns</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="default-size-rows-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSpinButton" id="default-size-rows-spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="numeric">True</property>
+ <property name="input_hints">GTK_INPUT_HINT_NO_EMOJI</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="default-size-rows-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">rows</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="buttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="default-size-reset-button">
+ <property name="label" translatable="yes">Rese_t</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="custom-font-checkbutton">
+ <property name="label" translatable="yes">Custom _font:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_start">12</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFontButton" id="font-selector">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="focus_on_click">False</property>
+ <property name="font">Sans 12</property>
+ <property name="title" translatable="yes">Choose A Terminal Font</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="label" translatable="yes">Cell spaci_ng:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cell-width-scale-spinbutton</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSpinButton" id="cell-width-scale-spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment5</property>
+ <property name="digits">2</property>
+ <property name="numeric">True</property>
+ <property name="input_hints">GTK_INPUT_HINT_NO_EMOJI</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cell-width-scale-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSpinButton" id="cell-height-scale-spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment6</property>
+ <property name="digits">2</property>
+ <property name="numeric">True</property>
+ <property name="input_hints">GTK_INPUT_HINT_NO_EMOJI</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cell-height-scale-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cell-scale-reset-button">
+ <property name="label" translatable="yes">Rese_t</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label481">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="label" translatable="yes">Allow b_linking text:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">text-blink-mode-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="text-blink-mode-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model5</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="label" translatable="yes">Cursor</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="left_attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label480">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="label" translatable="yes">Cursor _shape:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cursor-shape-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="cursor-shape-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model1</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="left_attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="label" translatable="yes">Cursor blin_king:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">cursor-blink-mode-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">7</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="cursor-blink-mode-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model7</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer5"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">7</property>
+ <property name="left_attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">12</property>
+ <property name="label" translatable="yes">Sound</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="top_attach">8</property>
+ <property name="left_attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="bell-checkbutton">
+ <property name="label" translatable="yes">Terminal _bell</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_start">12</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">9</property>
+ <property name="left_attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="valign">end</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkLabel" id="profile-uuid-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Profile ID:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="profile-uuid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">100</property>
+ <property name="left_attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Text</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox90">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox82">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label39">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Text and Background Color</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment10105">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox94">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="use-theme-colors-checkbutton">
+ <property name="label" translatable="yes">_Use colors from system theme</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="colors-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="builtin-color-schemes-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="color-scheme-combobox-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Built-in sche_mes:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">color-scheme-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="color-scheme-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="focus_on_click">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Text</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Background</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="foreground-colorpicker-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">_Default color:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">foreground-colorpicker</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="foreground-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Text Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="background-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Background Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="bold-color-checkbutton">
+ <property name="label" translatable="yes">Bo_ld color:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="bold-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Bold Text Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="underline-color-checkbutton">
+ <property name="label" translatable="yes">_Underline color:</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="underline-colorpicker">
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Underlined Text Color</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="cursor-colors-checkbutton">
+ <property name="label" translatable="yes">Cu_rsor color:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="cursor-foreground-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Cursor Foreground Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="cursor-background-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Cursor Background Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="highlight-colors-checkbutton">
+ <property name="label" translatable="yes">_Highlight color:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="highlight-foreground-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Highlight Foreground Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="highlight-background-colorpicker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <property name="title" translatable="yes">Choose Terminal Highlight Background Color</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="palette-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label42">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Palette</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment10106">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkGrid" id="table25">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="palette-optionmenu-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Built-in _schemes:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">palette-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="palette-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model4</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer4"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label43">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Color p_alette:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">palette-colorpicker-0</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="palette-table">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-0">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-8">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-9">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-10">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-11">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-12">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-13">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-14">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="palette-colorpicker-15">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">dummy</property>
+ <property name="show-editor">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">7</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="bold-is-bright-checkbutton">
+ <property name="label" translatable="yes">Show _bold text in bright colors</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label45">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Colors</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="table27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="scrollbar-checkbutton">
+ <property name="label" translatable="yes">_Show scrollbar</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="scroll-on-output-checkbutton">
+ <property name="label" translatable="yes">Scroll on _output</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="scroll-on-keystroke-checkbutton">
+ <property name="label" translatable="yes">Scroll on _keystroke</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="scrollback-limited-checkbutton">
+ <property name="label" translatable="yes">_Limit scrollback to:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ <property name="hexpand">False</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="scrollback-box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkSpinButton" id="scrollback-lines-spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment4</property>
+ <property name="numeric">True</property>
+ <property name="input_hints">GTK_INPUT_HINT_NO_EMOJI</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="scrollback-lines-spinbutton-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">lines</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">scrollback-lines-spinbutton</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label60">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Scrolling</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="login-shell-checkbutton">
+ <property name="label" translatable="yes">_Run command as a login shell</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="use-custom-command-checkbutton">
+ <property name="label" translatable="yes">Ru_n a custom command instead of my shell</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="custom-command-entry-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Custom co_mmand:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">custom-command-entry</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="custom-command-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="input_hints">GTK_INPUT_HINT_NO_EMOJI</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="preserve-working-directory-checkbutton-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Preserve working directory:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">preserve-working-directory-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="preserve-working-directory-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">preserve-working-directory-model</property>
+ <property name="focus_on_click">False</property>
+ <property name="halign">start</property>
+ <child>
+ <object class="GtkCellRendererText" id="preserve-working-directory-renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="exit-action-combobox-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">When command _exits:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">exit-action-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="exit-action-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model3</property>
+ <property name="focus_on_click">False</property>
+ <property name="halign">start</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer3"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Command</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="table30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="backspace-binding-combobox-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Backspace key generates:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">backspace-binding-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="backspace-binding-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model6</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer6"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">0</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="delete-binding-combobox-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Delete key generates:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">delete-binding-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="delete-binding-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model6</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer7"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="encoding-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Encoding:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">encoding-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="encoding-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="focus_on_click">False</property>
+ <property name="id_column">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="encoding-combobox-renderer"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cjk-ambiguous-width-label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ambiguous-_width characters:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="mnemonic_widget">cjk-ambiguous-width-combobox</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="cjk-ambiguous-width-combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">cjk-ambiguous-width-model</property>
+ <property name="focus_on_click">False</property>
+ <property name="id_column">1</property>
+ <child>
+ <object class="GtkCellRendererText" id="cjk-ambiguous-width-renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="left_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="enable-sixel-checkbutton">
+ <property name="label" translatable="yes">Enable _SIXEL images</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="reset-compat-defaults-button">
+ <property name="label" translatable="yes">_Reset Compatibility Options to Defaults</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="left_attach">0</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Compatibility</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">profile-prefs</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="dialogue-buttonbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="help-button">
+ <property name="label">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <property name="focus_on_click">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close-button">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <property name="focus_on_click">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopoverMenu" id="popover-menu">
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="margin">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="action-name">win.clone</property>
+ <property name="text" translatable="yes">Clone…</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="action-name">win.rename</property>
+ <property name="text" translatable="yes">Rename…</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="action-name">win.delete</property>
+ <property name="text" translatable="yes">Delete…</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="action-name">win.set-as-default</property>
+ <property name="text" translatable="yes">Set as default</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopover" id="popover-dialog">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="border_width">12</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="popover-dialog-label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="margin_bottom">6</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="popover-dialog-label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="popover-dialog-entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">start</property>
+ <property name="margin_top">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="popover-dialog-cancel">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="popover-dialog-ok">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/prefs-main.cc b/src/prefs-main.cc
new file mode 100644
index 0000000..568eb3c
--- /dev/null
+++ b/src/prefs-main.cc
@@ -0,0 +1,274 @@
+/*
+ * Copyright © 2008, 2010, 2011, 2021, 2022 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 <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-i18n.hh"
+#include "terminal-defines.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-settings-bridge-backend.hh"
+#include "terminal-settings-bridge-generated.h"
+
+// Reduce the default timeout to something that should still always work,
+// but not hang the process for long periods of time if something does
+// go wrong. See issue #7935.
+#define BRIDGE_TIMEOUT 5000 /* ms */
+
+static char* arg_profile_uuid = nullptr;
+static char* arg_hint = nullptr;
+static int arg_bus_fd = -1;
+static int arg_timestamp = -1;
+
+static const GOptionEntry options[] = {
+ {"profile", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &arg_profile_uuid, "Profile", "UUID"},
+ {"hint", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &arg_hint, "Hint", "HINT"},
+ {"bus-fd", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &arg_bus_fd, "Bus FD", "FD"},
+ {"timestamp", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &arg_timestamp, "Timestamp", "VALUE"},
+ {nullptr}
+};
+
+static GSettings*
+profile_from_uuid(TerminalApp* app,
+ char const* uuid_str) noexcept
+{
+ if (!uuid_str)
+ return nullptr;
+
+ auto const profiles_list = terminal_app_get_profiles_list(app);
+
+ GSettings* profile = nullptr;
+ if (g_str_equal(uuid_str, "default"))
+ profile = terminal_settings_list_ref_default_child(profiles_list);
+ else if (terminal_settings_list_valid_uuid(uuid_str))
+ profile = terminal_settings_list_ref_child(profiles_list, uuid_str);
+
+ return profile;
+}
+
+static void
+preferences_cb(GSimpleAction* action,
+ GVariant* parameter,
+ void* user_data)
+{
+ auto const app = terminal_app_get();
+
+ gs_free char* uuid_str = nullptr;
+ g_variant_lookup(parameter, "profile", "s", &uuid_str);
+
+ gs_unref_object auto profile = profile_from_uuid(app, uuid_str);
+
+ gs_free char* hint_str = nullptr;
+ g_variant_lookup(parameter, "hint", "s", &hint_str);
+
+ guint32 ts = 0;
+ g_variant_lookup(parameter, "timestamp", "u", &ts);
+
+ terminal_app_edit_preferences(app, profile, hint_str, ts);
+}
+
+static void
+connection_closed_cb(GDBusConnection* connection,
+ gboolean peer_vanished,
+ GError* error,
+ void* user_data)
+{
+ auto ptr = reinterpret_cast<void**>(user_data);
+
+ if (error)
+ g_printerr("D-Bus connection closed: %s\n", error->message);
+
+ // As per glib docs, unref the connection at this point
+ g_object_unref(g_steal_pointer(ptr));
+
+ // Exit cleanly
+ auto const app = g_application_get_default();
+ if (app)
+ g_application_quit(app);
+}
+
+int
+main(int argc,
+ char* argv[])
+{
+ // Sanitise environment
+ g_unsetenv("CHARSET");
+ g_unsetenv("DBUS_STARTER_BUS_TYPE");
+ // Not interested in silly debug spew polluting the journal, bug #749195
+ if (g_getenv("G_ENABLE_DIAGNOSTIC") == nullptr)
+ g_setenv("G_ENABLE_DIAGNOSTIC", "0", true);
+ // Maybe: g_setenv("GSETTINGS_BACKEND", "bridge", true);
+
+ if (setlocale(LC_ALL, "") == nullptr) {
+ g_printerr("Locale not supported.\n");
+ return _EXIT_FAILURE_UNSUPPORTED_LOCALE;
+ }
+
+ terminal_i18n_init(true);
+
+ char const* charset = nullptr;
+ if (!g_get_charset(&charset)) {
+ g_printerr("Non UTF-8 locale (%s) is not supported!\n", charset);
+ return _EXIT_FAILURE_NO_UTF8;
+ }
+
+ _terminal_debug_init();
+
+ auto const home_dir = g_get_home_dir();
+ if (home_dir == nullptr || chdir(home_dir) < 0)
+ (void) chdir("/");
+
+ g_set_prgname("gnome-terminal-preferences");
+ g_set_application_name(_("Preferences"));
+
+ gs_free_error GError *error = nullptr;
+ if (!gtk_init_with_args(&argc, &argv, nullptr, options, nullptr, &error)) {
+ g_printerr("Failed to parse arguments: %s\n", error->message);
+ return _EXIT_FAILURE_GTK_INIT;
+ }
+
+ gs_unref_object GDBusConnection* connection = nullptr;
+ gs_unref_object GSettingsBackend* backend = nullptr;
+ gs_unref_object GSimpleActionGroup* action_group = nullptr;
+ auto export_id = 0u;
+ if (arg_bus_fd != -1) {
+ gs_unref_object auto socket = g_socket_new_from_fd(arg_bus_fd, &error);
+ if (!socket) {
+ g_printerr("Failed to create bridge socket: %s\n", error->message);
+ close(arg_bus_fd);
+ return EXIT_FAILURE;
+ }
+
+ gs_unref_object auto sockconn =
+ g_socket_connection_factory_create_connection(socket);
+ if (!G_IS_IO_STREAM(sockconn)) {
+ g_printerr("Bridge socket has incorrect type %s\n", G_OBJECT_TYPE_NAME(sockconn));
+ return EXIT_FAILURE;
+ }
+
+ connection =
+ g_dbus_connection_new_sync(G_IO_STREAM(sockconn),
+ nullptr, // guid=nullptr for the client
+ GDBusConnectionFlags(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+ G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING),
+ nullptr, // auth observer,
+ nullptr, // cancellable,
+ &error);
+ if (!connection) {
+ g_printerr("Failed to create bus: %s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ GActionEntry const action_entries[] = {
+ { "preferences", preferences_cb, "a{sv}", nullptr, nullptr },
+ };
+
+ action_group = g_simple_action_group_new();
+ g_action_map_add_action_entries(G_ACTION_MAP(action_group),
+ action_entries, G_N_ELEMENTS(action_entries),
+ nullptr);
+
+ export_id = g_dbus_connection_export_action_group(connection,
+ TERMINAL_PREFERENCES_OBJECT_PATH,
+ G_ACTION_GROUP(action_group),
+ &error);
+ if (export_id == 0) {
+ g_printerr("Failed to export actions: %s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ g_dbus_connection_start_message_processing(connection);
+
+ gs_unref_object auto bridge =
+ terminal_settings_bridge_proxy_new_sync
+ (connection,
+ GDBusProxyFlags(
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ nullptr, // no name
+ TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH,
+ nullptr, // cancellable
+ &error);
+ if (!bridge) {
+ g_printerr("Failed to create settings bridge proxy: %s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ if (!_terminal_debug_on(TERMINAL_DEBUG_BRIDGE)) {
+ g_dbus_proxy_set_default_timeout(G_DBUS_PROXY(bridge), BRIDGE_TIMEOUT);
+ }
+
+ backend = terminal_settings_bridge_backend_new(bridge);
+
+ g_dbus_connection_set_exit_on_close(connection, false);
+ g_signal_connect(connection, "closed", G_CALLBACK(connection_closed_cb), &connection);
+ }
+
+ gs_unref_object auto app =
+ terminal_app_new(TERMINAL_PREFERENCES_APPLICATION_ID,
+ GApplicationFlags(G_APPLICATION_NON_UNIQUE |
+ G_APPLICATION_IS_SERVICE),
+ backend);
+
+ // Need to startup the application now, otherwise we can't use
+ // gtk_application_add_window() before g_application_run() below.
+ // This should always succeed.
+ if (!g_application_register(G_APPLICATION(app), nullptr, &error)) {
+ g_printerr("Failed to register application: %s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ // If started from gnome-terminal-server, the "preferences" action
+ // will be activated to actually show the preferences dialogue. However
+ // if started directly, need to show the dialogue right now.
+ if (!connection) {
+ gs_unref_object GSettings* profile = profile_from_uuid(TERMINAL_APP(app),
+ arg_profile_uuid);
+ if (arg_profile_uuid && !profile) {
+ g_printerr("No profile with UUID \"%s\": %s\n", arg_profile_uuid, error->message);
+ return EXIT_FAILURE;
+ }
+
+ terminal_app_edit_preferences(TERMINAL_APP(app),
+ profile,
+ arg_hint,
+ unsigned(arg_timestamp));
+ }
+
+ auto const r = g_application_run(app, 0, nullptr);
+
+ if (connection && export_id != 0) {
+ g_dbus_connection_unexport_action_group(connection, export_id);
+ export_id = 0;
+ }
+
+ if (connection &&
+ !g_dbus_connection_flush_sync(connection, nullptr, &error)) {
+ g_printerr("Failed to flush D-Bus connection: %s\n", error->message);
+ }
+
+ return r;
+}
diff --git a/src/prefs.gresource.xml b/src/prefs.gresource.xml
new file mode 100644
index 0000000..db48976
--- /dev/null
+++ b/src/prefs.gresource.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2012, 2022 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, or (at your option)
+ any later version.
+
+ This program is distributed in the hope conf 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/>.
+-->
+<gresources>
+ <gresource prefix="/org/gnome/terminal">
+ <file alias="css/terminal.css" compressed="true">terminal.common.css</file>
+ <file alias="ui/preferences.ui" compressed="true" preprocess="xml-stripblanks">preferences.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/profile-editor.cc b/src/profile-editor.cc
new file mode 100644
index 0000000..0cb99b6
--- /dev/null
+++ b/src/profile-editor.cc
@@ -0,0 +1,1504 @@
+/*
+ * Copyright © 2002 Havoc Pennington
+ * Copyright © 2002 Mathias Hasselmann
+ * Copyright © 2008, 2011, 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 <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "terminal-app.hh"
+#include "terminal-enums.hh"
+#include "profile-editor.hh"
+#include "terminal-prefs.hh"
+#include "terminal-schemas.hh"
+#include "terminal-type-builtins.hh"
+#include "terminal-util.hh"
+#include "terminal-profiles-list.hh"
+#include "terminal-libgsystem.hh"
+
+
+/* Wrapper around g_signal_connect that maintains a list of the
+ * handlers installed, and can disconnect them all. */
+typedef struct {
+ gpointer instance;
+ gulong handler_id;
+} ProfilePrefsSignal;
+
+static void
+profile_prefs_register_signal_handler (gpointer instance,
+ gulong handler_id)
+{
+ ProfilePrefsSignal sig;
+ sig.instance = instance;
+ sig.handler_id = handler_id;
+ g_array_append_val (the_pref_data->profile_signals, sig);
+}
+
+static gulong
+profile_prefs_signal_connect (gpointer instance,
+ const gchar *detailed_signal,
+ GCallback c_handler,
+ gpointer data)
+{
+ gulong handler_id = g_signal_connect(instance, detailed_signal, c_handler, data);
+ profile_prefs_register_signal_handler (instance, handler_id);
+ return handler_id;
+}
+
+static void
+profile_prefs_signal_handlers_disconnect_all (void)
+{
+ for (guint i = 0; i < the_pref_data->profile_signals->len; i++) {
+ ProfilePrefsSignal *sig = &g_array_index (the_pref_data->profile_signals, ProfilePrefsSignal, i);
+ g_signal_handler_disconnect (sig->instance, sig->handler_id);
+ }
+ g_array_set_size (the_pref_data->profile_signals, 0);
+}
+
+
+/* Wrappers around g_settings_bind and friends that maintain a list of the
+ * bindings installed, and can unbind them all. */
+typedef struct {
+ gpointer object;
+ char *property;
+} ProfilePrefsBinding;
+
+static void
+profile_prefs_register_settings_binding (gpointer object,
+ const char *property)
+{
+ ProfilePrefsBinding bind;
+ bind.object = object;
+ bind.property = g_strdup (property);
+ g_array_append_val (the_pref_data->profile_bindings, bind);
+}
+
+static void
+profile_prefs_settings_bind (GSettings *settings,
+ const gchar *key,
+ gpointer object,
+ const gchar *property,
+ GSettingsBindFlags flags)
+{
+ profile_prefs_register_settings_binding (object, property);
+ g_settings_bind (settings, key, object, property, flags);
+}
+
+static void
+profile_prefs_settings_bind_with_mapping (GSettings *settings,
+ const gchar *key,
+ gpointer object,
+ const gchar *property,
+ GSettingsBindFlags flags,
+ GSettingsBindGetMapping get_mapping,
+ GSettingsBindSetMapping set_mapping,
+ GType (*user_data)(void),
+ GDestroyNotify destroy)
+{
+ profile_prefs_register_settings_binding (object, property);
+ g_settings_bind_with_mapping (settings, key, object, property, flags, get_mapping, set_mapping, (void*)user_data, destroy);
+}
+
+static void
+profile_prefs_settings_bind_writable (GSettings *settings,
+ const gchar *key,
+ gpointer object,
+ const gchar *property,
+ gboolean inverted)
+{
+ profile_prefs_register_settings_binding (object, property);
+ g_settings_bind_writable (settings, key, object, property, inverted);
+}
+
+static void
+profile_prefs_settings_unbind_all (void)
+{
+ for (guint i = 0; i < the_pref_data->profile_bindings->len; i++) {
+ ProfilePrefsBinding *bind = &g_array_index (the_pref_data->profile_bindings, ProfilePrefsBinding, i);
+ g_settings_unbind (bind->object, bind->property);
+ g_free (bind->property);
+ }
+ g_array_set_size (the_pref_data->profile_bindings, 0);
+}
+
+
+typedef struct _TerminalColorScheme TerminalColorScheme;
+
+struct _TerminalColorScheme
+{
+ const char *name;
+ const GdkRGBA foreground;
+ const GdkRGBA background;
+};
+
+#define COLOR(r, g, b) { .red = (r) / 255.0, .green = (g) / 255.0, .blue = (b) / 255.0, .alpha = 1.0 }
+
+static const TerminalColorScheme color_schemes[] = {
+ { N_("Black on light yellow"),
+ COLOR (0x00, 0x00, 0x00),
+ COLOR (0xff, 0xff, 0xdd)
+ },
+ { N_("Black on white"),
+ COLOR (0x00, 0x00, 0x00),
+ COLOR (0xff, 0xff, 0xff)
+ },
+ { N_("Gray on black"),
+ COLOR (0xaa, 0xaa, 0xaa),
+ COLOR (0x00, 0x00, 0x00)
+ },
+ { N_("Green on black"),
+ COLOR (0x00, 0xff, 0x00),
+ COLOR (0x00, 0x00, 0x00)
+ },
+ { N_("White on black"),
+ COLOR (0xff, 0xff, 0xff),
+ COLOR (0x00, 0x00, 0x00)
+ },
+ /* Translators: "GNOME" is the name of a colour scheme, "light" can be translated */
+ { N_("GNOME light"),
+ COLOR (0x17, 0x14, 0x21), /* Palette entry 0 */
+ COLOR (0xff, 0xff, 0xff) /* Palette entry 15 */
+ },
+ /* Translators: "GNOME" is the name of a colour scheme, "dark" can be translated */
+ { N_("GNOME dark"),
+ COLOR (0xd0, 0xcf, 0xcc), /* Palette entry 7 */
+ COLOR (0x17, 0x14, 0x21) /* Palette entry 0 */
+ },
+ /* Translators: "Tango" is the name of a colour scheme, "light" can be translated */
+ { N_("Tango light"),
+ COLOR (0x2e, 0x34, 0x36),
+ COLOR (0xee, 0xee, 0xec)
+ },
+ /* Translators: "Tango" is the name of a colour scheme, "dark" can be translated */
+ { N_("Tango dark"),
+ COLOR (0xd3, 0xd7, 0xcf),
+ COLOR (0x2e, 0x34, 0x36)
+ },
+ /* Translators: "Solarized" is the name of a colour scheme, "light" can be translated */
+ { N_("Solarized light"),
+ COLOR (0x65, 0x7b, 0x83), /* 11: base00 */
+ COLOR (0xfd, 0xf6, 0xe3) /* 15: base3 */
+ },
+ /* Translators: "Solarized" is the name of a colour scheme, "dark" can be translated */
+ { N_("Solarized dark"),
+ COLOR (0x83, 0x94, 0x96), /* 12: base0 */
+ COLOR (0x00, 0x2b, 0x36) /* 8: base03 */
+ },
+};
+
+#define TERMINAL_PALETTE_SIZE (16)
+
+enum
+{
+ TERMINAL_PALETTE_GNOME = 0,
+ TERMINAL_PALETTE_TANGO = 1,
+ TERMINAL_PALETTE_LINUX = 2,
+ TERMINAL_PALETTE_XTERM = 3,
+ TERMINAL_PALETTE_RXVT = 4,
+ TERMINAL_PALETTE_SOLARIZED = 5,
+ TERMINAL_PALETTE_N_BUILTINS
+};
+
+static const GdkRGBA terminal_palettes[TERMINAL_PALETTE_N_BUILTINS][TERMINAL_PALETTE_SIZE] =
+{
+ /* Based on GNOME 3.32 palette: https://developer.gnome.org/hig/stable/icon-design.html.en#palette */
+ {
+ COLOR (0x17, 0x14, 0x21), /* Blend of Dark 4 and Black */
+ COLOR (0xc0, 0x1c, 0x28), /* Red 4 */
+ COLOR (0x26, 0xa2, 0x69), /* Green 5 */
+ COLOR (0xa2, 0x73, 0x4c), /* Blend of Brown 2 and Brown 3 */
+ COLOR (0x12, 0x48, 0x8b), /* Blend of Blue 5 and Dark 4 */
+ COLOR (0xa3, 0x47, 0xba), /* Purple 3 */
+ COLOR (0x2a, 0xa1, 0xb3), /* Linear addition Blue 5 + Green 5, darkened slightly */
+ COLOR (0xd0, 0xcf, 0xcc), /* Blend of Light 3 and Light 4 */
+ COLOR (0x5e, 0x5c, 0x64), /* Dark 2 */
+ COLOR (0xf6, 0x61, 0x51), /* Red 1 */
+ COLOR (0x33, 0xd1, 0x7a), /* Green 3 */
+ COLOR (0xe9, 0xad, 0x0c), /* Blend of Yellow 4 and Yellow 5 */
+ COLOR (0x2a, 0x7b, 0xde), /* Blend of Blue 3 and Blue 4 */
+ COLOR (0xc0, 0x61, 0xcb), /* Purple 2 */
+ COLOR (0x33, 0xc7, 0xde), /* Linear addition Blue 4 + Green 4, darkened slightly */
+ COLOR (0xff, 0xff, 0xff) /* Light 1 */
+ },
+
+ /* Tango palette */
+ {
+ COLOR (0x2e, 0x34, 0x36),
+ COLOR (0xcc, 0x00, 0x00),
+ COLOR (0x4e, 0x9a, 0x06),
+ COLOR (0xc4, 0xa0, 0x00),
+ COLOR (0x34, 0x65, 0xa4),
+ COLOR (0x75, 0x50, 0x7b),
+ COLOR (0x06, 0x98, 0x9a),
+ COLOR (0xd3, 0xd7, 0xcf),
+ COLOR (0x55, 0x57, 0x53),
+ COLOR (0xef, 0x29, 0x29),
+ COLOR (0x8a, 0xe2, 0x34),
+ COLOR (0xfc, 0xe9, 0x4f),
+ COLOR (0x72, 0x9f, 0xcf),
+ COLOR (0xad, 0x7f, 0xa8),
+ COLOR (0x34, 0xe2, 0xe2),
+ COLOR (0xee, 0xee, 0xec)
+ },
+
+ /* Linux palette */
+ {
+ COLOR (0x00, 0x00, 0x00),
+ COLOR (0xaa, 0x00, 0x00),
+ COLOR (0x00, 0xaa, 0x00),
+ COLOR (0xaa, 0x55, 0x00),
+ COLOR (0x00, 0x00, 0xaa),
+ COLOR (0xaa, 0x00, 0xaa),
+ COLOR (0x00, 0xaa, 0xaa),
+ COLOR (0xaa, 0xaa, 0xaa),
+ COLOR (0x55, 0x55, 0x55),
+ COLOR (0xff, 0x55, 0x55),
+ COLOR (0x55, 0xff, 0x55),
+ COLOR (0xff, 0xff, 0x55),
+ COLOR (0x55, 0x55, 0xff),
+ COLOR (0xff, 0x55, 0xff),
+ COLOR (0x55, 0xff, 0xff),
+ COLOR (0xff, 0xff, 0xff)
+ },
+
+ /* XTerm palette */
+ {
+ COLOR (0x00, 0x00, 0x00),
+ COLOR (0xcd, 0x00, 0x00),
+ COLOR (0x00, 0xcd, 0x00),
+ COLOR (0xcd, 0xcd, 0x00),
+ COLOR (0x00, 0x00, 0xee),
+ COLOR (0xcd, 0x00, 0xcd),
+ COLOR (0x00, 0xcd, 0xcd),
+ COLOR (0xe5, 0xe5, 0xe5),
+ COLOR (0x7f, 0x7f, 0x7f),
+ COLOR (0xff, 0x00, 0x00),
+ COLOR (0x00, 0xff, 0x00),
+ COLOR (0xff, 0xff, 0x00),
+ COLOR (0x5c, 0x5c, 0xff),
+ COLOR (0xff, 0x00, 0xff),
+ COLOR (0x00, 0xff, 0xff),
+ COLOR (0xff, 0xff, 0xff)
+ },
+
+ /* RXVT palette */
+ {
+ COLOR (0x00, 0x00, 0x00),
+ COLOR (0xcd, 0x00, 0x00),
+ COLOR (0x00, 0xcd, 0x00),
+ COLOR (0xcd, 0xcd, 0x00),
+ COLOR (0x00, 0x00, 0xcd),
+ COLOR (0xcd, 0x00, 0xcd),
+ COLOR (0x00, 0xcd, 0xcd),
+ COLOR (0xfa, 0xeb, 0xd7),
+ COLOR (0x40, 0x40, 0x40),
+ COLOR (0xff, 0x00, 0x00),
+ COLOR (0x00, 0xff, 0x00),
+ COLOR (0xff, 0xff, 0x00),
+ COLOR (0x00, 0x00, 0xff),
+ COLOR (0xff, 0x00, 0xff),
+ COLOR (0x00, 0xff, 0xff),
+ COLOR (0xff, 0xff, 0xff)
+ },
+
+ /* Solarized palette (1.0.0beta2): http://ethanschoonover.com/solarized */
+ {
+ COLOR (0x07, 0x36, 0x42), /* 0: base02 */
+ COLOR (0xdc, 0x32, 0x2f), /* 1: red */
+ COLOR (0x85, 0x99, 0x00), /* 2: green */
+ COLOR (0xb5, 0x89, 0x00), /* 3: yellow */
+ COLOR (0x26, 0x8b, 0xd2), /* 4: blue */
+ COLOR (0xd3, 0x36, 0x82), /* 5: magenta */
+ COLOR (0x2a, 0xa1, 0x98), /* 6: cyan */
+ COLOR (0xee, 0xe8, 0xd5), /* 7: base2 */
+ COLOR (0x00, 0x2b, 0x36), /* 8: base03 */
+ COLOR (0xcb, 0x4b, 0x16), /* 9: orange */
+ COLOR (0x58, 0x6e, 0x75), /* 10: base01 */
+ COLOR (0x65, 0x7b, 0x83), /* 11: base00 */
+ COLOR (0x83, 0x94, 0x96), /* 12: base0 */
+ COLOR (0x6c, 0x71, 0xc4), /* 13: violet */
+ COLOR (0x93, 0xa1, 0xa1), /* 14: base1 */
+ COLOR (0xfd, 0xf6, 0xe3) /* 15: base3 */
+ },
+};
+
+#undef COLOR
+
+static void profile_colors_notify_scheme_combo_cb (GSettings *profile,
+ const char *key,
+ GtkComboBox *combo);
+
+static void profile_palette_notify_scheme_combo_cb (GSettings *profile,
+ const char *key,
+ GtkComboBox *combo);
+
+static void profile_palette_notify_colorpickers_cb (GSettings *profile,
+ const char *key,
+ gpointer user_data);
+
+static void profile_notify_encoding_combo_cb (GSettings *profile,
+ const char *key,
+ GtkComboBox *combo);
+
+enum {
+ ENCODINGS_COL_ID,
+ ENCODINGS_COL_TEXT
+};
+
+/* gdk_rgba_equal is too strict! */
+static gboolean
+rgba_equal (const GdkRGBA *a,
+ const GdkRGBA *b)
+{
+ gdouble dr, dg, db;
+
+ dr = a->red - b->red;
+ dg = a->green - b->green;
+ db = a->blue - b->blue;
+
+ return (dr * dr + dg * dg + db * db) < 1e-4;
+}
+
+static gboolean
+palette_cmp (const GdkRGBA *ca,
+ const GdkRGBA *cb)
+{
+ guint i;
+
+ for (i = 0; i < TERMINAL_PALETTE_SIZE; ++i)
+ if (!rgba_equal (&ca[i], &cb[i]))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+palette_is_builtin (const GdkRGBA *colors,
+ gsize n_colors,
+ guint *n)
+{
+ guint i;
+
+ if (n_colors != TERMINAL_PALETTE_SIZE)
+ return FALSE;
+
+ for (i = 0; i < TERMINAL_PALETTE_N_BUILTINS; ++i)
+ {
+ if (palette_cmp (colors, terminal_palettes[i]))
+ {
+ *n = i;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+modify_palette_entry (GSettings *profile,
+ guint i,
+ const GdkRGBA *color)
+{
+ gs_free GdkRGBA *colors;
+ gsize n_colors;
+
+ /* FIXMEchpe: this can be optimised, don't really need to parse the colours! */
+
+ colors = terminal_g_settings_get_rgba_palette (profile, TERMINAL_PROFILE_PALETTE_KEY, &n_colors);
+
+ if (i < n_colors)
+ {
+ colors[i] = *color;
+ terminal_g_settings_set_rgba_palette (profile, TERMINAL_PROFILE_PALETTE_KEY,
+ colors, n_colors);
+ }
+}
+
+static void
+color_scheme_combo_changed_cb (GtkWidget *combo,
+ GParamSpec *pspec,
+ GSettings *profile)
+{
+ guint i;
+
+ i = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+
+ if (i < G_N_ELEMENTS (color_schemes))
+ {
+ g_signal_handlers_block_by_func (profile, (void*)profile_colors_notify_scheme_combo_cb, combo);
+ terminal_g_settings_set_rgba (profile, TERMINAL_PROFILE_FOREGROUND_COLOR_KEY, &color_schemes[i].foreground);
+ terminal_g_settings_set_rgba (profile, TERMINAL_PROFILE_BACKGROUND_COLOR_KEY, &color_schemes[i].background);
+ g_signal_handlers_unblock_by_func (profile, (void*)profile_colors_notify_scheme_combo_cb, combo);
+ }
+ else
+ {
+ /* "custom" selected, no change */
+ }
+}
+
+static void
+profile_colors_notify_scheme_combo_cb (GSettings *profile,
+ const char *key,
+ GtkComboBox *combo)
+{
+ GdkRGBA fg, bg;
+ guint i;
+
+ terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_FOREGROUND_COLOR_KEY, &fg);
+ terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_BACKGROUND_COLOR_KEY, &bg);
+
+ for (i = 0; i < G_N_ELEMENTS (color_schemes); ++i)
+ {
+ if (rgba_equal (&fg, &color_schemes[i].foreground) &&
+ rgba_equal (&bg, &color_schemes[i].background))
+ break;
+ }
+ /* If we didn't find a match, then we get the last combo box item which is "custom" */
+
+ g_signal_handlers_block_by_func (combo, (void*)color_scheme_combo_changed_cb, profile);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), i);
+ g_signal_handlers_unblock_by_func (combo, (void*)color_scheme_combo_changed_cb, profile);
+}
+
+static void
+palette_scheme_combo_changed_cb (GtkComboBox *combo,
+ GParamSpec *pspec,
+ GSettings *profile)
+{
+ int i;
+
+ i = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+
+ g_signal_handlers_block_by_func (profile, (void*)profile_colors_notify_scheme_combo_cb, combo);
+ if (i < TERMINAL_PALETTE_N_BUILTINS)
+ terminal_g_settings_set_rgba_palette (profile, TERMINAL_PROFILE_PALETTE_KEY,
+ terminal_palettes[i], TERMINAL_PALETTE_SIZE);
+ else
+ {
+ /* "custom" selected, no change */
+ }
+ g_signal_handlers_unblock_by_func (profile, (void*)profile_colors_notify_scheme_combo_cb, combo);
+}
+
+static void
+profile_palette_notify_scheme_combo_cb (GSettings *profile,
+ const char *key,
+ GtkComboBox *combo)
+{
+ gs_free GdkRGBA *colors;
+ gsize n_colors;
+ guint i;
+
+ colors = terminal_g_settings_get_rgba_palette (profile, TERMINAL_PROFILE_PALETTE_KEY, &n_colors);
+ if (!palette_is_builtin (colors, n_colors, &i))
+ /* If we didn't find a match, then we want the last combo
+ * box item which is "custom"
+ */
+ i = TERMINAL_PALETTE_N_BUILTINS;
+
+ g_signal_handlers_block_by_func (combo, (void*)palette_scheme_combo_changed_cb, profile);
+ gtk_combo_box_set_active (combo, i);
+ g_signal_handlers_unblock_by_func (combo, (void*)palette_scheme_combo_changed_cb, profile);
+}
+
+static void
+palette_color_notify_cb (GtkColorButton *button,
+ GParamSpec *pspec,
+ GSettings *profile)
+{
+ GdkRGBA color;
+ guint i;
+
+ gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &color);
+ i = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (button), "palette-entry-index"));
+
+ g_signal_handlers_block_by_func (profile, (void*)profile_palette_notify_colorpickers_cb, nullptr);
+ modify_palette_entry (profile, i, &color);
+ g_signal_handlers_unblock_by_func (profile, (void*)profile_palette_notify_colorpickers_cb, nullptr);
+}
+
+static void
+profile_palette_notify_colorpickers_cb (GSettings *profile,
+ const char *key,
+ gpointer user_data)
+{
+ GtkWidget *w;
+ GtkBuilder *builder = the_pref_data->builder;
+ gs_free GdkRGBA *colors;
+ gsize n_colors, i;
+
+ g_assert (strcmp (key, TERMINAL_PROFILE_PALETTE_KEY) == 0);
+
+ colors = terminal_g_settings_get_rgba_palette (profile, TERMINAL_PROFILE_PALETTE_KEY, &n_colors);
+
+ n_colors = MIN (n_colors, TERMINAL_PALETTE_SIZE);
+ for (i = 0; i < n_colors; i++)
+ {
+ char name[32];
+
+ g_snprintf (name, sizeof (name), "palette-colorpicker-%" G_GSIZE_FORMAT, i);
+ w = (GtkWidget *) gtk_builder_get_object (builder, name);
+
+ g_signal_handlers_block_by_func (w, (void*)palette_color_notify_cb, profile);
+ gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (w), &colors[i]);
+ g_signal_handlers_unblock_by_func (w, (void*)palette_color_notify_cb, profile);
+ }
+}
+
+static void
+custom_command_entry_changed_cb (GtkEntry *entry)
+{
+ const char *command;
+ gs_free_error GError *error = nullptr;
+
+ command = gtk_entry_get_text (entry);
+
+ if (command[0] == '\0' ||
+ g_shell_parse_argv (command, nullptr, nullptr, &error))
+ {
+ gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, nullptr);
+ }
+ else
+ {
+ gs_free char *tooltip;
+
+ gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning");
+
+ tooltip = g_strdup_printf (_("Error parsing command: %s"), error->message);
+ gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, tooltip);
+ }
+}
+
+static void
+default_size_reset_cb (GtkWidget *button,
+ GSettings *profile)
+{
+ g_settings_reset (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS_KEY);
+ g_settings_reset (profile, TERMINAL_PROFILE_DEFAULT_SIZE_ROWS_KEY);
+}
+
+static void
+cell_scale_reset_cb (GtkWidget *button,
+ GSettings *profile)
+{
+ g_settings_reset (profile, TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY);
+ g_settings_reset (profile, TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY);
+}
+
+static void
+reset_compat_defaults_cb (GtkWidget *button,
+ GSettings *profile)
+{
+ g_settings_reset (profile, TERMINAL_PROFILE_DELETE_BINDING_KEY);
+ g_settings_reset (profile, TERMINAL_PROFILE_BACKSPACE_BINDING_KEY);
+ g_settings_reset (profile, TERMINAL_PROFILE_ENCODING_KEY);
+ g_settings_reset (profile, TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY);
+ g_settings_reset (profile, TERMINAL_PROFILE_ENABLE_SIXEL_KEY);
+}
+
+static gboolean
+tree_model_id_to_iter_recurse (GtkTreeModel *model,
+ int id_column,
+ const char *active_id,
+ GtkTreeIter *iter,
+ GtkTreeIter *result_iter)
+{
+ do {
+ /* Descend the tree */
+ GtkTreeIter child_iter;
+ if (gtk_tree_model_iter_children(model, &child_iter, iter) &&
+ tree_model_id_to_iter_recurse (model, id_column, active_id, &child_iter, result_iter))
+ return TRUE;
+
+ gs_free char *id = nullptr;
+ gtk_tree_model_get (model, iter, id_column, &id, -1);
+ if (g_strcmp0 (id, active_id) == 0) {
+ *result_iter = *iter;
+ return TRUE;
+ }
+ } while (gtk_tree_model_iter_next (model, iter));
+
+ return FALSE;
+}
+
+static gboolean
+tree_model_id_to_iter (GtkTreeModel *model,
+ int id_column,
+ const char *active_id,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter first_iter;
+
+ return gtk_tree_model_get_iter_first(model, &first_iter) &&
+ tree_model_id_to_iter_recurse(model, id_column, active_id, &first_iter, iter);
+}
+
+static void
+profile_encoding_combo_changed_cb (GtkComboBox *combo,
+ GSettings *profile)
+{
+ GtkTreeIter iter;
+
+ if (!gtk_combo_box_get_active_iter(combo, &iter))
+ return;
+
+ gs_free char *encoding = nullptr;
+ gtk_tree_model_get(gtk_combo_box_get_model(combo),
+ &iter,
+ ENCODINGS_COL_ID, &encoding,
+ -1);
+ if (encoding == nullptr)
+ return;
+
+ g_signal_handlers_block_by_func (profile, (void*)profile_notify_encoding_combo_cb, combo);
+ g_settings_set_string(profile, TERMINAL_PROFILE_ENCODING_KEY, encoding);
+ g_signal_handlers_unblock_by_func (profile, (void*)profile_notify_encoding_combo_cb, combo);
+}
+
+static void
+profile_notify_encoding_combo_cb (GSettings *profile,
+ const char *key,
+ GtkComboBox *combo)
+{
+ gs_free char *encoding = nullptr;
+ g_settings_get(profile, key, "s", &encoding);
+
+ g_signal_handlers_block_by_func (combo, (void*)profile_encoding_combo_changed_cb, profile);
+
+ GtkTreeIter iter;
+ if (tree_model_id_to_iter(gtk_combo_box_get_model(combo),
+ ENCODINGS_COL_ID,
+ encoding,
+ &iter)) {
+ gtk_combo_box_set_active_iter(combo, &iter);
+ } else {
+ gtk_combo_box_set_active(combo, -1);
+ }
+
+ g_signal_handlers_unblock_by_func (combo, (void*)profile_encoding_combo_changed_cb, profile);
+}
+
+/*
+ * initialize widgets
+ */
+
+static void
+init_color_scheme_menu (GtkWidget *widget)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+ gs_unref_object GtkListStore *store;
+ guint i;
+
+ store = gtk_list_store_new (1, G_TYPE_STRING);
+ for (i = 0; i < G_N_ELEMENTS (color_schemes); ++i)
+ gtk_list_store_insert_with_values (store, &iter, -1,
+ 0, _(color_schemes[i].name),
+ -1);
+ gtk_list_store_insert_with_values (store, &iter, -1,
+ 0, _("Custom"),
+ -1);
+
+ gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (store));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), renderer, "text", 0, nullptr);
+}
+
+typedef enum {
+ GROUP_UTF8,
+ GROUP_CJKV,
+ GROUP_OBSOLETE,
+ LAST_GROUP
+} EncodingGroup;
+
+typedef struct {
+ const char *charset;
+ const char *name;
+ EncodingGroup group;
+} EncodingEntry;
+
+/* These MUST be sorted by charset so that bsearch can work! */
+static const EncodingEntry encodings[] = {
+ { "BIG5", N_("Chinese Traditional"), GROUP_CJKV },
+ { "BIG5-HKSCS", N_("Chinese Traditional"), GROUP_CJKV },
+ { "CP866", N_("Cyrillic/Russian"), GROUP_OBSOLETE },
+ { "EUC-JP", N_("Japanese"), GROUP_CJKV },
+ { "EUC-KR", N_("Korean"), GROUP_CJKV },
+ { "EUC-TW", N_("Chinese Traditional"), GROUP_CJKV },
+ { "GB18030", N_("Chinese Simplified"), GROUP_CJKV },
+ { "GB2312", N_("Chinese Simplified"), GROUP_CJKV },
+ { "GBK", N_("Chinese Simplified"), GROUP_CJKV },
+ { "IBM850", N_("Western"), GROUP_OBSOLETE },
+ { "IBM852", N_("Central European"), GROUP_OBSOLETE },
+ { "IBM855", N_("Cyrillic"), GROUP_OBSOLETE },
+ { "IBM857", N_("Turkish"), GROUP_OBSOLETE },
+ { "IBM862", N_("Hebrew"), GROUP_OBSOLETE },
+ { "IBM864", N_("Arabic"), GROUP_OBSOLETE },
+ { "ISO-8859-1", N_("Western"), GROUP_OBSOLETE },
+ { "ISO-8859-10", N_("Nordic"), GROUP_OBSOLETE },
+ { "ISO-8859-13", N_("Baltic"), GROUP_OBSOLETE },
+ { "ISO-8859-14", N_("Celtic"), GROUP_OBSOLETE },
+ { "ISO-8859-15", N_("Western"), GROUP_OBSOLETE },
+ { "ISO-8859-16", N_("Romanian"), GROUP_OBSOLETE },
+ { "ISO-8859-2", N_("Central European"), GROUP_OBSOLETE },
+ { "ISO-8859-3", N_("South European"), GROUP_OBSOLETE },
+ { "ISO-8859-4", N_("Baltic"), GROUP_OBSOLETE },
+ { "ISO-8859-5", N_("Cyrillic"), GROUP_OBSOLETE },
+ { "ISO-8859-6", N_("Arabic"), GROUP_OBSOLETE },
+ { "ISO-8859-7", N_("Greek"), GROUP_OBSOLETE },
+ { "ISO-8859-8", N_("Hebrew Visual"), GROUP_OBSOLETE },
+ { "ISO-8859-8-I", N_("Hebrew"), GROUP_OBSOLETE },
+ { "ISO-8859-9", N_("Turkish"), GROUP_OBSOLETE },
+ { "KOI8-R", N_("Cyrillic"), GROUP_OBSOLETE },
+ { "KOI8-U", N_("Cyrillic/Ukrainian"), GROUP_OBSOLETE },
+ { "MAC-CYRILLIC", N_("Cyrillic"), GROUP_OBSOLETE },
+ { "MAC_ARABIC", N_("Arabic"), GROUP_OBSOLETE },
+ { "MAC_CE", N_("Central European"), GROUP_OBSOLETE },
+ { "MAC_CROATIAN", N_("Croatian"), GROUP_OBSOLETE },
+ { "MAC_GREEK", N_("Greek"), GROUP_OBSOLETE },
+ { "MAC_HEBREW", N_("Hebrew"), GROUP_OBSOLETE },
+ { "MAC_ROMAN", N_("Western"), GROUP_OBSOLETE },
+ { "MAC_ROMANIAN", N_("Romanian"), GROUP_OBSOLETE },
+ { "MAC_TURKISH", N_("Turkish"), GROUP_OBSOLETE },
+ { "MAC_UKRAINIAN", N_("Cyrillic/Ukrainian"), GROUP_OBSOLETE },
+ { "SHIFT_JIS", N_("Japanese"), GROUP_CJKV },
+ { "TIS-620", N_("Thai"), GROUP_OBSOLETE },
+ { "UHC", N_("Korean"), GROUP_CJKV },
+ { "UTF-8", N_("Unicode"), GROUP_UTF8 },
+ { "WINDOWS-1250", N_("Central European"), GROUP_OBSOLETE },
+ { "WINDOWS-1251", N_("Cyrillic"), GROUP_OBSOLETE },
+ { "WINDOWS-1252", N_("Western"), GROUP_OBSOLETE },
+ { "WINDOWS-1253", N_("Greek"), GROUP_OBSOLETE },
+ { "WINDOWS-1254", N_("Turkish"), GROUP_OBSOLETE },
+ { "WINDOWS-1255", N_("Hebrew"), GROUP_OBSOLETE},
+ { "WINDOWS-1256", N_("Arabic"), GROUP_OBSOLETE },
+ { "WINDOWS-1257", N_("Baltic"), GROUP_OBSOLETE },
+ { "WINDOWS-1258", N_("Vietnamese"), GROUP_OBSOLETE },
+};
+
+static const struct {
+ EncodingGroup group;
+ const char *name;
+} encodings_group_names[] = {
+ { GROUP_UTF8, N_("Unicode") },
+ { GROUP_CJKV, N_("Legacy CJK Encodings") },
+ { GROUP_OBSOLETE, N_("Obsolete Encodings") },
+};
+
+#define EM_DASH "—"
+
+static void
+append_encodings_for_group (GtkTreeStore *store,
+ EncodingGroup group,
+ gboolean submenu)
+{
+ GtkTreeIter parent_iter;
+
+ if (submenu) {
+ gtk_tree_store_insert_with_values (store,
+ &parent_iter,
+ nullptr,
+ -1,
+ ENCODINGS_COL_ID, nullptr,
+ ENCODINGS_COL_TEXT, _(encodings_group_names[group].name),
+ -1);
+ }
+
+ for (guint i = 0; i < G_N_ELEMENTS (encodings); i++) {
+ if (encodings[i].group != group)
+ continue;
+
+ /* Skip encodings not supported by ICU */
+ if (terminal_util_translate_encoding (encodings[i].charset) == nullptr)
+ continue;
+
+ gs_free char *name = g_strdup_printf ("%s " EM_DASH " %s",
+ _(encodings[i].name), encodings[i].charset);
+
+ GtkTreeIter iter;
+ gtk_tree_store_insert_with_values (store,
+ &iter,
+ submenu ? &parent_iter : nullptr,
+ -1,
+ ENCODINGS_COL_ID, encodings[i].charset,
+ ENCODINGS_COL_TEXT, name,
+ -1);
+ }
+}
+
+static GtkTreeStore *
+encodings_tree_store_new (void)
+{
+ GtkTreeStore *store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+
+ append_encodings_for_group(store, GROUP_UTF8, FALSE); /* UTF-8 in main menu */
+ append_encodings_for_group(store, GROUP_CJKV, TRUE);
+ append_encodings_for_group(store, GROUP_OBSOLETE, TRUE);
+
+ return store;
+}
+
+static void
+init_encodings_combo (GtkWidget *widget)
+{
+ gs_unref_object GtkTreeStore *store = encodings_tree_store_new ();
+ gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (store));
+}
+
+static gboolean
+s_to_rgba (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ const char *s;
+ GdkRGBA color;
+
+ g_variant_get (variant, "&s", &s);
+ if (!gdk_rgba_parse (&color, s))
+ return FALSE;
+
+ color.alpha = 1.0;
+ g_value_set_boxed (value, &color);
+ return TRUE;
+}
+
+static GVariant *
+rgba_to_s (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ GdkRGBA *color;
+ gs_free char *s = nullptr;
+
+ color = reinterpret_cast<GdkRGBA*>(g_value_get_boxed (value));
+ if (color == nullptr)
+ return nullptr;
+
+ s = gdk_rgba_to_string (color);
+ return g_variant_new_string (s);
+}
+
+static gboolean
+string_to_enum (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GType (* get_type) (void) = (GType (*)(void))user_data;
+ GEnumClass *klass;
+ GEnumValue *eval = nullptr;
+ const char *s;
+ guint i;
+
+ g_variant_get (variant, "&s", &s);
+
+ klass = reinterpret_cast<GEnumClass*>(g_type_class_ref (get_type ()));
+ for (i = 0; i < klass->n_values; ++i) {
+ if (strcmp (klass->values[i].value_nick, s) != 0)
+ continue;
+
+ eval = &klass->values[i];
+ break;
+ }
+
+ if (eval)
+ g_value_set_int (value, eval->value);
+
+ g_type_class_unref (klass);
+
+ return eval != nullptr;
+}
+
+static GVariant *
+enum_to_string (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ GType (* get_type) (void) = (GType (*)(void))user_data;
+ GEnumClass *klass;
+ GEnumValue *eval = nullptr;
+ int val;
+ guint i;
+ GVariant *variant = nullptr;
+
+ val = g_value_get_int (value);
+
+ klass = reinterpret_cast<GEnumClass*>(g_type_class_ref (get_type ()));
+ for (i = 0; i < klass->n_values; ++i) {
+ if (klass->values[i].value != val)
+ continue;
+
+ eval = &klass->values[i];
+ break;
+ }
+
+ if (eval)
+ variant = g_variant_new_string (eval->value_nick);
+
+ g_type_class_unref (klass);
+
+ return variant;
+}
+
+static gboolean
+scrollbar_policy_to_bool (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ const char *str;
+
+ g_variant_get (variant, "&s", &str);
+ g_value_set_boolean (value, g_str_equal (str, "always"));
+
+ return TRUE;
+}
+
+static GVariant *
+bool_to_scrollbar_policy (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ return g_variant_new_string (g_value_get_boolean (value) ? "always" : "never");
+}
+
+static gboolean
+monospace_filter (const PangoFontFamily *family,
+ const PangoFontFace *face,
+ gpointer data)
+{
+ return pango_font_family_is_monospace ((PangoFontFamily *) family);
+}
+
+/* Called once per Preferences window, to initialize stuff that doesn't depend on the profile being edited */
+void
+profile_prefs_init (void)
+{
+ GtkWidget *w;
+ GtkBuilder *builder = the_pref_data->builder;
+ char *text;
+
+ the_pref_data->profile_signals = g_array_new (FALSE, FALSE, sizeof (ProfilePrefsSignal));
+ the_pref_data->profile_bindings = g_array_new (FALSE, FALSE, sizeof (ProfilePrefsBinding));
+
+ w = (GtkWidget *) gtk_builder_get_object (builder, "color-scheme-combobox");
+ init_color_scheme_menu (w);
+
+ w = (GtkWidget *) gtk_builder_get_object (builder, "encoding-combobox");
+ init_encodings_combo (w);
+
+ /* Translators: Appears as: [numeric entry] × width */
+ text = g_strdup_printf ("× %s", _("width"));
+ gtk_label_set_text ((GtkLabel *) gtk_builder_get_object (builder, "cell-width-scale-label"),
+ text);
+ g_free (text);
+ /* Translators: Appears as: [numeric entry] × height */
+ text = g_strdup_printf ("× %s", _("height"));
+ gtk_label_set_text ((GtkLabel *) gtk_builder_get_object (builder, "cell-height-scale-label"),
+ text);
+ g_free (text);
+}
+
+/* Called each time the user switches away from a profile, so it's no longer being edited */
+void
+profile_prefs_unload (void)
+{
+ profile_prefs_signal_handlers_disconnect_all ();
+ profile_prefs_settings_unbind_all ();
+}
+
+/* Called each time the user selects a new profile to edit */
+void
+profile_prefs_load (const char *uuid, GSettings *profile)
+{
+ GtkWidget *w;
+ GtkBuilder *builder = the_pref_data->builder;
+ guint i;
+
+ profile_prefs_unload ();
+
+ gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (builder, "profile-uuid")),
+ uuid);
+
+ profile_prefs_signal_connect (gtk_builder_get_object (builder, "default-size-reset-button"),
+ "clicked",
+ G_CALLBACK (default_size_reset_cb),
+ profile);
+ profile_prefs_signal_connect (gtk_builder_get_object (builder, "cell-scale-reset-button"),
+ "clicked",
+ G_CALLBACK (cell_scale_reset_cb),
+ profile);
+
+ /* Hook up the palette colorpickers and combo box */
+
+ for (i = 0; i < TERMINAL_PALETTE_SIZE; ++i)
+ {
+ char name[32];
+ char *text;
+
+ g_snprintf (name, sizeof (name), "palette-colorpicker-%u", i);
+ w = (GtkWidget *) gtk_builder_get_object (builder, name);
+
+ g_object_set_data (G_OBJECT (w), "palette-entry-index", GUINT_TO_POINTER (i));
+
+ text = g_strdup_printf (_("Choose Palette Color %u"), i);
+ gtk_color_button_set_title (GTK_COLOR_BUTTON (w), text);
+ g_free (text);
+
+ text = g_strdup_printf (_("Palette entry %u"), i);
+ gtk_widget_set_tooltip_text (w, text);
+ g_free (text);
+
+ profile_prefs_signal_connect (w, "notify::rgba",
+ G_CALLBACK (palette_color_notify_cb),
+ profile);
+ }
+
+ profile_palette_notify_colorpickers_cb (profile, TERMINAL_PROFILE_PALETTE_KEY, nullptr);
+ profile_prefs_signal_connect (profile, "changed::" TERMINAL_PROFILE_PALETTE_KEY,
+ G_CALLBACK (profile_palette_notify_colorpickers_cb),
+ nullptr);
+
+ w = (GtkWidget *) gtk_builder_get_object (builder, "palette-combobox");
+ profile_prefs_signal_connect (w, "notify::active",
+ G_CALLBACK (palette_scheme_combo_changed_cb),
+ profile);
+
+ profile_palette_notify_scheme_combo_cb (profile, TERMINAL_PROFILE_PALETTE_KEY, GTK_COMBO_BOX (w));
+ profile_prefs_signal_connect (profile, "changed::" TERMINAL_PROFILE_PALETTE_KEY,
+ G_CALLBACK (profile_palette_notify_scheme_combo_cb),
+ w);
+
+ /* Hook up the color scheme pickers and combo box */
+ w = (GtkWidget *) gtk_builder_get_object (builder, "color-scheme-combobox");
+ profile_prefs_signal_connect (w, "notify::active",
+ G_CALLBACK (color_scheme_combo_changed_cb),
+ profile);
+
+ profile_colors_notify_scheme_combo_cb (profile, nullptr, GTK_COMBO_BOX (w));
+ profile_prefs_signal_connect (profile, "changed::" TERMINAL_PROFILE_FOREGROUND_COLOR_KEY,
+ G_CALLBACK (profile_colors_notify_scheme_combo_cb),
+ w);
+ profile_prefs_signal_connect (profile, "changed::" TERMINAL_PROFILE_BACKGROUND_COLOR_KEY,
+ G_CALLBACK (profile_colors_notify_scheme_combo_cb),
+ w);
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "custom-command-entry"));
+ custom_command_entry_changed_cb (GTK_ENTRY (w));
+ profile_prefs_signal_connect (w, "changed",
+ G_CALLBACK (custom_command_entry_changed_cb), nullptr);
+
+ profile_prefs_signal_connect (gtk_builder_get_object (builder, "reset-compat-defaults-button"),
+ "clicked",
+ G_CALLBACK (reset_compat_defaults_cb),
+ profile);
+
+ profile_prefs_settings_bind_with_mapping (profile,
+ TERMINAL_PROFILE_BACKGROUND_COLOR_KEY,
+ gtk_builder_get_object (builder,
+ "background-colorpicker"),
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ profile_prefs_settings_bind_with_mapping (profile,
+ TERMINAL_PROFILE_BACKSPACE_BINDING_KEY,
+ gtk_builder_get_object (builder,
+ "backspace-binding-combobox"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ vte_erase_binding_get_type, nullptr);
+ profile_prefs_settings_bind (profile,
+ TERMINAL_PROFILE_BOLD_IS_BRIGHT_KEY,
+ gtk_builder_get_object (builder, "bold-is-bright-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY,
+ gtk_builder_get_object (builder,
+ "bold-color-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "bold-colorpicker"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY,
+ w,
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_BOLD_COLOR_KEY,
+ w,
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_NO_SENSITIVITY),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "cell-height-scale-spinbutton"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY,
+ gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (w)),
+ "value",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "cell-width-scale-spinbutton"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY,
+ gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (w)),
+ "value",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY,
+ gtk_builder_get_object (builder,
+ "cursor-colors-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "cursor-foreground-colorpicker"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY,
+ w,
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_CURSOR_FOREGROUND_COLOR_KEY,
+ w,
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_NO_SENSITIVITY),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "cursor-background-colorpicker"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY,
+ w,
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_CURSOR_BACKGROUND_COLOR_KEY,
+ w,
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_NO_SENSITIVITY),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY,
+ gtk_builder_get_object (builder,
+ "highlight-colors-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "highlight-foreground-colorpicker"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY,
+ w,
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_HIGHLIGHT_FOREGROUND_COLOR_KEY,
+ w,
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_NO_SENSITIVITY),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "highlight-background-colorpicker"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY,
+ w,
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_HIGHLIGHT_BACKGROUND_COLOR_KEY,
+ w,
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_NO_SENSITIVITY),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_CURSOR_SHAPE_KEY,
+ gtk_builder_get_object (builder,
+ "cursor-shape-combobox"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ vte_cursor_shape_get_type, nullptr);
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_CURSOR_BLINK_MODE_KEY,
+ gtk_builder_get_object (builder,
+ "cursor-blink-mode-combobox"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ vte_cursor_blink_mode_get_type, nullptr);
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_TEXT_BLINK_MODE_KEY,
+ gtk_builder_get_object (builder,
+ "text-blink-mode-combobox"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ vte_text_blink_mode_get_type, nullptr);
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CUSTOM_COMMAND_KEY,
+ gtk_builder_get_object (builder,
+ "custom-command-entry"),
+ "text",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "default-size-columns-spinbutton"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS_KEY,
+ gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (w)),
+ "value",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "default-size-rows-spinbutton"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_DEFAULT_SIZE_ROWS_KEY,
+ gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (w)),
+ "value",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_DELETE_BINDING_KEY,
+ gtk_builder_get_object (builder,
+ "delete-binding-combobox"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ vte_erase_binding_get_type, nullptr);
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_EXIT_ACTION_KEY,
+ gtk_builder_get_object (builder,
+ "exit-action-combobox"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ terminal_exit_action_get_type, nullptr);
+ w = (GtkWidget*) gtk_builder_get_object (builder, "font-selector");
+ gtk_font_chooser_set_filter_func (GTK_FONT_CHOOSER (w), monospace_filter, nullptr, nullptr);
+#if GTK_CHECK_VERSION (3, 24, 0)
+ gtk_font_chooser_set_level (GTK_FONT_CHOOSER (w),
+ GtkFontChooserLevel(GTK_FONT_CHOOSER_LEVEL_FAMILY |
+ GTK_FONT_CHOOSER_LEVEL_SIZE));
+#endif
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_FONT_KEY,
+ w,
+ "font-name",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ profile_prefs_settings_bind_with_mapping (profile,
+ TERMINAL_PROFILE_FOREGROUND_COLOR_KEY,
+ gtk_builder_get_object (builder,
+ "foreground-colorpicker"),
+ "rgba",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) s_to_rgba,
+ (GSettingsBindSetMapping) rgba_to_s,
+ nullptr, nullptr);
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_LOGIN_SHELL_KEY,
+ gtk_builder_get_object (builder,
+ "login-shell-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = GTK_WIDGET (gtk_builder_get_object (builder, "scrollback-lines-spinbutton"));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_SCROLLBACK_LINES_KEY,
+ gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (w)),
+ "value",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY,
+ gtk_builder_get_object (builder,
+ "scrollback-limited-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY,
+ gtk_builder_get_object (builder,
+ "scrollback-box"),
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_with_mapping (profile,
+ TERMINAL_PROFILE_SCROLLBAR_POLICY_KEY,
+ gtk_builder_get_object (builder,
+ "scrollbar-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) scrollbar_policy_to_bool,
+ (GSettingsBindSetMapping) bool_to_scrollbar_policy,
+ nullptr, nullptr);
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE_KEY,
+ gtk_builder_get_object (builder,
+ "scroll-on-keystroke-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_SCROLL_ON_OUTPUT_KEY,
+ gtk_builder_get_object (builder,
+ "scroll-on-output-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY,
+ gtk_builder_get_object (builder,
+ "custom-font-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN));
+
+ w = (GtkWidget *) gtk_builder_get_object (builder, "preserve-working-directory-combobox");
+ profile_prefs_settings_bind_with_mapping (profile, TERMINAL_PROFILE_PRESERVE_WORKING_DIRECTORY_KEY, w,
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET),
+ (GSettingsBindGetMapping) string_to_enum,
+ (GSettingsBindSetMapping) enum_to_string,
+ terminal_preserve_working_directory_get_type, nullptr);
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY,
+ gtk_builder_get_object (builder,
+ "use-custom-command-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_USE_THEME_COLORS_KEY,
+ gtk_builder_get_object (builder,
+ "use-theme-colors-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_AUDIBLE_BELL_KEY,
+ gtk_builder_get_object (builder, "bell-checkbutton"),
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ profile_prefs_settings_bind (profile,
+ TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY,
+ gtk_builder_get_object (builder, "custom-command-entry-label"),
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind (profile,
+ TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY,
+ gtk_builder_get_object (builder, "custom-command-entry"),
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind (profile,
+ TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY,
+ gtk_builder_get_object (builder, "font-selector"),
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind (profile,
+ TERMINAL_PROFILE_USE_THEME_COLORS_KEY,
+ gtk_builder_get_object (builder, "colors-box"),
+ "sensitive",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ profile_prefs_settings_bind_writable (profile,
+ TERMINAL_PROFILE_PALETTE_KEY,
+ gtk_builder_get_object (builder, "palette-box"),
+ "sensitive",
+ FALSE);
+
+ /* Compatibility options */
+ w = (GtkWidget *) gtk_builder_get_object (builder, "encoding-combobox");
+ profile_prefs_signal_connect (w, "changed",
+ G_CALLBACK (profile_encoding_combo_changed_cb),
+ profile);
+
+ profile_notify_encoding_combo_cb (profile, TERMINAL_PROFILE_ENCODING_KEY, GTK_COMBO_BOX (w));
+ profile_prefs_signal_connect (profile, "changed::" TERMINAL_PROFILE_ENCODING_KEY,
+ G_CALLBACK (profile_notify_encoding_combo_cb),
+ w);
+
+ w = (GtkWidget *) gtk_builder_get_object (builder, "cjk-ambiguous-width-combobox");
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY,
+ w,
+ "active-id",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+
+ w = (GtkWidget *) gtk_builder_get_object (builder, "enable-sixel-checkbutton");
+ profile_prefs_settings_bind (profile, TERMINAL_PROFILE_ENABLE_SIXEL_KEY, w,
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_SET));
+ gtk_widget_set_visible (w, (vte_get_feature_flags() & VTE_FEATURE_FLAG_SIXEL) != 0);
+}
+
+/* Called once per Preferences window, to destroy stuff that doesn't depend on the profile being edited */
+void
+profile_prefs_destroy (void)
+{
+ profile_prefs_unload ();
+
+ g_array_free (the_pref_data->profile_signals, TRUE);
+ g_array_free (the_pref_data->profile_bindings, TRUE);
+}
diff --git a/src/profile-editor.hh b/src/profile-editor.hh
new file mode 100644
index 0000000..e83d393
--- /dev/null
+++ b/src/profile-editor.hh
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2002 Havoc Pennington
+ *
+ * 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/>.
+ */
+
+#ifndef TERMINAL_PROFILE_EDITOR_H
+#define TERMINAL_PROFILE_EDITOR_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void profile_prefs_init (void);
+
+void profile_prefs_destroy (void);
+
+void profile_prefs_unload (void);
+
+void profile_prefs_load (const char *uuid,
+ GSettings *profile);
+
+G_END_DECLS
+
+#endif /* TERMINAL_PROFILE_EDITOR_H */
diff --git a/src/search-popover.ui b/src/search-popover.ui
new file mode 100644
index 0000000..4bea44d
--- /dev/null
+++ b/src/search-popover.ui
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.19.0 -->
+<interface>
+ <requires lib="gtk+" version="3.16"/>
+ <template class="TerminalSearchPopover" parent="GtkWindow">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Find</property>
+ <property name="resizable">False</property>
+ <property name="skip_pager_hint">True</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">12</property>
+ <property name="margin_right">12</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="box4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">30</property>
+ <property name="primary_icon_name">edit-find-symbolic</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">False</property>
+ <property name="placeholder_text" translatable="yes">Find</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="search_prev_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Find previous occurrence</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-up-symbolic</property>
+ <property name="use_fallback">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="search_next_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Find next occurrence</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-down-symbolic</property>
+ <property name="use_fallback">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="reveal_button">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Toggle search options</property>
+ <property name="focus_on_click">False</property>
+ <property name="active">True</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">view-context-menu-symbolic</property>
+ <property name="use_fallback">True</property>
+ </object>
+ </child>
+ <accessibility>
+ <relation type="controller-for" target="revealer"/>
+ </accessibility>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="focus_on_click">False</property>
+ <child>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-close-symbolic</property>
+ <property name="use_fallback">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="transition_type">none</property>
+ <property name="reveal_child">True</property>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="match_case_checkbutton">
+ <property name="label" translatable="yes">_Match case</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="entire_word_checkbutton">
+ <property name="label" translatable="yes">Match _entire word only</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="regex_checkbutton">
+ <property name="label" translatable="yes">Match as _regular expression</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="wrap_around_checkbutton">
+ <property name="label" translatable="yes">_Wrap around</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="xalign">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/server.cc b/src/server.cc
new file mode 100644
index 0000000..48c09f3
--- /dev/null
+++ b/src/server.cc
@@ -0,0 +1,195 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2008, 2010, 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <locale.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-gdbus.hh"
+#include "terminal-i18n.hh"
+#include "terminal-defines.hh"
+#include "terminal-libgsystem.hh"
+
+static char *app_id = nullptr;
+
+#define INACTIVITY_TIMEOUT (100 /* ms */)
+
+static gboolean
+option_app_id_cb (const gchar *option_name,
+ const gchar *value,
+ gpointer data,
+ GError **error)
+{
+ if (!g_application_id_is_valid (value) ||
+ !g_dbus_is_name (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 (app_id);
+ app_id = g_strdup (value);
+
+ return TRUE;
+}
+
+static const GOptionEntry options[] = {
+ { "app-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, (void*)option_app_id_cb, "Application ID", "ID" },
+ { nullptr }
+};
+
+/* We use up to 8 FDs per terminal, so let's bump the limit way up.
+ * However we need to restore the original limit for the child processes.
+ */
+
+static struct rlimit sv_rlimit_nofile;
+
+static void
+atfork_child_restore_rlimit_nofile (void)
+{
+ if (setrlimit (RLIMIT_NOFILE, &sv_rlimit_nofile) < 0)
+ _exit (127);
+}
+
+static gboolean
+increase_rlimit_nofile (void)
+{
+ struct rlimit l;
+
+ if (getrlimit (RLIMIT_NOFILE, &sv_rlimit_nofile) < 0)
+ return FALSE;
+
+ if (pthread_atfork (nullptr, nullptr, atfork_child_restore_rlimit_nofile) != 0)
+ return FALSE;
+
+ l.rlim_cur = l.rlim_max = sv_rlimit_nofile.rlim_max;
+ if (setrlimit (RLIMIT_NOFILE, &l) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+init_server (int argc,
+ char *argv[],
+ GApplication **application)
+{
+ if (G_UNLIKELY ((getuid () != geteuid () ||
+ getgid () != getegid ()) &&
+ geteuid () == 0 &&
+ getegid () == 0)) {
+ g_printerr ("Wrong euid/egid, exiting.\n");
+ return _EXIT_FAILURE_WRONG_ID;
+ }
+
+ if (setlocale (LC_ALL, "") == nullptr) {
+ g_printerr ("Locale not supported.\n");
+ return _EXIT_FAILURE_UNSUPPORTED_LOCALE;
+ }
+
+ terminal_i18n_init (TRUE);
+
+ g_unsetenv ("CHARSET");
+ const char *charset;
+ if (!g_get_charset (&charset)) {
+ g_printerr ("Non UTF-8 locale (%s) is not supported!\n", charset);
+ return _EXIT_FAILURE_NO_UTF8;
+ }
+
+ /* Sanitise environment */
+ g_unsetenv ("DBUS_STARTER_BUS_TYPE");
+
+ /* Not interested in silly debug spew polluting the journal, bug #749195 */
+ if (g_getenv ("G_ENABLE_DIAGNOSTIC") == nullptr)
+ g_setenv ("G_ENABLE_DIAGNOSTIC", "0", TRUE);
+
+ _terminal_debug_init ();
+
+ /* Change directory to $HOME so we don't prevent unmounting, e.g. if the
+ * factory is started by nautilus-open-terminal. See bug #565328.
+ * On failure back to /.
+ */
+ const char *home_dir = g_get_home_dir ();
+ if (home_dir == nullptr || chdir (home_dir) < 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+ chdir ("/");
+#pragma GCC diagnostic pop
+
+ g_set_prgname ("gnome-terminal-server");
+ g_set_application_name (_("Terminal"));
+
+ GError *error = nullptr;
+ if (!gtk_init_with_args (&argc, &argv, nullptr, options, nullptr, &error)) {
+ if (error != nullptr) {
+ g_printerr ("Failed to parse arguments: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ return _EXIT_FAILURE_GTK_INIT;
+ }
+
+ if (!increase_rlimit_nofile ()) {
+ auto const errsv = errno;
+ g_printerr ("Failed to increase RLIMIT_NOFILE: %s\n", g_strerror(errsv));
+ }
+
+ /* Now we can create the app */
+ auto const app = terminal_app_new (app_id, G_APPLICATION_IS_SERVICE, nullptr);
+ g_free (app_id);
+ app_id = nullptr;
+
+ /* We stay around a bit after the last window closed */
+ g_application_set_inactivity_timeout (app, INACTIVITY_TIMEOUT);
+
+ *application = app;
+ return 0;
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gs_unref_object GApplication *app = nullptr;
+ int r = init_server (argc, argv, &app);
+ if (r != 0)
+ return r;
+
+ /* Note that this flushes the D-Bus connection just before quitting,
+ * thus ensuring that all pending signal emissions (e.g. child-exited)
+ * are delivered.
+ */
+ return g_application_run (app, 0, nullptr);
+}
diff --git a/src/terminal-accels.cc b/src/terminal-accels.cc
new file mode 100644
index 0000000..6629810
--- /dev/null
+++ b/src/terminal-accels.cc
@@ -0,0 +1,615 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington, Red Hat Inc.
+ * Copyright © 2008, 2011, 2012, 2013 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 <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "terminal-accels.hh"
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-schemas.hh"
+#include "terminal-intl.hh"
+#include "terminal-util.hh"
+#include "terminal-libgsystem.hh"
+
+/* NOTES
+ *
+ * There are two sources of keybindings changes, from GSettings and from
+ * the accel map (happens with in-place menu editing).
+ *
+ * When a keybinding gsettings key changes, we propagate that into the
+ * accel map.
+ * When the accel map changes, we queue a sync to GSettings.
+ *
+ * To avoid infinite loops, we short-circuit in both directions
+ * if the value is unchanged from last known.
+ *
+ * In the keybinding editor, when editing or clearing an accel, we write
+ * the change directly to GSettings and rely on the callback to
+ * actually apply the change to the accel map.
+ */
+
+#define KEY_CLOSE_TAB "close-tab"
+#define KEY_CLOSE_WINDOW "close-window"
+#define KEY_COPY "copy"
+#define KEY_COPY_HTML "copy-html"
+#define KEY_DETACH_TAB "detach-tab"
+#define KEY_EXPORT "export"
+#define KEY_FIND "find"
+#define KEY_FIND_CLEAR "find-clear"
+#define KEY_FIND_PREV "find-previous"
+#define KEY_FIND_NEXT "find-next"
+#define KEY_FULL_SCREEN "full-screen"
+#define KEY_HEADER_MENU "header-menu"
+#define KEY_HELP "help"
+#define KEY_MOVE_TAB_LEFT "move-tab-left"
+#define KEY_MOVE_TAB_RIGHT "move-tab-right"
+#define KEY_NEW_TAB "new-tab"
+#define KEY_NEW_WINDOW "new-window"
+#define KEY_NEXT_TAB "next-tab"
+#define KEY_PASTE "paste"
+#define KEY_PREFERENCES "preferences"
+#define KEY_PREV_TAB "prev-tab"
+#define KEY_PRINT "print"
+#define KEY_READ_ONLY "read-only"
+#define KEY_RESET_AND_CLEAR "reset-and-clear"
+#define KEY_RESET "reset"
+#define KEY_SAVE_CONTENTS "save-contents"
+#define KEY_SELECT_ALL "select-all"
+#define KEY_TOGGLE_MENUBAR "toggle-menubar"
+#define KEY_ZOOM_IN "zoom-in"
+#define KEY_ZOOM_NORMAL "zoom-normal"
+#define KEY_ZOOM_OUT "zoom-out"
+#define KEY_SWITCH_TAB_PREFIX "switch-to-tab-"
+
+#if 1
+/*
+* We don't want to enable content saving until vte supports it async.
+* So we disable this code for stable versions.
+*/
+#include "terminal-version.hh"
+
+#if (TERMINAL_MINOR_VERSION & 1) != 0
+#define ENABLE_SAVE
+#else
+#undef ENABLE_SAVE
+#endif
+#endif
+
+typedef struct
+{
+ const char *user_visible_name;
+ const char *settings_key;
+ const char *action_name;
+ const GVariantType *action_parameter_type;
+ const char *action_parameter;
+ GVariant *parameter;
+ const char *shadow_action_name;
+} KeyEntry;
+
+typedef struct
+{
+ KeyEntry *key_entry;
+ guint n_elements;
+ const char *user_visible_name;
+ gboolean headerbar_only;
+} KeyEntryList;
+
+#define ENTRY_FULL(name, key, action, type, parameter, shadow_name) \
+ { name, key, "win." action, (const GVariantType *) type, parameter, nullptr, shadow_name }
+#define ENTRY(name, key, action, type, parameter) \
+ ENTRY_FULL (name, key, action, type, parameter, "win.shadow")
+#define ENTRY_MDI(name, key, action, type, parameter) \
+ ENTRY_FULL (name, key, action, type, parameter, "win.shadow-mdi")
+
+static KeyEntry file_entries[] = {
+ ENTRY (N_("New Tab"), KEY_NEW_TAB, "new-terminal", "(ss)", "('tab','current')" ),
+ ENTRY (N_("New Window"), KEY_NEW_WINDOW, "new-terminal", "(ss)", "('window','current')"),
+#ifdef ENABLE_SAVE
+ ENTRY (N_("Save Contents"), KEY_SAVE_CONTENTS, "save-contents", nullptr, nullptr ),
+#endif
+#ifdef ENABLE_EXPORT
+ ENTRY (N_("Export"), KEY_EXPORT, "export", nullptr, nullptr ),
+#endif
+#ifdef ENABLE_PRINT
+ ENTRY (N_("Print"), KEY_PRINT, "print", nullptr, nullptr ),
+#endif
+ ENTRY (N_("Close Tab"), KEY_CLOSE_TAB, "close", "s", "'tab'" ),
+ ENTRY (N_("Close Window"), KEY_CLOSE_WINDOW, "close", "s", "'window'" ),
+};
+
+static KeyEntry edit_entries[] = {
+ ENTRY (N_("Copy"), KEY_COPY, "copy", "s", "'text'" ),
+ ENTRY (N_("Copy as HTML"), KEY_COPY_HTML, "copy", "s", "'html'" ),
+ ENTRY (N_("Paste"), KEY_PASTE, "paste-text", nullptr, nullptr ),
+ ENTRY (N_("Select All"), KEY_SELECT_ALL, "select-all", nullptr, nullptr ),
+ ENTRY (N_("Preferences"), KEY_PREFERENCES, "edit-preferences", nullptr, nullptr ),
+};
+
+static KeyEntry search_entries[] = {
+ ENTRY (N_("Find"), KEY_FIND, "find", nullptr, nullptr),
+ ENTRY (N_("Find Next"), KEY_FIND_NEXT, "find-forward", nullptr, nullptr),
+ ENTRY (N_("Find Previous"), KEY_FIND_PREV, "find-backward", nullptr, nullptr),
+ ENTRY (N_("Clear Highlight"), KEY_FIND_CLEAR, "find-clear", nullptr, nullptr)
+};
+
+static KeyEntry view_entries[] = {
+ ENTRY (N_("Hide and Show Menubar"), KEY_TOGGLE_MENUBAR, "menubar-visible", nullptr, nullptr),
+ ENTRY (N_("Full Screen"), KEY_FULL_SCREEN, "fullscreen", nullptr, nullptr),
+ ENTRY (N_("Zoom In"), KEY_ZOOM_IN, "zoom-in", nullptr, nullptr),
+ ENTRY (N_("Zoom Out"), KEY_ZOOM_OUT, "zoom-out", nullptr, nullptr),
+ ENTRY (N_("Normal Size"), KEY_ZOOM_NORMAL, "zoom-normal", nullptr, nullptr)
+};
+
+static KeyEntry terminal_entries[] = {
+ ENTRY (N_("Read-Only"), KEY_READ_ONLY, "read-only", nullptr, nullptr ),
+ ENTRY (N_("Reset"), KEY_RESET, "reset", "b", "false"),
+ ENTRY (N_("Reset and Clear"), KEY_RESET_AND_CLEAR, "reset", "b", "true" ),
+};
+
+static KeyEntry tabs_entries[] = {
+ ENTRY_MDI (N_("Switch to Previous Tab"), KEY_PREV_TAB, "tab-switch-left", nullptr, nullptr),
+ ENTRY_MDI (N_("Switch to Next Tab"), KEY_NEXT_TAB, "tab-switch-right", nullptr, nullptr),
+ ENTRY_MDI (N_("Move Tab to the Left"), KEY_MOVE_TAB_LEFT, "tab-move-left", nullptr, nullptr),
+ ENTRY_MDI (N_("Move Tab to the Right"), KEY_MOVE_TAB_RIGHT, "tab-move-right", nullptr, nullptr),
+ ENTRY_MDI (N_("Detach Tab"), KEY_DETACH_TAB, "tab-detach", nullptr, nullptr),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "1", "active-tab", "i", "0"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "2", "active-tab", "i", "1"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "3", "active-tab", "i", "2"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "4", "active-tab", "i", "3"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "5", "active-tab", "i", "4"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "6", "active-tab", "i", "5"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "7", "active-tab", "i", "6"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "8", "active-tab", "i", "7"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "9", "active-tab", "i", "8"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "10", "active-tab", "i", "9"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "11", "active-tab", "i", "10"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "12", "active-tab", "i", "11"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "13", "active-tab", "i", "12"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "14", "active-tab", "i", "13"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "15", "active-tab", "i", "14"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "16", "active-tab", "i", "15"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "17", "active-tab", "i", "16"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "18", "active-tab", "i", "17"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "19", "active-tab", "i", "18"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "20", "active-tab", "i", "19"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "21", "active-tab", "i", "20"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "22", "active-tab", "i", "21"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "23", "active-tab", "i", "22"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "24", "active-tab", "i", "23"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "25", "active-tab", "i", "24"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "26", "active-tab", "i", "25"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "27", "active-tab", "i", "26"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "28", "active-tab", "i", "27"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "29", "active-tab", "i", "28"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "30", "active-tab", "i", "29"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "31", "active-tab", "i", "30"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "32", "active-tab", "i", "31"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "33", "active-tab", "i", "32"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "34", "active-tab", "i", "33"),
+ ENTRY_MDI (nullptr, KEY_SWITCH_TAB_PREFIX "35", "active-tab", "i", "34"),
+ ENTRY_MDI (N_("Switch to Last Tab"), KEY_SWITCH_TAB_PREFIX "last", "active-tab", "i", "-1"),
+};
+
+static KeyEntry help_entries[] = {
+ ENTRY (N_("Contents"), KEY_HELP, "help", nullptr, nullptr)
+};
+
+static KeyEntry global_entries[] = {
+ ENTRY (N_("Show Primary Menu"), KEY_HEADER_MENU, "header-menu", nullptr, nullptr),
+};
+
+#undef ENTRY_FULL
+#undef ENTRY
+#undef ENTRY_MDI
+
+static KeyEntryList all_entries[] =
+{
+ { file_entries, G_N_ELEMENTS (file_entries), N_("File"), FALSE },
+ { edit_entries, G_N_ELEMENTS (edit_entries), N_("Edit"), FALSE },
+ { view_entries, G_N_ELEMENTS (view_entries), N_("View"), FALSE },
+ { search_entries, G_N_ELEMENTS (search_entries), N_("Search"), FALSE },
+ { terminal_entries, G_N_ELEMENTS (terminal_entries), N_("Terminal"), FALSE },
+ { tabs_entries, G_N_ELEMENTS (tabs_entries), N_("Tabs"), FALSE },
+ { help_entries, G_N_ELEMENTS (help_entries), N_("Help"), FALSE },
+ { global_entries, G_N_ELEMENTS (global_entries), N_("Global"), TRUE },
+};
+
+enum
+{
+ ACTION_COLUMN,
+ KEYVAL_COLUMN,
+ N_COLUMNS
+};
+
+static GHashTable *settings_key_to_entry;
+static GSettings *keybinding_settings = nullptr;
+
+GS_DEFINE_CLEANUP_FUNCTION(GtkTreePath*, _terminal_local_free_tree_path, gtk_tree_path_free)
+#define terminal_free_tree_path __attribute__((__cleanup__(_terminal_local_free_tree_path)))
+
+static char*
+binding_name (guint keyval,
+ GdkModifierType mask)
+{
+ if (keyval != 0)
+ return gtk_accelerator_name (keyval, mask);
+
+ return g_strdup ("disabled");
+}
+
+static void
+key_changed_cb (GSettings *settings,
+ const char *settings_key,
+ gpointer user_data)
+{
+ GtkApplication *application = (GtkApplication*)user_data;
+ const gchar *accels[2] = { nullptr, nullptr };
+
+ _terminal_debug_print (TERMINAL_DEBUG_ACCELS,
+ "key %s changed\n",
+ settings_key);
+
+ KeyEntry *key_entry = reinterpret_cast<KeyEntry*>
+ (g_hash_table_lookup (settings_key_to_entry, settings_key));
+ if (!key_entry)
+ {
+ /* shouldn't really happen, but let's be safe */
+ _terminal_debug_print (TERMINAL_DEBUG_ACCELS,
+ " WARNING: KeyEntry for changed key not found, bailing out\n");
+ return;
+ }
+
+ gs_free char *value = g_settings_get_string (settings, settings_key);
+
+ gs_free char *detailed = g_action_print_detailed_name (key_entry->action_name,
+ key_entry->parameter);
+ gs_unref_variant GVariant *shadow_parameter = g_variant_new_string (detailed);
+ gs_free char *shadow_detailed = g_action_print_detailed_name (key_entry->shadow_action_name,
+ shadow_parameter);
+
+ /* We want to always consume the action's accelerators, even if the corresponding
+ * action is insensitive, so the corresponding shortcut key escape code isn't sent
+ * to the terminal. See bug #453193, bug #138609, and bug #559728.
+ * Since GtkApplication's accelerators don't use GtkAccelGroup, we have no way
+ * to intercept/chain on its activation. The only way to do this that I found
+ * was to install an extra action with the same accelerator that shadows
+ * the real action and gets activated when the shadowed action is disabled.
+ */
+
+ if (g_str_equal (value, "disabled")) {
+ accels[0] = nullptr;
+ } else {
+ accels[0] = value;
+ }
+
+ gtk_application_set_accels_for_action (application,
+ detailed,
+ accels);
+ gtk_application_set_accels_for_action (application,
+ shadow_detailed,
+ accels);
+}
+
+static void
+add_accel_entries (GApplication*application,
+ KeyEntry *entries,
+ guint n_entries)
+{
+ guint j;
+
+ for (j = 0; j < n_entries; ++j) {
+ KeyEntry *key_entry = &entries[j];
+ if (key_entry->action_parameter) {
+ GError *err = nullptr;
+ key_entry->parameter = g_variant_parse (key_entry->action_parameter_type,
+ key_entry->action_parameter,
+ nullptr, nullptr, &err);
+ g_assert_no_error (err);
+
+ g_assert (key_entry->parameter != nullptr);
+ }
+
+ g_hash_table_insert (settings_key_to_entry,
+ (gpointer) key_entry->settings_key,
+ key_entry);
+
+ key_changed_cb (keybinding_settings, key_entry->settings_key, application);
+ }
+}
+
+void
+terminal_accels_init (GApplication *application,
+ GSettings *settings,
+ gboolean use_headerbar)
+{
+ guint i, j;
+
+ keybinding_settings = (GSettings*)g_object_ref (settings);
+
+ settings_key_to_entry = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* Initialise names of tab_switch_entries */
+ j = 1;
+ for (i = 0; i < G_N_ELEMENTS (tabs_entries); i++)
+ {
+ gs_free char *name = nullptr;
+
+ if (tabs_entries[i].user_visible_name != nullptr)
+ continue;
+
+ name = g_strdup_printf (_("Switch to Tab %u"), j++);
+ tabs_entries[i].user_visible_name = g_intern_string (name);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (all_entries); ++i) {
+ if (!use_headerbar && all_entries[i].headerbar_only)
+ continue;
+
+ add_accel_entries (application, all_entries[i].key_entry, all_entries[i].n_elements);
+ }
+
+ g_signal_connect (keybinding_settings, "changed", G_CALLBACK (key_changed_cb), application);
+}
+
+void
+terminal_accels_shutdown (void)
+{
+ guint i, j;
+
+ for (i = 0; i < G_N_ELEMENTS (all_entries); ++i) {
+ for (j = 0; j < all_entries[i].n_elements; ++j) {
+ KeyEntry *key_entry;
+
+ key_entry = &(all_entries[i].key_entry[j]);
+ if (key_entry->parameter)
+ g_variant_unref (key_entry->parameter);
+ }
+ }
+
+ g_signal_handlers_disconnect_by_func (keybinding_settings,
+ (void*)key_changed_cb,
+ g_application_get_default ());
+
+ g_clear_pointer (&settings_key_to_entry, (GDestroyNotify) g_hash_table_unref);
+ g_clear_object (&keybinding_settings);
+}
+
+static gboolean
+foreach_row_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ const char *key = (char const*)data;
+ KeyEntry *key_entry;
+
+ gtk_tree_model_get (model, iter,
+ KEYVAL_COLUMN, &key_entry,
+ -1);
+
+ if (key_entry == nullptr ||
+ !g_str_equal (key_entry->settings_key, key))
+ return FALSE;
+
+ gtk_tree_model_row_changed (model, path, iter);
+ return TRUE;
+}
+
+static void
+treeview_key_changed_cb (GSettings *settings,
+ const char *key,
+ GtkTreeView *tree_view)
+{
+ gtk_tree_model_foreach (gtk_tree_view_get_model (tree_view), foreach_row_cb, (gpointer) key);
+}
+
+static void
+accel_set_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ KeyEntry *ke;
+
+ gtk_tree_model_get (model, iter,
+ KEYVAL_COLUMN, &ke,
+ -1);
+
+ if (ke == nullptr) {
+ /* This is a title row */
+ g_object_set (cell,
+ "visible", FALSE,
+ nullptr);
+ } else {
+ gs_free char *value;
+ guint key;
+ GdkModifierType mods;
+ gboolean writable;
+ GtkWidget *button = (GtkWidget*)data;
+
+ value = g_settings_get_string (keybinding_settings, ke->settings_key);
+ gtk_accelerator_parse (value, &key, &mods);
+
+ writable = g_settings_is_writable (keybinding_settings, ke->settings_key) &&
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "sensitive", writable,
+ "editable", writable,
+ "accel-key", key,
+ "accel-mods", mods,
+ nullptr);
+ }
+}
+
+static void
+accel_update (GtkTreeView *view,
+ GtkCellRendererAccel *cell,
+ gchar *path_string,
+ guint keyval,
+ GdkModifierType mask)
+{
+ GtkTreeModel *model;
+ terminal_free_tree_path GtkTreePath *path = nullptr;
+ GtkTreeIter iter;
+ KeyEntry *ke;
+ gs_free char *str = nullptr;
+
+ model = gtk_tree_view_get_model (view);
+
+ path = gtk_tree_path_new_from_string (path_string);
+ if (!path)
+ return;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ return;
+
+ gtk_tree_model_get (model, &iter, KEYVAL_COLUMN, &ke, -1);
+
+ /* sanity check */
+ if (ke == nullptr)
+ return;
+
+ str = binding_name (keyval, mask);
+ g_settings_set_string (keybinding_settings, ke->settings_key, str);
+}
+
+static void
+accel_edited_callback (GtkCellRendererAccel *cell,
+ gchar *path_string,
+ guint keyval,
+ GdkModifierType mask,
+ guint hardware_keycode,
+ GtkTreeView *view)
+{
+ accel_update (view, cell, path_string, keyval, mask);
+}
+
+static void
+accel_cleared_callback (GtkCellRendererAccel *cell,
+ gchar *path_string,
+ GtkTreeView *view)
+{
+ accel_update (view, cell, path_string, 0, GdkModifierType(0));
+}
+
+static void
+treeview_destroy_cb (GtkWidget *tree_view,
+ gpointer user_data)
+{
+ g_signal_handlers_disconnect_by_func (keybinding_settings,
+ (void*)treeview_key_changed_cb,
+ tree_view);
+}
+
+#ifdef ENABLE_DEBUG
+static void
+row_changed (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ _terminal_debug_print (TERMINAL_DEBUG_ACCELS,
+ "ROW-CHANGED [%s]\n", gtk_tree_path_to_string (path) /* leak */);
+}
+#endif
+
+void
+terminal_accels_fill_treeview (GtkWidget *tree_view,
+ GtkWidget *disable_shortcuts_button)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+ gs_unref_object GtkTreeStore *tree = nullptr;
+ guint i;
+
+ /* Column 1 */
+ cell_renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("_Action"),
+ cell_renderer,
+ "text", ACTION_COLUMN,
+ nullptr);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ /* Column 2 */
+ cell_renderer = gtk_cell_renderer_accel_new ();
+ g_object_set (cell_renderer,
+ "editable", TRUE,
+ "accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_GTK,
+ nullptr);
+
+ g_signal_connect (cell_renderer, "accel-edited",
+ G_CALLBACK (accel_edited_callback), tree_view);
+ g_signal_connect (cell_renderer, "accel-cleared",
+ G_CALLBACK (accel_cleared_callback), tree_view);
+
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Shortcut _Key"));
+ gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+ gtk_tree_view_column_set_cell_data_func (column, cell_renderer, accel_set_func,
+ disable_shortcuts_button, nullptr);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ /* Add the data */
+
+ tree = gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
+
+#ifdef ENABLE_DEBUG
+ _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_ACCELS)
+ g_signal_connect (tree, "row-changed", G_CALLBACK (row_changed), nullptr);
+#endif
+
+ for (i = 0; i < G_N_ELEMENTS (all_entries); ++i)
+ {
+ GtkTreeIter parent_iter;
+ guint j;
+
+ gtk_tree_store_insert_with_values (tree, &parent_iter, nullptr, -1,
+ ACTION_COLUMN, _(all_entries[i].user_visible_name),
+ KEYVAL_COLUMN, nullptr,
+ -1);
+
+ for (j = 0; j < all_entries[i].n_elements; ++j)
+ {
+ KeyEntry *key_entry = &(all_entries[i].key_entry[j]);
+ GtkTreeIter iter;
+
+ gtk_tree_store_insert_with_values (tree, &iter, &parent_iter, -1,
+ ACTION_COLUMN, _(key_entry->user_visible_name),
+ KEYVAL_COLUMN, key_entry,
+ -1);
+ }
+ }
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), GTK_TREE_MODEL (tree));
+
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));
+
+ g_signal_connect (keybinding_settings, "changed",
+ G_CALLBACK (treeview_key_changed_cb), tree_view);
+ g_signal_connect (tree_view, "destroy",
+ G_CALLBACK (treeview_destroy_cb), tree);
+}
diff --git a/src/terminal-accels.hh b/src/terminal-accels.hh
new file mode 100644
index 0000000..8c860a3
--- /dev/null
+++ b/src/terminal-accels.hh
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ *
+ * 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/>.
+ */
+
+#ifndef TERMINAL_ACCELS_H
+#define TERMINAL_ACCELS_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void terminal_accels_init (GApplication *application,
+ GSettings *settings,
+ gboolean use_headerbar);
+
+void terminal_accels_shutdown (void);
+
+void terminal_accels_fill_treeview (GtkWidget *treeview,
+ GtkWidget *disable_shortcuts_button);
+
+G_END_DECLS
+
+#endif /* TERMINAL_ACCELS_H */
diff --git a/src/terminal-app.cc b/src/terminal-app.cc
new file mode 100644
index 0000000..b6740e3
--- /dev/null
+++ b/src/terminal-app.cc
@@ -0,0 +1,1671 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2008, 2010, 2011, 2015, 2017, 2022 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 <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
+#include "terminal-intl.hh"
+#include "terminal-debug.hh"
+#include "terminal-app.hh"
+#include "terminal-accels.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-profiles-list.hh"
+#include "terminal-util.hh"
+#include "terminal-schemas.hh"
+#include "terminal-settings-utils.hh"
+#include "terminal-defines.hh"
+#include "terminal-libgsystem.hh"
+
+#ifdef TERMINAL_SERVER
+#include "terminal-gdbus.hh"
+#include "terminal-prefs-process.hh"
+#include "terminal-screen-container.hh"
+#include "terminal-screen.hh"
+#include "terminal-window.hh"
+#endif
+
+#ifdef TERMINAL_PREFERENCES
+#include "terminal-prefs.hh"
+#endif
+
+#ifndef TERMINAL_SERVER
+#undef ENABLE_SEARCH_PROVIDER
+#endif
+
+#ifdef ENABLE_SEARCH_PROVIDER
+#include "terminal-search-provider.hh"
+#endif /* ENABLE_SEARCH_PROVIDER */
+
+#include <sys/wait.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#define GNOME_TERMINAL_PREFERENCES_ICON_NAME "org.gnome.Terminal.Preferences"
+
+#define DESKTOP_INTERFACE_SETTINGS_SCHEMA "org.gnome.desktop.interface"
+
+#define SYSTEM_PROXY_SETTINGS_SCHEMA "org.gnome.system.proxy"
+#define SYSTEM_HTTP_PROXY_SETTINGS_SCHEMA "org.gnome.system.proxy.http"
+#define SYSTEM_HTTPS_PROXY_SETTINGS_SCHEMA "org.gnome.system.proxy.https"
+#define SYSTEM_FTP_PROXY_SETTINGS_SCHEMA "org.gnome.system.proxy.ftp"
+#define SYSTEM_SOCKS_PROXY_SETTINGS_SCHEMA "org.gnome.system.proxy.socks"
+
+#define GTK_SETTING_PREFER_DARK_THEME "gtk-application-prefer-dark-theme"
+
+#define GTK_DEBUG_SETTING_SCHEMA "org.gtk.Settings.Debug"
+
+#ifdef DISUNIFY_NEW_TERMINAL_SECTION
+#error Use a gsettings override instead
+#endif
+
+enum {
+ PROP_SETTINGS_BACKEND = 1,
+ PROP_IS_DEFAULT_TERMINAL,
+ PROP_ASK_DEFAULT_TERMINAL,
+};
+
+/*
+ * 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 gnome-terminal 1.x for why.
+ */
+
+struct _TerminalAppClass {
+ GtkApplicationClass parent_class;
+
+ void (* clipboard_targets_changed) (TerminalApp *app,
+ GtkClipboard *clipboard);
+};
+
+struct _TerminalApp
+{
+ GtkApplication parent_instance;
+
+ TerminalSettingsList *profiles_list;
+
+ GSettingsBackend* settings_backend;
+ GSettingsSchemaSource* schema_source;
+ GSettings *global_settings;
+ GSettings *desktop_interface_settings;
+ GSettings *system_proxy_settings;
+ GSettings* system_proxy_protocol_settings[4];
+ GSettings *gtk_debug_settings;
+
+#ifdef TERMINAL_SERVER
+ GDBusObjectManagerServer *object_manager;
+ GHashTable *screen_map;
+
+#ifdef ENABLE_SEARCH_PROVIDER
+ TerminalSearchProvider *search_provider;
+#endif /* ENABLE_SEARCH_PROVIDER */
+
+ GMenuModel *menubar;
+ GMenu *menubar_new_terminal_section;
+ GMenu *menubar_set_profile_section;
+
+ GMenuModel *profilemenu;
+ GMenuModel *headermenu;
+ GMenu *headermenu_set_profile_section;
+
+ GMenu *set_profile_menu;
+
+ GtkClipboard *clipboard;
+ GdkAtom *clipboard_targets;
+ int n_clipboard_targets;
+
+ GWeakRef prefs_process_ref;
+
+#endif /* TERMINAL_SERVER */
+
+ gboolean ask_default;
+ gboolean xte_is_default;
+ gboolean unified_menu;
+ gboolean use_headerbar;
+};
+
+enum
+{
+ CLIPBOARD_TARGETS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* Debugging helper */
+
+static void
+terminal_app_init_debug (void)
+{
+#ifdef ENABLE_DEBUG
+ const char *env = g_getenv ("GTK_TEXT_DIR");
+ if (env != nullptr) {
+ if (g_str_equal (env, "help")) {
+ g_printerr ("Usage: GTK_TEXT_DIR=ltr|rtl\n");
+ } else {
+ GtkTextDirection dir;
+ if (g_str_equal (env, "rtl"))
+ dir = GTK_TEXT_DIR_RTL;
+ else
+ dir = GTK_TEXT_DIR_LTR;
+
+ gtk_widget_set_default_direction (dir);
+ }
+ }
+
+ env = g_getenv ("GTK_SETTINGS");
+ if (env == nullptr)
+ return;
+
+ GObject *settings = G_OBJECT (gtk_settings_get_default ());
+ GObjectClass *settings_class = G_OBJECT_GET_CLASS (settings);
+
+ if (g_str_equal (env, "help")) {
+ g_printerr ("Usage: GTK_SETTINGS=setting[,setting…] where 'setting' is one of these:\n");
+
+ guint n_props;
+ GParamSpec **props = g_object_class_list_properties (settings_class, &n_props);
+ for (guint i = 0; i < n_props; i++) {
+ if (G_PARAM_SPEC_VALUE_TYPE (props[i]) != G_TYPE_BOOLEAN)
+ continue;
+
+ GValue value = { 0, };
+ g_value_init (&value, G_TYPE_BOOLEAN);
+ g_object_get_property (settings, props[i]->name, &value);
+ g_printerr (" %s (%s)\n", props[i]->name, g_value_get_boolean (&value) ? "true" : "false");
+ g_value_unset (&value);
+ }
+ g_printerr (" Use 'setting' to set to true, "
+ "'~setting' to set to false, "
+ "and '!setting' to invert.\n");
+ } else {
+ gs_strfreev char **tokens = g_strsplit (env, ",", -1);
+ for (guint i = 0; tokens[i] != nullptr; i++) {
+ const char *prop = tokens[i];
+ char c = prop[0];
+ if (c == '~' || c == '!')
+ prop++;
+
+ GParamSpec *pspec = g_object_class_find_property (settings_class, prop);
+ if (pspec == nullptr) {
+ g_printerr ("Setting \"%s\" does not exist.\n", prop);
+ } else if (G_PARAM_SPEC_VALUE_TYPE (pspec) != G_TYPE_BOOLEAN) {
+ g_printerr ("Setting \"%s\" is not boolean.\n", prop);
+ } else {
+ GValue value = { 0, };
+ g_value_init (&value, G_TYPE_BOOLEAN);
+ if (c == '!') {
+ g_object_get_property (settings, pspec->name, &value);
+ g_value_set_boolean (&value, !g_value_get_boolean (&value));
+ } else if (c == '~') {
+ g_value_set_boolean (&value, FALSE);
+ } else {
+ g_value_set_boolean (&value, TRUE);
+ }
+ g_object_set_property (settings, pspec->name, &value);
+ g_value_unset (&value);
+ }
+ }
+ }
+#endif
+}
+
+/* Helper functions */
+
+static gboolean
+strv_contains_gnome (char **strv)
+{
+ if (strv == nullptr)
+ return FALSE;
+
+ for (int i = 0; strv[i] != nullptr; i++) {
+ if (g_ascii_strcasecmp (strv[i], "gnome") == 0 ||
+ g_ascii_strcasecmp (strv[i], "gnome-classic") == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * terminal_app_should_use_headerbar:
+ *
+ * Determines if the app should use headerbars. This is determined
+ * * If the pref is set, the pref value is used
+ * * Otherwise, if XDG_CURRENT_DESKTOP contains GNOME or GNOME-Classic,
+ * headerbar is used
+ * * Otherwise, headerbar is not used.
+ */
+static gboolean
+terminal_app_should_use_headerbar (TerminalApp *app)
+{
+ gboolean set, use;
+ g_settings_get (app->global_settings, TERMINAL_SETTING_HEADERBAR_KEY, "mb", &set, &use);
+ if (set)
+ return use;
+
+ gs_strfreev auto desktops = terminal_util_get_desktops();
+ return strv_contains_gnome(desktops);
+}
+
+static gboolean
+load_css_from_resource (GApplication *application,
+ GtkCssProvider *provider,
+ gboolean theme)
+{
+ const char *base_path;
+ gs_free char *uri;
+ gs_unref_object GFile *file;
+ gs_free_error GError *error = nullptr;
+
+ base_path = g_application_get_resource_base_path (application);
+
+ if (theme) {
+ gs_free char *str, *theme_name;
+
+ g_object_get (gtk_settings_get_default (), "gtk-theme-name", &str, nullptr);
+ theme_name = g_ascii_strdown (str, -1);
+ uri = g_strdup_printf ("resource://%s/css/%s/terminal.css", base_path, theme_name);
+ } else {
+ uri = g_strdup_printf ("resource://%s/css/terminal.css", base_path);
+ }
+
+ file = g_file_new_for_uri (uri);
+ if (!g_file_query_exists (file, nullptr /* cancellable */))
+ return FALSE;
+
+ if (!gtk_css_provider_load_from_file (provider, file, &error))
+ g_assert_no_error (error);
+
+ return TRUE;
+}
+
+static void
+add_css_provider (GApplication *application,
+ gboolean theme)
+{
+ gs_unref_object GtkCssProvider *provider;
+
+ provider = gtk_css_provider_new ();
+ if (!load_css_from_resource (application, provider, theme))
+ return;
+
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+static void
+app_load_css (GApplication *application)
+{
+ add_css_provider (application, FALSE);
+ add_css_provider (application, TRUE);
+}
+
+char *
+terminal_app_new_profile (TerminalApp *app,
+ GSettings *base_profile,
+ const char *name)
+{
+ char *uuid;
+
+ if (base_profile) {
+ gs_free char *base_uuid;
+
+ base_uuid = terminal_settings_list_dup_uuid_from_child (app->profiles_list, base_profile);
+ uuid = terminal_settings_list_clone_child (app->profiles_list, base_uuid, name);
+ } else {
+ uuid = terminal_settings_list_add_child (app->profiles_list, name);
+ }
+
+ return uuid;
+}
+
+void
+terminal_app_remove_profile (TerminalApp *app,
+ GSettings *profile)
+{
+ g_return_if_fail (TERMINAL_IS_APP (app));
+ g_return_if_fail (G_IS_SETTINGS (profile));
+
+ gs_unref_object GSettings *default_profile = terminal_settings_list_ref_default_child (app->profiles_list);
+ if (default_profile == profile)
+ return;
+
+#ifdef TERMINAL_SERVER
+ /* First, we need to switch any screen using this profile to the default profile */
+ gs_free_list GList *screens = g_hash_table_get_values (app->screen_map);
+ for (GList *l = screens; l != nullptr; l = l->next) {
+ TerminalScreen *screen = TERMINAL_SCREEN (l->data);
+ if (terminal_screen_get_profile (screen) != profile)
+ continue;
+
+ terminal_screen_set_profile (screen, default_profile);
+ }
+#endif /* TERMINAL_SERVER */
+
+ /* Now we can safely remove the profile */
+ gs_free char *uuid = terminal_settings_list_dup_uuid_from_child (app->profiles_list, profile);
+ terminal_settings_list_remove_child (app->profiles_list, uuid);
+}
+
+static void
+terminal_app_theme_variant_changed_cb (GSettings *settings,
+ const char *key,
+ GtkSettings *gtk_settings)
+{
+ TerminalThemeVariant theme;
+
+ theme = TerminalThemeVariant(g_settings_get_enum (settings, key));
+ if (theme == TERMINAL_THEME_VARIANT_SYSTEM)
+ gtk_settings_reset_property (gtk_settings, GTK_SETTING_PREFER_DARK_THEME);
+ else
+ g_object_set (gtk_settings,
+ GTK_SETTING_PREFER_DARK_THEME,
+ theme == TERMINAL_THEME_VARIANT_DARK,
+ nullptr);
+}
+
+/* Submenus for New Terminal per profile, and to change profiles */
+
+static void
+terminal_app_check_default(TerminalApp* app)
+{
+#ifdef TERMINAL_SERVER
+ // 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;
+#endif /* TERMINAL_SERVER */
+
+ // 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;
+} ProfileData;
+
+static void
+profile_data_clear (ProfileData *data)
+{
+ g_free (data->uuid);
+ g_free (data->label);
+}
+
+typedef struct {
+ GArray *array;
+ TerminalApp *app;
+} ProfilesForeachData;
+
+static void
+foreach_profile_cb (TerminalSettingsList *list,
+ const char *uuid,
+ GSettings *profile,
+ ProfilesForeachData *user_data)
+{
+ ProfileData data;
+ data.uuid = g_strdup (uuid);
+ data.label = g_settings_get_string (profile, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+
+ g_array_append_val (user_data->array, data);
+
+ /* only connect if we haven't seen this profile before */
+ if (g_signal_handler_find (profile,
+ GSignalMatchType(G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA),
+ 0, 0, nullptr,
+ (void*)terminal_app_update_profile_menus, user_data->app) == 0)
+ g_signal_connect_swapped (profile, "changed::" TERMINAL_PROFILE_VISIBLE_NAME_KEY,
+ G_CALLBACK (terminal_app_update_profile_menus), user_data->app);
+}
+
+static int
+compare_profile_label_cb (gconstpointer ap,
+ gconstpointer bp)
+{
+ const ProfileData *a = (ProfileData const*)ap;
+ const ProfileData *b = (ProfileData const*)bp;
+
+ return g_utf8_collate (a->label, b->label);
+}
+
+static void
+menu_append_numbered (GMenu *menu,
+ const char *label,
+ int num,
+ const char *action_name,
+ GVariant *target /* floating, consumed */)
+{
+ gs_free_gstring GString *str;
+ gs_unref_object GMenuItem *item;
+ const char *p;
+
+ /* Who'd use more that 4 underscores in a profile name... */
+ str = g_string_sized_new (strlen (label) + 4 + 1 + 8);
+
+ if (num < 10)
+ g_string_append_printf (str, "_%Id. ", num);
+ else if (num < 36)
+ g_string_append_printf (str, "_%c. ", (char)('A' + num - 10));
+
+ /* Append the label with underscores elided */
+ for (p = label; *p; p++) {
+ if (*p == '_')
+ g_string_append (str, "__");
+ else
+ g_string_append_c (str, *p);
+ }
+
+ item = g_menu_item_new (str->str, nullptr);
+ g_menu_item_set_action_and_target_value (item, action_name, target);
+ g_menu_append_item (menu, item);
+}
+
+static void
+append_new_terminal_item (GMenu *section,
+ const char *label,
+ const char *target,
+ ProfileData *data,
+ guint n_profiles)
+{
+ gs_unref_object GMenuItem *item = g_menu_item_new (label, nullptr);
+
+ if (n_profiles > 1) {
+ gs_unref_object GMenu *submenu = g_menu_new ();
+
+ for (guint i = 0; i < n_profiles; i++) {
+ menu_append_numbered (submenu, data[i].label, i + 1,
+ "win.new-terminal",
+ g_variant_new ("(ss)", target, data[i].uuid));
+ }
+
+ g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, G_MENU_MODEL (submenu));
+ } else {
+ g_menu_item_set_action_and_target (item, "win.new-terminal",
+ "(ss)", target, "current");
+ }
+ g_menu_append_item (section, item);
+}
+
+static void
+fill_header_new_terminal_menu (GMenuModel *menu,
+ ProfileData *data,
+ guint n_profiles)
+{
+ gs_unref_object GMenu *section = nullptr;
+
+ if (n_profiles <= 1)
+ return;
+
+ section = g_menu_new ();
+
+ for (guint i = 0; i < n_profiles; i++) {
+ menu_append_numbered (section, data[i].label, i + 1,
+ "win.new-terminal",
+ g_variant_new ("(ss)", "default", data[i].uuid));
+ }
+
+ g_menu_append_section (G_MENU (menu), _("New Terminal"), G_MENU_MODEL (section));
+}
+
+static void
+fill_new_terminal_section (TerminalApp *app,
+ GMenu *section,
+ ProfileData *profiles,
+ guint n_profiles)
+{
+ if (terminal_app_get_menu_unified (app)) {
+ append_new_terminal_item (section, _("New _Terminal"), "default", profiles, n_profiles);
+ } else {
+ append_new_terminal_item (section, _("New _Tab"), "tab", profiles, n_profiles);
+ append_new_terminal_item (section, _("New _Window"), "window", profiles, n_profiles);
+ }
+}
+
+static GMenu *
+set_profile_submenu_new (ProfileData *data,
+ guint n_profiles)
+{
+ /* No submenu if there's only one profile */
+ if (n_profiles <= 1)
+ return nullptr;
+
+ GMenu *menu = g_menu_new ();
+ for (guint i = 0; i < n_profiles; i++) {
+ menu_append_numbered (menu, data[i].label, i + 1,
+ "win.profile",
+ g_variant_new_string (data[i].uuid));
+ }
+
+ return menu;
+}
+
+static void
+terminal_app_update_profile_menus (TerminalApp *app)
+{
+ g_clear_object (&app->set_profile_menu);
+
+ /* Get profiles list and sort by label */
+ gs_unref_array GArray *array = g_array_sized_new (FALSE, TRUE, sizeof (ProfileData),
+ terminal_settings_list_get_n_children (app->profiles_list));
+ g_array_set_clear_func (array, (GDestroyNotify) profile_data_clear);
+
+ ProfilesForeachData data = { array, app };
+ terminal_settings_list_foreach_child (app->profiles_list,
+ (TerminalSettingsListForeachFunc) foreach_profile_cb,
+ &data);
+ g_array_sort (array, compare_profile_label_cb);
+
+ ProfileData *profiles = (ProfileData*) array->data;
+ guint n_profiles = array->len;
+
+ app->set_profile_menu = set_profile_submenu_new (profiles, n_profiles);
+
+ if (app->menubar != nullptr) {
+ g_menu_remove_all (G_MENU (app->menubar_new_terminal_section));
+ fill_new_terminal_section (app, app->menubar_new_terminal_section, profiles, n_profiles);
+
+ g_menu_remove_all (G_MENU (app->menubar_set_profile_section));
+ if (app->set_profile_menu != nullptr) {
+ g_menu_append_submenu (app->menubar_set_profile_section, _("Change _Profile"),
+ G_MENU_MODEL (app->set_profile_menu));
+ }
+ }
+
+ if (app->profilemenu != nullptr) {
+ g_menu_remove_all (G_MENU (app->profilemenu));
+ fill_header_new_terminal_menu (app->profilemenu, profiles, n_profiles);
+ }
+
+ if (app->headermenu != nullptr) {
+ g_menu_remove_all (G_MENU (app->headermenu_set_profile_section));
+ if (app->set_profile_menu != nullptr) {
+ g_menu_append_submenu (app->headermenu_set_profile_section, _("_Profile"),
+ G_MENU_MODEL (app->set_profile_menu));
+ }
+ }
+}
+
+static GMenuModel *
+terminal_app_create_menubar (TerminalApp *app,
+ gboolean shell_shows_menubar)
+{
+ /* If the menubar is shown by the shell, omit mnemonics for the submenus. This is because Alt+F etc.
+ * are more important to be usable in the terminal, the menu cannot be replaced runtime (to toggle
+ * between mnemonic and non-mnemonic versions), gtk-enable-mnemonics or gtk_window_set_mnemonic_modifier()
+ * don't effect the menubar either, so there wouldn't be a way to disable Alt+F for File etc. otherwise.
+ * Furthermore, the menu would even grab mnemonics from the File and Preferences windows.
+ * In Unity, Alt+F10 opens the menubar, this should be good enough for keyboard navigation.
+ * If the menubar is shown by the app, toggling mnemonics is handled in terminal-window.c using
+ * gtk_window_set_mnemonic_modifier().
+ * See bug 792978 for details. */
+ terminal_util_load_objects_resource (shell_shows_menubar ? "/org/gnome/terminal/ui/menubar-without-mnemonics.ui"
+ : "/org/gnome/terminal/ui/menubar-with-mnemonics.ui",
+ "menubar", &app->menubar,
+ "new-terminal-section", &app->menubar_new_terminal_section,
+ "set-profile-section", &app->menubar_set_profile_section,
+ nullptr);
+
+ /* Install profile sections */
+ terminal_app_update_profile_menus (app);
+
+ return app->menubar;
+}
+
+static void
+terminal_app_create_headermenu (TerminalApp *app)
+{
+ terminal_util_load_objects_resource ("/org/gnome/terminal/ui/headerbar-menu.ui",
+ "headermenu", &app->headermenu,
+ "set-profile-section", &app->headermenu_set_profile_section,
+ nullptr);
+
+ /* Install profile sections */
+ terminal_app_update_profile_menus (app);
+}
+
+static void
+terminal_app_create_profilemenu (TerminalApp *app)
+{
+ app->profilemenu = G_MENU_MODEL (g_menu_new ());
+
+ /* Install profile sections */
+ terminal_app_update_profile_menus (app);
+}
+
+/* Clipboard */
+
+static void
+free_clipboard_targets (TerminalApp *app)
+{
+ g_free (app->clipboard_targets);
+ app->clipboard_targets = nullptr;
+ app->n_clipboard_targets = 0;
+}
+
+static void
+update_clipboard_targets (TerminalApp *app,
+ GdkAtom *targets,
+ int n_targets)
+{
+ free_clipboard_targets (app);
+
+ /* Sometimes we receive targets == nullptr but n_targets == -1 */
+ if (targets != nullptr && n_targets < 255) {
+ app->clipboard_targets = reinterpret_cast<GdkAtom*>
+ (g_memdup (targets, sizeof (targets[0]) * n_targets));
+ app->n_clipboard_targets = n_targets;
+ }
+}
+
+static void
+clipboard_targets_received_cb (GtkClipboard *clipboard,
+ GdkAtom *targets,
+ int n_targets,
+ TerminalApp *app)
+{
+ update_clipboard_targets (app, targets, n_targets);
+
+ _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_CLIPBOARD) {
+ g_printerr ("Clipboard has %d targets:", app->n_clipboard_targets);
+
+ int i;
+ for (i = 0; i < app->n_clipboard_targets; i++) {
+ gs_free char *atom_name = gdk_atom_name (app->clipboard_targets[i]);
+ g_printerr (" %s", atom_name);
+ }
+ g_printerr ("\n");
+ }
+
+ g_signal_emit (app, signals[CLIPBOARD_TARGETS_CHANGED], 0, clipboard);
+}
+
+static void
+clipboard_owner_change_cb (GtkClipboard *clipboard,
+ GdkEvent *event G_GNUC_UNUSED,
+ TerminalApp *app)
+{
+ _terminal_debug_print (TERMINAL_DEBUG_CLIPBOARD,
+ "Clipboard owner changed\n");
+
+ clipboard_targets_received_cb (clipboard, nullptr, 0, app); /* clear */
+
+ /* We can do this without holding a reference to @app since
+ * the app lives as long as the process.
+ */
+ gtk_clipboard_request_targets (clipboard,
+ (GtkClipboardTargetsReceivedFunc) clipboard_targets_received_cb,
+ app);
+}
+
+/* Preferences */
+
+struct PrefsLaunchData {
+ GWeakRef app_ref;
+ char* profile_uuid;
+ char* hint;
+ unsigned timestamp;
+};
+
+static auto
+prefs_launch_data_new(TerminalApp* app,
+ char const* profile_uuid,
+ char const* hint,
+ unsigned timestamp)
+{
+ auto data = g_new(PrefsLaunchData, 1);
+ g_weak_ref_init(&data->app_ref, app);
+ data->profile_uuid = g_strdup(profile_uuid);
+ data->hint = g_strdup(hint);
+ data->timestamp = timestamp;
+
+ return data;
+}
+
+static void
+prefs_launch_data_free(PrefsLaunchData* data)
+{
+ g_weak_ref_clear(&data->app_ref);
+ g_free(data->profile_uuid);
+ g_free(data->hint);
+ g_free(data);
+}
+
+static void
+launch_prefs_cb(GObject* source,
+ GAsyncResult* result,
+ void* user_data)
+{
+ auto const data = reinterpret_cast<PrefsLaunchData*>(user_data);
+ auto const app = reinterpret_cast<TerminalApp*>(g_weak_ref_get(&data->app_ref));
+
+ // @process holds a ref on itself via the g_subprocess_wait_async() call,
+ // so we only keep a weak ref that gets cleared when the process exits.
+ gs_free_error GError* error = nullptr;
+ gs_unref_object auto process = terminal_prefs_process_new_finish(result, &error);
+ if (app)
+ g_weak_ref_init(&app->prefs_process_ref, process);
+
+ if (process) {
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Preferences process launched successfully.\n");
+
+ terminal_prefs_process_show(process,
+ data->profile_uuid,
+ data->hint,
+ data->timestamp);
+ } else {
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Failed to launch preferences process: %s\n", error->message);
+ }
+
+ prefs_launch_data_free(data);
+}
+
+/* Callbacks from former app menu.
+ * The preferences one is still used with the "--preferences" cmdline option. */
+
+static void
+app_menu_preferences_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalApp *app = (TerminalApp*)user_data;
+
+ terminal_app_edit_preferences (app, nullptr, nullptr, gtk_get_current_event_time());
+}
+
+static void
+app_menu_help_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ terminal_util_show_help (nullptr);
+}
+
+static void
+app_menu_about_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ terminal_util_show_about ();
+}
+
+static void
+app_menu_quit_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GtkApplication *application = (GtkApplication*)user_data;
+ GtkWindow *window;
+
+ window = gtk_application_get_active_window (application);
+ if (TERMINAL_IS_WINDOW (window))
+ terminal_window_request_close (TERMINAL_WINDOW (window));
+ else /* a dialogue */
+ gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+#endif /* TERMINAL_SERVER */
+
+/* Class implementation */
+
+G_DEFINE_TYPE (TerminalApp, terminal_app, GTK_TYPE_APPLICATION)
+
+/* GApplicationClass impl */
+
+static void
+terminal_app_activate (GApplication *application)
+{
+ /* No-op required because GApplication is stupid */
+}
+
+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);
+
+ /* Need to set the WM class (bug #685742) */
+#if defined(TERMINAL_SERVER)
+ gdk_set_program_class("Gnome-terminal");
+#elif defined(TERMINAL_PREFERENCES)
+ gdk_set_program_class("Gnome-terminal-preferences");
+#else
+#error
+#endif
+
+ app_load_css (application);
+
+#ifdef TERMINAL_SERVER
+ GActionEntry const action_entries[] = {
+ { "preferences", app_menu_preferences_cb, nullptr, nullptr, nullptr },
+ { "help", app_menu_help_cb, nullptr, nullptr, nullptr },
+ { "about", app_menu_about_cb, nullptr, nullptr, nullptr },
+ { "quit", app_menu_quit_cb, nullptr, nullptr, nullptr }
+ };
+
+ g_action_map_add_action_entries (G_ACTION_MAP (application),
+ action_entries, G_N_ELEMENTS (action_entries),
+ application);
+
+ /* Figure out whether the shell shows the menubar */
+ gboolean shell_shows_menubar;
+ g_object_get (gtk_settings_get_default (),
+ "gtk-shell-shows-menubar", &shell_shows_menubar,
+ nullptr);
+
+ /* Create menubar */
+ terminal_app_create_menubar (app, shell_shows_menubar);
+
+ /* Keep dynamic menus updated */
+ g_signal_connect_swapped (app->profiles_list, "children-changed",
+ G_CALLBACK (terminal_app_update_profile_menus), app);
+
+ /* Show/hide the menubar as appropriate: If the shell wants to show the menubar, make it available. */
+ if (shell_shows_menubar)
+ gtk_application_set_menubar (GTK_APPLICATION (app),
+ terminal_app_get_menubar (app));
+
+#endif /* TERMINAL_SERVER */
+
+ terminal_app_check_default(app);
+
+ _terminal_debug_print (TERMINAL_DEBUG_SERVER, "Startup complete\n");
+}
+
+/* GObjectClass impl */
+
+static void
+terminal_app_init (TerminalApp* app)
+{
+#ifdef TERMINAL_SERVER
+ g_weak_ref_init(&app->prefs_process_ref, nullptr);
+
+ app->screen_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, nullptr);
+#endif
+}
+
+static void
+terminal_app_constructed(GObject *object)
+{
+ auto app = TERMINAL_APP(object);
+
+ G_OBJECT_CLASS(terminal_app_parent_class)->constructed(object);
+
+ terminal_app_init_debug ();
+
+#if defined(TERMINAL_SERVER)
+ gtk_window_set_default_icon_name (GNOME_TERMINAL_ICON_NAME);
+#elif defined(TERMINAL_PREFERENCES)
+ gtk_window_set_default_icon_name(GNOME_TERMINAL_PREFERENCES_ICON_NAME);
+#else
+#error
+#endif
+
+ if (app->settings_backend == nullptr)
+ app->settings_backend = g_settings_backend_get_default ();
+
+ app->schema_source = terminal_g_settings_schema_source_get_default();
+
+ /* Desktop proxy settings */
+ app->system_proxy_settings = terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ SYSTEM_PROXY_SETTINGS_SCHEMA);
+
+ /* Since there is no way to get the schema ID of a child schema, we cannot
+ * verify that the installed schemas are correct. Also, due to a glib bug
+ * (https://gitlab.gnome.org/GNOME/glib/-/issues/1884) g_settings_get_child()
+ * doesn't work with non-default schema sources.
+ * So instead of using g_settings_get_child() on the SYSTEM_PROXY_SETTINGS_SCHEMA,
+ * we construct the child GSettings directly.
+ */
+ app->system_proxy_protocol_settings[TERMINAL_PROXY_HTTP] =
+ terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ SYSTEM_HTTP_PROXY_SETTINGS_SCHEMA);
+ app->system_proxy_protocol_settings[TERMINAL_PROXY_HTTPS] =
+ terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ SYSTEM_HTTPS_PROXY_SETTINGS_SCHEMA);
+ app->system_proxy_protocol_settings[TERMINAL_PROXY_FTP] =
+ terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ SYSTEM_FTP_PROXY_SETTINGS_SCHEMA);
+ app->system_proxy_protocol_settings[TERMINAL_PROXY_SOCKS] =
+ terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ SYSTEM_SOCKS_PROXY_SETTINGS_SCHEMA);
+
+ /* Desktop Interface settings */
+ app->desktop_interface_settings = terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ DESKTOP_INTERFACE_SETTINGS_SCHEMA);
+
+ /* Terminal global settings */
+ app->global_settings = terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ TERMINAL_SETTING_SCHEMA);
+
+ /* Gtk debug settings */
+ app->gtk_debug_settings = terminal_g_settings_new(app->settings_backend,
+ app->schema_source,
+ GTK_DEBUG_SETTING_SCHEMA);
+
+ /* These are internal settings that exists only for distributions
+ * to override, so we cache them on startup and don't react to changes.
+ */
+ app->unified_menu = g_settings_get_boolean (app->global_settings, TERMINAL_SETTING_UNIFIED_MENU_KEY);
+ app->use_headerbar = terminal_app_should_use_headerbar (app);
+
+ GtkSettings *gtk_settings = gtk_settings_get_default ();
+ terminal_app_theme_variant_changed_cb (app->global_settings,
+ TERMINAL_SETTING_THEME_VARIANT_KEY, gtk_settings);
+ g_signal_connect (app->global_settings,
+ "changed::" TERMINAL_SETTING_THEME_VARIANT_KEY,
+ G_CALLBACK (terminal_app_theme_variant_changed_cb),
+ gtk_settings);
+
+#ifdef TERMINAL_SERVER
+ /* Clipboard targets */
+ GdkDisplay *display = gdk_display_get_default ();
+ app->clipboard = gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
+ clipboard_owner_change_cb (app->clipboard, nullptr, app);
+ g_signal_connect (app->clipboard, "owner-change",
+ G_CALLBACK (clipboard_owner_change_cb), app);
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY(display) &&
+ !gdk_display_supports_selection_notification (display))
+ g_printerr ("Display does not support owner-change; copy/paste will be broken!\n");
+#endif
+#endif /* TERMINAL_SERVER */
+
+ /* Get the profiles */
+ app->profiles_list = terminal_profiles_list_new(app->settings_backend,
+ app->schema_source);
+
+ gs_unref_object auto settings =
+ terminal_g_settings_new_with_path(app->settings_backend,
+ app->schema_source,
+ TERMINAL_KEYBINDINGS_SCHEMA,
+ TERMINAL_KEYBINDINGS_SCHEMA_PATH);
+ terminal_accels_init (G_APPLICATION (app), settings, app->use_headerbar);
+}
+
+static void
+terminal_app_finalize (GObject *object)
+{
+ auto app = TERMINAL_APP(object);
+
+#ifdef TERMINAL_SERVER
+ g_signal_handlers_disconnect_by_func (app->clipboard,
+ (void*)clipboard_owner_change_cb,
+ app);
+ free_clipboard_targets (app);
+
+ g_signal_handlers_disconnect_by_func (app->profiles_list,
+ (void*)terminal_app_update_profile_menus,
+ app);
+ g_hash_table_destroy (app->screen_map);
+#endif
+
+ g_object_unref (app->global_settings);
+ g_object_unref (app->desktop_interface_settings);
+ g_object_unref (app->system_proxy_settings);
+ for (int i = 0; i < 4; ++i)
+ g_object_unref(app->system_proxy_protocol_settings[i]);
+ g_clear_object (&app->gtk_debug_settings);
+ g_settings_schema_source_unref(app->schema_source);
+ g_clear_object (&app->settings_backend);
+
+#ifdef TERMINAL_SERVER
+ g_clear_object (&app->menubar);
+ g_clear_object (&app->menubar_new_terminal_section);
+ g_clear_object (&app->menubar_set_profile_section);
+ g_clear_object (&app->profilemenu);
+ g_clear_object (&app->headermenu);
+ g_clear_object (&app->headermenu_set_profile_section);
+ g_clear_object (&app->set_profile_menu);
+
+ {
+ gs_unref_object auto process = reinterpret_cast<TerminalPrefsProcess*>(g_weak_ref_get(&app->prefs_process_ref));
+ if (process)
+ terminal_prefs_process_abort(process);
+ }
+
+ g_weak_ref_clear(&app->prefs_process_ref);
+#endif /* TERMINAL_SERVER */
+
+ terminal_accels_shutdown ();
+
+ 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,
+ GValue const* value,
+ GParamSpec* pspec)
+{
+ auto app = TERMINAL_APP(object);
+
+ switch (prop_id) {
+ 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;
+ }
+}
+
+#ifdef TERMINAL_SERVER
+
+static gboolean
+terminal_app_dbus_register (GApplication *application,
+ GDBusConnection *connection,
+ const gchar *object_path,
+ GError **error)
+{
+ TerminalApp *app = TERMINAL_APP (application);
+ gs_unref_object TerminalObjectSkeleton *object = nullptr;
+ gs_unref_object TerminalFactory *factory = nullptr;
+
+ if (!G_APPLICATION_CLASS (terminal_app_parent_class)->dbus_register (application,
+ connection,
+ object_path,
+ error))
+ return FALSE;
+
+#ifdef ENABLE_SEARCH_PROVIDER
+ if (g_settings_get_boolean (app->global_settings, TERMINAL_SETTING_SHELL_INTEGRATION_KEY)) {
+ gs_unref_object TerminalSearchProvider *search_provider;
+
+ search_provider = terminal_search_provider_new ();
+
+ if (!terminal_search_provider_dbus_register (search_provider,
+ connection,
+ TERMINAL_SEARCH_PROVIDER_PATH,
+ error))
+ return FALSE;
+
+ gs_transfer_out_value (&app->search_provider, &search_provider);
+ }
+#endif /* ENABLE_SEARCH_PROVIDER */
+
+ object = terminal_object_skeleton_new (TERMINAL_FACTORY_OBJECT_PATH);
+ factory = terminal_factory_impl_new ();
+ terminal_object_skeleton_set_factory (object, factory);
+
+ app->object_manager = g_dbus_object_manager_server_new (TERMINAL_OBJECT_PATH_PREFIX);
+ g_dbus_object_manager_server_export (app->object_manager, G_DBUS_OBJECT_SKELETON (object));
+
+ /* And export the object */
+ g_dbus_object_manager_server_set_connection (app->object_manager, connection);
+ return TRUE;
+}
+
+static void
+terminal_app_dbus_unregister (GApplication *application,
+ GDBusConnection *connection,
+ const gchar *object_path)
+{
+ TerminalApp *app = TERMINAL_APP (application);
+
+ if (app->object_manager) {
+ g_dbus_object_manager_server_unexport (app->object_manager, TERMINAL_FACTORY_OBJECT_PATH);
+ g_object_unref (app->object_manager);
+ app->object_manager = nullptr;
+ }
+
+#ifdef ENABLE_SEARCH_PROVIDER
+ if (app->search_provider) {
+ terminal_search_provider_dbus_unregister (app->search_provider, connection, TERMINAL_SEARCH_PROVIDER_PATH);
+ g_object_unref (app->search_provider);
+ app->search_provider = nullptr;
+ }
+#endif /* ENABLE_SEARCH_PROVIDER */
+
+ G_APPLICATION_CLASS (terminal_app_parent_class)->dbus_unregister (application,
+ connection,
+ object_path);
+}
+
+#endif /* TERMINAL_SERVER */
+
+static void
+terminal_app_class_init (TerminalAppClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GApplicationClass *g_application_class = G_APPLICATION_CLASS (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
+ (object_class,
+ PROP_SETTINGS_BACKEND,
+ g_param_spec_object("settings-backend", nullptr, nullptr,
+ G_TYPE_SETTINGS_BACKEND,
+ 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
+ g_application_class->dbus_register = terminal_app_dbus_register;
+ g_application_class->dbus_unregister = terminal_app_dbus_unregister;
+#endif
+
+ signals[CLIPBOARD_TARGETS_CHANGED] =
+ g_signal_new (I_("clipboard-targets-changed"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalAppClass, clipboard_targets_changed),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_OBJECT);
+}
+
+/* Public API */
+
+GApplication*
+terminal_app_new(char const* app_id,
+ GApplicationFlags flags,
+ GSettingsBackend* backend)
+{
+ return reinterpret_cast<GApplication*>
+ (g_object_new (TERMINAL_TYPE_APP,
+ "application-id", app_id ? app_id : TERMINAL_APPLICATION_ID,
+ "flags", flags,
+ "settings-backend", backend,
+ nullptr));
+}
+
+#ifdef TERMINAL_SERVER
+
+TerminalScreen *
+terminal_app_get_screen_by_uuid (TerminalApp *app,
+ const char *uuid)
+{
+ g_return_val_if_fail (TERMINAL_IS_APP (app), nullptr);
+
+ return reinterpret_cast<TerminalScreen*>(g_hash_table_lookup (app->screen_map, uuid));
+}
+
+char *
+terminal_app_dup_screen_object_path (TerminalApp *app,
+ TerminalScreen *screen)
+{
+ char *object_path = g_strdup_printf (TERMINAL_RECEIVER_OBJECT_PATH_FORMAT,
+ terminal_screen_get_uuid (screen));
+ object_path = g_strdelimit (object_path, "-", '_');
+ g_assert (g_variant_is_object_path (object_path));
+ return object_path;
+}
+
+/**
+ * terminal_app_get_receiver_impl_by_object_path:
+ * @app:
+ * @object_path:
+ *
+ * Returns: (transfer full): the #TerminalReceiverImpl for @object_path, or %nullptr
+ */
+static TerminalReceiverImpl *
+terminal_app_get_receiver_impl_by_object_path (TerminalApp *app,
+ const char *object_path)
+{
+ gs_unref_object GDBusObject *skeleton =
+ g_dbus_object_manager_get_object (G_DBUS_OBJECT_MANAGER (app->object_manager),
+ object_path);
+ if (skeleton == nullptr || !TERMINAL_IS_OBJECT_SKELETON (skeleton))
+ return nullptr;
+
+ TerminalReceiverImpl *impl = nullptr;
+ g_object_get (skeleton, "receiver", &impl, nullptr);
+ if (impl == nullptr)
+ return nullptr;
+
+ g_assert (TERMINAL_IS_RECEIVER_IMPL (impl));
+ return impl;
+}
+
+/**
+ * terminal_app_get_screen_by_object_path:
+ * @app:
+ * @object_path:
+ *
+ * Returns: (transfer full): the #TerminalScreen for @object_path, or %nullptr
+ */
+TerminalScreen *
+terminal_app_get_screen_by_object_path (TerminalApp *app,
+ const char *object_path)
+{
+ gs_unref_object TerminalReceiverImpl *impl =
+ terminal_app_get_receiver_impl_by_object_path (app, object_path);
+ if (impl == nullptr)
+ return nullptr;
+
+ return terminal_receiver_impl_get_screen (impl);
+}
+
+void
+terminal_app_register_screen (TerminalApp *app,
+ TerminalScreen *screen)
+{
+ const char *uuid = terminal_screen_get_uuid (screen);
+ g_hash_table_insert (app->screen_map, g_strdup (uuid), screen);
+
+ gs_free char *object_path = terminal_app_dup_screen_object_path (app, screen);
+ TerminalObjectSkeleton *skeleton = terminal_object_skeleton_new (object_path);
+
+ TerminalReceiverImpl *impl = terminal_receiver_impl_new (screen);
+ terminal_object_skeleton_set_receiver (skeleton, TERMINAL_RECEIVER (impl));
+ g_object_unref (impl);
+
+ g_dbus_object_manager_server_export (app->object_manager,
+ G_DBUS_OBJECT_SKELETON (skeleton));
+}
+
+void
+terminal_app_unregister_screen (TerminalApp *app,
+ TerminalScreen *screen)
+{
+ const char *uuid = terminal_screen_get_uuid (screen);
+ gboolean found = g_hash_table_remove (app->screen_map, uuid);
+ g_warn_if_fail (found);
+ if (!found)
+ return; /* repeat unregistering */
+
+ gs_free char *object_path = terminal_app_dup_screen_object_path (app, screen);
+ gs_unref_object TerminalReceiverImpl *impl =
+ terminal_app_get_receiver_impl_by_object_path (app, object_path);
+ g_warn_if_fail (impl != nullptr);
+
+ if (impl != nullptr)
+ terminal_receiver_impl_unset_screen (impl);
+
+ g_dbus_object_manager_server_unexport (app->object_manager, object_path);
+}
+
+GdkAtom *
+terminal_app_get_clipboard_targets (TerminalApp *app,
+ GtkClipboard *clipboard,
+ int *n_targets)
+{
+ g_return_val_if_fail (TERMINAL_IS_APP (app), nullptr);
+ g_return_val_if_fail (n_targets != nullptr, nullptr);
+
+ if (clipboard != app->clipboard) {
+ *n_targets = 0;
+ return nullptr;
+ }
+
+ *n_targets = app->n_clipboard_targets;
+ return app->clipboard_targets;
+}
+
+#endif /* TERMINAL_SERVER */
+
+void
+terminal_app_edit_preferences(TerminalApp* app,
+ GSettings* profile,
+ char const* hint,
+ unsigned timestamp)
+{
+#ifdef TERMINAL_SERVER
+ gs_free char* uuid = nullptr;
+ if (profile)
+ uuid = terminal_settings_list_dup_uuid_from_child (app->profiles_list, profile);
+
+ gs_unref_object auto process = reinterpret_cast<TerminalPrefsProcess*>(g_weak_ref_get(&app->prefs_process_ref));
+ if (process) {
+ terminal_prefs_process_show(process,
+ uuid,
+ hint,
+ timestamp);
+ } else {
+ terminal_prefs_process_new_async(nullptr, // cancellable,
+ GAsyncReadyCallback(launch_prefs_cb),
+ prefs_launch_data_new(app, uuid, hint, timestamp));
+ }
+#endif /* TERMINAL_SERVER */
+#ifdef TERMINAL_PREFERENCES
+ terminal_prefs_show_preferences(profile, hint, timestamp);
+#endif
+}
+
+/**
+ * terminal_app_get_profiles_list:
+ *
+ * Returns: (transfer none): returns the singleton profiles list #TerminalSettingsList
+ */
+TerminalSettingsList *
+terminal_app_get_profiles_list (TerminalApp *app)
+{
+ return app->profiles_list;
+}
+
+#ifdef TERMINAL_SERVER
+
+/**
+ * terminal_app_get_menubar:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the main window menu bar as a #GMenuModel
+ */
+GMenuModel *
+terminal_app_get_menubar (TerminalApp *app)
+{
+ return app->menubar;
+}
+
+/**
+ * terminal_app_get_headermenu:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the main window headerbar menu bar as a #GMenuModel
+ */
+GMenuModel *
+terminal_app_get_headermenu (TerminalApp *app)
+{
+ if (app->headermenu == nullptr)
+ terminal_app_create_headermenu (app);
+
+ return app->headermenu;
+}
+
+/**
+ * terminal_app_get_profilemenu:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the main window headerbar profile menu as a #GMenuModel
+ */
+GMenuModel *
+terminal_app_get_profilemenu (TerminalApp *app)
+{
+ if (app->profilemenu == nullptr)
+ terminal_app_create_profilemenu (app);
+
+ return app->profilemenu;
+}
+
+/**
+ * terminal_app_get_profile_section:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the main window's menubar's profiles section as a #GMenuModel
+ */
+GMenuModel *
+terminal_app_get_profile_section (TerminalApp *app)
+{
+ return G_MENU_MODEL (app->set_profile_menu);
+}
+
+#endif /* TERMINAL_SERVER */
+
+/**
+ * terminal_app_get_settings_backend:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the #GSettingsBackend to use for all #GSettings instances
+ */
+GSettingsBackend*
+terminal_app_get_settings_backend(TerminalApp *app)
+{
+ return app->settings_backend;
+}
+
+/**
+ * terminal_app_get_schema_source:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the #GSettingsSchemaSource to use for all #GSettings instances
+ */
+GSettingsSchemaSource*
+terminal_app_get_schema_source(TerminalApp *app)
+{
+ return app->schema_source;
+}
+
+/**
+ * terminal_app_get_global_settings:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the cached #GSettings object for the org.gnome.Terminal.Preferences schema
+ */
+GSettings *
+terminal_app_get_global_settings (TerminalApp *app)
+{
+ return app->global_settings;
+}
+
+/**
+ * terminal_app_get_desktop_interface_settings:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the cached #GSettings object for the org.gnome.interface schema
+ */
+GSettings *
+terminal_app_get_desktop_interface_settings (TerminalApp *app)
+{
+ return app->desktop_interface_settings;
+}
+
+/**
+ * terminal_app_get_proxy_settings:
+ * @app: a #TerminalApp
+ *
+ * Returns: (tranfer none): the cached #GSettings object for the org.gnome.system.proxy schema
+ */
+GSettings *
+terminal_app_get_proxy_settings (TerminalApp *app)
+{
+ return app->system_proxy_settings;
+}
+
+/**
+ * terminal_app_get_proxy_settings_for_protocol:
+ * @app: a #TerminalApp
+ * @protocol: a #TerminalProxyProtocol
+ *
+ * Returns: (tranfer none): the cached #GSettings object for the org.gnome.system.proxy.@protocol schema
+ */
+GSettings*
+terminal_app_get_proxy_settings_for_protocol(TerminalApp *app,
+ TerminalProxyProtocol protocol)
+{
+ return app->system_proxy_protocol_settings[(int)protocol];
+}
+
+GSettings *
+terminal_app_get_gtk_debug_settings (TerminalApp *app)
+{
+ return app->gtk_debug_settings;
+}
+
+/**
+ * terminal_app_get_system_font:
+ * @app:
+ *
+ * Creates a #PangoFontDescription for the system monospace font.
+ *
+ * Returns: (transfer full): a new #PangoFontDescription
+ */
+PangoFontDescription *
+terminal_app_get_system_font (TerminalApp *app)
+{
+ gs_free char *font = nullptr;
+
+ g_return_val_if_fail (TERMINAL_IS_APP (app), nullptr);
+
+ font = g_settings_get_string (app->desktop_interface_settings, MONOSPACE_FONT_KEY_NAME);
+
+ return pango_font_description_from_string (font);
+}
+
+#ifdef TERMINAL_SERVER
+
+GDBusObjectManagerServer *
+terminal_app_get_object_manager (TerminalApp *app)
+{
+ g_warn_if_fail (app->object_manager != nullptr);
+ return app->object_manager;
+}
+
+#endif /* TERMINAL_SERVER */
+
+gboolean
+terminal_app_get_menu_unified (TerminalApp *app)
+{
+ g_return_val_if_fail (TERMINAL_IS_APP (app), TRUE);
+
+ return app->unified_menu;
+}
+
+gboolean
+terminal_app_get_use_headerbar (TerminalApp *app)
+{
+ g_return_val_if_fail (TERMINAL_IS_APP (app), FALSE);
+
+ return app->use_headerbar;
+}
+
+gboolean
+terminal_app_get_dialog_use_headerbar (TerminalApp *app)
+{
+ g_return_val_if_fail (TERMINAL_IS_APP (app), FALSE);
+
+ gboolean dialog_use_header;
+ g_object_get (gtk_settings_get_default (),
+ "gtk-dialogs-use-header", &dialog_use_header,
+ nullptr);
+
+ 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
new file mode 100644
index 0000000..9409864
--- /dev/null
+++ b/src/terminal-app.hh
@@ -0,0 +1,142 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2008 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/>.
+ */
+
+#ifndef TERMINAL_APP_H
+#define TERMINAL_APP_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+#include "terminal-profiles-list.hh"
+
+G_BEGIN_DECLS
+
+#define GNOME_TERMINAL_ICON_NAME "org.gnome.Terminal"
+
+#define TERMINAL_RESOURCES_PATH_PREFIX "/org/gnome/terminal"
+
+#define MONOSPACE_FONT_KEY_NAME "monospace-font-name"
+
+/* TerminalApp */
+
+#define TERMINAL_TYPE_APP (terminal_app_get_type ())
+#define TERMINAL_APP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TERMINAL_TYPE_APP, TerminalApp))
+#define TERMINAL_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TERMINAL_TYPE_APP, TerminalAppClass))
+#define TERMINAL_IS_APP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TERMINAL_TYPE_APP))
+#define TERMINAL_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TERMINAL_TYPE_APP))
+#define TERMINAL_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TERMINAL_TYPE_APP, TerminalAppClass))
+
+typedef struct _TerminalAppClass TerminalAppClass;
+typedef struct _TerminalApp TerminalApp;
+
+GType terminal_app_get_type (void);
+
+GApplication *terminal_app_new (const char *app_id,
+ GApplicationFlags flags,
+ GSettingsBackend* backend);
+
+#define terminal_app_get (TerminalApp *) g_application_get_default
+
+GDBusObjectManagerServer *terminal_app_get_object_manager (TerminalApp *app);
+
+GdkAtom *terminal_app_get_clipboard_targets (TerminalApp *app,
+ GtkClipboard *clipboard,
+ int *n_targets);
+
+void terminal_app_edit_preferences (TerminalApp *app,
+ GSettings *profile,
+ const char *widget_name,
+ unsigned timestamp);
+
+char *terminal_app_new_profile (TerminalApp *app,
+ GSettings *default_base_profile,
+ const char *name);
+
+void terminal_app_remove_profile (TerminalApp *app,
+ GSettings *profile);
+
+char *terminal_app_dup_screen_object_path (TerminalApp *app,
+ TerminalScreen *screen);
+
+TerminalScreen *terminal_app_get_screen_by_uuid (TerminalApp *app,
+ const char *uuid);
+
+TerminalScreen *terminal_app_get_screen_by_object_path (TerminalApp *app,
+ const char *object_path);
+
+void terminal_app_register_screen (TerminalApp *app,
+ TerminalScreen *screen);
+
+void terminal_app_unregister_screen (TerminalApp *app,
+ TerminalScreen *screen);
+
+TerminalSettingsList *terminal_app_get_profiles_list (TerminalApp *app);
+
+/* Menus */
+
+GMenuModel *terminal_app_get_menubar (TerminalApp *app);
+
+GMenuModel *terminal_app_get_headermenu (TerminalApp *app);
+
+GMenuModel *terminal_app_get_profilemenu (TerminalApp *app);
+
+GMenuModel *terminal_app_get_profile_section (TerminalApp *app);
+
+gboolean terminal_app_get_menu_unified (TerminalApp *app);
+
+gboolean terminal_app_get_use_headerbar (TerminalApp *app);
+
+gboolean terminal_app_get_dialog_use_headerbar (TerminalApp *app);
+
+/* GSettings */
+
+typedef enum {
+ TERMINAL_PROXY_HTTP = 0,
+ TERMINAL_PROXY_HTTPS = 1,
+ TERMINAL_PROXY_FTP = 2,
+ TERMINAL_PROXY_SOCKS = 3,
+} TerminalProxyProtocol;
+
+GSettingsBackend* terminal_app_get_settings_backend(TerminalApp* app);
+
+GSettingsSchemaSource* terminal_app_get_schema_source(TerminalApp* app);
+
+GSettings *terminal_app_get_global_settings (TerminalApp *app);
+
+GSettings *terminal_app_get_desktop_interface_settings (TerminalApp *app);
+
+GSettings *terminal_app_get_proxy_settings (TerminalApp *app);
+
+GSettings *terminal_app_get_proxy_settings_for_protocol(TerminalApp *app,
+ TerminalProxyProtocol protocol);
+
+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-client-utils.cc b/src/terminal-client-utils.cc
new file mode 100644
index 0000000..cea473e
--- /dev/null
+++ b/src/terminal-client-utils.cc
@@ -0,0 +1,484 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2011, 2013 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 <unistd.h>
+#include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-libgsystem.hh"
+
+#ifdef TERMINAL_PREFERENCES
+#include "terminal-debug.hh"
+#endif
+
+#include <string.h>
+
+#include <gio/gio.h>
+
+#ifndef TERMINAL_NAUTILUS
+#include <gdk/gdk.h>
+#if defined(TERMINAL_COMPILATION) && defined(GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#endif
+#endif
+
+#ifdef ENABLE_DEBUG
+
+static char*
+get_binary_path_if_uninstalled(char const* install_dir) noexcept
+{
+#ifdef __linux__
+ char buf[1024];
+ auto const r = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
+ if (r < 0 || r >= ssize_t(sizeof(buf)))
+ return nullptr;
+
+ buf[r] = '\0'; // nul terminate
+
+ gs_free auto path = g_path_get_dirname(buf);
+ if (!path)
+ return nullptr;
+
+ if (g_str_equal(path, install_dir))
+ return nullptr;
+
+ return reinterpret_cast<char*>(g_steal_pointer(&path));
+#else
+ return nullptr;
+#endif /* __linux__ */
+}
+
+static char*
+get_path_if_uninstalled(char const* exe_install_dir,
+ char const* file_name,
+ GFileTest tests)
+{
+ gs_free auto path = get_binary_path_if_uninstalled(exe_install_dir);
+ if (!path)
+ return nullptr;
+
+ gs_free auto file = g_build_filename(path, file_name, nullptr);
+ if (!g_file_test(file, GFileTest(tests | G_FILE_TEST_EXISTS)))
+ return nullptr;
+
+ return reinterpret_cast<char*>(g_steal_pointer(&path));
+}
+
+#endif /* ENABLE_DEBUG */
+
+/**
+ * terminal_client_find_file_uninstalled:
+ * @exe_install_dir: the directory where the current exe is installed
+ * @file_install_dir: the directory where the file to locate is installed
+ * @file_name: the name of the file to locate
+ * @tests: extra tests from #GFileTest
+ *
+ * Tries to locate the directory that contains @file_name in a build directory,
+ * and returns the directory. If @file_name is not found, returns the
+ * installed location for it.
+ */
+char*
+terminal_client_get_directory_uninstalled(char const* exe_install_dir,
+ char const *file_install_dir,
+ char const* file_name,
+ GFileTest tests)
+{
+#ifdef ENABLE_DEBUG
+ auto path = get_path_if_uninstalled(exe_install_dir, file_name, tests);
+ if (path)
+ return path;
+#endif /* ENABLE_DEBUG */
+
+ return g_strdup(file_install_dir);
+}
+
+/**
+ * terminal_client_find_file_uninstalled:
+ * @exe_install_dir: the directory where the current exe is installed
+ * @file_install_dir: the directory where the file to locate is installed
+ * @file_name: the name of the file to locate
+ * @tests: extra tests from #GFileTest
+ *
+ * Tries to locate the file @file_name in a build directory, and
+ * returns a full path to it. If @file_name is not found, returns the
+ * installed location for it.
+ */
+char*
+terminal_client_get_file_uninstalled(char const* exe_install_dir,
+ char const *file_install_dir,
+ char const* file_name,
+ GFileTest tests)
+{
+#ifdef ENABLE_DEBUG
+ gs_free auto path = get_path_if_uninstalled(exe_install_dir, file_name, tests);
+ if (path)
+ return g_build_filename(path, file_name, nullptr);
+#endif /* ENABLE_DEBUG */
+
+ return g_build_filename(file_install_dir, file_name, nullptr);
+}
+
+/**
+ * terminal_client_append_create_instance_options:
+ * @builder: a #GVariantBuilder of #GVariantType "a{sv}"
+ * @display: (array element-type=guint8):
+ * @startup_id:
+ * @activation_token:
+ * @geometry:
+ * @role:
+ * @profile:
+ * @title:
+ * @maximise_window:
+ * @fullscreen_window:
+ *
+ * Appends common options to @builder.
+ */
+void
+terminal_client_append_create_instance_options (GVariantBuilder *builder,
+ const char *display_name,
+ const char *startup_id,
+ const char *activation_token,
+ const char *geometry,
+ const char *role,
+ const char *profile,
+ const char *encoding,
+ const char *title,
+ gboolean active,
+ gboolean maximise_window,
+ gboolean fullscreen_window)
+{
+ /* Bytestring options */
+ if (display_name != nullptr)
+ g_variant_builder_add (builder, "{sv}",
+ "display", g_variant_new_bytestring (display_name));
+ if (startup_id)
+ g_variant_builder_add (builder, "{sv}",
+ "desktop-startup-id", g_variant_new_bytestring (startup_id));
+ if (activation_token)
+ g_variant_builder_add (builder, "{sv}",
+ "activation-token", g_variant_new_string (activation_token));
+
+ /* String options */
+ if (profile)
+ g_variant_builder_add (builder, "{sv}",
+ "profile", g_variant_new_string (profile));
+ if (encoding)
+ g_variant_builder_add (builder, "{sv}",
+ "encoding", g_variant_new_string (encoding));
+ if (title)
+ g_variant_builder_add (builder, "{sv}",
+ "title", g_variant_new_string (title));
+ if (geometry)
+ g_variant_builder_add (builder, "{sv}",
+ "geometry", g_variant_new_string (geometry));
+ if (role)
+ g_variant_builder_add (builder, "{sv}",
+ "role", g_variant_new_string (role));
+
+ /* Boolean options */
+ if (active)
+ g_variant_builder_add (builder, "{sv}",
+ "active", g_variant_new_boolean (active));
+
+ if (maximise_window)
+ g_variant_builder_add (builder, "{sv}",
+ "maximize-window", g_variant_new_boolean (TRUE));
+ if (fullscreen_window)
+ g_variant_builder_add (builder, "{sv}",
+ "fullscreen-window", g_variant_new_boolean (TRUE));
+}
+
+char const* const*
+terminal_client_get_environment_filters (void)
+{
+ static char const* filters[] = {
+ "COLORFGBG",
+ "COLORTERM",
+ "COLUMNS",
+ "DEFAULT_COLORS",
+ "DESKTOP_STARTUP_ID",
+ "EXIT_CODE",
+ "EXIT_STATUS",
+ "GIO_LAUNCHED_DESKTOP_FILE",
+ "GIO_LAUNCHED_DESKTOP_FILE_PID",
+ "GJS_DEBUG_OUTPUT",
+ "GJS_DEBUG_TOPICS",
+ "GNOME_DESKTOP_ICON",
+ "INVOCATION_ID",
+ "JOURNAL_STREAM",
+ "LINES",
+ "LISTEN_FDNAMES",
+ "LISTEN_FDS",
+ "LISTEN_PID",
+ "MAINPID",
+ "MANAGERPID",
+ "NOTIFY_SOCKET",
+ "NOTIFY_SOCKET",
+ "PIDFILE",
+ "PWD",
+ "REMOTE_ADDR",
+ "REMOTE_PORT",
+ "SERVICE_RESULT",
+ "SHLVL",
+ "STY",
+ "TERM",
+ "TERMCAP",
+ "TMUX",
+ "TMUX_PANE",
+ "VTE_VERSION",
+ "WATCHDOG_PID",
+ "WATCHDOG_USEC",
+ "WCWIDTH_CJK_LEGACY",
+ "WINDOWID",
+ "XDG_ACTIVATION_TOKEN",
+ nullptr
+ };
+
+ return filters;
+}
+
+char const* const*
+terminal_client_get_environment_prefix_filters (void)
+{
+ static char const* filters[] = {
+ "GNOME_TERMINAL_",
+ // "VTE_", ?
+
+ /* other terminals */
+ "FOOT_",
+ "ITERM2_",
+ "MC_",
+ "MINTTY_",
+ "PUTTY_",
+ "RXVT_",
+ "TERM_",
+ "URXVT_",
+ "WEZTERM_",
+ "XTERM_",
+ nullptr
+ };
+
+ return filters;
+}
+
+static char const* const*
+terminal_client_get_environment_prefix_filters_excludes(void)
+{
+ static char const* filters[] = {
+ "MC_XDG_OPEN",
+
+ nullptr,
+ };
+
+ return filters;
+}
+
+bool
+terminal_client_get_environment_prefix_filters_is_excluded(char const* env)
+{
+ auto const excludes = terminal_client_get_environment_prefix_filters_excludes();
+ for (auto j = 0; excludes[j]; ++j) {
+ if (g_str_equal(excludes[j], env))
+ return true;
+ }
+
+ return false;
+}
+
+static char**
+terminal_environ_unsetenv_prefix (char** envv,
+ char const* prefix)
+{
+ if (!envv)
+ return envv;
+
+ for (auto i = 0; envv[i]; ++i) {
+ if (!g_str_has_prefix (envv[i], prefix))
+ continue;
+
+ auto const equal = strchr(envv[i], '=');
+ g_assert(equal != nullptr);
+ gs_free char* env = g_strndup(envv[i], equal - envv[i]);
+
+ if (terminal_client_get_environment_prefix_filters_is_excluded(env))
+ continue;
+
+ envv = g_environ_unsetenv (envv, env);
+ }
+
+ return envv;
+}
+
+/**
+ * terminal_client_filter_environment:
+ * @envv: (transfer full): the environment
+ *
+ * Filters unwanted variables from @envv, and returns it.
+ *
+ * Returns: (transfer full): the filtered environment
+ */
+char**
+terminal_client_filter_environment (char** envv)
+{
+ if (envv == nullptr)
+ return nullptr;
+
+ auto filters = terminal_client_get_environment_filters ();
+ for (auto i = 0; filters[i]; ++i)
+ envv = g_environ_unsetenv (envv, filters[i]);
+
+ filters = terminal_client_get_environment_prefix_filters ();
+ for (auto i = 0; filters[i]; ++i)
+ envv = terminal_environ_unsetenv_prefix (envv, filters[i]);
+
+ return envv;
+}
+
+/**
+ * terminal_client_append_exec_options:
+ * @builder: a #GVariantBuilder of #GVariantType "a{sv}"
+ * @pass_environment: whether to pass the current environment
+ * @working_directory: (allow-none): the cwd, or %nullptr
+ * @fd_array: (array lenght=fd_array_len):
+ * @fd_array_len:
+ * @shell:
+ *
+ * Appends the environment and the working directory to @builder.
+ */
+void
+terminal_client_append_exec_options (GVariantBuilder *builder,
+ gboolean pass_environment,
+ const char *working_directory,
+ PassFdElement *fd_array,
+ gsize fd_array_len,
+ gboolean shell)
+{
+ if (pass_environment) {
+ gs_strfreev char **envv;
+
+ envv = g_get_environ ();
+ envv = terminal_client_filter_environment (envv);
+
+ envv = g_environ_unsetenv (envv, TERMINAL_ENV_SERVICE_NAME);
+ envv = g_environ_unsetenv (envv, TERMINAL_ENV_SCREEN);
+
+ g_variant_builder_add (builder, "{sv}",
+ "environ",
+ g_variant_new_bytestring_array ((const char * const *) envv, -1));
+ }
+
+ if (working_directory)
+ g_variant_builder_add (builder, "{sv}",
+ "cwd", g_variant_new_bytestring (working_directory));
+
+ if (shell)
+ g_variant_builder_add (builder, "{sv}",
+ "shell",
+ g_variant_new_boolean (TRUE));
+
+ if (fd_array_len) {
+ gsize i;
+
+ g_variant_builder_open (builder, G_VARIANT_TYPE ("{sv}"));
+ g_variant_builder_add (builder, "s", "fd-set");
+
+ g_variant_builder_open (builder, G_VARIANT_TYPE ("v"));
+ g_variant_builder_open (builder, G_VARIANT_TYPE ("a(ih)"));
+ for (i = 0; i < fd_array_len; i++) {
+ g_variant_builder_add (builder, "(ih)", fd_array[i].fd, fd_array[i].index);
+ }
+ g_variant_builder_close (builder); /* a(ih) */
+ g_variant_builder_close (builder); /* v */
+
+ g_variant_builder_close (builder); /* {sv} */
+ }
+}
+
+#ifndef TERMINAL_NAUTILUS
+
+/**
+ * terminal_client_get_fallback_startup_id:
+ *
+ * Returns: a fallback startup ID, or %nullptr
+ */
+char *
+terminal_client_get_fallback_startup_id (void)
+{
+#if defined(TERMINAL_COMPILATION) && defined(GDK_WINDOWING_X11)
+ GdkDisplay *display;
+ Display *xdisplay;
+ Window xwindow;
+ XEvent event;
+
+ display = gdk_display_get_default ();
+ if (display == nullptr || !GDK_IS_X11_DISPLAY (display))
+ goto out;
+
+ xdisplay = GDK_DISPLAY_XDISPLAY (display);
+
+ {
+ XSetWindowAttributes attrs;
+ Atom atom_name;
+ Atom atom_type;
+ const char *name;
+
+ attrs.override_redirect = True;
+ attrs.event_mask = PropertyChangeMask | StructureNotifyMask;
+
+ xwindow =
+ XCreateWindow (xdisplay,
+ RootWindow (xdisplay, 0),
+ -100, -100, 1, 1,
+ 0,
+ CopyFromParent,
+ CopyFromParent,
+ (Visual *)CopyFromParent,
+ CWOverrideRedirect | CWEventMask,
+ &attrs);
+
+ atom_name = XInternAtom (xdisplay, "WM_NAME", TRUE);
+ g_assert (atom_name != None);
+ atom_type = XInternAtom (xdisplay, "STRING", TRUE);
+ g_assert (atom_type != None);
+
+ name = "Fake Window";
+ XChangeProperty (xdisplay,
+ xwindow, atom_name,
+ atom_type,
+ 8, PropModeReplace, (unsigned char *)name, strlen (name));
+ }
+
+ XWindowEvent (xdisplay,
+ xwindow,
+ PropertyChangeMask,
+ &event);
+
+ XDestroyWindow(xdisplay, xwindow);
+
+ return g_strdup_printf ("_TIME%lu", event.xproperty.time);
+out:
+#endif
+ return nullptr;
+}
+
+#endif /* !TERMINAL_NAUTILUS */
diff --git a/src/terminal-client-utils.hh b/src/terminal-client-utils.hh
new file mode 100644
index 0000000..4771acf
--- /dev/null
+++ b/src/terminal-client-utils.hh
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TERMINAL_CLIENT_UTILS_H
+#define TERMINAL_CLIENT_UTILS_H
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+G_BEGIN_DECLS
+
+char* terminal_client_get_directory_uninstalled(char const* exe_install_dir,
+ char const *file_install_dir,
+ char const* file_name,
+ GFileTest tests);
+
+char* terminal_client_get_file_uninstalled(char const* exe_install_dir,
+ char const *file_install_dir,
+ char const* file_name,
+ GFileTest tests);
+
+void terminal_client_append_create_instance_options (GVariantBuilder *builder,
+ const char *display_name,
+ const char *startup_id,
+ const char *activation_token,
+ const char *geometry,
+ const char *role,
+ const char *profile,
+ const char *encoding,
+ const char *title,
+ gboolean active,
+ gboolean maximise_window,
+ gboolean fullscreen_window);
+
+typedef struct {
+ int index;
+ int fd;
+} PassFdElement;
+
+void terminal_client_append_exec_options (GVariantBuilder *builder,
+ gboolean pass_environment,
+ const char *working_directory,
+ PassFdElement *fd_array,
+ gsize fd_array_len,
+ gboolean shell);
+
+char * terminal_client_get_fallback_startup_id (void) G_GNUC_MALLOC;
+
+char const* const* terminal_client_get_environment_filters (void);
+
+char const* const* terminal_client_get_environment_prefix_filters (void);
+
+bool terminal_client_get_environment_prefix_filters_is_excluded(char const* env);
+
+char** terminal_client_filter_environment (char** envv) G_GNUC_MALLOC;
+
+G_END_DECLS
+
+#endif /* TERMINAL_UTIL_UTILS_H */
diff --git a/src/terminal-debug.cc b/src/terminal-debug.cc
new file mode 100644
index 0000000..bf2db7e
--- /dev/null
+++ b/src/terminal-debug.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2002,2003 Red Hat, Inc.
+ *
+ * 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 <glib.h>
+
+#include "terminal-debug.hh"
+
+TerminalDebugFlags _terminal_debug_flags;
+
+void
+_terminal_debug_init(void)
+{
+#ifdef ENABLE_DEBUG
+ const GDebugKey keys[] = {
+ { "accels", TERMINAL_DEBUG_ACCELS },
+ { "clipboard", TERMINAL_DEBUG_CLIPBOARD },
+ { "encodings", TERMINAL_DEBUG_ENCODINGS },
+ { "server", TERMINAL_DEBUG_SERVER },
+ { "geometry", TERMINAL_DEBUG_GEOMETRY },
+ { "mdi", TERMINAL_DEBUG_MDI },
+ { "processes", TERMINAL_DEBUG_PROCESSES },
+ { "profile", TERMINAL_DEBUG_PROFILE },
+ { "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"),
+ keys, G_N_ELEMENTS (keys)));
+
+#endif /* ENABLE_DEBUG */
+}
+
diff --git a/src/terminal-debug.hh b/src/terminal-debug.hh
new file mode 100644
index 0000000..fedc9e4
--- /dev/null
+++ b/src/terminal-debug.hh
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+/* The interfaces in this file are subject to change at any time. */
+
+#ifndef ENABLE_DEBUG_H
+#define ENABLE_DEBUG_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ TERMINAL_DEBUG_ACCELS = 1 << 0,
+ TERMINAL_DEBUG_CLIPBOARD = 1 << 1,
+ TERMINAL_DEBUG_ENCODINGS = 1 << 2,
+ TERMINAL_DEBUG_SERVER = 1 << 3,
+ TERMINAL_DEBUG_GEOMETRY = 1 << 4,
+ TERMINAL_DEBUG_MDI = 1 << 5,
+ TERMINAL_DEBUG_PROCESSES = 1 << 6,
+ TERMINAL_DEBUG_PROFILE = 1 << 7,
+ 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);
+
+extern TerminalDebugFlags _terminal_debug_flags;
+static inline gboolean _terminal_debug_on (TerminalDebugFlags flags) G_GNUC_CONST G_GNUC_UNUSED;
+
+static inline gboolean
+_terminal_debug_on (TerminalDebugFlags flags)
+{
+ return (_terminal_debug_flags & flags) == flags;
+}
+
+#ifdef ENABLE_DEBUG
+#define _TERMINAL_DEBUG_IF(flags) if (G_UNLIKELY (_terminal_debug_on (flags)))
+#else
+#define _TERMINAL_DEBUG_IF(flags) if (0)
+#endif
+
+#if defined(__GNUC__) && G_HAVE_GNUC_VARARGS
+#define _terminal_debug_print(flags, fmt, ...) \
+ G_STMT_START { _TERMINAL_DEBUG_IF(flags) g_printerr(fmt, ##__VA_ARGS__); } G_STMT_END
+#else
+#include <stdarg.h>
+#include <glib/gstdio.h>
+static void _terminal_debug_print (guint flags, const char *fmt, ...)
+{
+ if (_terminal_debug_on (flags)) {
+ va_list ap;
+ va_start (ap, fmt);
+ g_vfprintf (stderr, fmt, ap);
+ va_end (ap);
+ }
+}
+#endif
+
+G_END_DECLS
+
+#endif /* !ENABLE_DEBUG_H */
diff --git a/src/terminal-defines.hh b/src/terminal-defines.hh
new file mode 100644
index 0000000..cd85d60
--- /dev/null
+++ b/src/terminal-defines.hh
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TERMINAL_DEFINES_H
+#define TERMINAL_DEFINES_H
+
+G_BEGIN_DECLS
+
+enum {
+ _EXIT_FAILURE_WRONG_ID = 7,
+ _EXIT_FAILURE_NO_UTF8 = 8,
+ _EXIT_FAILURE_UNSUPPORTED_LOCALE = 9,
+ _EXIT_FAILURE_GTK_INIT = 10
+};
+
+#define TERMINAL_APPLICATION_ID "org.gnome.Terminal"
+
+#define TERMINAL_OBJECT_PATH_PREFIX "/org/gnome/Terminal"
+#define TERMINAL_OBJECT_INTERFACE_PREFIX "org.gnome.Terminal"
+
+#define TERMINAL_FACTORY_OBJECT_PATH TERMINAL_OBJECT_PATH_PREFIX "/Factory0"
+#define TERMINAL_FACTORY_INTERFACE_NAME TERMINAL_OBJECT_INTERFACE_PREFIX ".Factory0"
+
+#define TERMINAL_RECEIVER_OBJECT_PATH_FORMAT TERMINAL_OBJECT_PATH_PREFIX "/screen/%s"
+#define TEMRINAL_RECEIVER_INTERFACE_NAME TERMINAL_OBJECT_INTERFACE_PREFIX ".Terminal0"
+
+#define TERMINAL_SEARCH_PROVIDER_PATH TERMINAL_OBJECT_PATH_PREFIX "/SearchProvider"
+
+#define TERMINAL_SETTINGS_BRIDGE_INTERFACE_NAME "org.gnome.Terminal.SettingsBridge0"
+#define TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH TERMINAL_OBJECT_PATH_PREFIX "/SettingsBridge"
+
+#define TERMINAL_PREFERENCES_APPLICATION_ID TERMINAL_APPLICATION_ID ".Preferences"
+#define TERMINAL_PREFERENCES_OBJECT_PATH TERMINAL_OBJECT_PATH_PREFIX "/Preferences"
+
+#define TERMINAL_ENV_SERVICE_NAME "GNOME_TERMINAL_SERVICE"
+#define TERMINAL_ENV_SCREEN "GNOME_TERMINAL_SCREEN"
+
+#define TERMINAL_PREFERENCES_BINARY_NAME "gnome-terminal-preferences"
+
+G_END_DECLS
+
+#endif /* !TERMINAL_DEFINES_H */
diff --git a/src/terminal-enums.hh b/src/terminal-enums.hh
new file mode 100644
index 0000000..93f1460
--- /dev/null
+++ b/src/terminal-enums.hh
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2002 Mathias Hasselmann
+ * Copyright © 2008, 2010 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/>.
+ */
+
+#ifndef TERMINAL_ENUMS_H
+#define TERMINAL_ENUMS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ TERMINAL_NEW_TERMINAL_MODE_WINDOW,
+ TERMINAL_NEW_TERMINAL_MODE_TAB
+} TerminalNewTerminalMode;
+
+typedef enum {
+ TERMINAL_NEW_TAB_POSITION_LAST,
+ TERMINAL_NEW_TAB_POSITION_NEXT
+} TerminalNewTabPosition;
+
+typedef enum
+{
+ TERMINAL_EXIT_CLOSE,
+ TERMINAL_EXIT_RESTART,
+ TERMINAL_EXIT_HOLD
+} TerminalExitAction;
+
+typedef enum {
+ TERMINAL_SETTINGS_LIST_FLAG_NONE = 0,
+ TERMINAL_SETTINGS_LIST_FLAG_HAS_DEFAULT = 1 << 0,
+ TERMINAL_SETTINGS_LIST_FLAG_ALLOW_EMPTY = 1 << 1
+} TerminalSettingsListFlags;
+
+typedef enum {
+ TERMINAL_CJK_WIDTH_NARROW = 1,
+ TERMINAL_CJK_WIDTH_WIDE = 2
+} TerminalCJKWidth;
+
+typedef enum {
+ TERMINAL_THEME_VARIANT_SYSTEM = 0,
+ TERMINAL_THEME_VARIANT_LIGHT = 1,
+ TERMINAL_THEME_VARIANT_DARK = 2
+} TerminalThemeVariant;
+
+typedef enum {
+ TERMINAL_PRESERVE_WORKING_DIRECTORY_NEVER = 0,
+ TERMINAL_PRESERVE_WORKING_DIRECTORY_SAFE = 1,
+ TERMINAL_PRESERVE_WORKING_DIRECTORY_ALWAYS = 2,
+} TerminalPreserveWorkingDirectory;
+
+G_END_DECLS
+
+#endif /* TERMINAL_ENUMS_H */
diff --git a/src/terminal-gdbus.cc b/src/terminal-gdbus.cc
new file mode 100644
index 0000000..55f3391
--- /dev/null
+++ b/src/terminal-gdbus.cc
@@ -0,0 +1,573 @@
+/*
+ * Copyright © 2011, 2012 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-gdbus.hh"
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-mdi-container.hh"
+#include "terminal-util.hh"
+#include "terminal-window.hh"
+#include "terminal-libgsystem.hh"
+
+/* ------------------------------------------------------------------------- */
+
+#define TERMINAL_RECEIVER_IMPL_GET_PRIVATE(impl)(G_TYPE_INSTANCE_GET_PRIVATE ((impl), TERMINAL_TYPE_RECEIVER_IMPL, TerminalReceiverImplPrivate))
+
+struct _TerminalReceiverImplPrivate {
+ TerminalScreen *screen; /* unowned! */
+};
+
+enum {
+ PROP_0,
+ PROP_SCREEN
+};
+
+/* helper functions */
+
+static void
+child_exited_cb (VteTerminal *terminal,
+ int exit_code,
+ TerminalReceiver *receiver)
+{
+ terminal_receiver_emit_child_exited (receiver, exit_code);
+}
+
+static void
+terminal_receiver_impl_set_screen (TerminalReceiverImpl *impl,
+ TerminalScreen *screen)
+{
+ TerminalReceiverImplPrivate *priv;
+
+ g_return_if_fail (TERMINAL_IS_RECEIVER_IMPL (impl));
+ g_return_if_fail (screen == nullptr || TERMINAL_IS_SCREEN (screen));
+
+ priv = impl->priv;
+ if (priv->screen == screen)
+ return;
+
+ if (priv->screen) {
+ g_signal_handlers_disconnect_matched (priv->screen,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, nullptr, nullptr, impl);
+ }
+
+ priv->screen = screen;
+ if (screen) {
+ g_signal_connect (screen, "child-exited",
+ G_CALLBACK (child_exited_cb),
+ impl);
+ }
+
+ g_object_notify (G_OBJECT (impl), "screen");
+}
+
+/* Class implementation */
+
+namespace {
+
+typedef struct {
+ TerminalReceiver *receiver;
+ GDBusMethodInvocation *invocation;
+} ExecData;
+
+} // anon namespace
+
+static void
+exec_data_free (ExecData *data)
+{
+ g_object_unref (data->receiver);
+ g_object_unref (data->invocation);
+ g_free (data);
+}
+
+static void
+exec_cb (TerminalScreen *screen, /* unused, may be %nullptr */
+ GError *error, /* set on error, %nullptr on success */
+ ExecData *data)
+{
+ /* Note: these calls transfer the ref */
+ g_object_ref (data->invocation);
+ if (error) {
+ g_dbus_method_invocation_return_gerror (data->invocation, error);
+ } else {
+ terminal_receiver_complete_exec (data->receiver, data->invocation, nullptr /* outfdlist */);
+ }
+}
+
+static gboolean
+terminal_receiver_impl_exec (TerminalReceiver *receiver,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ GVariant *options,
+ GVariant *arguments)
+{
+ TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (receiver);
+ TerminalReceiverImplPrivate *priv = impl->priv;
+ const char *working_directory;
+ gboolean shell;
+ gsize exec_argc;
+ gs_free char **exec_argv = nullptr; /* container needs to be freed, strings not owned */
+ gs_free char **envv = nullptr; /* container needs to be freed, strings not owned */
+ gs_unref_variant GVariant *fd_array = nullptr;
+
+ if (priv->screen == nullptr) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Terminal already closed");
+ return TRUE; /* handled */
+ }
+
+ if (!g_variant_lookup (options, "cwd", "^&ay", &working_directory))
+ working_directory = nullptr;
+ if (!g_variant_lookup (options, "shell", "b", &shell))
+ shell = FALSE;
+ if (!g_variant_lookup (options, "environ", "^a&ay", &envv))
+ envv = nullptr;
+ if (!g_variant_lookup (options, "fd-set", "@a(ih)", &fd_array))
+ fd_array = nullptr;
+
+ /* Check environment */
+ if (!terminal_util_check_envv((const char * const*)envv)) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Malformed environment");
+ return TRUE; /* handled */
+ }
+
+ /* Check FD passing */
+ if ((fd_list != nullptr) ^ (fd_array != nullptr)) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Must pass both fd-set options and a FD list");
+ return TRUE; /* handled */
+ }
+ if (fd_list != nullptr && fd_array != nullptr) {
+ const int *fd_array_data;
+ gsize fd_array_data_len, i;
+ int n_fds;
+
+ fd_array_data = reinterpret_cast<int const*>
+ (g_variant_get_fixed_array (fd_array, &fd_array_data_len, 2 * sizeof (int)));
+ n_fds = g_unix_fd_list_get_length (fd_list);
+ for (i = 0; i < fd_array_data_len; i++) {
+ const int fd = fd_array_data[2 * i];
+ const int idx = fd_array_data[2 * i + 1];
+
+ if (fd == -1) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Passing of invalid FD %d not supported", fd);
+ return TRUE; /* handled */
+ }
+ if (fd == STDIN_FILENO ||
+ fd == STDOUT_FILENO ||
+ fd == STDERR_FILENO) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Passing of std%s not supported",
+ fd == STDIN_FILENO ? "in" : fd == STDOUT_FILENO ? "out" : "err");
+ return TRUE; /* handled */
+ }
+ if (idx < 0 || idx >= n_fds) {
+ g_dbus_method_invocation_return_error_literal (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Handle out of range");
+ return TRUE; /* handled */
+ }
+ }
+ }
+
+ if (working_directory != nullptr)
+ _terminal_debug_print (TERMINAL_DEBUG_SERVER,
+ "CWD is '%s'\n", working_directory);
+
+ exec_argv = (char **) g_variant_get_bytestring_array (arguments, &exec_argc);
+
+ ExecData *exec_data = g_new (ExecData, 1);
+ exec_data->receiver = (TerminalReceiver*)g_object_ref (receiver);
+ /* We want to transfer the ownership of @invocation to ExecData here, but
+ * we have to temporarily ref it so that in the error case below (where
+ * terminal_screen_exec() frees the exec data via the supplied callback,
+ * the g_dbus_method_invocation_take_error() calll still can take ownership
+ * of the invocation's ref passed to this function (terminal_receiver_impl_exec()).
+ */
+ exec_data->invocation = (GDBusMethodInvocation*)g_object_ref (invocation);
+
+ GError *err = nullptr;
+ if (!terminal_screen_exec (priv->screen,
+ exec_argc > 0 ? exec_argv : nullptr,
+ envv,
+ shell,
+ working_directory,
+ fd_list, fd_array,
+ (TerminalScreenExecCallback) exec_cb,
+ exec_data /* adopted */,
+ (GDestroyNotify) exec_data_free,
+ nullptr /* cancellable */,
+ &err)) {
+ /* Transfers ownership of @invocation */
+ g_dbus_method_invocation_take_error (invocation, err);
+ }
+
+ /* Now we can remove that extra ref again. */
+ g_object_unref (invocation);
+
+ return TRUE; /* handled */
+}
+
+static void
+terminal_receiver_impl_iface_init (TerminalReceiverIface *iface)
+{
+ iface->handle_exec = terminal_receiver_impl_exec;
+}
+
+G_DEFINE_TYPE_WITH_CODE (TerminalReceiverImpl, terminal_receiver_impl, TERMINAL_TYPE_RECEIVER_SKELETON,
+ G_IMPLEMENT_INTERFACE (TERMINAL_TYPE_RECEIVER, terminal_receiver_impl_iface_init))
+
+static void
+terminal_receiver_impl_init (TerminalReceiverImpl *impl)
+{
+ impl->priv = TERMINAL_RECEIVER_IMPL_GET_PRIVATE (impl);
+}
+
+static void
+terminal_receiver_impl_dispose (GObject *object)
+{
+ TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (object);
+
+ terminal_receiver_impl_set_screen (impl, nullptr);
+
+ G_OBJECT_CLASS (terminal_receiver_impl_parent_class)->dispose (object);
+}
+
+static void
+terminal_receiver_impl_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (object);
+
+ switch (prop_id) {
+ case PROP_SCREEN:
+ g_value_set_object (value, terminal_receiver_impl_get_screen (impl));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_receiver_impl_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalReceiverImpl *impl = TERMINAL_RECEIVER_IMPL (object);
+
+ switch (prop_id) {
+ case PROP_SCREEN:
+ terminal_receiver_impl_set_screen (impl, (TerminalScreen*)g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_receiver_impl_class_init (TerminalReceiverImplClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = terminal_receiver_impl_dispose;
+ gobject_class->get_property = terminal_receiver_impl_get_property;
+ gobject_class->set_property = terminal_receiver_impl_set_property;
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SCREEN,
+ g_param_spec_object ("screen", nullptr, nullptr,
+ TERMINAL_TYPE_SCREEN,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS)));
+
+ g_type_class_add_private (gobject_class, sizeof (TerminalReceiverImplPrivate));
+}
+
+/* public API */
+
+/**
+ * terminal_receiver_impl_new:
+ * @screen: a #TerminalScreen
+ *
+ * Returns: a new #TerminalReceiverImpl for @screen
+ */
+TerminalReceiverImpl *
+terminal_receiver_impl_new (TerminalScreen *screen)
+{
+ return reinterpret_cast<TerminalReceiverImpl*>
+ (g_object_new (TERMINAL_TYPE_RECEIVER_IMPL,
+ "screen", screen,
+ nullptr));
+}
+
+/**
+ * terminal_receiver_impl_get_screen:
+ * @impl: a #TerminalReceiverImpl
+ *
+ * Returns: (transfer none): the impl's #TerminalScreen, or %nullptr
+ */
+TerminalScreen *
+terminal_receiver_impl_get_screen (TerminalReceiverImpl *impl)
+{
+ g_return_val_if_fail (TERMINAL_IS_RECEIVER_IMPL (impl), nullptr);
+
+ return impl->priv->screen;
+}
+
+/**
+ * terminal_receiver_impl_unget_screen:
+ * @impl: a #TerminalReceiverImpl
+ *
+ * Unsets the impls #TerminalScreen.
+ */
+void
+terminal_receiver_impl_unset_screen (TerminalReceiverImpl *impl)
+{
+ g_return_if_fail (TERMINAL_IS_RECEIVER_IMPL (impl));
+
+ terminal_receiver_impl_set_screen (impl, nullptr);
+}
+
+/* ---------------------------------------------------------------------------
+ * TerminalFactoryImpl
+ * ---------------------------------------------------------------------------
+ */
+
+struct _TerminalFactoryImplPrivate {
+ gpointer dummy;
+};
+
+static gboolean
+terminal_factory_impl_create_instance (TerminalFactory *factory,
+ GDBusMethodInvocation *invocation,
+ GVariant *options)
+{
+ TerminalApp *app = terminal_app_get ();
+
+ /* If a parent screen is specified, use that to fill in missing information */
+ TerminalScreen *parent_screen = nullptr;
+ const char *parent_screen_object_path;
+ if (g_variant_lookup (options, "parent-screen", "&o", &parent_screen_object_path)) {
+ parent_screen = terminal_app_get_screen_by_object_path (app, parent_screen_object_path);
+ if (parent_screen == nullptr) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Failed to get screen from object path %s",
+ parent_screen_object_path);
+ return TRUE;
+ }
+ }
+
+ /* Try getting a parent window, first by parent screen then by window ID;
+ * if that fails, create a new window.
+ */
+ TerminalWindow *window = nullptr;
+ gboolean have_new_window = FALSE;
+ const char *window_from_screen_object_path;
+ if (g_variant_lookup (options, "window-from-screen", "&o", &window_from_screen_object_path)) {
+ TerminalScreen *window_screen =
+ terminal_app_get_screen_by_object_path (app, window_from_screen_object_path);
+ if (window_screen == nullptr) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Failed to get screen from object path %s",
+ parent_screen_object_path);
+ return TRUE;
+ }
+
+ GtkWidget *win = gtk_widget_get_toplevel (GTK_WIDGET (window_screen));
+ if (TERMINAL_IS_WINDOW (win))
+ window = TERMINAL_WINDOW (win);
+ }
+
+ /* Support old client */
+ guint window_id;
+ if (window == nullptr && g_variant_lookup (options, "window-id", "u", &window_id)) {
+ GtkWindow *win = gtk_application_get_window_by_id (GTK_APPLICATION (app), window_id);
+
+ if (!TERMINAL_IS_WINDOW (win)) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Nonexisting window %u referenced",
+ window_id);
+ return TRUE;
+ }
+
+ window = TERMINAL_WINDOW (win);
+ }
+
+ /* Still no parent window? Create a new one */
+ if (window == nullptr) {
+ const char *startup_id, *role, *activation_token;
+ gboolean start_maximized, start_fullscreen;
+
+ window = terminal_window_new (G_APPLICATION (app));
+ have_new_window = TRUE;
+
+ if (g_variant_lookup (options, "activation-token", "&s", &activation_token))
+ gtk_window_set_startup_id (GTK_WINDOW (window), activation_token);
+ else if (g_variant_lookup (options, "desktop-startup-id", "^&ay", &startup_id))
+ gtk_window_set_startup_id (GTK_WINDOW (window), startup_id);
+
+ /* Overwrite the default, unique window role set in terminal_window_init */
+ if (g_variant_lookup (options, "role", "&s", &role))
+ gtk_window_set_role (GTK_WINDOW (window), role);
+
+ gboolean show_menubar;
+ if (g_variant_lookup (options, "show-menubar", "b", &show_menubar))
+ terminal_window_set_menubar_visible (window, show_menubar);
+
+ if (g_variant_lookup (options, "fullscreen-window", "b", &start_fullscreen) &&
+ start_fullscreen) {
+ gtk_window_fullscreen (GTK_WINDOW (window));
+ }
+ if (g_variant_lookup (options, "maximize-window", "b", &start_maximized) &&
+ start_maximized) {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+
+ have_new_window = TRUE;
+ }
+
+ g_assert_nonnull (window);
+
+ const char *title;
+ if (!g_variant_lookup (options, "title", "&s", &title))
+ title = nullptr;
+
+ double zoom;
+ if (!g_variant_lookup (options, "zoom", "d", &zoom)) {
+ if (parent_screen != nullptr)
+ zoom = vte_terminal_get_font_scale (VTE_TERMINAL (parent_screen));
+ else
+ zoom = 1.0;
+ }
+
+ /* Look up the profile */
+ gs_unref_object GSettings *profile = nullptr;
+ const char *profile_uuid;
+ if (!g_variant_lookup (options, "profile", "&s", &profile_uuid))
+ profile_uuid = nullptr;
+
+ if (profile_uuid == nullptr && parent_screen != nullptr) {
+ profile = terminal_screen_ref_profile (parent_screen);
+ } else {
+ GError *err = nullptr;
+ profile = terminal_profiles_list_ref_profile_by_uuid (terminal_app_get_profiles_list (app),
+ profile_uuid /* default if nullptr */,
+ &err);
+ if (profile == nullptr) {
+ g_dbus_method_invocation_return_gerror (invocation, err);
+ g_error_free (err);
+ return TRUE;
+ }
+ }
+
+ g_assert_nonnull (profile);
+
+ /* Now we can create the new screen */
+ TerminalScreen *screen = terminal_screen_new (profile, title, zoom);
+ terminal_window_add_screen (window, screen, -1);
+
+ /* Apply window properties */
+ gboolean active;
+ if (g_variant_lookup (options, "active", "b", &active) &&
+ active) {
+ terminal_window_switch_screen (window, screen);
+ gtk_widget_grab_focus (GTK_WIDGET (screen));
+ }
+
+ if (have_new_window) {
+ const char *geometry;
+
+ if (g_variant_lookup (options, "geometry", "&s", &geometry) &&
+ !terminal_window_parse_geometry (window, geometry))
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "Invalid geometry string \"%s\"", geometry);
+ }
+
+ gboolean present_window;
+ gboolean present_window_set = g_variant_lookup (options, "present-window", "b", &present_window);
+
+ if (have_new_window || (present_window_set && present_window))
+ gtk_window_present (GTK_WINDOW (window));
+
+ gs_free char *object_path = terminal_app_dup_screen_object_path (app, screen);
+ terminal_factory_complete_create_instance (factory, invocation, object_path);
+
+ return TRUE; /* handled */
+}
+
+static void
+terminal_factory_impl_iface_init (TerminalFactoryIface *iface)
+{
+ iface->handle_create_instance = terminal_factory_impl_create_instance;
+}
+
+G_DEFINE_TYPE_WITH_CODE (TerminalFactoryImpl, terminal_factory_impl, TERMINAL_TYPE_FACTORY_SKELETON,
+ G_IMPLEMENT_INTERFACE (TERMINAL_TYPE_FACTORY, terminal_factory_impl_iface_init))
+
+static void
+terminal_factory_impl_init (TerminalFactoryImpl *impl)
+{
+ impl->priv = G_TYPE_INSTANCE_GET_PRIVATE (impl, TERMINAL_TYPE_FACTORY_IMPL, TerminalFactoryImplPrivate);
+}
+
+static void
+terminal_factory_impl_class_init (TerminalFactoryImplClass *klass)
+{
+ /* g_type_class_add_private (klass, sizeof (TerminalFactoryImplPrivate)); */
+}
+
+/**
+ * terminal_factory_impl_new:
+ *
+ * Returns: (transfer full): a new #TerminalFactoryImpl
+ */
+TerminalFactory *
+terminal_factory_impl_new (void)
+{
+ return reinterpret_cast<TerminalFactory*>
+ (g_object_new (TERMINAL_TYPE_FACTORY_IMPL, nullptr));
+}
diff --git a/src/terminal-gdbus.hh b/src/terminal-gdbus.hh
new file mode 100644
index 0000000..38c24bc
--- /dev/null
+++ b/src/terminal-gdbus.hh
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TERMINAL_RECEIVER_IMPL_H
+#define TERMINAL_RECEIVER_IMPL_H
+
+#include <glib-object.h>
+
+#include "terminal-gdbus-generated.h"
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_RECEIVER_IMPL (terminal_receiver_impl_get_type ())
+#define TERMINAL_RECEIVER_IMPL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_RECEIVER_IMPL, TerminalReceiverImpl))
+#define TERMINAL_RECEIVER_IMPL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_RECEIVER_IMPL, TerminalReceiverImplClass))
+#define TERMINAL_IS_RECEIVER_IMPL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_RECEIVER_IMPL))
+#define TERMINAL_IS_RECEIVER_IMPL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_RECEIVER_IMPL))
+#define TERMINAL_RECEIVER_IMPL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_RECEIVER_IMPL, TerminalReceiverImplClass))
+
+typedef struct _TerminalReceiverImpl TerminalReceiverImpl;
+typedef struct _TerminalReceiverImplClass TerminalReceiverImplClass;
+typedef struct _TerminalReceiverImplPrivate TerminalReceiverImplPrivate;
+
+struct _TerminalReceiverImpl
+{
+ TerminalReceiverSkeleton parent_instance;
+
+ /*< private >*/
+ TerminalReceiverImplPrivate *priv;
+};
+
+struct _TerminalReceiverImplClass
+{
+ TerminalReceiverSkeletonClass parent_class;
+};
+
+GType terminal_receiver_impl_get_type (void);
+
+TerminalReceiverImpl *terminal_receiver_impl_new (TerminalScreen *screen);
+
+TerminalScreen *terminal_receiver_impl_get_screen (TerminalReceiverImpl *impl);
+
+void terminal_receiver_impl_unset_screen (TerminalReceiverImpl *impl);
+
+/* ------------------------------------------------------------------------- */
+
+#define TERMINAL_TYPE_FACTORY_IMPL (terminal_factory_impl_get_type ())
+#define TERMINAL_FACTORY_IMPL(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TERMINAL_TYPE_FACTORY_IMPL, TerminalFactoryImpl))
+#define TERMINAL_FACTORY_IMPL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TERMINAL_TYPE_FACTORY_IMPL, TerminalFactoryImplClass))
+#define TERMINAL_IS_FACTORY_IMPL(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TERMINAL_TYPE_FACTORY_IMPL))
+#define TERMINAL_IS_FACTORY_IMPL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TERMINAL_TYPE_FACTORY_IMPL))
+#define TERMINAL_FACTORY_IMPL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TERMINAL_TYPE_FACTORY_IMPL, TerminalFactoryImplClass))
+
+typedef struct _TerminalFactoryImpl TerminalFactoryImpl;
+typedef struct _TerminalFactoryImplPrivate TerminalFactoryImplPrivate;
+typedef struct _TerminalFactoryImplClass TerminalFactoryImplClass;
+
+struct _TerminalFactoryImplClass {
+ TerminalFactorySkeletonClass parent_class;
+};
+
+struct _TerminalFactoryImpl
+{
+ TerminalFactorySkeleton parent_instance;
+
+ TerminalFactoryImplPrivate *priv;
+};
+
+GType terminal_factory_impl_get_type (void);
+
+TerminalFactory *terminal_factory_impl_new (void);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_RECEIVER_IMPL_H */
diff --git a/src/terminal-headerbar.cc b/src/terminal-headerbar.cc
new file mode 100644
index 0000000..38bae5a
--- /dev/null
+++ b/src/terminal-headerbar.cc
@@ -0,0 +1,170 @@
+/*
+ * Copyright © 2018 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 <glib/gi18n.h>
+
+#include "terminal-headerbar.hh"
+#include "terminal-app.hh"
+#include "terminal-libgsystem.hh"
+
+typedef struct _TerminalHeaderbarPrivate TerminalHeaderbarPrivate;
+
+struct _TerminalHeaderbar
+{
+ GtkHeaderBar parent_instance;
+};
+
+struct _TerminalHeaderbarClass
+{
+ GtkHeaderBarClass parent_class;
+};
+
+struct _TerminalHeaderbarPrivate
+{
+ GtkWidget *profilebutton;
+ GtkWidget *menubutton;
+};
+
+enum {
+ PROP_0,
+ LAST_PROP
+};
+
+enum {
+ LAST_SIGNAL
+};
+
+/* static guint signals[LAST_SIGNAL]; */
+/* static GParamSpec *pspecs[LAST_PROP]; */
+
+G_DEFINE_TYPE_WITH_PRIVATE (TerminalHeaderbar, terminal_headerbar, GTK_TYPE_HEADER_BAR)
+
+#define PRIV(obj) ((TerminalHeaderbarPrivate *) terminal_headerbar_get_instance_private ((TerminalHeaderbar *)(obj)))
+
+static void
+profilemenu_items_changed_cb (GMenuModel *menu,
+ int position G_GNUC_UNUSED,
+ int removed G_GNUC_UNUSED,
+ int added G_GNUC_UNUSED,
+ TerminalHeaderbarPrivate *priv)
+{
+ if (g_menu_model_get_n_items (menu) > 0)
+ gtk_widget_show (priv->profilebutton);
+ else
+ gtk_widget_hide (priv->profilebutton);
+}
+
+/* Class implementation */
+
+static void
+terminal_headerbar_init (TerminalHeaderbar *headerbar)
+{
+
+ TerminalHeaderbarPrivate *priv = PRIV (headerbar);
+ GtkWidget *widget = GTK_WIDGET (headerbar);
+ TerminalApp *app = terminal_app_get ();
+ GMenuModel *profilemenu;
+
+ gtk_widget_init_template (widget);
+
+ gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->menubutton),
+ terminal_app_get_headermenu (app));
+
+ profilemenu = terminal_app_get_profilemenu (app);
+ gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (priv->profilebutton),
+ profilemenu);
+
+ g_signal_connect (profilemenu, "items-changed",
+ G_CALLBACK (profilemenu_items_changed_cb), priv);
+ profilemenu_items_changed_cb (profilemenu, 0, 0, 0, priv);
+}
+
+static void
+terminal_headerbar_dispose (GObject *object)
+{
+ TerminalHeaderbar *headerbar = TERMINAL_HEADERBAR (object);
+ TerminalHeaderbarPrivate *priv = PRIV (headerbar);
+ TerminalApp *app = terminal_app_get ();
+
+ GMenuModel *profilemenu = terminal_app_get_profilemenu (app);
+ g_signal_handlers_disconnect_by_func (profilemenu,
+ (void*)profilemenu_items_changed_cb,
+ priv);
+
+ G_OBJECT_CLASS (terminal_headerbar_parent_class)->dispose (object);
+}
+
+static void
+terminal_headerbar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ // TerminalHeaderbar *headerbar = TERMINAL_HEADERBAR (object);
+
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_headerbar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_headerbar_class_init (TerminalHeaderbarClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->dispose = terminal_headerbar_dispose;
+ gobject_class->get_property = terminal_headerbar_get_property;
+ gobject_class->set_property = terminal_headerbar_set_property;
+
+ /* g_object_class_install_properties (gobject_class, G_N_ELEMENTS (pspecs), pspecs); */
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/terminal/ui/headerbar.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalHeaderbar, menubutton);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalHeaderbar, profilebutton);
+}
+
+/* public API */
+
+/**
+ * terminal_headerbar_new:
+ *
+ * Returns: a new #TerminalHeaderbar
+ */
+GtkWidget *
+terminal_headerbar_new (void)
+{
+ return reinterpret_cast<GtkWidget*>
+ (g_object_new (TERMINAL_TYPE_HEADERBAR, nullptr));
+}
diff --git a/src/terminal-headerbar.hh b/src/terminal-headerbar.hh
new file mode 100644
index 0000000..c8aabd6
--- /dev/null
+++ b/src/terminal-headerbar.hh
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2018 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/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_HEADERBAR (terminal_headerbar_get_type ())
+#define TERMINAL_HEADERBAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_HEADERBAR, TerminalHeaderbar))
+#define TERMINAL_HEADERBAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_HEADERBAR, TerminalHeaderbarClass))
+#define TERMINAL_IS_HEADERBAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_HEADERBAR))
+#define TERMINAL_IS_HEADERBAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_HEADERBAR))
+#define TERMINAL_HEADERBAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_HEADERBAR, TerminalHeaderbarClass))
+
+typedef struct _TerminalHeaderbar TerminalHeaderbar;
+typedef struct _TerminalHeaderbarClass TerminalHeaderbarClass;
+
+GType terminal_headerbar_get_type (void);
+
+GtkWidget *terminal_headerbar_new (void);
+
+G_END_DECLS
diff --git a/src/terminal-headerbar.ui b/src/terminal-headerbar.ui
new file mode 100644
index 0000000..047cdfe
--- /dev/null
+++ b/src/terminal-headerbar.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2018 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, or (at your option)
+ any later version.
+
+ This program is distributed in the hope conf 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/>.
+-->
+<interface>
+ <template class="TerminalHeaderbar" parent="GtkHeaderBar">
+ <property name="can-focus">False</property>
+ <property name="visible">True</property>
+ <property name="show-close-button">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action-name">win.new-terminal</property>
+ <property name="action-target">('tab-default','current')</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">tab-new-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="profilebutton">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use-popover">False</property>
+ <style>
+ <class name="image-button"/>
+ <class name="disclosure-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="menubutton">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action-name">win.header-menu</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="action-name">win.find</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/terminal-headermenu.ui b/src/terminal-headermenu.ui
new file mode 100644
index 0000000..42d5296
--- /dev/null
+++ b/src/terminal-headermenu.ui
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2012 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/>.
+-->
+<interface>
+ <menu id="headermenu">
+ <section>
+ <attribute name="display-hint">horizontal-buttons</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Zoom _Out</attribute>
+ <attribute name="verb-icon">zoom-out-symbolic</attribute>
+ <attribute name="action">win.zoom-out</attribute>
+ </item>
+ <item>
+ <attribute name="label">100%</attribute>
+ <attribute name="action">win.zoom-normal</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Zoom _In</attribute>
+ <attribute name="verb-icon">zoom-in-symbolic</attribute>
+ <attribute name="action">win.zoom-in</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">New _Window</attribute>
+ <attribute name="action">win.new-terminal</attribute>
+ <attribute name="target" type="(ss)">('window', 'current')</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Full Screen</attribute>
+ <attribute name="action">win.enter-fullscreen</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Read-_Only</attribute>
+ <attribute name="action">win.read-only</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Set _Title…</attribute>
+ <attribute name="action">win.set-title</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ <section id="set-profile-section" />
+ <submenu>
+ <attribute name="label" translatable="yes">_Advanced</attribute>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Reset</attribute>
+ <attribute name="action">win.reset</attribute>
+ <attribute name="target" type="b">false</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Reset and C_lear</attribute>
+ <attribute name="action">win.reset</attribute>
+ <attribute name="target" type="b">true</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_1. 80×24</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(80, 24)</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_2. 80×43</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(80, 43)</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_3. 132×24</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(132, 24)</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_4. 132×43</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(132, 43)</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Inspector</attribute>
+ <attribute name="action">win.inspector</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </submenu>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Preferences</attribute>
+ <attribute name="action">app.preferences</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Help</attribute>
+ <attribute name="action">app.help</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_About</attribute>
+ <attribute name="action">app.about</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/terminal-i18n.cc b/src/terminal-i18n.cc
new file mode 100644
index 0000000..162c49f
--- /dev/null
+++ b/src/terminal-i18n.cc
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2002 Havoc Pennington
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "terminal-i18n.hh"
+
+#include <libintl.h>
+
+void
+terminal_i18n_init (gboolean set_default)
+{
+ bindtextdomain (GETTEXT_PACKAGE, TERM_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ if (set_default)
+ textdomain (GETTEXT_PACKAGE);
+}
diff --git a/src/terminal-i18n.hh b/src/terminal-i18n.hh
new file mode 100644
index 0000000..689bb82
--- /dev/null
+++ b/src/terminal-i18n.hh
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2002 Havoc Pennington
+ *
+ * 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/>.
+ */
+
+#ifndef TERMINAL_I18N_H
+#define TERMINAL_I18N_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void terminal_i18n_init (gboolean set_default);
+
+G_END_DECLS
+
+#endif /* TERMINAL_I18N_H */
diff --git a/src/terminal-icon-button.cc b/src/terminal-icon-button.cc
new file mode 100644
index 0000000..b499824
--- /dev/null
+++ b/src/terminal-icon-button.cc
@@ -0,0 +1,50 @@
+/*
+ * terminal-icon-button.c
+ *
+ * Copyright © 2010 - Paolo Borelli
+ * Copyright © 2011 - Ignacio Casal Quinteiro
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-icon-button.hh"
+#include "terminal-libgsystem.hh"
+
+GtkWidget *
+terminal_icon_button_new (const char *gicon_name)
+{
+ GtkWidget *button, *image;
+ gs_unref_object GIcon *icon;
+
+ button = (GtkWidget *) g_object_new (GTK_TYPE_BUTTON,
+ "relief", GTK_RELIEF_NONE,
+ "focus-on-click", FALSE,
+ nullptr);
+
+ icon = g_themed_icon_new_with_default_fallbacks (gicon_name);
+ image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
+
+ gtk_widget_show (image);
+ gtk_container_add (GTK_CONTAINER (button), image);
+
+ return button;
+}
+
+GtkWidget *
+terminal_close_button_new (void)
+{
+ return terminal_icon_button_new ("window-close-symbolic");
+}
diff --git a/src/terminal-icon-button.hh b/src/terminal-icon-button.hh
new file mode 100644
index 0000000..ec0a8db
--- /dev/null
+++ b/src/terminal-icon-button.hh
@@ -0,0 +1,33 @@
+/*
+ * terminal-close-button.h
+ *
+ * Copyright © 2010 - Paolo Borelli
+ *
+ * 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/>.
+ */
+
+#ifndef __TERMINAL_ICON_BUTTON_H__
+#define __TERMINAL_ICON_BUTTON_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *terminal_icon_button_new (const char *gicon_name);
+
+GtkWidget *terminal_close_button_new (void);
+
+G_END_DECLS
+
+#endif /* __TERMINAL_ICON_BUTTON_H__ */
diff --git a/src/terminal-info-bar.cc b/src/terminal-info-bar.cc
new file mode 100644
index 0000000..1b451a6
--- /dev/null
+++ b/src/terminal-info-bar.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2010 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "terminal-info-bar.hh"
+#include "terminal-libgsystem.hh"
+
+#include <gtk/gtk.h>
+
+#define TERMINAL_INFO_BAR_GET_PRIVATE(info_bar)(G_TYPE_INSTANCE_GET_PRIVATE ((info_bar), TERMINAL_TYPE_INFO_BAR, TerminalInfoBarPrivate))
+
+struct _TerminalInfoBarPrivate
+{
+ GtkWidget *content_box;
+};
+
+G_DEFINE_TYPE (TerminalInfoBar, terminal_info_bar, GTK_TYPE_INFO_BAR)
+
+/* helper functions */
+
+static void
+terminal_info_bar_init (TerminalInfoBar *bar)
+{
+ GtkInfoBar *info_bar = GTK_INFO_BAR (bar);
+ TerminalInfoBarPrivate *priv;
+
+ priv = bar->priv = TERMINAL_INFO_BAR_GET_PRIVATE (bar);
+
+ priv->content_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (info_bar)),
+ priv->content_box, TRUE, TRUE, 0);
+}
+
+static void
+terminal_info_bar_class_init (TerminalInfoBarClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (gobject_class, sizeof (TerminalInfoBarPrivate));
+}
+
+/* public API */
+
+/**
+ * terminal_info_bar_new:
+ * @type: a #GtkMessageType
+ *
+ * Returns: a new #TerminalInfoBar for @screen
+ */
+GtkWidget *
+terminal_info_bar_new (GtkMessageType type,
+ const char *first_button_text,
+ ...)
+{
+ GtkWidget *info_bar;
+ va_list args;
+
+ info_bar = reinterpret_cast<GtkWidget*>
+ (g_object_new (TERMINAL_TYPE_INFO_BAR,
+ "message-type", type,
+ "show-close-button", true,
+ nullptr));
+
+ va_start (args, first_button_text);
+ while (first_button_text != nullptr) {
+ int response_id;
+
+ response_id = va_arg (args, int);
+ gtk_info_bar_add_button (GTK_INFO_BAR (info_bar),
+ first_button_text, response_id);
+
+ first_button_text = va_arg (args, const char *);
+ }
+ va_end (args);
+
+ return info_bar;
+}
+
+void
+terminal_info_bar_format_text (TerminalInfoBar *bar,
+ const char *format,
+ ...)
+{
+ TerminalInfoBarPrivate *priv;
+ gs_free char *text = nullptr;
+ GtkWidget *label;
+ va_list args;
+
+ g_return_if_fail (TERMINAL_IS_INFO_BAR (bar));
+
+ priv = bar->priv;
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ label = gtk_label_new (text);
+
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_label_set_yalign (GTK_LABEL (label), 0.0);
+
+ gtk_box_pack_start (GTK_BOX (priv->content_box), label, FALSE, FALSE, 0);
+ gtk_widget_show_all (priv->content_box);
+}
diff --git a/src/terminal-info-bar.hh b/src/terminal-info-bar.hh
new file mode 100644
index 0000000..87a5234
--- /dev/null
+++ b/src/terminal-info-bar.hh
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2010 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/>.
+ */
+
+#ifndef TERMINAL_INFO_BAR_H
+#define TERMINAL_INFO_BAR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_INFO_BAR (terminal_info_bar_get_type ())
+#define TERMINAL_INFO_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_INFO_BAR, TerminalInfoBar))
+#define TERMINAL_INFO_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_INFO_BAR, TerminalInfoBarClass))
+#define TERMINAL_IS_INFO_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_INFO_BAR))
+#define TERMINAL_IS_INFO_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_INFO_BAR))
+#define TERMINAL_INFO_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_INFO_BAR, TerminalInfoBarClass))
+
+typedef struct _TerminalInfoBar TerminalInfoBar;
+typedef struct _TerminalInfoBarClass TerminalInfoBarClass;
+typedef struct _TerminalInfoBarPrivate TerminalInfoBarPrivate;
+
+struct _TerminalInfoBar
+{
+ GtkInfoBar parent_instance;
+
+ /*< private >*/
+ TerminalInfoBarPrivate *priv;
+};
+
+struct _TerminalInfoBarClass
+{
+ GtkInfoBarClass parent_class;
+};
+
+GType terminal_info_bar_get_type (void);
+
+GtkWidget *terminal_info_bar_new (GtkMessageType type,
+ const char *first_button_text,
+ ...) G_GNUC_NULL_TERMINATED;
+
+void terminal_info_bar_format_text (TerminalInfoBar *bar,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_INFO_BAR_H */
diff --git a/src/terminal-intl.hh b/src/terminal-intl.hh
new file mode 100644
index 0000000..daaa963
--- /dev/null
+++ b/src/terminal-intl.hh
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2002 Havoc Pennington
+ *
+ * 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/>.
+ */
+
+#ifndef TERMINAL_INTL_H
+#define TERMINAL_INTL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define I_(string) g_intern_static_string (string)
+
+G_END_DECLS
+
+#endif /* TERMINAL_INTL_H */
diff --git a/src/terminal-libgsystem.hh b/src/terminal-libgsystem.hh
new file mode 100644
index 0000000..370cb28
--- /dev/null
+++ b/src/terminal-libgsystem.hh
@@ -0,0 +1,317 @@
+/*
+ * Copyright © 2012 Colin Walters <walters@verbum.org>.
+ *
+ * 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/>.
+ */
+
+#ifndef __GSYSTEM_LOCAL_ALLOC_H__
+#define __GSYSTEM_LOCAL_ALLOC_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define gs_transfer_out_value(outp, srcp) G_STMT_START { \
+ if (outp) \
+ { \
+ *(outp) = *(srcp); \
+ *(srcp) = nullptr; \
+ } \
+ } G_STMT_END;
+
+#define GS_DEFINE_CLEANUP_FUNCTION(Type, name, func) \
+ static inline void name (void *v) \
+ { \
+ func (*(Type*)v); \
+ }
+
+#define GS_DEFINE_CLEANUP_FUNCTION0(Type, name, func) \
+ static inline void name (void *v) \
+ { \
+ if (*(Type*)v) \
+ func (*(Type*)v); \
+ }
+
+/* These functions shouldn't be invoked directly;
+ * they are stubs that:
+ * 1) Take a pointer to the location (typically itself a pointer).
+ * 2) Provide %nullptr-safety where it doesn't exist already (e.g. g_object_unref)
+ */
+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)
+GS_DEFINE_CLEANUP_FUNCTION0(GList*, gs_local_list_free, g_list_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GMatchInfo*, gs_local_match_info_free, g_match_info_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GOptionContext*, gs_local_option_context_free, g_option_context_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GRegex*, gs_local_regex_unref, g_regex_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GSettingsSchema*, gs_local_settings_schema_unref, g_settings_schema_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GSettingsSchemaKey*, gs_local_settings_schema_key_unref, g_settings_schema_key_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariant*, gs_local_variant_unref, g_variant_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantBuilder*, gs_local_variant_builder_unref, g_variant_builder_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantIter*, gs_local_variant_iter_free, g_variant_iter_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantType*, gs_local_variant_type_free, g_variant_type_free)
+
+GS_DEFINE_CLEANUP_FUNCTION(char**, gs_local_strfreev, g_strfreev)
+GS_DEFINE_CLEANUP_FUNCTION(void*, gs_local_free, g_free)
+
+/* special */
+
+static inline void gs_local_gstring_free (void *v) \
+{ \
+ if (*(GString**)v) \
+ g_string_free (*(GString**)v, TRUE); \
+}
+
+/**
+ * gs_free:
+ *
+ * Call g_free() on a variable location when it goes out of scope.
+ */
+#define gs_free __attribute__ ((cleanup(gs_local_free)))
+
+/**
+ * gs_unref_object:
+ *
+ * Call g_object_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_object_unref(), the variable may be
+ * %nullptr.
+ */
+#define gs_unref_object __attribute__ ((cleanup(gs_local_obj_unref)))
+
+/**
+ * gs_unref_variant:
+ *
+ * Call g_variant_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_variant_unref(), the variable may be
+ * %nullptr.
+ */
+#define gs_unref_variant __attribute__ ((cleanup(gs_local_variant_unref)))
+
+/**
+ * gs_free_variant_iter:
+ *
+ * Call g_variant_iter_free() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_free_variant_iter __attribute__ ((cleanup(gs_local_variant_iter_free)))
+
+/**
+ * gs_free_variant_builder:
+ *
+ * Call g_variant_builder_unref() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_unref_variant_builder __attribute__ ((cleanup(gs_local_variant_builder_unref)))
+
+/**
+ * gs_free_variant_type:
+ *
+ * Call g_variant_type_free() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_free_variant_type __attribute__ ((cleanup(gs_local_variant_type_free)))
+
+/**
+ * gs_unref_array:
+ *
+ * Call g_array_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_array_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_unref_array __attribute__ ((cleanup(gs_local_array_unref)))
+
+/**
+ * gs_unref_ptrarray:
+ *
+ * Call g_ptr_array_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_ptr_array_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_unref_ptrarray __attribute__ ((cleanup(gs_local_ptrarray_unref)))
+
+/**
+ * gs_unref_hashtable:
+ *
+ * Call g_hash_table_unref() on a variable location when it goes out
+ * of scope. Note that unlike g_hash_table_unref(), the variable may
+ * be %nullptr.
+ */
+#define gs_unref_hashtable __attribute__ ((cleanup(gs_local_hashtable_unref)))
+
+/**
+ * gs_unref_key_file:
+ *
+ * Call g_key_file_unref() on a variable location when it goes out
+ * of scope. Note that unlike g_key_file_unref(), the variable may
+ * be %nullptr.
+ */
+#define gs_unref_key_file __attribute__ ((cleanup(gs_local_key_file_unref)))
+
+/**
+ * gs_free_checksum:
+ *
+ * Call g_checksum_free() on a variable location when it goes out
+ * of scope. Note that unlike g_checksum_free(), the variable may
+ * be %nullptr.
+ */
+#define gs_free_checksum __attribute__ ((cleanup(gs_local_checksum_free)))
+
+/**
+ * gs_unref_date_time:
+ *
+ * Call g_date_time_free() on a variable location when it goes out
+ * of scope. Note that unlike g_date_time_free(), the variable may
+ * be %nullptr.
+ */
+#define gs_unref_date_time __attribute__ ((cleanup(gs_local_date_time_unref)))
+
+/**
+ * gs_unref_bytes:
+ *
+ * Call g_bytes_unref() on a variable location when it goes out
+ * of scope. Note that unlike g_bytes_unref(), the variable may
+ * be %nullptr.
+ */
+#define gs_unref_bytes __attribute__ ((cleanup(gs_local_bytes_unref)))
+
+/**
+ * gs_strfreev:
+ *
+ * Call g_strfreev() on a variable location when it goes out of scope.
+ */
+#define gs_strfreev __attribute__ ((cleanup(gs_local_strfreev)))
+
+/**
+ * gs_free_error:
+ *
+ * Call g_error_free() on a variable location when it goes out of scope.
+ */
+#define gs_free_error __attribute__ ((cleanup(gs_local_free_error)))
+
+/**
+ * gs_free_list:
+ *
+ * Call g_list_free() on a variable location when it goes out of scope.
+ */
+#define gs_free_list __attribute__ ((cleanup(gs_local_list_free)))
+
+/**
+ * gs_unref_regex:
+ *
+ * Call g_regex_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_regex_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_unref_regex __attribute__ ((cleanup(gs_local_regex_unref)))
+
+/**
+ * gs_free_match_info:
+ *
+ * Call g_regex_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_regex_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_free_match_info __attribute__ ((cleanup(gs_local_match_info_free)))
+
+/**
+ * gs_unref_settings_schema:
+ *
+ * Call g_settings_schema_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_settings_schema_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_unref_settings_schema __attribute__ ((cleanup(gs_local_settings_schema_unref)))
+
+/**
+ * gs_unref_settings_schema_source:
+ *
+ * Call g_settings_schema_source_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_settings_schema_source_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_unref_settings_schema_source __attribute__ ((cleanup(gs_local_settings_schema_source_unref)))
+
+/**
+ * gs_unref_settings_schema_key:
+ *
+ * Call g_settings_schema_key_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_settings_schema_key_unref(), the variable may be
+ * %nullptr.
+
+ */
+#define gs_unref_settings_schema_key __attribute__ ((cleanup(gs_local_settings_schema_key_unref)))
+
+/**
+ * gs_free_gstring:
+ *
+ * Call g_string_free(TRUE) on a variable location when it goes out
+ * of scope. Note that unlike g_string_free(), the variable may
+ * be %nullptr.
+ */
+#define gs_free_gstring __attribute__ ((cleanup(gs_local_gstring_free)))
+
+/**
+ * gs_free_option_context:
+ *
+ * Call g_regex_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_option_context_free(), the variable may be
+ * %NULL.
+
+ */
+#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<int*>(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-marshal.list b/src/terminal-marshal.list
new file mode 100644
index 0000000..3e91f60
--- /dev/null
+++ b/src/terminal-marshal.list
@@ -0,0 +1 @@
+BOOLEAN:STRING,INT,UINT
diff --git a/src/terminal-mdi-container.cc b/src/terminal-mdi-container.cc
new file mode 100644
index 0000000..f3c979d
--- /dev/null
+++ b/src/terminal-mdi-container.cc
@@ -0,0 +1,208 @@
+/*
+ * Copyright © 2008, 2010, 2011, 2012 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "terminal-mdi-container.hh"
+#include "terminal-debug.hh"
+#include "terminal-intl.hh"
+
+enum {
+ SCREEN_ADDED,
+ SCREEN_REMOVED,
+ SCREEN_SWITCHED,
+ SCREENS_REORDERED,
+ SCREEN_CLOSE_REQUEST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_INTERFACE (TerminalMdiContainer, terminal_mdi_container, GTK_TYPE_WIDGET)
+
+static void
+terminal_mdi_container_default_init (TerminalMdiContainerInterface *iface)
+{
+ signals[SCREEN_ADDED] =
+ g_signal_new (I_("screen-added"),
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalMdiContainerInterface, screen_added),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, TERMINAL_TYPE_SCREEN);
+
+ signals[SCREEN_ADDED] =
+ g_signal_new (I_("screen-removed"),
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalMdiContainerInterface, screen_added),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, TERMINAL_TYPE_SCREEN);
+
+ signals[SCREEN_ADDED] =
+ g_signal_new (I_("screen-switched"),
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalMdiContainerInterface, screen_switched),
+ nullptr, nullptr,
+ nullptr,
+ G_TYPE_NONE,
+ 2, TERMINAL_TYPE_SCREEN, TERMINAL_TYPE_SCREEN);
+
+ signals[SCREENS_REORDERED] =
+ g_signal_new (I_("screens-reordered"),
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalMdiContainerInterface, screens_reordered),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[SCREEN_CLOSE_REQUEST] =
+ g_signal_new (I_("screen-close-request"),
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalMdiContainerInterface, screen_close_request),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, TERMINAL_TYPE_SCREEN);
+
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("active-screen", nullptr, nullptr,
+ TERMINAL_TYPE_SCREEN,
+ GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+}
+
+/* public API */
+
+void
+terminal_mdi_container_add_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int position)
+{
+ g_return_if_fail (TERMINAL_IS_MDI_CONTAINER (container));
+ g_return_if_fail (TERMINAL_IS_SCREEN (screen));
+
+ TERMINAL_MDI_CONTAINER_GET_IFACE (container)->add_screen (container, screen, position);
+}
+
+void
+terminal_mdi_container_remove_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen)
+{
+ g_return_if_fail (TERMINAL_IS_MDI_CONTAINER (container));
+ g_return_if_fail (TERMINAL_IS_SCREEN (screen));
+
+ TERMINAL_MDI_CONTAINER_GET_IFACE (container)->remove_screen (container, screen);
+}
+
+TerminalScreen *
+terminal_mdi_container_get_active_screen (TerminalMdiContainer *container)
+{
+ g_return_val_if_fail (TERMINAL_IS_MDI_CONTAINER (container), nullptr);
+
+ return TERMINAL_MDI_CONTAINER_GET_IFACE (container)->get_active_screen (container);
+}
+
+void
+terminal_mdi_container_set_active_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen)
+{
+ g_return_if_fail (TERMINAL_IS_MDI_CONTAINER (container));
+ g_return_if_fail (TERMINAL_IS_SCREEN (screen));
+
+ TERMINAL_MDI_CONTAINER_GET_IFACE (container)->set_active_screen (container, screen);
+}
+
+
+GList *
+terminal_mdi_container_list_screens (TerminalMdiContainer *container)
+{
+ g_return_val_if_fail (TERMINAL_IS_MDI_CONTAINER (container), nullptr);
+
+ return TERMINAL_MDI_CONTAINER_GET_IFACE (container)->list_screens (container);
+}
+
+GList *
+terminal_mdi_container_list_screen_containers (TerminalMdiContainer *container)
+{
+ g_return_val_if_fail (TERMINAL_IS_MDI_CONTAINER (container), nullptr);
+
+ return TERMINAL_MDI_CONTAINER_GET_IFACE (container)->list_screen_containers (container);
+}
+
+int
+terminal_mdi_container_get_n_screens (TerminalMdiContainer *container)
+{
+ g_return_val_if_fail (TERMINAL_IS_MDI_CONTAINER (container), 0);
+
+ return TERMINAL_MDI_CONTAINER_GET_IFACE (container)->get_n_screens (container);
+}
+
+int
+terminal_mdi_container_get_active_screen_num (TerminalMdiContainer *container)
+{
+ g_return_val_if_fail (TERMINAL_IS_MDI_CONTAINER (container), -1);
+
+ return TERMINAL_MDI_CONTAINER_GET_IFACE (container)->get_active_screen_num (container);
+}
+
+void
+terminal_mdi_container_set_active_screen_num (TerminalMdiContainer *container,
+ int position)
+{
+ g_return_if_fail (TERMINAL_IS_MDI_CONTAINER (container));
+
+ TERMINAL_MDI_CONTAINER_GET_IFACE (container)->set_active_screen_num (container, position);
+}
+
+void
+terminal_mdi_container_reorder_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int new_position)
+{
+ g_return_if_fail (TERMINAL_IS_MDI_CONTAINER (container));
+
+ return TERMINAL_MDI_CONTAINER_GET_IFACE (container)->reorder_screen (container, screen, new_position);
+}
+
+void
+terminal_mdi_container_change_screen (TerminalMdiContainer *container,
+ int change)
+{
+ int active, n;
+
+ g_return_if_fail (TERMINAL_IS_MDI_CONTAINER (container));
+ g_return_if_fail (change == -1 || change == 1);
+
+ n = terminal_mdi_container_get_n_screens (container);
+ active = terminal_mdi_container_get_active_screen_num (container);
+
+ active += change;
+ if (active < 0)
+ active = n - 1;
+ else if (active >= n)
+ active = 0;
+
+ terminal_mdi_container_set_active_screen_num (container, active);
+}
diff --git a/src/terminal-mdi-container.hh b/src/terminal-mdi-container.hh
new file mode 100644
index 0000000..6915c8e
--- /dev/null
+++ b/src/terminal-mdi-container.hh
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2008, 2010, 2012 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/>.
+ */
+
+#ifndef TERMINAL_MDI_CONTAINER_H
+#define TERMINAL_MDI_CONTAINER_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_MDI_CONTAINER (terminal_mdi_container_get_type ())
+#define TERMINAL_MDI_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TERMINAL_TYPE_MDI_CONTAINER, TerminalMdiContainer))
+#define TERMINAL_IS_MDI_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TERMINAL_TYPE_MDI_CONTAINER))
+#define TERMINAL_MDI_CONTAINER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), TERMINAL_TYPE_MDI_CONTAINER, TerminalMdiContainerInterface))
+
+typedef struct _TerminalMdiContainer TerminalMdiContainer;
+typedef struct _TerminalMdiContainerInterface TerminalMdiContainerInterface;
+
+struct _TerminalMdiContainerInterface {
+ GTypeInterface parent_iface;
+
+ /* vfuncs */
+ void (* add_screen) (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int position);
+ void (* remove_screen) (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+ TerminalScreen * (* get_active_screen) (TerminalMdiContainer *container);
+ void (* set_active_screen) (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+ GList * (* list_screens) (TerminalMdiContainer *container);
+ GList * (* list_screen_containers) (TerminalMdiContainer *container);
+ int (* get_n_screens) (TerminalMdiContainer *container);
+ int (* get_active_screen_num) (TerminalMdiContainer *container);
+ void (* set_active_screen_num) (TerminalMdiContainer *container,
+ int position);
+ void (* reorder_screen) (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int new_position);
+
+ /* signals */
+ void (* screen_added) (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+ void (* screen_removed) (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+ void (* screen_switched) (TerminalMdiContainer *container,
+ TerminalScreen *old_active_screen,
+ TerminalScreen *new_active_screen);
+ void (* screens_reordered) (TerminalMdiContainer *container);
+ void (* screen_close_request) (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+};
+
+GType terminal_mdi_container_get_type (void);
+
+void terminal_mdi_container_add_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int position);
+
+void terminal_mdi_container_remove_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+
+TerminalScreen *terminal_mdi_container_get_active_screen (TerminalMdiContainer *container);
+
+void terminal_mdi_container_set_active_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen);
+
+void terminal_mdi_container_set_active_screen_num (TerminalMdiContainer *container,
+ int position);
+
+GList *terminal_mdi_container_list_screens (TerminalMdiContainer *container);
+
+GList *terminal_mdi_container_list_screen_containers (TerminalMdiContainer *container);
+
+int terminal_mdi_container_get_n_screens (TerminalMdiContainer *container);
+
+int terminal_mdi_container_get_active_screen_num (TerminalMdiContainer *container);
+
+void terminal_mdi_container_reorder_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int new_position);
+
+void terminal_mdi_container_change_screen (TerminalMdiContainer *container,
+ int change);
+
+G_END_DECLS
+
+#endif /* TERMINAL_MDI_CONTAINER_H */
diff --git a/src/terminal-menu-button.cc b/src/terminal-menu-button.cc
new file mode 100644
index 0000000..30d1c7c
--- /dev/null
+++ b/src/terminal-menu-button.cc
@@ -0,0 +1,149 @@
+/*
+ * Copyright © 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
+ * MERCHANMENUILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-menu-button.hh"
+#include "terminal-intl.hh"
+#include "terminal-libgsystem.hh"
+
+/* All this just because GtkToggleButton:toggled is RUN_FIRST (and the
+ * notify::active comes after the toggled signal). :-(
+ */
+
+enum
+{
+ UPDATE_MENU,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void popup_menu_selection_done_cb (GtkMenu *menu,
+ GtkMenuButton *button);
+
+/* The menu button sets itself insensitive when it has no menu.
+ * Work around this by using an empty menu.
+ */
+static void
+set_empty_menu (GtkMenuButton *button)
+{
+ gs_unref_object GMenu *menu = g_menu_new ();
+ gtk_menu_button_set_menu_model (button, G_MENU_MODEL (menu));
+}
+
+static void
+disconnect_popup_menu (GtkMenuButton *button)
+{
+ GtkMenu *popup_menu = gtk_menu_button_get_popup (button);
+
+ if (popup_menu)
+ g_signal_handlers_disconnect_by_func(popup_menu,
+ (void*)popup_menu_selection_done_cb,
+ button);
+}
+
+static void
+popup_menu_selection_done_cb (GtkMenu *menu,
+ GtkMenuButton *button)
+{
+ disconnect_popup_menu (button);
+ set_empty_menu (button);
+}
+
+/* Class implementation */
+
+G_DEFINE_TYPE (TerminalMenuButton, terminal_menu_button, GTK_TYPE_MENU_BUTTON);
+
+static void
+terminal_menu_button_init (TerminalMenuButton *button_)
+{
+ GtkButton *button = GTK_BUTTON (button_);
+ GtkMenuButton *menu_button = GTK_MENU_BUTTON (button_);
+
+ gtk_button_set_relief (button, GTK_RELIEF_NONE);
+ gtk_button_set_focus_on_click (button, FALSE);
+ gtk_menu_button_set_use_popover (menu_button, FALSE);
+ set_empty_menu (menu_button);
+}
+
+static void
+terminal_menu_button_toggled (GtkToggleButton *button)
+{
+ gboolean active = gtk_toggle_button_get_active (button); /* this is already the new state */
+
+ /* On activate, update the menu */
+ if (active)
+ g_signal_emit (button, signals[UPDATE_MENU], 0);
+
+ GTK_TOGGLE_BUTTON_CLASS (terminal_menu_button_parent_class)->toggled (button);
+}
+
+static void
+terminal_menu_button_update_menu (TerminalMenuButton *button)
+{
+ GtkMenuButton *gtk_button = GTK_MENU_BUTTON (button);
+ GtkMenu *popup_menu = gtk_menu_button_get_popup (gtk_button);
+
+ if (popup_menu)
+ g_signal_connect (popup_menu, "selection-done",
+ G_CALLBACK (popup_menu_selection_done_cb), button);
+}
+
+static void
+terminal_menu_button_dispose (GObject *object)
+{
+ disconnect_popup_menu (GTK_MENU_BUTTON (object));
+
+ G_OBJECT_CLASS (terminal_menu_button_parent_class)->dispose (object);
+}
+
+static void
+terminal_menu_button_class_init (TerminalMenuButtonClass *klass)
+{
+ klass->update_menu = terminal_menu_button_update_menu;
+
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = terminal_menu_button_dispose;
+
+ GtkToggleButtonClass *toggle_button_class = GTK_TOGGLE_BUTTON_CLASS (klass);
+ toggle_button_class->toggled = terminal_menu_button_toggled;
+
+ signals[UPDATE_MENU] =
+ g_signal_new (I_("update-menu"),
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalMenuButtonClass, update_menu),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+/* public API */
+
+/**
+ * terminal_menu_button_new:
+ *
+ * Returns: a new #TerminalMenuButton
+ */
+GtkWidget *
+terminal_menu_button_new (void)
+{
+ return reinterpret_cast<GtkWidget*>
+ (g_object_new (TERMINAL_TYPE_MENU_BUTTON, nullptr));
+}
diff --git a/src/terminal-menu-button.hh b/src/terminal-menu-button.hh
new file mode 100644
index 0000000..86197ab
--- /dev/null
+++ b/src/terminal-menu-button.hh
@@ -0,0 +1,57 @@
+/*
+ * 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
+ * MERCHANMENUILITY 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/>.
+ */
+
+#ifndef TERMINAL_MENU_BUTTON_H
+#define TERMINAL_MENU_BUTTON_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_MENU_BUTTON (terminal_menu_button_get_type ())
+#define TERMINAL_MENU_BUTTON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_MENU_BUTTON, TerminalMenuButton))
+#define TERMINAL_MENU_BUTTON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_MENU_BUTTON, TerminalMenuButtonClass))
+#define TERMINAL_IS_MENU_BUTTON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_MENU_BUTTON))
+#define TERMINAL_IS_MENU_BUTTON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_MENU_BUTTON))
+#define TERMINAL_MENU_BUTTON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_MENU_BUTTON, TerminalMenuButtonClass))
+
+typedef struct _TerminalMenuButton TerminalMenuButton;
+typedef struct _TerminalMenuButtonClass TerminalMenuButtonClass;
+typedef struct _TerminalMenuButtonPrivate TerminalMenuButtonPrivate;
+
+struct _TerminalMenuButton
+{
+ GtkMenuButton parent_instance;
+};
+
+struct _TerminalMenuButtonClass
+{
+ GtkMenuButtonClass parent_class;
+
+ /* Signals */
+ void (* update_menu) (TerminalMenuButton *menu_button);
+};
+
+GType terminal_menu_button_get_type (void);
+
+GtkWidget *terminal_menu_button_new (void);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_MENU_BUTTON_H */
diff --git a/src/terminal-menubar.ui.in b/src/terminal-menubar.ui.in
new file mode 100644
index 0000000..794d92d
--- /dev/null
+++ b/src/terminal-menubar.ui.in
@@ -0,0 +1,248 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2012, 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/>.
+-->
+<interface>
+ <menu id="menubar">
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">_File</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">File</attribute></WITHOUT_MNEMONIC>
+ <section id="new-terminal-section" />
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Save Contents…</attribute>
+ <attribute name="action">win.save-contents</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Export…</attribute>
+ <attribute name="action">win.export</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Print…</attribute>
+ <attribute name="action">win.print</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">C_lose Tab</attribute>
+ <attribute name="action">win.close</attribute>
+ <attribute name="target">tab</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Close Window</attribute>
+ <attribute name="action">win.close</attribute>
+ <attribute name="target">window</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">_Edit</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">Edit</attribute></WITHOUT_MNEMONIC>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Copy</attribute>
+ <attribute name="action">win.copy</attribute>
+ <attribute name="target">text</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Copy as _HTML</attribute>
+ <attribute name="action">win.copy</attribute>
+ <attribute name="target">html</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Paste</attribute>
+ <attribute name="action">win.paste-text</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Paste as _Filenames</attribute>
+ <attribute name="action">win.paste-uris</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Select _All</attribute>
+ <attribute name="action">win.select-all</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">P_references</attribute>
+ <attribute name="action">win.edit-preferences</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">_View</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">View</attribute></WITHOUT_MNEMONIC>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Show _Menubar</attribute>
+ <attribute name="action">win.menubar-visible</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Full Screen</attribute>
+ <attribute name="action">win.fullscreen</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Zoom _In</attribute>
+ <attribute name="action">win.zoom-in</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Normal Size</attribute>
+ <attribute name="action">win.zoom-normal</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Zoom _Out</attribute>
+ <attribute name="action">win.zoom-out</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">_Search</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">Search</attribute></WITHOUT_MNEMONIC>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Find…</attribute>
+ <attribute name="action">win.find</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Find _Next</attribute>
+ <attribute name="action">win.find-forward</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Find _Previous</attribute>
+ <attribute name="action">win.find-backward</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Clear Highlight</attribute>
+ <attribute name="action">win.find-clear</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">_Terminal</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">Terminal</attribute></WITHOUT_MNEMONIC>
+ <section>
+ <section id="set-profile-section" />
+ <item>
+ <attribute name="label" translatable="yes">Set _Title…</attribute>
+ <attribute name="action">win.set-title</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Read-_Only</attribute>
+ <attribute name="action">win.read-only</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Reset</attribute>
+ <attribute name="action">win.reset</attribute>
+ <attribute name="target" type="b">false</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Reset and C_lear</attribute>
+ <attribute name="action">win.reset</attribute>
+ <attribute name="target" type="b">true</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_1. 80×24</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(80, 24)</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_2. 80×43</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(80, 43)</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_3. 132×24</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(132, 24)</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_4. 132×43</attribute>
+ <attribute name="action">win.size-to</attribute>
+ <attribute name="target" type="(uu)">(132, 43)</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">Ta_bs</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">Tabs</attribute></WITHOUT_MNEMONIC>
+ <attribute name="action">win.tabs-menu</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Previous Tab</attribute>
+ <attribute name="action">win.tab-switch-left</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Next Tab</attribute>
+ <attribute name="action">win.tab-switch-right</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Move Terminal _Left</attribute>
+ <attribute name="action">win.tab-move-left</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Move Terminal _Right</attribute>
+ <attribute name="action">win.tab-move-right</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Detach Terminal</attribute>
+ <attribute name="action">win.tab-detach</attribute>
+ </item>
+ </section>
+ </submenu>
+ <submenu>
+ <WITH_MNEMONIC><attribute name="label" translatable="yes">_Help</attribute></WITH_MNEMONIC>
+ <WITHOUT_MNEMONIC><attribute name="label" translatable="yes">Help</attribute></WITHOUT_MNEMONIC>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Contents</attribute>
+ <attribute name="action">win.help</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_About</attribute>
+ <attribute name="action">win.about</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Inspector</attribute>
+ <attribute name="action">win.inspector</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ </submenu>
+ </menu>
+</interface>
diff --git a/src/terminal-nautilus.cc b/src/terminal-nautilus.cc
new file mode 100644
index 0000000..34968e5
--- /dev/null
+++ b/src/terminal-nautilus.cc
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2004, 2005 Free Software Foundation, Inc.
+ * Copyright © 2011 Christian Persch
+ * Author: Christian Neumair <chris@gnome-de.org>
+ *
+ * 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 <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+/* Work around https://gitlab.gnome.org/GNOME/nautilus/-/issues/1884 */
+extern "C" {
+#include <nautilus-extension.h>
+}
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "terminal-i18n.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-defines.hh"
+#include "terminal-gdbus-generated.h"
+
+/* Nautilus extension class */
+
+#undef TERMINAL_NAUTILUS
+
+#define TERMINAL_TYPE_NAUTILUS (terminal_nautilus_get_type ())
+#define TERMINAL_NAUTILUS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_NAUTILUS, TerminalNautilus))
+#define TERMINAL_NAUTILUS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_NAUTILUS, TerminalNautilusClass))
+#define TERMINAL_IS_NAUTILUS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_NAUTILUS))
+#define TERMINAL_IS_NAUTILUS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_NAUTILUS))
+#define TERMINAL_NAUTILUS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_NAUTILUS, TerminalNautilusClass))
+
+typedef struct _TerminalNautilus TerminalNautilus;
+typedef struct _TerminalNautilusClass TerminalNautilusClass;
+
+struct _TerminalNautilus {
+ GObject parent_instance;
+
+ GSettings *lockdown_prefs;
+};
+
+struct _TerminalNautilusClass {
+ GObjectClass parent_class;
+};
+
+static GType terminal_nautilus_get_type (void);
+
+/* Nautilus menu item class */
+
+#define TERMINAL_TYPE_NAUTILUS_MENU_ITEM (terminal_nautilus_menu_item_get_type ())
+#define TERMINAL_NAUTILUS_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_NAUTILUS_MENU_ITEM, TerminalNautilusMenuItem))
+#define TERMINAL_NAUTILUS_MENU_ITEM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_NAUTILUS_MENU_ITEM, TerminalNautilusMenuItemClass))
+#define TERMINAL_IS_NAUTILUS_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_NAUTILUS_MENU_ITEM))
+#define TERMINAL_IS_NAUTILUS_MENU_ITEM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_NAUTILUS_MENU_ITEM))
+#define TERMINAL_NAUTILUS_MENU_ITEM_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_NAUTILUS_MENU_ITEM, TerminalNautilusMenuItemClass))
+
+typedef struct _TerminalNautilusMenuItem TerminalNautilusMenuItem;
+typedef struct _TerminalNautilusMenuItemClass TerminalNautilusMenuItemClass;
+
+struct _TerminalNautilusMenuItem {
+ NautilusMenuItem parent_instance;
+
+ TerminalNautilus *nautilus;
+ NautilusFileInfo *file_info;
+ gboolean remote_terminal;
+};
+
+struct _TerminalNautilusMenuItemClass {
+ NautilusMenuItemClass parent_class;
+};
+
+static GType terminal_nautilus_menu_item_get_type (void);
+
+/* --- */
+
+#define TERMINAL_ICON_NAME "org.gnome.Terminal"
+
+typedef enum {
+ /* local files. Always open "conventionally", i.e. cd and spawn. */
+ FILE_INFO_LOCAL,
+ FILE_INFO_DESKTOP,
+ /* SFTP: Shell terminals are opened "remote" (i.e. with ssh client),
+ * commands are executed like OTHER.
+ */
+ FILE_INFO_SFTP,
+ /* OTHER: Terminals and commands are opened by mapping the URI back
+ * to ~/.gvfs, i.e. to the GVFS FUSE bridge.
+ */
+ FILE_INFO_OTHER
+} TerminalFileInfo;
+
+static TerminalFileInfo
+get_terminal_file_info_from_uri (const char *uri)
+{
+ TerminalFileInfo ret;
+ char *uri_scheme;
+
+ uri_scheme = g_uri_parse_scheme (uri);
+
+ if (uri_scheme == nullptr) {
+ ret = FILE_INFO_OTHER;
+ } else if (strcmp (uri_scheme, "file") == 0) {
+ ret = FILE_INFO_LOCAL;
+ } else if (strcmp (uri_scheme, "x-nautilus-desktop") == 0) {
+ ret = FILE_INFO_DESKTOP;
+ } else if (strcmp (uri_scheme, "sftp") == 0 ||
+ strcmp (uri_scheme, "ssh") == 0) {
+ ret = FILE_INFO_SFTP;
+ } else {
+ ret = FILE_INFO_OTHER;
+ }
+
+ g_free (uri_scheme);
+
+ return ret;
+}
+
+/* Helpers */
+
+#define NAUTILUS_SETTINGS_SCHEMA "org.gnome.Nautilus"
+#define GNOME_DESKTOP_LOCKDOWN_SETTINGS_SCHEMA "org.gnome.desktop.lockdown"
+
+/* a very simple URI parsing routine from Launchpad #333462, until GLib supports URI parsing (GNOME #489862) */
+#define SFTP_PREFIX "sftp://"
+static void
+parse_sftp_uri (GFile *file,
+ char **user,
+ char **host,
+ unsigned int *port,
+ char **path)
+{
+ char *tmp, *save;
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ g_assert (uri != nullptr);
+ save = uri;
+
+ *path = nullptr;
+ *user = nullptr;
+ *host = nullptr;
+ *port = 0;
+
+ /* skip intial 'sftp:// prefix */
+ g_assert (!strncmp (uri, SFTP_PREFIX, strlen (SFTP_PREFIX)));
+ uri += strlen (SFTP_PREFIX);
+
+ /* cut out the path */
+ tmp = strchr (uri, '/');
+ if (tmp != nullptr) {
+ *path = g_uri_unescape_string (tmp, "/");
+ *tmp = '\0';
+ }
+
+ /* read the username - it ends with @ */
+ tmp = strchr (uri, '@');
+ if (tmp != nullptr) {
+ *tmp++ = '\0';
+
+ *user = strdup (uri);
+ if (strchr (*user, ':') != nullptr) {
+ /* chop the password */
+ *(strchr (*user, ':')) = '\0';
+ }
+
+ uri = tmp;
+ }
+
+ /* now read the port, starts with : */
+ tmp = strchr (uri, ':');
+ if (tmp != nullptr) {
+ *tmp++ = '\0';
+ *port = atoi (tmp); /*FIXME: getservbyname*/
+ }
+
+ /* what is left is the host */
+ *host = strdup (uri);
+ g_free (save);
+}
+
+static char **
+ssh_argv (const char *uri,
+ int *argcp)
+{
+ GFile *file;
+ char **argv;
+ int argc;
+ char *host_name, *path, *user_name, *quoted_path;
+ guint host_port;
+
+ g_assert (uri != nullptr);
+
+ argv = g_new0 (char *, 9);
+ argc = 0;
+ argv[argc++] = g_strdup ("ssh");
+ argv[argc++] = g_strdup ("-t");
+
+ file = g_file_new_for_uri (uri);
+ parse_sftp_uri (file, &user_name, &host_name, &host_port, &path);
+ g_object_unref (file);
+
+ if (user_name != nullptr) {
+ argv[argc++ ]= g_strdup_printf ("%s@%s", user_name, host_name);
+ g_free (host_name);
+ g_free (user_name);
+ } else {
+ argv[argc++] = host_name;
+ }
+
+ if (host_port != 0) {
+ argv[argc++] = g_strdup ("-p");
+ argv[argc++] = g_strdup_printf ("%u", host_port);
+ }
+
+ /* FIXME to we have to consider the remote file encoding? */
+ quoted_path = g_shell_quote (path);
+
+ /* login shell */
+ argv[argc++] = g_strdup_printf ("cd %s && exec $SHELL -l", quoted_path);
+
+ g_free (path);
+ g_free (quoted_path);
+
+ *argcp = argc;
+ return argv;
+}
+
+static gboolean
+terminal_locked_down (TerminalNautilus *nautilus)
+{
+ return g_settings_get_boolean (nautilus->lockdown_prefs,
+ "disable-command-line");
+}
+
+/* used to determine for remote URIs whether GVFS is capable of mapping them to ~/.gvfs */
+static gboolean
+uri_has_local_path (const char *uri)
+{
+ GFile *file;
+ char *path;
+ gboolean ret;
+
+ file = g_file_new_for_uri (uri);
+ path = g_file_get_path (file);
+
+ ret = (path != nullptr);
+
+ g_free (path);
+ g_object_unref (file);
+
+ return ret;
+}
+
+/* Nautilus menu item class */
+
+namespace {
+
+typedef struct {
+ TerminalNautilus *nautilus;
+ guint32 timestamp;
+ char *path;
+ char *uri;
+ TerminalFileInfo info;
+ gboolean remote;
+} ExecData;
+
+} // anon namespace
+
+static void
+exec_data_free (ExecData *data)
+{
+ g_object_unref (data->nautilus);
+ g_free (data->path);
+ g_free (data->uri);
+
+ g_free (data);
+}
+
+/* FIXME: make this async */
+static gboolean
+create_terminal (ExecData *data /* transfer full */)
+{
+ TerminalFactory *factory;
+ TerminalReceiver *receiver;
+ GError *error = nullptr;
+ GVariantBuilder builder;
+ char *object_path;
+ char startup_id[32];
+ char **argv;
+ int argc;
+
+ factory = terminal_factory_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
+ TERMINAL_APPLICATION_ID,
+ TERMINAL_FACTORY_OBJECT_PATH,
+ nullptr /* cancellable */,
+ &error);
+ if (factory == nullptr) {
+ g_dbus_error_strip_remote_error (error);
+ g_printerr ("Error constructing proxy for %s:%s: %s\n",
+ TERMINAL_APPLICATION_ID, TERMINAL_FACTORY_OBJECT_PATH,
+ error->message);
+ g_error_free (error);
+ exec_data_free (data);
+ return FALSE;
+ }
+
+ g_snprintf (startup_id, sizeof (startup_id), "_TIME%u", data->timestamp);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ terminal_client_append_create_instance_options (&builder,
+ nullptr, // display name. FIXMEgtk4?
+ startup_id,
+ nullptr, /* activation_token */
+ nullptr /* geometry */,
+ nullptr /* role */,
+ nullptr /* use default profile */,
+ nullptr /* use profile encoding */,
+ nullptr /* title */,
+ TRUE, /* active */
+ FALSE /* maximised */,
+ FALSE /* fullscreen */);
+
+ if (!terminal_factory_call_create_instance_sync
+ (factory,
+ g_variant_builder_end (&builder),
+ &object_path,
+ nullptr /* cancellable */,
+ &error)) {
+ g_dbus_error_strip_remote_error (error);
+ g_printerr ("Error creating terminal: %s\n", error->message);
+ g_error_free (error);
+ g_object_unref (factory);
+ exec_data_free (data);
+ return FALSE;
+ }
+
+ g_object_unref (factory);
+
+ receiver = terminal_receiver_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
+ TERMINAL_APPLICATION_ID,
+ object_path,
+ nullptr /* cancellable */,
+ &error);
+ if (receiver == nullptr) {
+ g_dbus_error_strip_remote_error (error);
+ g_printerr ("Failed to create proxy for terminal: %s\n", error->message);
+ g_error_free (error);
+ g_free (object_path);
+ exec_data_free (data);
+ return FALSE;
+ }
+
+ g_free (object_path);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ terminal_client_append_exec_options (&builder,
+ TRUE, /* pass environment */
+ data->path,
+ nullptr, 0, /* FD array */
+ TRUE /* shell */);
+
+ if (data->info == FILE_INFO_SFTP &&
+ data->remote) {
+ argv = ssh_argv (data->uri, &argc);
+ } else {
+ argv = nullptr; argc = 0;
+ }
+
+ if (!terminal_receiver_call_exec_sync (receiver,
+ g_variant_builder_end (&builder),
+ g_variant_new_bytestring_array ((const char * const *) argv, argc),
+ nullptr /* in FD list */,
+ nullptr /* out FD list */,
+ nullptr /* cancellable */,
+ &error)) {
+ g_dbus_error_strip_remote_error (error);
+ g_printerr ("Error: %s\n", error->message);
+ g_error_free (error);
+ g_strfreev (argv);
+ g_object_unref (receiver);
+ exec_data_free (data);
+ return FALSE;
+ }
+
+ g_strfreev (argv);
+
+ exec_data_free (data);
+
+ g_object_unref (receiver);
+
+ return TRUE;
+}
+
+static void
+terminal_nautilus_menu_item_activate (NautilusMenuItem *item)
+{
+ TerminalNautilusMenuItem *menu_item = TERMINAL_NAUTILUS_MENU_ITEM (item);
+ TerminalNautilus *nautilus = menu_item->nautilus;
+ char *uri, *path;
+ TerminalFileInfo info;
+ ExecData *data;
+
+ uri = nautilus_file_info_get_activation_uri (menu_item->file_info);
+ if (uri == nullptr)
+ return;
+
+ path = nullptr;
+ info = get_terminal_file_info_from_uri (uri);
+
+ switch (info) {
+ case FILE_INFO_LOCAL:
+ path = g_filename_from_uri (uri, nullptr, nullptr);
+ break;
+
+ case FILE_INFO_DESKTOP:
+ path = g_strdup (g_get_home_dir ());
+ break;
+
+ case FILE_INFO_SFTP:
+ if (menu_item->remote_terminal)
+ break;
+
+ [[fallthrough]];
+ case FILE_INFO_OTHER: {
+ GFile *file;
+
+ /* map back remote URI to local path */
+ file = g_file_new_for_uri (uri);
+ path = g_file_get_path (file);
+ g_object_unref (file);
+ break;
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (path == nullptr && (info != FILE_INFO_SFTP || !menu_item->remote_terminal)) {
+ g_free (uri);
+ return;
+ }
+
+ data = g_new (ExecData, 1);
+ data->nautilus = (TerminalNautilus*)g_object_ref (nautilus);
+ data->timestamp = 0; // GDK_CURRENT_TIME
+ data->path = path;
+ data->uri = uri;
+ data->info = info;
+ data->remote = menu_item->remote_terminal;
+
+ create_terminal (data);
+}
+
+G_DEFINE_DYNAMIC_TYPE (TerminalNautilusMenuItem, terminal_nautilus_menu_item, NAUTILUS_TYPE_MENU_ITEM)
+
+static void
+terminal_nautilus_menu_item_init (TerminalNautilusMenuItem *nautilus_menu_item)
+{
+}
+
+static void
+terminal_nautilus_menu_item_dispose (GObject *object)
+{
+ TerminalNautilusMenuItem *menu_item = TERMINAL_NAUTILUS_MENU_ITEM (object);
+
+ if (menu_item->file_info != nullptr) {
+ g_object_unref (menu_item->file_info);
+ menu_item->file_info = nullptr;
+ }
+ if (menu_item->nautilus != nullptr) {
+ g_object_unref (menu_item->nautilus);
+ menu_item->nautilus = nullptr;
+ }
+
+ G_OBJECT_CLASS (terminal_nautilus_menu_item_parent_class)->dispose (object);
+}
+
+static void
+terminal_nautilus_menu_item_class_init (TerminalNautilusMenuItemClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ NautilusMenuItemClass *menu_item_class = NAUTILUS_MENU_ITEM_CLASS (klass);
+
+ gobject_class->dispose = terminal_nautilus_menu_item_dispose;
+
+ menu_item_class->activate = terminal_nautilus_menu_item_activate;
+}
+
+static void
+terminal_nautilus_menu_item_class_finalize (TerminalNautilusMenuItemClass *klass)
+{
+}
+
+static NautilusMenuItem *
+terminal_nautilus_menu_item_new (TerminalNautilus *nautilus,
+ NautilusFileInfo *file_info,
+ TerminalFileInfo terminal_file_info,
+ gboolean remote_terminal,
+ gboolean is_file_item)
+{
+ TerminalNautilusMenuItem *item;
+ const char *action_name;
+ const char *name;
+ const char *tooltip;
+
+ if (is_file_item) {
+ action_name = remote_terminal ? "TerminalNautilus:OpenRemote"
+ : "TerminalNautilus:OpenLocal";
+ } else {
+ action_name = remote_terminal ? "TerminalNautilus:OpenFolderRemote"
+ : "TerminalNautilus:OpenFolderLocal";
+ }
+
+ switch (terminal_file_info) {
+ case FILE_INFO_SFTP:
+ if (remote_terminal) {
+ name = _("Open in _Remote Terminal");
+ } else {
+ name = _("Open in _Local Terminal");
+ }
+
+ if (is_file_item) {
+ tooltip = _("Open the currently selected folder in a terminal");
+ } else {
+ tooltip = _("Open the currently open folder in a terminal");
+ }
+ break;
+
+ case FILE_INFO_LOCAL:
+ case FILE_INFO_OTHER:
+ name = _("Open in T_erminal");
+
+ if (is_file_item) {
+ tooltip = _("Open the currently selected folder in a terminal");
+ } else {
+ tooltip = _("Open the currently open folder in a terminal");
+ }
+ break;
+
+ case FILE_INFO_DESKTOP:
+ name = _("Open T_erminal");
+ tooltip = _("Open a terminal");
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ item = (TerminalNautilusMenuItem*)g_object_new (TERMINAL_TYPE_NAUTILUS_MENU_ITEM,
+ "name", action_name,
+ "label", name,
+ "tip", tooltip,
+ "icon", TERMINAL_ICON_NAME,
+ nullptr);
+
+ item->nautilus = (TerminalNautilus*)g_object_ref (nautilus);
+ item->file_info = (NautilusFileInfo*)g_object_ref (file_info);
+ item->remote_terminal = remote_terminal;
+
+ return (NautilusMenuItem *) item;
+}
+
+/* Nautilus extension class implementation */
+
+static GList *
+terminal_nautilus_get_background_items (NautilusMenuProvider *provider,
+ NautilusFileInfo *file_info)
+{
+ TerminalNautilus *nautilus = TERMINAL_NAUTILUS (provider);
+ gchar *uri;
+ GList *items;
+ NautilusMenuItem *item;
+ TerminalFileInfo terminal_file_info;
+
+ if (terminal_locked_down (nautilus))
+ return nullptr;
+
+ uri = nautilus_file_info_get_activation_uri (file_info);
+ if (uri == nullptr)
+ return nullptr;
+
+ items = nullptr;
+
+ terminal_file_info = get_terminal_file_info_from_uri (uri);
+
+
+ if (terminal_file_info == FILE_INFO_SFTP) {
+ /* remote SSH location */
+ item = terminal_nautilus_menu_item_new (nautilus,
+ file_info,
+ terminal_file_info,
+ TRUE,
+ FALSE);
+ items = g_list_append (items, item);
+ }
+
+ if (terminal_file_info == FILE_INFO_DESKTOP ||
+ uri_has_local_path (uri)) {
+ /* local locations and remote locations that offer local back-mapping */
+ item = terminal_nautilus_menu_item_new (nautilus,
+ file_info,
+ terminal_file_info,
+ FALSE,
+ FALSE);
+ items = g_list_append (items, item);
+ }
+
+ g_free (uri);
+
+ return items;
+}
+
+static GList *
+terminal_nautilus_get_file_items (NautilusMenuProvider *provider,
+ GList *files)
+{
+ TerminalNautilus *nautilus = TERMINAL_NAUTILUS (provider);
+ gchar *uri;
+ GList *items;
+ NautilusMenuItem *item;
+ NautilusFileInfo *file_info;
+ GFileType file_type;
+ TerminalFileInfo terminal_file_info;
+
+ if (terminal_locked_down (nautilus))
+ return nullptr;
+
+ /* Only add items when passed exactly one file */
+ if (files == nullptr || files->next != nullptr)
+ return nullptr;
+
+ file_info = (NautilusFileInfo *) files->data;
+ file_type = nautilus_file_info_get_file_type (file_info);
+ if (!nautilus_file_info_is_directory (file_info) &&
+ file_type != G_FILE_TYPE_SHORTCUT &&
+ file_type != G_FILE_TYPE_MOUNTABLE)
+ return nullptr;
+
+ uri = nautilus_file_info_get_activation_uri (file_info);
+ if (uri == nullptr)
+ return nullptr;
+
+ items = nullptr;
+
+ terminal_file_info = get_terminal_file_info_from_uri (uri);
+
+ switch (terminal_file_info) {
+ case FILE_INFO_LOCAL:
+ case FILE_INFO_SFTP:
+ case FILE_INFO_OTHER:
+ if (terminal_file_info == FILE_INFO_SFTP ||
+ uri_has_local_path (uri)) {
+ item = terminal_nautilus_menu_item_new (nautilus,
+ file_info,
+ terminal_file_info,
+ terminal_file_info == FILE_INFO_SFTP,
+ TRUE);
+ items = g_list_append (items, item);
+ }
+
+ if (terminal_file_info == FILE_INFO_SFTP &&
+ uri_has_local_path (uri)) {
+ item = terminal_nautilus_menu_item_new (nautilus,
+ file_info,
+ terminal_file_info,
+ FALSE,
+ TRUE);
+ items = g_list_append (items, item);
+ }
+
+ case FILE_INFO_DESKTOP:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_free (uri);
+
+ return items;
+}
+
+static void
+terminal_nautilus_menu_provider_interface_init (NautilusMenuProviderInterface *interface)
+{
+ interface->get_background_items = terminal_nautilus_get_background_items;
+ interface->get_file_items = terminal_nautilus_get_file_items;
+}
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (TerminalNautilus, terminal_nautilus, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (NAUTILUS_TYPE_MENU_PROVIDER,
+ terminal_nautilus_menu_provider_interface_init))
+
+static void
+terminal_nautilus_init (TerminalNautilus *nautilus)
+{
+ nautilus->lockdown_prefs = g_settings_new (GNOME_DESKTOP_LOCKDOWN_SETTINGS_SCHEMA);
+}
+
+static void
+terminal_nautilus_dispose (GObject *object)
+{
+ TerminalNautilus *nautilus = TERMINAL_NAUTILUS (object);
+
+ g_clear_object (&nautilus->lockdown_prefs);
+
+ G_OBJECT_CLASS (terminal_nautilus_parent_class)->dispose (object);
+}
+
+static void
+terminal_nautilus_class_init (TerminalNautilusClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = terminal_nautilus_dispose;
+
+ terminal_i18n_init (FALSE);
+}
+
+static void
+terminal_nautilus_class_finalize (TerminalNautilusClass *klass)
+{
+}
+
+/* Nautilus extension */
+
+static GType type_list[1];
+
+#define EXPORT extern "C" __attribute__((__visibility__("default")))
+
+EXPORT void
+nautilus_module_initialize (GTypeModule *module)
+{
+ terminal_nautilus_register_type (module);
+ terminal_nautilus_menu_item_register_type (module);
+
+ type_list[0] = TERMINAL_TYPE_NAUTILUS;
+}
+
+EXPORT void
+nautilus_module_shutdown (void)
+{
+}
+
+EXPORT void
+nautilus_module_list_types (const GType **types,
+ int *num_types)
+{
+ *types = type_list;
+ *num_types = G_N_ELEMENTS (type_list);
+}
diff --git a/src/terminal-notebook-menu.ui b/src/terminal-notebook-menu.ui
new file mode 100644
index 0000000..d5c7f41
--- /dev/null
+++ b/src/terminal-notebook-menu.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2012 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/>.
+-->
+<interface>
+ <menu id="notebook-popup">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Move Terminal _Left</attribute>
+ <attribute name="action">win.tab-move-left</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Move Terminal _Right</attribute>
+ <attribute name="action">win.tab-move-right</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Detach Terminal</attribute>
+ <attribute name="action">win.tab-detach</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Set _Title…</attribute>
+ <attribute name="action">win.set-title</attribute>
+ <attribute name="hidden-when">action-missing</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">C_lose Terminal</attribute>
+ <attribute name="action">win.close</attribute>
+ <attribute name="target">tab</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/src/terminal-notebook.cc b/src/terminal-notebook.cc
new file mode 100644
index 0000000..6686d34
--- /dev/null
+++ b/src/terminal-notebook.cc
@@ -0,0 +1,596 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2008, 2010, 2011, 2012 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "terminal-notebook.hh"
+
+#include <gtk/gtk.h>
+
+#include "terminal-debug.hh"
+#include "terminal-app.hh"
+#include "terminal-intl.hh"
+#include "terminal-mdi-container.hh"
+#include "terminal-screen-container.hh"
+#include "terminal-tab-label.hh"
+#include "terminal-schemas.hh"
+#include "terminal-libgsystem.hh"
+
+#define TERMINAL_NOTEBOOK_GET_PRIVATE(notebook)(G_TYPE_INSTANCE_GET_PRIVATE ((notebook), TERMINAL_TYPE_NOTEBOOK, TerminalNotebookPrivate))
+
+struct _TerminalNotebookPrivate
+{
+ TerminalScreen *active_screen;
+ GtkPolicyType policy;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE_SCREEN,
+ PROP_TAB_POLICY
+};
+
+#define ACTION_AREA_BORDER_WIDTH (2)
+#define ACTION_BUTTON_SPACING (6)
+
+/* helper functions */
+
+static void
+update_tab_visibility (TerminalNotebook *notebook,
+ int change)
+{
+ TerminalNotebookPrivate *priv = notebook->priv;
+ GtkNotebook *gtk_notebook = GTK_NOTEBOOK (notebook);
+ int new_n_pages;
+ gboolean show_tabs;
+
+ if (gtk_widget_in_destruction (GTK_WIDGET (notebook)))
+ return;
+
+ new_n_pages = gtk_notebook_get_n_pages (gtk_notebook) + change;
+ /* Don't do anything if we're going to have zero pages (and thus close the window) */
+ if (new_n_pages == 0)
+ return;
+
+ switch (priv->policy) {
+ case GTK_POLICY_ALWAYS:
+ show_tabs = TRUE;
+ break;
+ case GTK_POLICY_AUTOMATIC:
+ show_tabs = new_n_pages > 1;
+ break;
+ case GTK_POLICY_NEVER:
+ case GTK_POLICY_EXTERNAL:
+ default:
+ show_tabs = FALSE;
+ break;
+ }
+
+ gtk_notebook_set_show_tabs (gtk_notebook, show_tabs);
+}
+
+static void
+close_button_clicked_cb (TerminalTabLabel *tab_label,
+ gpointer user_data)
+{
+ TerminalScreen *screen;
+ TerminalNotebook *notebook;
+
+ screen = terminal_tab_label_get_screen (tab_label);
+
+ /* notebook is not passed as user_data because it can change during DND
+ * and the close button is not notified about that, see bug 731998.
+ */
+ notebook = TERMINAL_NOTEBOOK (gtk_widget_get_ancestor (GTK_WIDGET (screen),
+ TERMINAL_TYPE_NOTEBOOK));
+
+ if (notebook != nullptr)
+ g_signal_emit_by_name (notebook, "screen-close-request", screen);
+}
+
+
+static void
+remove_reorder_bindings (GtkBindingSet *binding_set,
+ guint keysym)
+{
+ guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
+ gtk_binding_entry_skip (binding_set, keysym, GDK_MOD1_MASK);
+ gtk_binding_entry_skip (binding_set, keypad_keysym, GDK_MOD1_MASK);
+}
+
+/* TerminalMdiContainer impl */
+
+static void
+terminal_notebook_add_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int position)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (container);
+ GtkNotebook *gtk_notebook = GTK_NOTEBOOK (notebook);
+ GtkWidget *screen_container, *tab_label;
+
+ g_warn_if_fail (gtk_widget_get_parent (GTK_WIDGET (screen)) == nullptr);
+
+ screen_container = terminal_screen_container_new (screen);
+ gtk_widget_show (screen_container);
+
+ update_tab_visibility (notebook, +1);
+
+ tab_label = terminal_tab_label_new (screen);
+ g_signal_connect (tab_label, "close-button-clicked",
+ G_CALLBACK (close_button_clicked_cb), nullptr);
+
+ gtk_notebook_insert_page (gtk_notebook,
+ screen_container,
+ tab_label,
+ position);
+ gtk_container_child_set (GTK_CONTAINER (notebook),
+ screen_container,
+ "tab-expand", TRUE,
+ "tab-fill", TRUE,
+ nullptr);
+ gtk_notebook_set_tab_reorderable (gtk_notebook, screen_container, TRUE);
+#if 0
+ gtk_notebook_set_tab_detachable (gtk_notebook, screen_container, TRUE);
+#endif
+}
+
+static void
+terminal_notebook_remove_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (container);
+ TerminalScreenContainer *screen_container;
+
+ g_warn_if_fail (gtk_widget_is_ancestor (GTK_WIDGET (screen), GTK_WIDGET (notebook)));
+
+ update_tab_visibility (notebook, -1);
+
+ screen_container = terminal_screen_container_get_from_screen (screen);
+ gtk_container_remove (GTK_CONTAINER (notebook),
+ GTK_WIDGET (screen_container));
+}
+
+static TerminalScreen *
+terminal_notebook_get_active_screen (TerminalMdiContainer *container)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (container);
+ GtkNotebook *gtk_notebook = GTK_NOTEBOOK (notebook);
+ GtkWidget *widget;
+
+ widget = gtk_notebook_get_nth_page (gtk_notebook, gtk_notebook_get_current_page (gtk_notebook));
+ return terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (widget));
+}
+
+static void
+terminal_notebook_set_active_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (container);
+ GtkNotebook *gtk_notebook = GTK_NOTEBOOK (notebook);
+ TerminalScreenContainer *screen_container;
+ GtkWidget *widget;
+
+ screen_container = terminal_screen_container_get_from_screen (screen);
+ widget = GTK_WIDGET (screen_container);
+
+ gtk_notebook_set_current_page (gtk_notebook,
+ gtk_notebook_page_num (gtk_notebook, widget));
+}
+
+static GList *
+terminal_notebook_list_screen_containers (TerminalMdiContainer *container)
+{
+ /* We are trusting that GtkNotebook will return pages in order */
+ return gtk_container_get_children (GTK_CONTAINER (container));
+}
+
+static GList *
+terminal_notebook_list_screens (TerminalMdiContainer *container)
+{
+ GList *list, *l;
+
+ list = terminal_notebook_list_screen_containers (container);
+ for (l = list; l != nullptr; l = l->next)
+ l->data = terminal_screen_container_get_screen ((TerminalScreenContainer *) l->data);
+
+ return list;
+}
+
+static int
+terminal_notebook_get_n_screens (TerminalMdiContainer *container)
+{
+ return gtk_notebook_get_n_pages (GTK_NOTEBOOK (container));
+}
+
+static int
+terminal_notebook_get_active_screen_num (TerminalMdiContainer *container)
+{
+ return gtk_notebook_get_current_page (GTK_NOTEBOOK (container));
+}
+
+static void
+terminal_notebook_set_active_screen_num (TerminalMdiContainer *container,
+ int position)
+{
+ GtkNotebook *gtk_notebook = GTK_NOTEBOOK (container);
+
+ gtk_notebook_set_current_page (gtk_notebook, position);
+}
+
+static void
+terminal_notebook_reorder_screen (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ int new_position)
+{
+ GtkNotebook *notebook = GTK_NOTEBOOK (container);
+ GtkWidget *child;
+ int n, pos;
+
+ g_return_if_fail (new_position == 1 || new_position == -1);
+
+ child = GTK_WIDGET (terminal_screen_container_get_from_screen (screen));
+ n = gtk_notebook_get_n_pages (notebook);
+ pos = gtk_notebook_page_num (notebook, child);
+
+ pos += new_position;
+ gtk_notebook_reorder_child (notebook, child,
+ pos < 0 ? n - 1 : pos < n ? pos : 0);
+}
+
+static void
+terminal_notebook_mdi_iface_init (TerminalMdiContainerInterface *iface)
+{
+ iface->add_screen = terminal_notebook_add_screen;
+ iface->remove_screen = terminal_notebook_remove_screen;
+ iface->get_active_screen = terminal_notebook_get_active_screen;
+ iface->set_active_screen = terminal_notebook_set_active_screen;
+ iface->list_screens = terminal_notebook_list_screens;
+ iface->list_screen_containers = terminal_notebook_list_screen_containers;
+ iface->get_n_screens = terminal_notebook_get_n_screens;
+ iface->get_active_screen_num = terminal_notebook_get_active_screen_num;
+ iface->set_active_screen_num = terminal_notebook_set_active_screen_num;
+ iface->reorder_screen = terminal_notebook_reorder_screen;
+}
+
+G_DEFINE_TYPE_WITH_CODE (TerminalNotebook, terminal_notebook, GTK_TYPE_NOTEBOOK,
+ G_IMPLEMENT_INTERFACE (TERMINAL_TYPE_MDI_CONTAINER, terminal_notebook_mdi_iface_init))
+
+/* GtkNotebookClass impl */
+
+static void
+terminal_notebook_switch_page (GtkNotebook *gtk_notebook,
+ GtkWidget *child,
+ guint page_num)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (gtk_notebook);
+ TerminalNotebookPrivate *priv = notebook->priv;
+ TerminalScreen *screen, *old_active_screen;
+
+ GTK_NOTEBOOK_CLASS (terminal_notebook_parent_class)->switch_page (gtk_notebook, child, page_num);
+
+ screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (child));
+
+ old_active_screen = priv->active_screen;
+ if (screen == old_active_screen)
+ return;
+
+ /* Workaround to remove gtknotebook's feature of computing its size based on
+ * all pages. When the widget is hidden, its size will not be taken into
+ * account.
+ * FIXME!
+ */
+// if (old_active_screen)
+// gtk_widget_hide (GTK_WIDGET (terminal_screen_container_get_from_screen (old_active_screen)));
+ /* Make sure that the widget is no longer hidden due to the workaround */
+// if (child)
+// gtk_widget_show (child);
+ if (old_active_screen)
+ gtk_widget_hide (GTK_WIDGET (old_active_screen));
+ if (screen)
+ gtk_widget_show (GTK_WIDGET (screen));
+
+ priv->active_screen = screen;
+
+ g_signal_emit_by_name (notebook, "screen-switched", old_active_screen, screen);
+ g_object_notify (G_OBJECT (notebook), "active-screen");
+}
+
+static void
+terminal_notebook_page_added (GtkNotebook *gtk_notebook,
+ GtkWidget *child,
+ guint page_num)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (gtk_notebook);
+ void (* page_added) (GtkNotebook *, GtkWidget *, guint) =
+ GTK_NOTEBOOK_CLASS (terminal_notebook_parent_class)->page_added;
+
+ if (page_added)
+ page_added (gtk_notebook, child, page_num);
+
+ update_tab_visibility (notebook, 0);
+ g_signal_emit_by_name (gtk_notebook, "screen-added",
+ terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (child)));
+}
+
+static void
+terminal_notebook_page_removed (GtkNotebook *gtk_notebook,
+ GtkWidget *child,
+ guint page_num)
+{
+ TerminalNotebook *notebook = TERMINAL_NOTEBOOK (gtk_notebook);
+ void (* page_removed) (GtkNotebook *, GtkWidget *, guint) =
+ GTK_NOTEBOOK_CLASS (terminal_notebook_parent_class)->page_removed;
+
+ if (page_removed)
+ page_removed (gtk_notebook, child, page_num);
+
+ update_tab_visibility (notebook, 0);
+ g_signal_emit_by_name (gtk_notebook, "screen-removed",
+ terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (child)));
+}
+
+static void
+terminal_notebook_page_reordered (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num)
+{
+ void (* page_reordered) (GtkNotebook *, GtkWidget *, guint) =
+ GTK_NOTEBOOK_CLASS (terminal_notebook_parent_class)->page_reordered;
+
+ if (page_reordered)
+ page_reordered (notebook, child, page_num);
+
+ g_signal_emit_by_name (notebook, "screens-reordered");
+}
+
+static GtkNotebook *
+terminal_notebook_create_window (GtkNotebook *notebook,
+ GtkWidget *page,
+ gint x,
+ gint y)
+{
+ return GTK_NOTEBOOK_CLASS (terminal_notebook_parent_class)->create_window (notebook, page, x, y);
+}
+
+/* GtkWidgetClass impl */
+
+static void
+terminal_notebook_grab_focus (GtkWidget *widget)
+{
+ TerminalScreen *screen;
+
+ screen = terminal_mdi_container_get_active_screen (TERMINAL_MDI_CONTAINER (widget));
+ gtk_widget_grab_focus (GTK_WIDGET (screen));
+}
+
+/* GObjectClass impl */
+
+static void
+terminal_notebook_init (TerminalNotebook *notebook)
+{
+ TerminalNotebookPrivate *priv;
+
+ priv = notebook->priv = TERMINAL_NOTEBOOK_GET_PRIVATE (notebook);
+
+ priv->active_screen = nullptr;
+ priv->policy = GTK_POLICY_AUTOMATIC;
+}
+
+static void
+terminal_notebook_constructed (GObject *object)
+{
+ GSettings *settings;
+ GtkWidget *widget = GTK_WIDGET (object);
+ GtkNotebook *notebook = GTK_NOTEBOOK (object);
+
+ G_OBJECT_CLASS (terminal_notebook_parent_class)->constructed (object);
+
+ settings = terminal_app_get_global_settings (terminal_app_get ());
+
+ update_tab_visibility (TERMINAL_NOTEBOOK (notebook), 0);
+ g_settings_bind (settings,
+ TERMINAL_SETTING_TAB_POLICY_KEY,
+ object,
+ "tab-policy",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+
+ g_settings_bind (settings,
+ TERMINAL_SETTING_TAB_POSITION_KEY,
+ object,
+ "tab-pos",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+
+ gtk_notebook_set_scrollable (notebook, TRUE);
+ gtk_notebook_set_show_border (notebook, FALSE);
+ gtk_notebook_set_group_name (notebook, I_("gnome-terminal-window"));
+
+ /* Necessary for scroll events */
+ gtk_widget_add_events (widget, GDK_SCROLL_MASK);
+}
+
+static void
+terminal_notebook_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalMdiContainer *mdi_container = TERMINAL_MDI_CONTAINER (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE_SCREEN:
+ g_value_set_object (value, terminal_notebook_get_active_screen (mdi_container));
+ break;
+ case PROP_TAB_POLICY:
+ g_value_set_enum (value, terminal_notebook_get_tab_policy (TERMINAL_NOTEBOOK (mdi_container)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_notebook_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalMdiContainer *mdi_container = TERMINAL_MDI_CONTAINER (object);
+
+ switch (prop_id) {
+ case PROP_ACTIVE_SCREEN:
+ terminal_notebook_set_active_screen (mdi_container, (TerminalScreen*)g_value_get_object (value));
+ break;
+ case PROP_TAB_POLICY:
+ terminal_notebook_set_tab_policy (TERMINAL_NOTEBOOK (mdi_container), GtkPolicyType(g_value_get_enum (value)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_notebook_class_init (TerminalNotebookClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
+
+ g_type_class_add_private (gobject_class, sizeof (TerminalNotebookPrivate));
+
+ gobject_class->constructed = terminal_notebook_constructed;
+ gobject_class->get_property = terminal_notebook_get_property;
+ gobject_class->set_property = terminal_notebook_set_property;
+
+ g_object_class_override_property (gobject_class, PROP_ACTIVE_SCREEN, "active-screen");
+
+ widget_class->grab_focus = terminal_notebook_grab_focus;
+
+ notebook_class->switch_page = terminal_notebook_switch_page;
+ notebook_class->create_window = terminal_notebook_create_window;
+ notebook_class->page_added = terminal_notebook_page_added;
+ notebook_class->page_removed = terminal_notebook_page_removed;
+ notebook_class->page_reordered = terminal_notebook_page_reordered;
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_TAB_POLICY,
+ g_param_spec_enum ("tab-policy", nullptr, nullptr,
+ GTK_TYPE_POLICY_TYPE,
+ GTK_POLICY_AUTOMATIC,
+ GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+ /* Remove unwanted and interfering keybindings */
+ GtkBindingSet *binding_set = gtk_binding_set_by_class (terminal_notebook_parent_class);
+ gtk_binding_entry_skip (binding_set, GDK_KEY_Page_Up, GdkModifierType(GDK_CONTROL_MASK));
+ gtk_binding_entry_skip (binding_set, GDK_KEY_Page_Up, GdkModifierType(GDK_CONTROL_MASK | GDK_MOD1_MASK));
+ gtk_binding_entry_skip (binding_set, GDK_KEY_Page_Down, GdkModifierType(GDK_CONTROL_MASK));
+ gtk_binding_entry_skip (binding_set, GDK_KEY_Page_Down, GdkModifierType(GDK_CONTROL_MASK | GDK_MOD1_MASK));
+ remove_reorder_bindings (binding_set, GDK_KEY_Up);
+ remove_reorder_bindings (binding_set, GDK_KEY_Down);
+ remove_reorder_bindings (binding_set, GDK_KEY_Left);
+ remove_reorder_bindings (binding_set, GDK_KEY_Right);
+ remove_reorder_bindings (binding_set, GDK_KEY_Home);
+ remove_reorder_bindings (binding_set, GDK_KEY_Home);
+ remove_reorder_bindings (binding_set, GDK_KEY_End);
+ remove_reorder_bindings (binding_set, GDK_KEY_End);
+}
+
+/* public API */
+
+/**
+ * terminal_notebook_new:
+ *
+ * Returns: (transfer full): a new #TerminalNotebook
+ */
+GtkWidget *
+terminal_notebook_new (void)
+{
+ return reinterpret_cast<GtkWidget*>
+ (g_object_new (TERMINAL_TYPE_NOTEBOOK, nullptr));
+}
+
+void
+terminal_notebook_set_tab_policy (TerminalNotebook *notebook,
+ GtkPolicyType policy)
+{
+ TerminalNotebookPrivate *priv = notebook->priv;
+
+ if (priv->policy == policy)
+ return;
+
+ priv->policy = policy;
+ update_tab_visibility (notebook, 0);
+
+ g_object_notify (G_OBJECT (notebook), "tab-policy");
+}
+
+GtkPolicyType
+terminal_notebook_get_tab_policy (TerminalNotebook *notebook)
+{
+ return notebook->priv->policy;
+}
+
+GtkWidget *
+terminal_notebook_get_action_box (TerminalNotebook *notebook,
+ GtkPackType pack_type)
+{
+ GtkNotebook *gtk_notebook;
+ GtkWidget *box, *inner_box;
+
+ g_return_val_if_fail (TERMINAL_IS_NOTEBOOK (notebook), nullptr);
+
+ gtk_notebook = GTK_NOTEBOOK (notebook);
+ box = gtk_notebook_get_action_widget (gtk_notebook, pack_type);
+ if (box != nullptr) {
+ gs_free_list GList *list;
+
+ list = gtk_container_get_children (GTK_CONTAINER (box));
+ g_assert (list->data != nullptr);
+ return (GtkWidget*)list->data;
+ }
+
+ /* Create container for the buttons */
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (box), ACTION_AREA_BORDER_WIDTH);
+
+ inner_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, ACTION_BUTTON_SPACING);
+ gtk_box_pack_start (GTK_BOX (box), inner_box, TRUE, FALSE, 0);
+ gtk_widget_show (inner_box);
+
+ gtk_notebook_set_action_widget (gtk_notebook, box, pack_type);
+ gtk_widget_show (box);
+
+ /* FIXME: this appears to be necessary to make the icon buttons contained
+ * in the action area render the same way as buttons in the tab labels (e.g.
+ * the close button). gtk+ bug?
+ */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_style_context_add_region (gtk_widget_get_style_context (box),
+ GTK_STYLE_REGION_TAB,
+ pack_type == GTK_PACK_START ? GTK_REGION_FIRST : GTK_REGION_LAST);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ return inner_box;
+}
diff --git a/src/terminal-notebook.hh b/src/terminal-notebook.hh
new file mode 100644
index 0000000..68cd51f
--- /dev/null
+++ b/src/terminal-notebook.hh
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2008, 2010, 2012 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/>.
+ */
+
+#ifndef TERMINAL_NOTEBOOK_H
+#define TERMINAL_NOTEBOOK_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_NOTEBOOK (terminal_notebook_get_type ())
+#define TERMINAL_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_NOTEBOOK, TerminalNotebook))
+#define TERMINAL_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_NOTEBOOK, TerminalNotebookClass))
+#define TERMINAL_IS_NOTEBOOK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_NOTEBOOK))
+#define TERMINAL_IS_NOTEBOOK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_NOTEBOOK))
+#define TERMINAL_NOTEBOOK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_NOTEBOOK, TerminalNotebookClass))
+
+typedef struct _TerminalNotebook TerminalNotebook;
+typedef struct _TerminalNotebookClass TerminalNotebookClass;
+typedef struct _TerminalNotebookPrivate TerminalNotebookPrivate;
+
+struct _TerminalNotebook
+{
+ GtkNotebook parent_instance;
+
+ /*< private >*/
+ TerminalNotebookPrivate *priv;
+};
+
+struct _TerminalNotebookClass
+{
+ GtkNotebookClass parent_class;
+};
+
+GType terminal_notebook_get_type (void);
+
+GtkWidget *terminal_notebook_new (void);
+
+void terminal_notebook_set_tab_policy (TerminalNotebook *notebook,
+ GtkPolicyType policy);
+GtkPolicyType terminal_notebook_get_tab_policy (TerminalNotebook *notebook);
+
+GtkWidget *terminal_notebook_get_action_box (TerminalNotebook *notebook,
+ GtkPackType pack_type);
+
+G_END_DECLS
+
+#endif /* TERMINAL_NOTEBOOK_H */
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;
+}
diff --git a/src/terminal-options.hh b/src/terminal-options.hh
new file mode 100644
index 0000000..48edb32
--- /dev/null
+++ b/src/terminal-options.hh
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2008 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/>.
+ */
+
+#ifndef TERMINAL_OPTIONS_H
+#define TERMINAL_OPTIONS_H
+
+#include <glib.h>
+#include <stdio.h>
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "terminal-profiles-list.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_CONFIG_VERSION (1) /* Bump this for any changes */
+#define TERMINAL_CONFIG_COMPAT_VERSION (1) /* Bump this for incompatible changes */
+
+#define TERMINAL_CONFIG_GROUP "GNOME Terminal Configuration"
+#define TERMINAL_CONFIG_PROP_VERSION "Version"
+#define TERMINAL_CONFIG_PROP_COMPAT_VERSION "CompatVersion"
+#define TERMINAL_CONFIG_PROP_WINDOWS "Windows"
+
+#define TERMINAL_CONFIG_WINDOW_PROP_ACTIVE_TAB "ActiveTerminal"
+#define TERMINAL_CONFIG_WINDOW_PROP_FULLSCREEN "Fullscreen"
+#define TERMINAL_CONFIG_WINDOW_PROP_GEOMETRY "Geometry"
+#define TERMINAL_CONFIG_WINDOW_PROP_MAXIMIZED "Maximized"
+#define TERMINAL_CONFIG_WINDOW_PROP_MENUBAR_VISIBLE "MenubarVisible"
+#define TERMINAL_CONFIG_WINDOW_PROP_ROLE "Role"
+#define TERMINAL_CONFIG_WINDOW_PROP_TABS "Terminals"
+
+#define TERMINAL_CONFIG_TERMINAL_PROP_HEIGHT "Height"
+#define TERMINAL_CONFIG_TERMINAL_PROP_COMMAND "Command"
+#define TERMINAL_CONFIG_TERMINAL_PROP_PROFILE_ID "ProfileID"
+#define TERMINAL_CONFIG_TERMINAL_PROP_TITLE "Title"
+#define TERMINAL_CONFIG_TERMINAL_PROP_WIDTH "Width"
+#define TERMINAL_CONFIG_TERMINAL_PROP_WORKING_DIRECTORY "WorkingDirectory"
+#define TERMINAL_CONFIG_TERMINAL_PROP_ZOOM "Zoom"
+
+enum
+{
+ SOURCE_DEFAULT = 0,
+ SOURCE_SESSION = 1
+};
+
+typedef struct
+{
+ GSettingsSchemaSource* schema_source; /* may be nullptr */
+ TerminalSettingsList *profiles_list; /* may be nullptr */
+
+ gboolean print_environment;
+
+ char *server_unique_name;
+ char *parent_screen_object_path;
+
+ char *server_app_id;
+ char *startup_id;
+ char *activation_token;
+ char *display_name;
+ gboolean show_preferences;
+ GList *initial_windows;
+ gboolean default_window_menubar_forced;
+ gboolean default_window_menubar_state;
+ gboolean default_fullscreen;
+ gboolean default_maximize;
+ gboolean default_wait;
+ char *default_role;
+ char *default_geometry;
+ char *default_working_dir;
+ char *default_title;
+ char **exec_argv;
+ char *default_profile;
+ gboolean default_profile_is_id;
+ gboolean no_environment;
+
+ gboolean execute;
+ double zoom;
+
+ gboolean sm_client_disable;
+ char *sm_client_id;
+ char *sm_config_prefix;
+
+ guint zoom_set : 1;
+ guint wait : 1;
+} TerminalOptions;
+
+typedef struct
+{
+ char *profile;
+ gboolean profile_is_id;
+ char **exec_argv;
+ char *title;
+ char *working_dir;
+ double zoom;
+ GUnixFDList *fd_list;
+ GArray *fd_array;
+ guint zoom_set : 1;
+ guint active : 1;
+ guint wait : 1;
+} InitialTab;
+
+typedef struct
+{
+ guint source_tag;
+ gboolean implicit_first_window;
+
+ GList *tabs; /* list of InitialTab */
+
+ gboolean force_menubar_state;
+ gboolean menubar_state;
+
+ gboolean start_fullscreen;
+ gboolean start_maximized;
+
+ char *geometry;
+ char *role;
+
+} InitialWindow;
+
+#define TERMINAL_OPTION_ERROR (g_quark_from_static_string ("terminal-option-error"))
+
+typedef enum {
+ TERMINAL_OPTION_ERROR_NOT_SUPPORTED,
+ TERMINAL_OPTION_ERROR_NOT_IN_FACTORY,
+ TERMINAL_OPTION_ERROR_EXCLUSIVE_OPTIONS,
+ TERMINAL_OPTION_ERROR_INVALID_CONFIG_FILE,
+ TERMINAL_OPTION_ERROR_INCOMPATIBLE_CONFIG_FILE
+} TerminalOptionError;
+
+TerminalOptions *terminal_options_parse (int *argcp,
+ char ***argvp,
+ GError **error);
+
+gboolean terminal_options_merge_config (TerminalOptions *options,
+ GKeyFile *key_file,
+ guint source_tag,
+ GError **error);
+
+void terminal_options_ensure_window (TerminalOptions *options);
+
+const char *terminal_options_get_service_name (TerminalOptions *options);
+
+const char *terminal_options_get_parent_screen_object_path (TerminalOptions *options);
+
+void terminal_options_free (TerminalOptions *options);
+
+typedef enum {
+ TERMINAL_VERBOSITY_QUIET = 0,
+ TERMINAL_VERBOSITY_NORMAL = 1,
+ TERMINAL_VERBOSITY_DETAIL = 2,
+ TERMINAL_VERBOSITY_DEBUG = 3
+} TerminalVerbosity;
+
+void terminal_fprintf (FILE* fp,
+ int verbosity_level,
+ const char* format,
+ ...) G_GNUC_PRINTF(3, 4);
+
+#define terminal_print_level(level,...) terminal_fprintf(stdout, TERMINAL_VERBOSITY_ ## level, __VA_ARGS__)
+#define terminal_printerr_level(level,...) terminal_fprintf(stderr, TERMINAL_VERBOSITY_ ## level, __VA_ARGS__)
+
+#define terminal_print(...) terminal_print_level(NORMAL, __VA_ARGS__)
+#define terminal_print_detail(...) terminal_print_level(DETAIL, __VA_ARGS__)
+#define terminal_print_debug(...) terminal_print_level(DEBUG, __VA_ARGS__)
+
+#define terminal_printerr_detail(...) terminal_printerr_level(DETAIL, __VA_ARGS__)
+#define terminal_printerr(...) terminal_printerr_level(NORMAL, __VA_ARGS__)
+#define terminal_printerr_debug(...) terminal_printerr_level(DEBUG, __VA_ARGS__)
+
+GLogWriterOutput terminal_log_writer (GLogLevelFlags log_level,
+ const GLogField *fields,
+ gsize n_fields,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_OPTIONS_H */
diff --git a/src/terminal-pcre2.hh b/src/terminal-pcre2.hh
new file mode 100644
index 0000000..af7e8e4
--- /dev/null
+++ b/src/terminal-pcre2.hh
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2015 Christian Persch
+ *
+ * This library 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 programme 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/>.
+ */
+
+#pragma once
+
+#ifdef __VTE_VTE_H__
+#error "Must include terminal-pcre2.h before vte/vte.h"
+#endif
+
+#define PCRE2_CODE_UNIT_WIDTH 0
+#include <pcre2.h>
diff --git a/src/terminal-prefs-process.cc b/src/terminal-prefs-process.cc
new file mode 100644
index 0000000..a19386f
--- /dev/null
+++ b/src/terminal-prefs-process.cc
@@ -0,0 +1,513 @@
+/*
+ * Copyright © 2020, 2022 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 <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include "terminal-settings-bridge-impl.hh"
+
+#include "terminal-app.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-intl.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-prefs-process.hh"
+
+#include "terminal-settings-bridge-generated.h"
+
+struct _TerminalPrefsProcess {
+ GObject parent_instance;
+
+ GSubprocess* subprocess;
+ GCancellable* cancellable;
+ GDBusConnection *connection;
+ TerminalSettingsBridgeImpl* bridge_impl;
+};
+
+struct _TerminalPrefsProcessClass {
+ GObjectClass parent_class;
+
+ // Signals
+ void (*exited)(TerminalPrefsProcess* process,
+ int status);
+};
+
+enum {
+ SIGNAL_EXITED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+// helper functions
+
+template<typename T>
+inline constexpr auto
+IMPL(T* that) noexcept
+{
+ return reinterpret_cast<TerminalPrefsProcess*>(that);
+}
+
+// BEGIN copied from vte/src/libc-glue.hh
+
+static inline int
+fd_get_descriptor_flags(int fd) noexcept
+{
+ auto flags = int{};
+ do {
+ flags = fcntl(fd, F_GETFD);
+ } while (flags == -1 && errno == EINTR);
+
+ return flags;
+}
+
+static inline int
+fd_set_descriptor_flags(int fd,
+ int flags) noexcept
+{
+ auto r = int{};
+ do {
+ r = fcntl(fd, F_SETFD, flags);
+ } while (r == -1 && errno == EINTR);
+
+ return r;
+}
+
+static inline int
+fd_change_descriptor_flags(int fd,
+ int set_flags,
+ int unset_flags) noexcept
+{
+ auto const flags = fd_get_descriptor_flags(fd);
+ if (flags == -1)
+ return -1;
+
+ auto const new_flags = (flags | set_flags) & ~unset_flags;
+ if (new_flags == flags)
+ return 0;
+
+ return fd_set_descriptor_flags(fd, new_flags);
+}
+
+static inline int
+fd_get_status_flags(int fd) noexcept
+{
+ auto flags = int{};
+ do {
+ flags = fcntl(fd, F_GETFL, 0);
+ } while (flags == -1 && errno == EINTR);
+
+ return flags;
+}
+
+static inline int
+fd_set_status_flags(int fd,
+ int flags) noexcept
+{
+ auto r = int{};
+ do {
+ r = fcntl(fd, F_SETFL, flags);
+ } while (r == -1 && errno == EINTR);
+
+ return r;
+}
+
+static inline int
+fd_change_status_flags(int fd,
+ int set_flags,
+ int unset_flags) noexcept
+{
+ auto const flags = fd_get_status_flags(fd);
+ if (flags == -1)
+ return -1;
+
+ auto const new_flags = (flags | set_flags) & ~unset_flags;
+ if (new_flags == flags)
+ return 0;
+
+ return fd_set_status_flags(fd, new_flags);
+}
+
+static inline int
+fd_set_cloexec(int fd) noexcept
+{
+ return fd_change_descriptor_flags(fd, FD_CLOEXEC, 0);
+}
+
+static inline int
+fd_set_nonblock(int fd) noexcept
+{
+ return fd_change_status_flags(fd, O_NONBLOCK, 0);
+}
+
+// END copied from vte
+
+static int
+socketpair_cloexec_nonblock(int domain,
+ int type,
+ int protocol,
+ int sv[2]) noexcept
+{
+ auto r = int{};
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+ r = socketpair(domain,
+ type | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ protocol,
+ sv);
+ if (r != -1)
+ return r;
+
+ // Maybe cloexec and/or nonblock aren't supported by the kernel
+ if (errno != EINVAL && errno != EPROTOTYPE)
+ return r;
+
+ // If so, fall back to applying the flags after the socketpair() call
+#endif /* SOCK_CLOEXEC && SOCK_NONBLOCK */
+
+ r = socketpair(domain, type, protocol, sv);
+ if (r == -1)
+ return r;
+
+ if (fd_set_cloexec(sv[0]) == -1 ||
+ fd_set_nonblock(sv[0]) == -1 ||
+ fd_set_cloexec(sv[1]) == -1 ||
+ fd_set_nonblock(sv[1]) == -1) {
+ close(sv[0]);
+ close(sv[1]);
+ return -1;
+ }
+
+ return r;
+}
+
+static void
+subprocess_wait_cb(GObject* source,
+ GAsyncResult* result,
+ void* user_data)
+{
+ gs_unref_object auto impl = IMPL(user_data); // ref added on g_subprocess_wait_async
+
+ gs_free_error GError* error = nullptr;
+ if (g_subprocess_wait_finish(impl->subprocess, result, &error)) {
+ } // else: @cancellable was cancelled
+
+ auto const status = g_subprocess_get_status(impl->subprocess);
+
+ g_signal_emit(impl, signals[SIGNAL_EXITED], 0, status);
+
+ g_clear_object(&impl->subprocess);
+}
+
+// GInitable implementation
+
+static gboolean
+terminal_prefs_process_initable_init(GInitable* initable,
+ GCancellable* cancellable,
+ GError** error) noexcept
+{
+ auto const impl = IMPL(initable);
+
+ // Create a private D-Bus connection between the server and the preferences
+ // process, over which we proxy the settings (since otherwise there would
+ // be no way to modify the server's settings on backends other than dconf).
+
+ int socket_fds[2]{-1, -1};
+ auto r = socketpair_cloexec_nonblock(AF_UNIX, SOCK_STREAM, 0, socket_fds);
+ if (r != 0) {
+ auto const errsv = errno;
+ g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
+ "Failed to create bridge socketpair: %s",
+ g_strerror(errsv));
+ return false;
+ }
+
+ // Launch process
+ auto const launcher = g_subprocess_launcher_new(GSubprocessFlags(0));
+ // or use G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE ?
+
+ // Note that g_subprocess_launcher_set_cwd() is not necessary since
+ // the server's cwd is already $HOME.
+ g_subprocess_launcher_set_environ(launcher, nullptr); // inherit server's environment
+ g_subprocess_launcher_unsetenv(launcher, "DBUS_SESSION_BUS_ADDRESS");
+ g_subprocess_launcher_unsetenv(launcher, "DBUS_STARTER_BUS_TYPE");
+ // ? g_subprocess_launcher_setenv(launcher, "GSETTINGS_BACKEND", "bridge", true);
+ g_subprocess_launcher_take_fd(launcher, socket_fds[1], 3);
+ socket_fds[1] = -1;
+
+ gs_free auto exe = terminal_client_get_file_uninstalled(TERM_LIBEXECDIR,
+ TERM_LIBEXECDIR,
+ TERMINAL_PREFERENCES_BINARY_NAME,
+ G_FILE_TEST_IS_EXECUTABLE);
+
+ char *argv[16];
+ auto argc = 0;
+ _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_BRIDGE) {
+ argv[argc++] = (char*)"vte-2.91";
+ argv[argc++] = (char*)"--fd=3";
+ argv[argc++] = (char*)"--";
+ argv[argc++] = (char*)"gdb";
+ argv[argc++] = (char*)"--args";
+ }
+ argv[argc++] = exe;
+ argv[argc++] = (char*)"--bus-fd=3";
+ argv[argc++] = nullptr;
+ g_assert(argc <= int(G_N_ELEMENTS(argv)));
+
+ impl->subprocess = g_subprocess_launcher_spawnv(launcher, // consumed
+ argv,
+ error);
+ if (!impl->subprocess) {
+ close(socket_fds[0]);
+ return false;
+ }
+
+ g_subprocess_wait_async(impl->subprocess,
+ impl->cancellable,
+ GAsyncReadyCallback(subprocess_wait_cb),
+ g_object_ref(initable));
+
+ // Create server end of the D-Bus connection
+ gs_unref_object auto socket = g_socket_new_from_fd(socket_fds[0], error);
+ socket_fds[0] = -1;
+
+ if (!socket) {
+ g_prefix_error(error, "Failed to create bridge GSocket: ");
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ gs_unref_object auto sockconn =
+ g_socket_connection_factory_create_connection(socket);
+ if (!G_IS_IO_STREAM(sockconn)) {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Bridge socket has incorrect type %s", G_OBJECT_TYPE_NAME(sockconn));
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ gs_free auto guid = g_dbus_generate_guid();
+
+ impl->connection =
+ g_dbus_connection_new_sync(G_IO_STREAM(sockconn),
+ guid,
+ GDBusConnectionFlags(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS),
+ nullptr, // auth observer,
+ cancellable,
+ error);
+ if (!impl->connection) {
+ g_prefix_error(error, "Failed to create bridge D-Bus connection: ");
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ g_dbus_connection_set_exit_on_close(impl->connection, false);
+
+ auto const app = terminal_app_get();
+ impl->bridge_impl =
+ terminal_settings_bridge_impl_new(terminal_app_get_settings_backend(app));
+ if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(impl->bridge_impl),
+ impl->connection,
+ TERMINAL_SETTINGS_BRIDGE_OBJECT_PATH,
+ error)) {
+ g_prefix_error(error, "Failed to export D-Bus skeleton: ");
+ g_subprocess_force_exit(impl->subprocess);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+terminal_prefs_process_initable_iface_init(GInitableIface* iface) noexcept
+{
+ iface->init = terminal_prefs_process_initable_init;
+}
+
+static void
+terminal_prefs_process_async_initable_iface_init(GAsyncInitableIface* iface) noexcept
+{
+ // Use the default implementation which runs the GInitiable in a thread.
+}
+
+// Class Implementation
+
+G_DEFINE_TYPE_WITH_CODE(TerminalPrefsProcess,
+ terminal_prefs_process,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE,
+ terminal_prefs_process_initable_iface_init)
+ G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE,
+ terminal_prefs_process_async_initable_iface_init))
+
+static void
+terminal_prefs_process_init(TerminalPrefsProcess* process) /* noexcept */
+{
+ g_application_hold(g_application_get_default());
+}
+
+static void
+terminal_prefs_process_finalize(GObject* object) noexcept
+{
+ auto const impl = IMPL(object);
+ g_clear_object(&impl->bridge_impl);
+ g_clear_object(&impl->connection);
+ g_clear_object(&impl->cancellable);
+ g_clear_object(&impl->subprocess);
+
+ G_OBJECT_CLASS(terminal_prefs_process_parent_class)->finalize(object);
+
+ g_application_release(g_application_get_default());
+}
+
+static void
+terminal_prefs_process_class_init(TerminalPrefsProcessClass* klass) /* noexcept */
+{
+ auto const gobject_class = G_OBJECT_CLASS(klass);
+ gobject_class->finalize = terminal_prefs_process_finalize;
+
+ signals[SIGNAL_EXITED] =
+ g_signal_new(I_("exited"),
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET(TerminalPrefsProcessClass, exited),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+ g_signal_set_va_marshaller(signals[SIGNAL_EXITED],
+ G_OBJECT_CLASS_TYPE(klass),
+ g_cclosure_marshal_VOID__INTv);
+}
+
+// public API
+
+TerminalPrefsProcess*
+terminal_prefs_process_new_finish(GAsyncResult* result,
+ GError** error)
+{
+ gs_unref_object auto source = G_ASYNC_INITABLE(g_async_result_get_source_object(result));
+ auto const o = g_async_initable_new_finish(source, result, error);
+ return reinterpret_cast<TerminalPrefsProcess*>(o);
+}
+
+void
+terminal_prefs_process_new_async(GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ void* user_data)
+{
+ g_async_initable_new_async(TERMINAL_TYPE_PREFS_PROCESS,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ // properties,
+ nullptr);
+}
+
+TerminalPrefsProcess*
+terminal_prefs_process_new_sync(GCancellable* cancellable,
+ GError** error)
+{
+ return reinterpret_cast<TerminalPrefsProcess*>
+ (g_initable_new(TERMINAL_TYPE_PREFS_PROCESS,
+ cancellable,
+ error,
+ // properties,
+ nullptr));
+}
+
+void
+terminal_prefs_process_abort(TerminalPrefsProcess* process)
+{
+ g_return_if_fail(TERMINAL_IS_PREFS_PROCESS(process));
+
+ auto const impl = IMPL(process);
+ if (impl->subprocess)
+ g_subprocess_force_exit(impl->subprocess);
+}
+
+#ifdef ENABLE_DEBUG
+
+static void
+show_cb(GObject* source,
+ GAsyncResult* result,
+ void* user_data)
+{
+ gs_unref_object auto process = IMPL(user_data); // added on g_dbus_connection_call
+
+ gs_free_error GError *error = nullptr;
+ gs_unref_variant auto rv = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source),
+ result,
+ &error);
+
+ if (error)
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE, "terminal_prefs_process_show failed: %s\n", error->message);
+}
+
+#endif /* ENABLE_DEBUG */
+
+void
+terminal_prefs_process_show(TerminalPrefsProcess* process,
+ char const* profile_uuid,
+ char const* hint,
+ unsigned timestamp)
+{
+ auto const impl = IMPL(process);
+
+ auto builder = GVariantBuilder{};
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("(sava{sv})"));
+ g_variant_builder_add(&builder, "s", "preferences");
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("av")); // parameter
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("v"));
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}"));
+ if (profile_uuid)
+ g_variant_builder_add(&builder, "{sv}", "profile", g_variant_new_string(profile_uuid));
+ if (hint)
+ g_variant_builder_add(&builder, "{sv}", "hint", g_variant_new_string(hint));
+ g_variant_builder_add(&builder, "{sv}", "timestamp", g_variant_new_uint32(timestamp));
+ g_variant_builder_close(&builder); // a{sv}
+ g_variant_builder_close(&builder); // v
+ g_variant_builder_close(&builder); // av
+ g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); // platform data
+ g_variant_builder_close(&builder); // a{sv}
+
+ g_dbus_connection_call(impl->connection,
+ nullptr, // since not on a message bus
+ TERMINAL_PREFERENCES_OBJECT_PATH,
+ "org.gtk.Actions",
+ "Activate",
+ g_variant_builder_end(&builder),
+ G_VARIANT_TYPE("()"),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ 30 * 1000, // ms timeout
+ impl->cancellable, // cancelleable
+#ifdef ENABLE_DEBUG
+ show_cb, // callback
+ g_object_ref(process) // callback data
+#else
+ nullptr, nullptr
+#endif
+ );
+}
diff --git a/src/terminal-prefs-process.hh b/src/terminal-prefs-process.hh
new file mode 100644
index 0000000..a93a532
--- /dev/null
+++ b/src/terminal-prefs-process.hh
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 2022 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_PREFS_PROCESS (terminal_prefs_process_get_type ())
+#define TERMINAL_PREFS_PROCESS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_PREFS_PROCESS, TerminalPrefsProcess))
+#define TERMINAL_PREFS_PROCESS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_PREFS_PROCESS, TerminalPrefsProcessClass))
+#define TERMINAL_IS_PREFS_PROCESS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_PREFS_PROCESS))
+#define TERMINAL_IS_PREFS_PROCESS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_PREFS_PROCESS))
+#define TERMINAL_PREFS_PROCESS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_PREFS_PROCESS, TerminalPrefsProcessClass))
+
+typedef struct _TerminalPrefsProcess TerminalPrefsProcess;
+typedef struct _TerminalPrefsProcessClass TerminalPrefsProcessClass;
+
+GType terminal_prefs_process_get_type(void);
+
+void terminal_prefs_process_new_async(GCancellable* cancellable,
+ GAsyncReadyCallback callback,
+ void* user_data);
+
+TerminalPrefsProcess* terminal_prefs_process_new_finish(GAsyncResult* result,
+ GError** error);
+
+TerminalPrefsProcess* terminal_prefs_process_new_sync(GCancellable* cancellable,
+ GError** error);
+
+void terminal_prefs_process_abort(TerminalPrefsProcess* process);
+
+void terminal_prefs_process_show(TerminalPrefsProcess* process,
+ char const* profile_uuid,
+ char const* hint,
+ unsigned timestamp);
+
+G_END_DECLS
diff --git a/src/terminal-prefs.cc b/src/terminal-prefs.cc
new file mode 100644
index 0000000..437c58a
--- /dev/null
+++ b/src/terminal-prefs.cc
@@ -0,0 +1,950 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington, Red Hat Inc.
+ * Copyright © 2008, 2011, 2012, 2013 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 <string.h>
+
+#include <uuid.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "profile-editor.hh"
+#include "terminal-prefs.hh"
+#include "terminal-accels.hh"
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-schemas.hh"
+#include "terminal-util.hh"
+#include "terminal-profiles-list.hh"
+#include "terminal-libgsystem.hh"
+
+PrefData *the_pref_data = nullptr; /* global */
+
+/* Bottom */
+
+static void
+prefs_dialog_help_button_clicked_cb (GtkWidget *button,
+ PrefData *data)
+{
+ terminal_util_show_help ("pref");
+}
+
+static void
+prefs_dialog_close_button_clicked_cb (GtkWidget *button,
+ PrefData *data)
+{
+ gtk_widget_destroy (data->dialog);
+}
+
+/* Sidebar */
+
+static inline GSimpleAction *
+lookup_action (GtkWindow *window,
+ const char *name)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), name);
+ g_return_val_if_fail (action != nullptr, nullptr);
+
+ return G_SIMPLE_ACTION (action);
+}
+
+/* Update the sidebar (visibility of icons, sensitivity of menu entries) to reflect the default and the selected profiles. */
+static void
+listbox_update (GtkListBox *box)
+{
+ int i;
+ GtkListBoxRow *row;
+ GSettings *profile;
+ gs_unref_object GSettings *default_profile;
+ GtkStack *stack;
+ GtkMenuButton *button;
+
+ default_profile = terminal_settings_list_ref_default_child (the_pref_data->profiles_list);
+
+ /* GTK+ doesn't seem to like if a popover is assigned to multiple buttons at once
+ * (not even temporarily), so make sure to remove it from the previous button first. */
+ for (i = 0; (row = gtk_list_box_get_row_at_index (box, i)) != nullptr; i++) {
+ button = (GtkMenuButton*)g_object_get_data (G_OBJECT (row), "popover-button");
+ gtk_menu_button_set_popover (button, nullptr);
+ }
+
+ for (i = 0; (row = gtk_list_box_get_row_at_index (box, i)) != nullptr; i++) {
+ profile = (GSettings*)g_object_get_data (G_OBJECT (row), "gsettings");
+
+ gboolean is_selected_profile = (profile != nullptr && profile == the_pref_data->selected_profile);
+ gboolean is_default_profile = (profile != nullptr && profile == default_profile);
+
+ stack = (GtkStack*)g_object_get_data (G_OBJECT (row), "home-stack");
+ gtk_stack_set_visible_child_name (stack, is_default_profile ? "home" : "placeholder");
+
+ stack = (GtkStack*)g_object_get_data (G_OBJECT (row), "popover-stack");
+ gtk_stack_set_visible_child_name (stack, is_selected_profile ? "button" : "placeholder");
+ if (is_selected_profile) {
+ g_simple_action_set_enabled (lookup_action (GTK_WINDOW (the_pref_data->dialog), "delete"), !is_default_profile);
+ g_simple_action_set_enabled (lookup_action (GTK_WINDOW (the_pref_data->dialog), "set-as-default"), !is_default_profile);
+
+ GtkPopover *popover_menu = GTK_POPOVER (gtk_builder_get_object (the_pref_data->builder, "popover-menu"));
+ button = (GtkMenuButton*)g_object_get_data (G_OBJECT (row), "popover-button");
+ gtk_menu_button_set_popover (button, GTK_WIDGET (popover_menu));
+ gtk_popover_set_relative_to (popover_menu, GTK_WIDGET (button));
+ }
+ }
+}
+
+static void
+update_window_title (void)
+{
+ GtkListBoxRow *row = the_pref_data->selected_list_box_row;
+ if (row == nullptr)
+ return;
+
+ GSettings *profile = (GSettings*)g_object_get_data (G_OBJECT (row), "gsettings");
+ GtkLabel *label = (GtkLabel*)g_object_get_data (G_OBJECT (row), "label");
+ const char *text = gtk_label_get_text (label);
+ gs_free char *subtitle;
+ gs_free char *title;
+
+ if (profile == nullptr) {
+ subtitle = g_strdup (text);
+ } else {
+ subtitle = g_strdup_printf (_("Profile “%s”"), text);
+ }
+
+ title = g_strdup_printf (_("Preferences – %s"), subtitle);
+ gtk_window_set_title (GTK_WINDOW (the_pref_data->dialog), title);
+}
+
+/* A new entry is selected in the sidebar */
+static void
+listbox_row_selected_cb (GtkListBox *box,
+ GtkListBoxRow *row,
+ GtkStack *stack)
+{
+ profile_prefs_unload ();
+
+ /* row can be nullptr intermittently during a profile meta operations */
+ g_free (the_pref_data->selected_profile_uuid);
+ if (row != nullptr) {
+ the_pref_data->selected_profile = (GSettings*)g_object_get_data (G_OBJECT (row), "gsettings");
+ the_pref_data->selected_profile_uuid = g_strdup ((char const*)g_object_get_data (G_OBJECT (row), "uuid"));
+ } else {
+ the_pref_data->selected_profile = nullptr;
+ the_pref_data->selected_profile_uuid = nullptr;
+ }
+ the_pref_data->selected_list_box_row = row;
+
+ listbox_update (box);
+
+ if (row != nullptr) {
+ if (the_pref_data->selected_profile != nullptr) {
+ profile_prefs_load (the_pref_data->selected_profile_uuid, the_pref_data->selected_profile);
+ }
+
+ char const* stack_child_name = (char const*)g_object_get_data (G_OBJECT (row), "stack_child_name");
+ gtk_stack_set_visible_child_name (stack, stack_child_name);
+ }
+
+ update_window_title ();
+}
+
+/* A profile's name changed, perhaps externally */
+static void
+profile_name_changed_cb (GtkLabel *label,
+ GParamSpec *pspec,
+ GtkListBoxRow *row)
+{
+ gtk_list_box_row_changed (row); /* trigger re-sorting */
+
+ if (row == the_pref_data->selected_list_box_row)
+ update_window_title ();
+}
+
+/* Select a profile in the sidebar by UUID */
+static gboolean
+listbox_select_profile (const char *uuid)
+{
+ GtkListBoxRow *row;
+ for (int i = 0; (row = gtk_list_box_get_row_at_index (the_pref_data->listbox, i)) != nullptr; i++) {
+ const char *rowuuid = (char const*) g_object_get_data (G_OBJECT (row), "uuid");
+ if (g_strcmp0 (rowuuid, uuid) == 0) {
+ g_signal_emit_by_name (row, "activate");
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* Create a new profile now, select it, update the UI. */
+static void
+profile_new_now (const char *name)
+{
+ gs_free char *uuid = terminal_app_new_profile (terminal_app_get (), nullptr, name);
+
+ listbox_select_profile (uuid);
+}
+
+/* Clone the selected profile now, select it, update the UI. */
+static void
+profile_clone_now (const char *name)
+{
+ if (the_pref_data->selected_profile == nullptr)
+ return;
+
+ gs_free char *uuid = terminal_app_new_profile (terminal_app_get (), the_pref_data->selected_profile, name);
+
+ listbox_select_profile (uuid);
+}
+
+/* Rename the selected profile now, update the UI. */
+static void
+profile_rename_now (const char *name)
+{
+ if (the_pref_data->selected_profile == nullptr)
+ return;
+
+ /* This will automatically trigger a call to profile_name_changed_cb(). */
+ g_settings_set_string (the_pref_data->selected_profile, TERMINAL_PROFILE_VISIBLE_NAME_KEY, name);
+}
+
+/* Delete the selected profile now, update the UI. */
+static void
+profile_delete_now (const char *dummy)
+{
+ if (the_pref_data->selected_profile == nullptr)
+ return;
+
+ /* Prepare to select the next one, or if there's no such then the previous one. */
+ int index = gtk_list_box_row_get_index (the_pref_data->selected_list_box_row);
+ GtkListBoxRow *new_selected_row = gtk_list_box_get_row_at_index (the_pref_data->listbox, index + 1);
+ if (new_selected_row == nullptr)
+ new_selected_row = gtk_list_box_get_row_at_index (the_pref_data->listbox, index - 1);
+ GSettings *new_selected_profile = (GSettings*)g_object_get_data (G_OBJECT (new_selected_row), "gsettings");
+ gs_free char *uuid = nullptr;
+ if (new_selected_profile != nullptr)
+ uuid = terminal_settings_list_dup_uuid_from_child (the_pref_data->profiles_list, new_selected_profile);
+
+ terminal_app_remove_profile (terminal_app_get (), the_pref_data->selected_profile);
+
+ listbox_select_profile (uuid);
+}
+
+/* "Set as default" selected. Do it now without asking for confirmation. */
+static void
+profile_set_as_default_cb (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ if (the_pref_data->selected_profile_uuid == nullptr)
+ return;
+
+ /* This will automatically trigger a call to listbox_update() via "default-changed". */
+ terminal_settings_list_set_default_child (the_pref_data->profiles_list, the_pref_data->selected_profile_uuid);
+}
+
+
+static void
+popover_dialog_cancel_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ GtkPopover *popover_dialog = GTK_POPOVER (gtk_builder_get_object (the_pref_data->builder, "popover-dialog"));
+
+ gtk_popover_popdown (popover_dialog);
+}
+
+static void
+popover_dialog_ok_clicked_cb (GtkButton *button,
+ void (*fn) (const char *))
+{
+ GtkEntry *entry = GTK_ENTRY (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-entry"));
+ const char *name = gtk_entry_get_text (entry);
+
+ /* Perform what we came for */
+ (*fn) (name);
+
+ /* Hide/popdown the popover */
+ popover_dialog_cancel_clicked_cb (button, nullptr);
+}
+
+static void
+popover_dialog_closed_cb (GtkPopover *popover,
+ gpointer user_data)
+{
+
+ GtkEntry *entry = GTK_ENTRY (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-entry"));
+ gtk_entry_set_text (entry, "");
+
+ GtkButton *ok = GTK_BUTTON (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-ok"));
+ GtkButton *cancel = GTK_BUTTON (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-cancel"));
+
+ g_signal_handlers_disconnect_matched (ok, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ (void*)popover_dialog_ok_clicked_cb, nullptr);
+ g_signal_handlers_disconnect_matched (cancel, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ (void*)popover_dialog_cancel_clicked_cb, nullptr);
+ g_signal_handlers_disconnect_matched (popover, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ (void*)popover_dialog_closed_cb, nullptr);
+}
+
+
+/* Updates the OK button's sensitivity (insensitive if entry field is empty or whitespace only).
+ * The entry's initial value and OK's initial sensitivity have to match in the .ui file. */
+static void
+popover_dialog_notify_text_cb (GtkEntry *entry,
+ GParamSpec *pspec,
+ GtkWidget *ok)
+{
+ gs_free char *text = g_strchomp (g_strdup (gtk_entry_get_text (entry)));
+ gtk_widget_set_sensitive (ok, text[0] != '\0');
+}
+
+
+/* Common dialog for entering new profile name, or confirming deletion */
+static void
+profile_popup_dialog (GtkWidget *relative_to,
+ const char *header,
+ const char *body,
+ const char *entry_text,
+ const char *ok_text,
+ void (*fn) (const char *))
+{
+ GtkLabel *label1 = GTK_LABEL (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-label1"));
+ gtk_label_set_text (label1, header);
+
+ GtkLabel *label2 = GTK_LABEL (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-label2"));
+ gtk_label_set_text (label2, body);
+
+ GtkEntry *entry = GTK_ENTRY (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-entry"));
+ if (entry_text != nullptr) {
+ gtk_entry_set_text (entry, entry_text);
+ gtk_widget_show (GTK_WIDGET (entry));
+ } else {
+ gtk_entry_set_text (entry, "."); /* to make the OK button sensitive */
+ gtk_widget_hide (GTK_WIDGET (entry));
+ }
+
+ GtkButton *ok = GTK_BUTTON (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-ok"));
+ gtk_button_set_label (ok, ok_text);
+ GtkButton *cancel = GTK_BUTTON (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-cancel"));
+ GtkPopover *popover_dialog = GTK_POPOVER (gtk_builder_get_object (the_pref_data->builder, "popover-dialog"));
+
+ g_signal_connect (ok, "clicked", G_CALLBACK (popover_dialog_ok_clicked_cb), (void*)fn);
+ g_signal_connect (cancel, "clicked", G_CALLBACK (popover_dialog_cancel_clicked_cb), nullptr);
+ g_signal_connect (popover_dialog, "closed", G_CALLBACK (popover_dialog_closed_cb), nullptr);
+
+ gtk_popover_set_relative_to (popover_dialog, relative_to);
+ gtk_popover_set_position (popover_dialog, GTK_POS_BOTTOM);
+ gtk_popover_set_default_widget (popover_dialog, GTK_WIDGET (ok));
+
+ gtk_popover_popup (popover_dialog);
+
+ gtk_widget_grab_focus (entry_text != nullptr ? GTK_WIDGET (entry) : GTK_WIDGET (cancel));
+}
+
+/* "New" selected, ask for profile name */
+static void
+profile_new_cb (GtkButton *button,
+ gpointer user_data)
+{
+ profile_popup_dialog (GTK_WIDGET (the_pref_data->new_profile_button),
+ _("New Profile"),
+ _("Enter name for new profile with default settings:"),
+ "",
+ _("Create"),
+ profile_new_now);
+}
+
+/* "Clone" selected, ask for profile name */
+static void
+profile_clone_cb (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gs_free char *name = g_settings_get_string (the_pref_data->selected_profile, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+
+ gs_free char *label = g_strdup_printf (_("Enter name for new profile based on “%s”:"), name);
+ gs_free char *clone_name = g_strdup_printf (_("%s (Copy)"), name);
+
+ profile_popup_dialog (GTK_WIDGET (the_pref_data->selected_list_box_row),
+ _("Clone Profile"),
+ label,
+ clone_name,
+ _("Clone"),
+ profile_clone_now);
+}
+
+/* "Rename" selected, ask for new name */
+static void
+profile_rename_cb (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ if (the_pref_data->selected_profile == nullptr)
+ return;
+
+ gs_free char *name = g_settings_get_string (the_pref_data->selected_profile, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+
+ gs_free char *label = g_strdup_printf (_("Enter new name for profile “%s”:"), name);
+
+ profile_popup_dialog (GTK_WIDGET (the_pref_data->selected_list_box_row),
+ _("Rename Profile"),
+ label,
+ name,
+ _("Rename"),
+ profile_rename_now);
+}
+
+/* "Delete" selected, ask for confirmation */
+static void
+profile_delete_cb (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ if (the_pref_data->selected_profile == nullptr)
+ return;
+
+ gs_free char *name = g_settings_get_string (the_pref_data->selected_profile, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+
+ gs_free char *label = g_strdup_printf (_("Really delete profile “%s”?"), name);
+
+ profile_popup_dialog (GTK_WIDGET (the_pref_data->selected_list_box_row),
+ _("Delete Profile"),
+ label,
+ nullptr,
+ _("Delete"),
+ profile_delete_now);
+}
+
+/* Create a (non-header) row of the sidebar, either a global or a profile entry. */
+static GtkListBoxRow *
+listbox_create_row (const char *name,
+ const char *stack_child_name,
+ const char *uuid,
+ GSettings *gsettings /* adopted */,
+ gpointer sort_order)
+{
+ GtkListBoxRow *row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
+
+ g_object_set_data_full (G_OBJECT (row), "stack_child_name", g_strdup (stack_child_name), g_free);
+ g_object_set_data_full (G_OBJECT (row), "uuid", g_strdup (uuid), g_free);
+ if (gsettings != nullptr)
+ g_object_set_data_full (G_OBJECT (row), "gsettings", gsettings, (GDestroyNotify)g_object_unref);
+ g_object_set_data (G_OBJECT (row), "sort_order", sort_order);
+
+ GtkBox *hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
+ gtk_widget_set_margin_start (GTK_WIDGET (hbox), 6);
+ gtk_widget_set_margin_end (GTK_WIDGET (hbox), 6);
+ gtk_widget_set_margin_top (GTK_WIDGET (hbox), 6);
+ gtk_widget_set_margin_bottom (GTK_WIDGET (hbox), 6);
+
+ GtkLabel *label = GTK_LABEL (gtk_label_new (name));
+ if (gsettings != nullptr) {
+ g_signal_connect (label, "notify::label", G_CALLBACK (profile_name_changed_cb), row);
+ g_settings_bind (gsettings,
+ TERMINAL_PROFILE_VISIBLE_NAME_KEY,
+ label,
+ "label",
+ G_SETTINGS_BIND_GET);
+ }
+ gtk_label_set_xalign (label, 0);
+ gtk_box_pack_start (hbox, GTK_WIDGET (label), TRUE, TRUE, 0);
+ g_object_set_data (G_OBJECT (row), "label", label);
+
+ /* Always add the "default" symbol and the "menu" button, even on rows of global prefs.
+ * Use GtkStack to possible achieve visibility:hidden on it.
+ * This is so that all listbox rows have the same dimensions, and the width doesn't change
+ * as you switch the default profile. */
+
+ GtkStack *popover_stack = GTK_STACK (gtk_stack_new ());
+ gtk_widget_set_margin_start (GTK_WIDGET (popover_stack), 6);
+ GtkMenuButton *popover_button = GTK_MENU_BUTTON (gtk_menu_button_new ());
+ gtk_button_set_relief (GTK_BUTTON (popover_button), GTK_RELIEF_NONE);
+ gtk_stack_add_named (popover_stack, GTK_WIDGET (popover_button), "button");
+ GtkLabel *popover_label = GTK_LABEL (gtk_label_new (""));
+ gtk_stack_add_named (popover_stack, GTK_WIDGET (popover_label), "placeholder");
+ g_object_set_data (G_OBJECT (row), "popover-stack", popover_stack);
+ g_object_set_data (G_OBJECT (row), "popover-button", popover_button);
+
+ gtk_box_pack_end (hbox, GTK_WIDGET (popover_stack), FALSE, FALSE, 0);
+
+ GtkStack *home_stack = GTK_STACK (gtk_stack_new ());
+ gtk_widget_set_margin_start (GTK_WIDGET (home_stack), 12);
+ GtkImage *home_image = GTK_IMAGE (gtk_image_new_from_icon_name ("emblem-default-symbolic", GTK_ICON_SIZE_BUTTON));
+ gtk_widget_set_tooltip_text (GTK_WIDGET (home_image), _("This is the default profile"));
+ gtk_stack_add_named (home_stack, GTK_WIDGET (home_image), "home");
+ GtkLabel *home_label = GTK_LABEL (gtk_label_new (""));
+ gtk_stack_add_named (home_stack, GTK_WIDGET (home_label), "placeholder");
+ g_object_set_data (G_OBJECT (row), "home-stack", home_stack);
+
+ gtk_box_pack_end (hbox, GTK_WIDGET (home_stack), FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (hbox));
+
+ gtk_widget_show_all (GTK_WIDGET (row));
+
+ gtk_stack_set_visible_child_name (popover_stack, "placeholder");
+ gtk_stack_set_visible_child_name (home_stack, "placeholder");
+
+ return row;
+}
+
+/* Add all the non-profile rows to the sidebar */
+static void
+listbox_add_all_globals (PrefData *data)
+{
+ GtkListBoxRow *row;
+
+ row = listbox_create_row (_("General"),
+ "general-prefs",
+ nullptr, nullptr, (gpointer) 0);
+ gtk_list_box_insert (data->listbox, GTK_WIDGET (row), -1);
+
+ row = listbox_create_row (_("Shortcuts"),
+ "shortcut-prefs",
+ nullptr, nullptr, (gpointer) 1);
+ gtk_list_box_insert (data->listbox, GTK_WIDGET (row), -1);
+}
+
+/* Remove all the profile rows from the sidebar */
+static void
+listbox_remove_all_profiles (PrefData *data)
+{
+ int i = 0;
+
+ data->selected_profile = nullptr;
+ g_free (data->selected_profile_uuid);
+ data->selected_profile_uuid = nullptr;
+ profile_prefs_unload ();
+
+ GtkListBoxRow *row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (the_pref_data->listbox), 0);
+ g_signal_emit_by_name (row, "activate");
+
+ while ((row = gtk_list_box_get_row_at_index (data->listbox, i)) != nullptr) {
+ if (g_object_get_data (G_OBJECT (row), "gsettings") != nullptr) {
+ gtk_widget_destroy (GTK_WIDGET (row));
+ } else {
+ i++;
+ }
+ }
+}
+
+/* Add all the profiles to the sidebar */
+static void
+listbox_add_all_profiles (PrefData *data)
+{
+ GList *list, *l;
+ GtkListBoxRow *row;
+
+ list = terminal_settings_list_ref_children (data->profiles_list);
+
+ for (l = list; l != nullptr; l = l->next) {
+ GSettings *profile = (GSettings *) l->data;
+ gs_free gchar *uuid = terminal_settings_list_dup_uuid_from_child (data->profiles_list, profile);
+
+ row = listbox_create_row (nullptr,
+ "profile-prefs",
+ uuid,
+ profile /* adopts */,
+ (gpointer) 42);
+ gtk_list_box_insert (data->listbox, GTK_WIDGET (row), -1);
+ }
+
+ g_list_free(list); /* the items themselves were adopted into the model above */
+
+ listbox_update (data->listbox); /* FIXME: This is not needed but I don't know why :-) */
+}
+
+/* Re-add all the profiles to the sidebar.
+ * This is called when a profile is added or removed, and also when the list of profiles is
+ * modified externally.
+ * Try to keep the selected profile, whenever possible.
+ * When the list is modified externally, the terminal_settings_list_*() methods seem to preserve
+ * the GSettings object for every profile that remains in the list. There's no guarantee however
+ * that a newly created GSettings can't receive the same address that a ceased one used to have.
+ * So don't rely on GSettings* to keep track of the selected profile, use the UUID instead. */
+static void
+listbox_readd_profiles (PrefData *data)
+{
+ gs_free char *uuid = g_strdup (data->selected_profile_uuid);
+
+ listbox_remove_all_profiles (data);
+ listbox_add_all_profiles (data);
+
+ if (uuid != nullptr)
+ listbox_select_profile (uuid);
+}
+
+/* Create a header row ("Global" or "Profiles +") */
+static GtkWidget *
+listboxrow_create_header (const char *text,
+ gboolean visible_button)
+{
+ GtkBox *hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
+ gtk_widget_set_margin_start (GTK_WIDGET (hbox), 6);
+ gtk_widget_set_margin_end (GTK_WIDGET (hbox), 6);
+ gtk_widget_set_margin_top (GTK_WIDGET (hbox), 6);
+ gtk_widget_set_margin_bottom (GTK_WIDGET (hbox), 6);
+
+ GtkLabel *label = GTK_LABEL (gtk_label_new (nullptr));
+ gs_free char *markup = g_markup_printf_escaped ("<b>%s</b>", text);
+ gtk_label_set_markup (label, markup);
+ gtk_label_set_xalign (label, 0);
+ gtk_box_pack_start (hbox, GTK_WIDGET (label), TRUE, TRUE, 0);
+
+ /* Always add a "new profile" button. Use GtkStack to possible achieve visibility:hidden on it.
+ * This is so that both header rows have the same dimensions. */
+
+ GtkStack *stack = GTK_STACK (gtk_stack_new ());
+ GtkButton *button = GTK_BUTTON (gtk_button_new_from_icon_name ("list-add-symbolic", GTK_ICON_SIZE_BUTTON));
+ gtk_button_set_relief (button, GTK_RELIEF_NONE);
+ gtk_stack_add_named (stack, GTK_WIDGET (button), "button");
+ GtkLabel *labelx = GTK_LABEL (gtk_label_new (""));
+ gtk_stack_add_named (stack, GTK_WIDGET (labelx), "placeholder");
+
+ gtk_box_pack_end (hbox, GTK_WIDGET (stack), FALSE, FALSE, 0);
+
+ gtk_widget_show_all (GTK_WIDGET (hbox));
+
+ if (visible_button) {
+ gtk_stack_set_visible_child_name (stack, "button");
+ g_signal_connect (button, "clicked", G_CALLBACK (profile_new_cb), nullptr);
+ the_pref_data->new_profile_button = GTK_WIDGET (button);
+ } else {
+ gtk_stack_set_visible_child_name (stack, "placeholder");
+ }
+
+ return GTK_WIDGET (hbox);
+}
+
+/* Manage the creation or removal of the header row ("Global" or "Profiles +") */
+static void
+listboxrow_update_header (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ if (before == nullptr) {
+ if (gtk_list_box_row_get_header (row) == nullptr) {
+ gtk_list_box_row_set_header (row, listboxrow_create_header (_("Global"), FALSE));
+ }
+ return;
+ }
+
+ GSettings *profile = (GSettings*)g_object_get_data (G_OBJECT (row), "gsettings");
+ if (profile != nullptr) {
+ GSettings *profile_before = (GSettings*)g_object_get_data (G_OBJECT (before), "gsettings");
+ if (profile_before != nullptr) {
+ gtk_list_box_row_set_header (row, nullptr);
+ } else {
+ if (gtk_list_box_row_get_header (row) == nullptr) {
+ gtk_list_box_row_set_header (row, listboxrow_create_header (_("Profiles"), TRUE));
+ }
+ }
+ }
+}
+
+/* Sort callback for rows of the sidebar (global and profile ones).
+ * Global ones are kept at the top in fixed order. This is implemented via sort_order
+ * which is an integer disguised as a pointer for ease of implementation.
+ * Profile ones are sorted lexicographically. */
+static gint
+listboxrow_compare_cb (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+ gpointer sort_order_1 = g_object_get_data (G_OBJECT (row1), "sort_order");
+ gpointer sort_order_2 = g_object_get_data (G_OBJECT (row2), "sort_order");
+
+ if (sort_order_1 != sort_order_2)
+ return sort_order_1 < sort_order_2 ? -1 : 1;
+
+ GtkLabel *label1 = (GtkLabel*)g_object_get_data (G_OBJECT (row1), "label");
+ const char *text1 = gtk_label_get_text (label1);
+ GtkLabel *label2 = (GtkLabel*)g_object_get_data (G_OBJECT (row2), "label");
+ const char *text2 = gtk_label_get_text (label2);
+
+ return g_utf8_collate (text1, text2);
+}
+
+/* Keybindings tab */
+
+/* Make sure the treeview is repainted with the correct text color, see bug 792139. */
+static void
+shortcuts_button_toggled_cb (GtkWidget *widget,
+ GtkTreeView *tree_view)
+{
+ gtk_widget_queue_draw (GTK_WIDGET (tree_view));
+}
+
+/* misc */
+
+static void
+prefs_dialog_destroy_cb (GtkWidget *widget,
+ PrefData *data)
+{
+ /* Don't run this handler again */
+ g_signal_handlers_disconnect_by_func (widget, (void*)prefs_dialog_destroy_cb, data);
+
+ g_signal_handlers_disconnect_by_func (data->profiles_list,
+ (void*)listbox_readd_profiles, data);
+ g_signal_handlers_disconnect_by_func (data->profiles_list,
+ (void*)listbox_update, data->listbox);
+
+ profile_prefs_destroy ();
+
+ g_object_unref (data->builder);
+ g_free (data->selected_profile_uuid);
+ 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,
+ unsigned timestamp)
+{
+ TerminalApp *app = terminal_app_get ();
+ PrefData *data;
+ GtkWidget *dialog, *tree_view;
+ GtkWidget *show_menubar_button, *disable_mnemonics_button, *disable_menu_accel_button;
+ GtkWidget *disable_shortcuts_button;
+ GtkWidget *theme_variant_label, *theme_variant_combo;
+ GtkWidget *new_terminal_mode_label, *new_terminal_mode_combo;
+ 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[] = {
+ { "clone", profile_clone_cb, nullptr, nullptr, nullptr },
+ { "rename", profile_rename_cb, nullptr, nullptr, nullptr },
+ { "delete", profile_delete_cb, nullptr, nullptr, nullptr },
+ { "set-as-default", profile_set_as_default_cb, nullptr, nullptr, nullptr },
+ };
+
+ if (the_pref_data != nullptr)
+ goto done;
+
+ {
+ the_pref_data = g_new0 (PrefData, 1);
+ data = the_pref_data;
+ data->profiles_list = terminal_app_get_profiles_list (app);
+
+ /* FIXME this method is only used from here. Inline it here instead. */
+ data->builder = terminal_util_load_widgets_resource ("/org/gnome/terminal/ui/preferences.ui",
+ "preferences-dialog",
+ "preferences-dialog", &dialog,
+ "dialogue-content-box", &content_box,
+ "general-frame", &general_frame,
+ "keybindings-frame", &keybindings_frame,
+ "close-button", &close_button,
+ "help-button", &help_button,
+ "default-show-menubar-checkbutton", &show_menubar_button,
+ "theme-variant-label", &theme_variant_label,
+ "theme-variant-combobox", &theme_variant_combo,
+ "new-terminal-mode-label", &new_terminal_mode_label,
+ "new-terminal-mode-combobox", &new_terminal_mode_combo,
+ "disable-mnemonics-checkbutton", &disable_mnemonics_button,
+ "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,
+ nullptr);
+
+ data->dialog = dialog;
+
+ gtk_window_set_application (GTK_WINDOW (data->dialog), GTK_APPLICATION (terminal_app_get ()));
+
+ terminal_util_bind_mnemonic_label_sensitivity (dialog);
+
+ settings = terminal_app_get_global_settings (app);
+
+ g_action_map_add_action_entries (G_ACTION_MAP (dialog),
+ action_entries, G_N_ELEMENTS (action_entries),
+ data);
+
+ /* Sidebar */
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (data->listbox),
+ listboxrow_update_header,
+ nullptr,
+ nullptr);
+ g_signal_connect (data->listbox, "row-selected", G_CALLBACK (listbox_row_selected_cb), data->stack);
+ gtk_list_box_set_sort_func (data->listbox, listboxrow_compare_cb, nullptr, nullptr);
+
+ listbox_add_all_globals (data);
+ listbox_add_all_profiles (data);
+ g_signal_connect_swapped (data->profiles_list, "children-changed",
+ G_CALLBACK (listbox_readd_profiles), data);
+ g_signal_connect_swapped (data->profiles_list, "default-changed",
+ G_CALLBACK (listbox_update), data->listbox);
+
+ GtkEntry *entry = GTK_ENTRY (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-entry"));
+ GtkButton *ok = GTK_BUTTON (gtk_builder_get_object (the_pref_data->builder, "popover-dialog-ok"));
+ g_signal_connect (entry, "notify::text", G_CALLBACK (popover_dialog_notify_text_cb), ok);
+
+ /* General page */
+
+ gboolean shell_shows_menubar;
+ g_object_get (gtk_settings_get_default (),
+ "gtk-shell-shows-menubar", &shell_shows_menubar,
+ nullptr);
+ if (shell_shows_menubar || terminal_app_get_use_headerbar (app)) {
+ gtk_widget_set_visible (show_menubar_button, FALSE);
+ } else {
+ g_settings_bind (settings,
+ TERMINAL_SETTING_DEFAULT_SHOW_MENUBAR_KEY,
+ show_menubar_button,
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET));
+ }
+
+ g_settings_bind (settings,
+ TERMINAL_SETTING_THEME_VARIANT_KEY,
+ theme_variant_combo,
+ "active-id",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET));
+
+ if (terminal_app_get_menu_unified (app) ||
+ terminal_app_get_use_headerbar (app)) {
+ g_settings_bind (settings,
+ TERMINAL_SETTING_NEW_TERMINAL_MODE_KEY,
+ new_terminal_mode_combo,
+ "active-id",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET));
+ } else {
+ gtk_widget_set_visible (new_terminal_mode_label, FALSE);
+ gtk_widget_set_visible (new_terminal_mode_combo, FALSE);
+ }
+
+ g_settings_bind (settings,
+ TERMINAL_SETTING_NEW_TAB_POSITION_KEY,
+ new_tab_position_combo,
+ "active-id",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET));
+
+ if (shell_shows_menubar) {
+ gtk_widget_set_visible (disable_mnemonics_button, FALSE);
+ } else {
+ g_settings_bind (settings,
+ TERMINAL_SETTING_ENABLE_MNEMONICS_KEY,
+ disable_mnemonics_button,
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET));
+ }
+ g_settings_bind (settings,
+ TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY,
+ disable_menu_accel_button,
+ "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,
+ TERMINAL_SETTING_ENABLE_SHORTCUTS_KEY,
+ disable_shortcuts_button,
+ "active",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET));
+
+ g_signal_connect (disable_shortcuts_button, "toggled",
+ G_CALLBACK (shortcuts_button_toggled_cb), tree_view);
+
+ terminal_accels_fill_treeview (tree_view, disable_shortcuts_button);
+
+ /* Profile page */
+
+ profile_prefs_init ();
+
+ /* Move action widgets to titlebar when headerbar is used */
+ if (terminal_app_get_dialog_use_headerbar (app)) {
+ GtkWidget *headerbar;
+ GtkWidget *bbox;
+
+ headerbar = (GtkWidget*)g_object_new (GTK_TYPE_HEADER_BAR,
+ "show-close-button", TRUE,
+ nullptr);
+ bbox = gtk_widget_get_parent (help_button);
+
+ gtk_container_remove (GTK_CONTAINER (bbox), (GtkWidget*)g_object_ref (help_button));
+ gtk_header_bar_pack_start (GTK_HEADER_BAR (headerbar), help_button);
+ g_object_unref (help_button);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (help_button),
+ "text-button");
+
+ gtk_widget_show (headerbar);
+ gtk_widget_hide (bbox);
+
+ gtk_window_set_titlebar (GTK_WINDOW (dialog), headerbar);
+
+ /* Remove extra spacing around the content, and extra frames */
+ g_object_set (G_OBJECT (content_box), "margin", 0, nullptr);
+ gtk_frame_set_shadow_type (GTK_FRAME (general_frame), GTK_SHADOW_NONE);
+ gtk_frame_set_shadow_type (GTK_FRAME (keybindings_frame), GTK_SHADOW_NONE);
+ }
+
+ /* misc */
+
+ g_signal_connect (close_button, "clicked", G_CALLBACK (prefs_dialog_close_button_clicked_cb), data);
+ g_signal_connect (help_button, "clicked", G_CALLBACK (prefs_dialog_help_button_clicked_cb), data);
+ g_signal_connect (dialog, "destroy", G_CALLBACK (prefs_dialog_destroy_cb), data);
+
+ g_object_add_weak_pointer (G_OBJECT (dialog), (gpointer *) &the_pref_data);
+ }
+
+done:
+ if (profile != nullptr) {
+ gs_free char *uuid = terminal_settings_list_dup_uuid_from_child (the_pref_data->profiles_list, profile);
+ listbox_select_profile (uuid);
+ } else {
+ GtkListBoxRow *row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (the_pref_data->listbox), 0);
+ g_signal_emit_by_name (row, "activate");
+ }
+
+ terminal_util_dialog_focus_widget (the_pref_data->builder, widget_name);
+
+ gtk_window_present_with_time(GTK_WINDOW(the_pref_data->dialog), timestamp);
+}
diff --git a/src/terminal-prefs.hh b/src/terminal-prefs.hh
new file mode 100644
index 0000000..e1ce784
--- /dev/null
+++ b/src/terminal-prefs.hh
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2013 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/>.
+ */
+
+#ifndef TERMINAL_PREFS_H
+#define TERMINAL_PREFS_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-profiles-list.hh"
+
+G_BEGIN_DECLS
+
+/* FIXME move back to the .c file if profile-editor.c is also merged there,
+ * also remove the terminal-profiles-list.h incude above. */
+/* FIXME PrefData is a very bad name, rename to PrefsDialog maybe? */
+
+/* Everything about a preferences dialog */
+typedef struct {
+ TerminalSettingsList *profiles_list;
+
+ GSettings *selected_profile;
+ GtkListBoxRow *selected_list_box_row;
+ char *selected_profile_uuid; /* a copy thereof, to survive changes to profiles_list */
+
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+ GtkListBox *listbox;
+ GtkWidget *new_profile_button;
+ GtkWidget *stack;
+
+ GArray *profile_signals;
+ GArray *profile_bindings;
+} PrefData;
+
+extern PrefData *the_pref_data; /* global */
+
+void terminal_prefs_show_preferences(GSettings* profile,
+ char const* widget_name,
+ unsigned timestamp);
+
+G_END_DECLS
+
+#endif /* TERMINAL_PREFS_H */
diff --git a/src/terminal-profiles-list.cc b/src/terminal-profiles-list.cc
new file mode 100644
index 0000000..e791087
--- /dev/null
+++ b/src/terminal-profiles-list.cc
@@ -0,0 +1,272 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2011, 2013 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-profiles-list.hh"
+#include "terminal-schemas.hh"
+#include "terminal-libgsystem.hh"
+
+#include <string.h>
+#include <uuid.h>
+
+/* Counts occurrences of @str in @strv */
+static guint
+strv_contains (char **strv,
+ const char *str,
+ guint *idx)
+{
+ guint n, i;
+
+ if (strv == nullptr)
+ return 0;
+
+ n = 0;
+ for (i = 0; strv[i]; i++) {
+ if (strcmp (strv[i], str) == 0) {
+ n++;
+ if (idx)
+ *idx = i;
+ }
+ }
+
+ return n;
+}
+
+static gboolean
+valid_uuid (const char *str,
+ GError **error)
+{
+ if (terminal_settings_list_valid_uuid (str))
+ return TRUE;
+
+ g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "\"%s\" is not a valid UUID", str);
+ return FALSE;
+}
+
+/**
+ * terminal_profiles_list_new:
+ * @backend: a #GSettingsBackend
+ * @schema_source: a #GSettingsSchemaSource
+ *
+ * Returns: (transfer full): a new #TerminalSettingsList for the profiles list
+ */
+TerminalSettingsList *
+terminal_profiles_list_new(GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source)
+{
+ return terminal_settings_list_new (backend,
+ schema_source,
+ TERMINAL_PROFILES_PATH_PREFIX,
+ TERMINAL_PROFILES_LIST_SCHEMA,
+ TERMINAL_PROFILE_SCHEMA,
+ TERMINAL_SETTINGS_LIST_FLAG_HAS_DEFAULT);
+}
+
+static void
+get_profile_names (TerminalSettingsList *list,
+ char ***profilesp,
+ char ***namesp)
+{
+ char **profiles, **names;
+ guint i, n;
+
+ *profilesp = profiles = terminal_settings_list_dupv_children (list);
+
+ n = g_strv_length (profiles);
+ *namesp = names = g_new0 (char *, n + 1);
+ for (i = 0; i < n; i++) {
+ gs_unref_object GSettings *profile;
+
+ profile = terminal_settings_list_ref_child (list, profiles[i]);
+ names[i] = g_settings_get_string (profile, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+ }
+
+ names[n] = nullptr;
+}
+
+/**
+ * terminal_profiles_list_ref_children_sorted:
+ * @list:
+ *
+ * Returns: (transfer full):
+ */
+GList *
+terminal_profiles_list_ref_children_sorted (TerminalSettingsList *list)
+{
+ return g_list_sort (terminal_settings_list_ref_children (list),
+ terminal_profiles_compare);
+}
+
+/**
+ * terminal_profiles_list_dup_uuid:
+ * @list:
+ * @uuid: (allow-none):
+ * @error:
+ *
+ * Returns: (transfer full): the UUID of the profile specified by @uuid, or %nullptr
+ */
+char *
+terminal_profiles_list_dup_uuid (TerminalSettingsList *list,
+ const char *uuid,
+ GError **error)
+{
+ char *rv;
+
+ if (uuid == nullptr) {
+ rv = terminal_settings_list_dup_default_child (list);
+ if (rv == nullptr)
+ goto err;
+ return rv;
+ } else if (!valid_uuid (uuid, error))
+ return nullptr;
+
+ if (terminal_settings_list_has_child (list, uuid))
+ return g_strdup (uuid);
+
+ err:
+ g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "No profile with UUID \"%s\" exists", uuid);
+ return nullptr;
+}
+
+/**
+ * terminal_profiles_list_ref_profile_by_uuid_or_name:
+ * @list:
+ * @uuid:
+ * @error:
+ *
+ * Returns: (transfer full): the profile #GSettings specified by @uuid, or %nullptr
+ */
+GSettings *
+terminal_profiles_list_ref_profile_by_uuid (TerminalSettingsList *list,
+ const char *uuid,
+ GError **error)
+{
+ gs_free char *profile_uuid;
+ GSettings *profile;
+
+ profile_uuid = terminal_profiles_list_dup_uuid (list, uuid, error);
+ if (profile_uuid == nullptr)
+ return nullptr;
+
+ profile = terminal_settings_list_ref_child (list, profile_uuid);
+ return profile;
+}
+
+/**
+ * terminal_profiles_list_get_profile_by_uuid:
+ * @list:
+ * @uuid: (allow-none):
+ * @error:
+ *
+ * Returns: (transfer full): the UUID of the profile specified by @uuid, or %nullptr
+ */
+char *
+terminal_profiles_list_dup_uuid_or_name (TerminalSettingsList *list,
+ const char *uuid_or_name,
+ GError **error)
+{
+ char **profiles, **profile_names;
+ char *rv;
+ guint n, i;
+
+ rv = terminal_profiles_list_dup_uuid (list, uuid_or_name, nullptr);
+ if (rv != nullptr)
+ return rv;
+
+ /* Not found as UUID; try finding a profile with this string as 'visible-name' */
+ get_profile_names (list, &profiles, &profile_names);
+ n = strv_contains (profile_names, uuid_or_name, &i);
+
+ if (n == 0) {
+ g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "No profile with UUID or name \"%s\" exists", uuid_or_name);
+ rv = nullptr;
+ } else if (n != 1) {
+ g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+ "No profile with UUID \"%s\" found and name is ambiguous", uuid_or_name);
+ rv = nullptr;
+ } else {
+ rv = g_strdup (profiles[i]);
+ }
+
+ g_strfreev (profiles);
+ g_strfreev (profile_names);
+
+ return rv;
+}
+
+/**
+ * terminal_profiles_list_ref_profile_by_uuid_or_name:
+ * @list:
+ * @uuid:
+ * @error:
+ *
+ * Returns: (transfer full): the profile #GSettings specified by @uuid, or %nullptr
+ */
+GSettings *
+terminal_profiles_list_ref_profile_by_uuid_or_name (TerminalSettingsList *list,
+ const char *uuid_or_name,
+ GError **error)
+{
+ gs_free char *uuid;
+ GSettings *profile;
+
+ uuid = terminal_profiles_list_dup_uuid_or_name (list, uuid_or_name, error);
+ if (uuid == nullptr)
+ return nullptr;
+
+ profile = terminal_settings_list_ref_child (list, uuid);
+ g_assert (profile != nullptr);
+ return profile;
+}
+
+int
+terminal_profiles_compare (gconstpointer pa,
+ gconstpointer pb)
+{
+ GSettings *a = (GSettings *) pa;
+ GSettings *b = (GSettings *) pb;
+ gs_free char *na = nullptr;
+ gs_free char *nb = nullptr;
+ gs_free char *patha = nullptr;
+ gs_free char *pathb = nullptr;
+ int result;
+
+ if (pa == pb)
+ return 0;
+ if (pa == nullptr)
+ return 1;
+ if (pb == nullptr)
+ return -1;
+
+ na = g_settings_get_string (a, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+ nb = g_settings_get_string (b, TERMINAL_PROFILE_VISIBLE_NAME_KEY);
+ result = g_utf8_collate (na, nb);
+ if (result != 0)
+ return result;
+
+ g_object_get (a, "path", &patha, nullptr);
+ g_object_get (b, "path", &pathb, nullptr);
+ return strcmp (patha, pathb);
+}
diff --git a/src/terminal-profiles-list.hh b/src/terminal-profiles-list.hh
new file mode 100644
index 0000000..afd8da5
--- /dev/null
+++ b/src/terminal-profiles-list.hh
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2013 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/>.
+ */
+
+#ifndef TERMINAL_PROFILES_LIST_H
+#define TERMINAL_PROFILES_LIST_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "terminal-settings-list.hh"
+
+G_BEGIN_DECLS
+
+TerminalSettingsList *terminal_profiles_list_new(GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source);
+
+GList *terminal_profiles_list_ref_children_sorted (TerminalSettingsList *list);
+
+char *terminal_profiles_list_dup_uuid (TerminalSettingsList *list,
+ const char *uuid,
+ GError **error);
+
+GSettings *terminal_profiles_list_ref_profile_by_uuid (TerminalSettingsList *list,
+ const char *uuid,
+ GError **error);
+
+char *terminal_profiles_list_dup_uuid_or_name (TerminalSettingsList *list,
+ const char *uuid_or_name,
+ GError **error);
+
+GSettings *terminal_profiles_list_ref_profile_by_uuid_or_name (TerminalSettingsList *list,
+ const char *uuid_or_name,
+ GError **error);
+
+int terminal_profiles_compare (gconstpointer pa,
+ gconstpointer pb);
+
+G_END_DECLS
+
+#endif /* TERMINAL_PROFILES_LIST_H */
diff --git a/src/terminal-regex.cc b/src/terminal-regex.cc
new file mode 100644
index 0000000..3856c64
--- /dev/null
+++ b/src/terminal-regex.cc
@@ -0,0 +1,390 @@
+/*
+ * Copyright © 2015 Egmont Koblinger
+ *
+ * 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 <glib.h>
+#include <stdio.h>
+
+#include "terminal-regex.hh"
+
+#ifdef TERMINAL_REGEX_MAIN
+
+/* Shorthand for expecting the pattern to match the entire input string */
+#define ENTIRE ((char *) 1)
+
+static char*
+get_match (const char *pattern, const char *string, GRegexMatchFlags match_flags)
+{
+ GRegex *regex;
+ GMatchInfo *match_info;
+ gchar *match;
+
+ regex = g_regex_new (pattern, GRegexCompileFlags(0), GRegexMatchFlags(0), nullptr);
+ g_regex_match (regex, string, match_flags, &match_info);
+ match = g_match_info_fetch (match_info, 0);
+
+ g_free (regex);
+ g_free (match_info);
+ return match;
+}
+
+/* Macros rather than functions to report useful line numbers on failure. */
+#define assert_match(__pattern, __string, __expected) do { \
+ gchar *__actual_match = get_match(__pattern, __string, GRegexMatchFlags(0)); \
+ const gchar *__expected_match = __expected; \
+ if (__expected_match == ENTIRE) __expected_match = __string; \
+ g_assert_cmpstr(__actual_match, ==, __expected_match); \
+ g_free (__actual_match); \
+} while (0)
+
+#define assert_match_anchored(__pattern, __string, __expected) do { \
+ gchar *__actual_match = get_match(__pattern, __string, G_REGEX_MATCH_ANCHORED); \
+ const gchar *__expected_match = __expected; \
+ if (__expected_match == ENTIRE) __expected_match = __string; \
+ g_assert_cmpstr(__actual_match, ==, __expected_match); \
+ g_free (__actual_match); \
+} while (0)
+
+int
+main (int argc, char **argv)
+{
+ /* SCHEME is case insensitive */
+ assert_match_anchored (SCHEME, "http", ENTIRE);
+ assert_match_anchored (SCHEME, "HTTPS", ENTIRE);
+
+ /* USER is nonempty, alphanumeric, dot, plus and dash */
+ assert_match_anchored (USER, "", nullptr);
+ assert_match_anchored (USER, "dr.john-smith", ENTIRE);
+ assert_match_anchored (USER, "abc+def@ghi", "abc+def");
+
+ /* PASS is optional colon-prefixed value, allowing quite some characters, but definitely not @ */
+ assert_match_anchored (PASS, "", ENTIRE);
+ assert_match_anchored (PASS, "nocolon", "");
+ assert_match_anchored (PASS, ":s3cr3T", ENTIRE);
+ assert_match_anchored (PASS, ":$?#@host", ":$?#");
+
+ /* Hostname of at least 1 component, containing at least one non-digit in at least one of the segments */
+ assert_match_anchored (HOSTNAME1, "example.com", ENTIRE);
+ assert_match_anchored (HOSTNAME1, "a-b.c-d", ENTIRE);
+ assert_match_anchored (HOSTNAME1, "a_b", "a"); /* TODO: can/should we totally abort here? */
+ assert_match_anchored (HOSTNAME1, "déjà-vu.com", ENTIRE);
+ assert_match_anchored (HOSTNAME1, "➡.ws", ENTIRE);
+ assert_match_anchored (HOSTNAME1, "cömbining-áccents", ENTIRE);
+ assert_match_anchored (HOSTNAME1, "12", nullptr);
+ assert_match_anchored (HOSTNAME1, "12.34", nullptr);
+ assert_match_anchored (HOSTNAME1, "12.ab", ENTIRE);
+// assert_match_anchored (HOSTNAME1, "ab.12", nullptr); /* errr... could we fail here?? */
+
+ /* Hostname of at least 2 components, containing at least one non-digit in at least one of the segments */
+ assert_match_anchored (HOSTNAME2, "example.com", ENTIRE);
+ assert_match_anchored (HOSTNAME2, "example", nullptr);
+ assert_match_anchored (HOSTNAME2, "12", nullptr);
+ assert_match_anchored (HOSTNAME2, "12.34", nullptr);
+ assert_match_anchored (HOSTNAME2, "12.ab", ENTIRE);
+ assert_match_anchored (HOSTNAME2, "ab.12", nullptr);
+// assert_match_anchored (HOSTNAME2, "ab.cd.12", nullptr); /* errr... could we fail here?? */
+
+ /* IPv4 segment (number between 0 and 255) */
+ assert_match_anchored (DEFS "(?&S4)", "0", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "1", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "9", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "10", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "99", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "100", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "200", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "250", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "255", ENTIRE);
+ assert_match_anchored (DEFS "(?&S4)", "256", nullptr);
+ assert_match_anchored (DEFS "(?&S4)", "260", nullptr);
+ assert_match_anchored (DEFS "(?&S4)", "300", nullptr);
+ assert_match_anchored (DEFS "(?&S4)", "1000", nullptr);
+ assert_match_anchored (DEFS "(?&S4)", "", nullptr);
+ assert_match_anchored (DEFS "(?&S4)", "a1b", nullptr);
+
+ /* IPv4 addresses */
+ assert_match_anchored (DEFS "(?&IPV4)", "11.22.33.44", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV4)", "0.1.254.255", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV4)", "75.150.225.300", nullptr);
+ assert_match_anchored (DEFS "(?&IPV4)", "1.2.3.4.5", "1.2.3.4"); /* we could also bail out and not match at all */
+
+ /* IPv6 addresses */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:::22", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22::33:44::55:66", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "dead::beef", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV6)", "faded::bee", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "live::pork", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "::1", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV6)", "11::22:33::44", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:::33", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "dead:beef::192.168.1.1", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV6)", "192.168.1.1", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77:87654", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22::33:45678", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:192.168.1.12345", nullptr);
+
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77", nullptr); /* no :: */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77:88", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77:88:99", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "::11:22:33:44:55:66:77", ENTIRE); /* :: at the start */
+ assert_match_anchored (DEFS "(?&IPV6)", "::11:22:33:44:55:66:77:88", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33::44:55:66:77", ENTIRE); /* :: in the middle */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33::44:55:66:77:88", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77::", ENTIRE); /* :: at the end */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77:88::", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "::", ENTIRE); /* :: only */
+
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:192.168.1.1", nullptr); /* no :: */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:192.168.1.1", ENTIRE);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66:77:192.168.1.1", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "::11:22:33:44:55:192.168.1.1", ENTIRE); /* :: at the start */
+ assert_match_anchored (DEFS "(?&IPV6)", "::11:22:33:44:55:66:192.168.1.1", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33::44:55:192.168.1.1", ENTIRE); /* :: in the imddle */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33::44:55:66:192.168.1.1", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55::192.168.1.1", ENTIRE); /* :: at the end(ish) */
+ assert_match_anchored (DEFS "(?&IPV6)", "11:22:33:44:55:66::192.168.1.1", nullptr);
+ assert_match_anchored (DEFS "(?&IPV6)", "::192.168.1.1", ENTIRE); /* :: only(ish) */
+
+ /* URL_HOST is either a hostname, or an IPv4 address, or a bracket-enclosed IPv6 address */
+ assert_match_anchored (DEFS URL_HOST, "example", ENTIRE);
+ assert_match_anchored (DEFS URL_HOST, "example.com", ENTIRE);
+ assert_match_anchored (DEFS URL_HOST, "11.22.33.44", ENTIRE);
+ assert_match_anchored (DEFS URL_HOST, "[11.22.33.44]", nullptr);
+ assert_match_anchored (DEFS URL_HOST, "dead::be:ef", "dead"); /* TODO: can/should we totally abort here? */
+ assert_match_anchored (DEFS URL_HOST, "[dead::be:ef]", ENTIRE);
+
+ /* EMAIL_HOST is either an at least two-component hostname, or a bracket-enclosed IPv[46] address */
+ assert_match_anchored (DEFS EMAIL_HOST, "example", nullptr);
+ assert_match_anchored (DEFS EMAIL_HOST, "example.com", ENTIRE);
+ assert_match_anchored (DEFS EMAIL_HOST, "11.22.33.44", nullptr);
+ assert_match_anchored (DEFS EMAIL_HOST, "[11.22.33.44]", ENTIRE);
+ assert_match_anchored (DEFS EMAIL_HOST, "[11.22.33.456]", nullptr);
+ assert_match_anchored (DEFS EMAIL_HOST, "dead::be:ef", nullptr);
+ assert_match_anchored (DEFS EMAIL_HOST, "[dead::be:ef]", ENTIRE);
+
+ /* Number between 1 and 65535 (helper for port) */
+ assert_match_anchored (N_1_65535, "0", nullptr);
+ assert_match_anchored (N_1_65535, "1", ENTIRE);
+ assert_match_anchored (N_1_65535, "10", ENTIRE);
+ assert_match_anchored (N_1_65535, "100", ENTIRE);
+ assert_match_anchored (N_1_65535, "1000", ENTIRE);
+ assert_match_anchored (N_1_65535, "10000", ENTIRE);
+ assert_match_anchored (N_1_65535, "60000", ENTIRE);
+ assert_match_anchored (N_1_65535, "65000", ENTIRE);
+ assert_match_anchored (N_1_65535, "65500", ENTIRE);
+ assert_match_anchored (N_1_65535, "65530", ENTIRE);
+ assert_match_anchored (N_1_65535, "65535", ENTIRE);
+ assert_match_anchored (N_1_65535, "65536", nullptr);
+ assert_match_anchored (N_1_65535, "65540", nullptr);
+ assert_match_anchored (N_1_65535, "65600", nullptr);
+ assert_match_anchored (N_1_65535, "66000", nullptr);
+ assert_match_anchored (N_1_65535, "70000", nullptr);
+ assert_match_anchored (N_1_65535, "100000", nullptr);
+ assert_match_anchored (N_1_65535, "", nullptr);
+ assert_match_anchored (N_1_65535, "a1b", nullptr);
+
+ /* PORT is an optional colon-prefixed value */
+ assert_match_anchored (PORT, "", ENTIRE);
+ assert_match_anchored (PORT, ":1", ENTIRE);
+ assert_match_anchored (PORT, ":65535", ENTIRE);
+ assert_match_anchored (PORT, ":65536", ""); /* TODO: can/should we totally abort here? */
+
+ /* Parentheses are only allowed in matching pairs, see bug 763980. */
+ /* TODO: add tests for PATHCHARS and PATHNONTERM; and/or URLPATH */
+ assert_match_anchored (DEFS URLPATH, "/ab/cd", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/ab/cd.html.", "/ab/cd.html");
+ assert_match_anchored (DEFS URLPATH, "/The_Offspring_(album)", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/The_Offspring)", "/The_Offspring");
+ assert_match_anchored (DEFS URLPATH, "/a((b(c)d)e(f))", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/a((b(c)d)e(f)))", "/a((b(c)d)e(f))");
+ assert_match_anchored (DEFS URLPATH, "/a(b).(c).", "/a(b).(c)");
+ assert_match_anchored (DEFS URLPATH, "/a.(b.(c.).).(d.(e.).).)", "/a.(b.(c.).).(d.(e.).)");
+ assert_match_anchored (DEFS URLPATH, "/a)b(c", "/a");
+ assert_match_anchored (DEFS URLPATH, "/.", "/");
+ assert_match_anchored (DEFS URLPATH, "/(.", "/");
+ assert_match_anchored (DEFS URLPATH, "/).", "/");
+ assert_match_anchored (DEFS URLPATH, "/().", "/()");
+ assert_match_anchored (DEFS URLPATH, "/", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "?", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "?param=value", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "#", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "#anchor", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/php?param[]=value1&param[]=value2", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/foo?param1[index1]=value1&param2[index2]=value2", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/[[[]][]]", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/[([])]([()])", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/([()])[([])]", ENTIRE);
+ assert_match_anchored (DEFS URLPATH, "/[(])", "/");
+ assert_match_anchored (DEFS URLPATH, "/([)]", "/");
+
+
+ /* Put the components together and test the big picture */
+
+ assert_match (REGEX_URL_AS_IS, "There's no URL here http:/foo", nullptr);
+ assert_match (REGEX_URL_AS_IS, "Visit http://example.com for details", "http://example.com");
+ assert_match (REGEX_URL_AS_IS, "Trailing dot http://foo/bar.html.", "http://foo/bar.html");
+ assert_match (REGEX_URL_AS_IS, "Trailing ellipsis http://foo/bar.html...", "http://foo/bar.html");
+ assert_match (REGEX_URL_AS_IS, "Trailing comma http://foo/bar,baz,", "http://foo/bar,baz");
+ assert_match (REGEX_URL_AS_IS, "Trailing semicolon http://foo/bar;baz;", "http://foo/bar;baz");
+ assert_match (REGEX_URL_AS_IS, "See <http://foo/bar>", "http://foo/bar");
+ assert_match (REGEX_URL_AS_IS, "<http://foo.bar/asdf.qwer.html>", "http://foo.bar/asdf.qwer.html");
+ assert_match (REGEX_URL_AS_IS, "Go to http://192.168.1.1.", "http://192.168.1.1");
+ assert_match (REGEX_URL_AS_IS, "If not, see <http://www.gnu.org/licenses/>.", "http://www.gnu.org/licenses/");
+ assert_match (REGEX_URL_AS_IS, "<a href=\"http://foo/bar\">foo</a>", "http://foo/bar");
+ assert_match (REGEX_URL_AS_IS, "<a href='http://foo/bar'>foo</a>", "http://foo/bar");
+ assert_match (REGEX_URL_AS_IS, "<url>http://foo/bar</url>", "http://foo/bar");
+
+ assert_match (REGEX_URL_AS_IS, "http://", nullptr);
+ assert_match (REGEX_URL_AS_IS, "http://a", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://aa.", "http://aa");
+ assert_match (REGEX_URL_AS_IS, "http://aa.b", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://aa.bb", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://aa.bb/c", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://aa.bb/cc", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://aa.bb/cc/", ENTIRE);
+
+ assert_match (REGEX_URL_AS_IS, "HtTp://déjà-vu.com:10000/déjà/vu", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "HTTP://joe:sEcReT@➡.ws:1080", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "https://cömbining-áccents", ENTIRE);
+
+ assert_match (REGEX_URL_AS_IS, "http://111.222.33.44", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://111.222.33.44/", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://111.222.33.44/foo", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://1.2.3.4:5555/xyz", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "https://[dead::beef]:12345/ipv6", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "https://[dead::beef:11.22.33.44]", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://1.2.3.4:", "http://1.2.3.4"); /* TODO: can/should we totally abort here? */
+ assert_match (REGEX_URL_AS_IS, "https://dead::beef/no-brackets-ipv6", "https://dead"); /* ditto */
+ assert_match (REGEX_URL_AS_IS, "http://111.222.333.444/", nullptr);
+ assert_match (REGEX_URL_AS_IS, "http://1.2.3.4:70000", "http://1.2.3.4"); /* TODO: can/should we totally abort here? */
+ assert_match (REGEX_URL_AS_IS, "http://[dead::beef:111.222.333.444]", nullptr);
+
+ /* '?' or '#' without '/', #7888 */
+ assert_match (REGEX_URL_AS_IS, "http://foo.bar?", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://foo.bar?param=value", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://foo.bar:12345?param=value", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://1.2.3.4?param=value", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://[dead::beef]?param=value", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://foo.bar#", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://foo.bar#anchor", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://foo.bar:12345#anchor", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://1.2.3.4#anchor", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://[dead::beef]#anchor", ENTIRE);
+
+ /* Username, password */
+ assert_match (REGEX_URL_AS_IS, "http://joe@example.com", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://user.name:sec.ret@host.name", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://joe:secret@[::1]", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://dudewithnopassword:@example.com", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://safeguy:!#$%^&*@host", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http://invalidusername!@host", "http://invalidusername");
+
+ assert_match (REGEX_URL_AS_IS, "http://ab.cd/ef?g=h&i=j|k=l#m=n:o=p", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "http:///foo", nullptr);
+
+ /* Parentheses are only allowed in matching pairs, see bug 763980. */
+ assert_match (REGEX_URL_AS_IS, "https://en.wikipedia.org/wiki/The_Offspring_(album)", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "[markdown](https://en.wikipedia.org/wiki/The_Offspring)", "https://en.wikipedia.org/wiki/The_Offspring");
+ assert_match (REGEX_URL_AS_IS, "[markdown](https://en.wikipedia.org/wiki/The_Offspring_(album))", "https://en.wikipedia.org/wiki/The_Offspring_(album)");
+ assert_match (REGEX_URL_AS_IS, "[markdown](http://foo.bar/(a(b)c)d)e)f", "http://foo.bar/(a(b)c)d");
+ assert_match (REGEX_URL_AS_IS, "[markdown](http://foo.bar/a)b(c", "http://foo.bar/a");
+
+ /* Apostrophes are allowed, except at trailing position if the URL is preceded by an apostrophe, see bug 448044. */
+ assert_match (REGEX_URL_AS_IS, "https://en.wikipedia.org/wiki/Moore's_law", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "<a href=\"https://en.wikipedia.org/wiki/Moore's_law\">", "https://en.wikipedia.org/wiki/Moore's_law");
+ assert_match (REGEX_URL_AS_IS, "https://en.wikipedia.org/wiki/Cryin'", ENTIRE);
+ assert_match (REGEX_URL_AS_IS, "<a href=\"https://en.wikipedia.org/wiki/Cryin'\">", "https://en.wikipedia.org/wiki/Cryin'");
+ assert_match (REGEX_URL_AS_IS, "<a href='https://en.wikipedia.org/wiki/Aerosmith'>", "https://en.wikipedia.org/wiki/Aerosmith");
+
+ /* No scheme */
+ assert_match (REGEX_URL_HTTP, "www.foo.bar/baz", ENTIRE);
+ assert_match (REGEX_URL_HTTP, "WWW3.foo.bar/baz", ENTIRE);
+ assert_match (REGEX_URL_HTTP, "FTP.FOO.BAR/BAZ", ENTIRE); /* FIXME if no scheme is given and url starts with ftp, can we make the protocol ftp instead of http? */
+ assert_match (REGEX_URL_HTTP, "ftpxy.foo.bar/baz", ENTIRE);
+// assert_match (REGEX_URL_HTTP, "ftp.123/baz", nullptr); /* errr... could we fail here?? */
+ assert_match (REGEX_URL_HTTP, "foo.bar/baz", nullptr);
+ assert_match (REGEX_URL_HTTP, "abc.www.foo.bar/baz", nullptr);
+ assert_match (REGEX_URL_HTTP, "uvwww.foo.bar/baz", nullptr);
+ assert_match (REGEX_URL_HTTP, "xftp.foo.bar/baz", nullptr);
+
+ /* file:/ or file://(hostname)?/ */
+ assert_match (REGEX_URL_FILE, "file:", nullptr);
+ assert_match (REGEX_URL_FILE, "file:/", ENTIRE);
+ assert_match (REGEX_URL_FILE, "file://", nullptr);
+ assert_match (REGEX_URL_FILE, "file:///", ENTIRE);
+ assert_match (REGEX_URL_FILE, "file:////", nullptr);
+ assert_match (REGEX_URL_FILE, "file:etc/passwd", nullptr);
+ assert_match (REGEX_URL_FILE, "File:/etc/passwd", ENTIRE);
+ assert_match (REGEX_URL_FILE, "FILE:///etc/passwd", ENTIRE);
+ assert_match (REGEX_URL_FILE, "file:////etc/passwd", nullptr);
+ assert_match (REGEX_URL_FILE, "file://host.name", nullptr);
+ assert_match (REGEX_URL_FILE, "file://host.name/", ENTIRE);
+ assert_match (REGEX_URL_FILE, "file://host.name/etc", ENTIRE);
+
+ assert_match (REGEX_URL_FILE, "See file:/.", "file:/");
+ assert_match (REGEX_URL_FILE, "See file:///.", "file:///");
+ assert_match (REGEX_URL_FILE, "See file:/lost+found.", "file:/lost+found");
+ assert_match (REGEX_URL_FILE, "See file:///lost+found.", "file:///lost+found");
+
+ /* Email */
+ assert_match (REGEX_EMAIL, "Write to foo@bar.com.", "foo@bar.com");
+ assert_match (REGEX_EMAIL, "Write to <foo@bar.com>", "foo@bar.com");
+ assert_match (REGEX_EMAIL, "Write to mailto:foo@bar.com.", "mailto:foo@bar.com");
+ assert_match (REGEX_EMAIL, "Write to MAILTO:FOO@BAR.COM.", "MAILTO:FOO@BAR.COM");
+ assert_match (REGEX_EMAIL, "Write to foo@[1.2.3.4]", "foo@[1.2.3.4]");
+ assert_match (REGEX_EMAIL, "Write to foo@[1.2.3.456]", nullptr);
+ assert_match (REGEX_EMAIL, "Write to foo@[1::2345]", "foo@[1::2345]");
+ assert_match (REGEX_EMAIL, "Write to foo@[dead::beef]", "foo@[dead::beef]");
+ assert_match (REGEX_EMAIL, "Write to foo@1.2.3.4", nullptr);
+ assert_match (REGEX_EMAIL, "Write to foo@1.2.3.456", nullptr);
+ assert_match (REGEX_EMAIL, "Write to foo@1::2345", nullptr);
+ assert_match (REGEX_EMAIL, "Write to foo@dead::beef", nullptr);
+ assert_match (REGEX_EMAIL, "<baz email=\"foo@bar.com\"/>", "foo@bar.com");
+ assert_match (REGEX_EMAIL, "<baz email='foo@bar.com'/>", "foo@bar.com");
+ assert_match (REGEX_EMAIL, "<email>foo@bar.com</email>", "foo@bar.com");
+
+ /* Sip, examples from rfc 3261 */
+ assert_match (REGEX_URL_VOIP, "sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sip:alice@atlanta.com", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sip:alice:secretword@atlanta.com;transport=tcp", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sips:alice@atlanta.com?subject=project%20x&priority=urgent", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sip:+1-212-555-1212:1234@gateway.com;user=phone", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sips:1212@gateway.com", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sip:alice@192.0.2.4", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "sip:atlanta.com;method=REGISTER?to=alice%40atlanta.com", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "SIP:alice;day=tuesday@atlanta.com", ENTIRE);
+ assert_match (REGEX_URL_VOIP, "Dial sip:alice@192.0.2.4.", "sip:alice@192.0.2.4");
+
+ /* Extremely long match, bug 770147 */
+ assert_match (REGEX_URL_AS_IS, "http://www.example.com/ThisPathConsistsOfMoreThan1024Characters"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", ENTIRE);
+
+ printf("terminal-regex tests passed :)\n");
+ return 0;
+}
+
+#endif /* TERMINAL_REGEX_MAIN */
diff --git a/src/terminal-regex.hh b/src/terminal-regex.hh
new file mode 100644
index 0000000..6fcc0f9
--- /dev/null
+++ b/src/terminal-regex.hh
@@ -0,0 +1,158 @@
+/*
+ * Copyright © 2015 Egmont Koblinger
+ *
+ * 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/>.
+ */
+
+/*
+ * Mini style-guide:
+ *
+ * #define'd fragments should preferably have an outermost group, for the
+ * exact same reason as why usually in C/C++ #define's the values are enclosed
+ * in parentheses: that is, so that you don't get surprised when you use the
+ * macro and append a quantifier.
+ *
+ * For repeated fragments prefer regex-style (?(DEFINE)(?<NAME>(...))) and use
+ * as (?&NAME), so that the regex string and the compiled regex object is
+ * smaller.
+ *
+ * Build small blocks, comment and unittest them heavily.
+ *
+ * Use free-spacing mode for improved readability. The hardest to read is
+ * which additional characters belong to a "(?" prefix. To improve
+ * readability, place a space after this, and for symmetry, before the closing
+ * parenthesis. Also place a space around "|" characters. No space before
+ * quantifiers. Try to be consistent with the existing style (yes I know the
+ * existing style is not consistent either, but please do your best).
+ *
+ * See http://www.rexegg.com/regex-disambiguation.html for all the "(?"
+ * syntaxes.
+ */
+
+#ifndef TERMINAL_REGEX_H
+#define TERMINAL_REGEX_H
+
+/* Lookbehind to see if there's a preceding apostrophe.
+ * Unlike the other *_DEF macros which define regex subroutines,
+ * this one is a named capture that defines APOS_START to either
+ * an apostrophe or the empty string, depending on the character
+ * preceding this APOS_START_DEF construct. */
+#define APOS_START_DEF "(?<APOS_START>(?<='))?"
+
+#define SCHEME "(?ix: news | telnet | nntp | https? | ftps? | sftp | webcal )"
+
+#define USERCHARS "-+.[:alnum:]"
+/* Nonempty username, e.g. "john.smith" */
+#define USER "[" USERCHARS "]+"
+
+#define PASSCHARS_CLASS "[-[:alnum:]\\Q,?;.:/!%$^*&~\"#'\\E]"
+/* Optional colon-prefixed password. I guess empty password should be allowed, right? E.g. ":secret", ":", "" */
+#define PASS "(?x: :" PASSCHARS_CLASS "* )?"
+
+/* Optional at-terminated username (with perhaps a password too), e.g. "joe@", "pete:secret@", "" */
+#define USERPASS "(?:" USER PASS "@)?"
+
+/* S4: IPv4 segment (number between 0 and 255) with lookahead at the end so that we don't match "25" in the string "256".
+ The lookahead could go to the last segment of IPv4 only but this construct allows nicer unittesting. */
+#define S4_DEF "(?(DEFINE)(?<S4>(?x: (?: [0-9] | [1-9][0-9] | 1[0-9]{2} | 2[0-4][0-9] | 25[0-5] ) (?! [0-9] ) )))"
+
+/* IPV4: Decimal IPv4, e.g. "1.2.3.4", with lookahead (implemented in S4) at the end so that we don't match "192.168.1.123" in the string "192.168.1.1234". */
+#define IPV4_DEF S4_DEF "(?(DEFINE)(?<IPV4>(?x: (?: (?&S4) \\. ){3} (?&S4) )))"
+
+/* IPv6, including embedded IPv4, e.g. "::1", "dead:beef::1.2.3.4".
+ * Lookahead for the next char not being a dot or digit, so it doesn't get stuck matching "dead:beef::1" in "dead:beef::1.2.3.4".
+ * This is not required since the surrounding brackets would trigger backtracking, but it allows nicer unittesting.
+ * TODO: more strict check (right number of colons, etc.)
+ * TODO: add zone_id: RFC 4007 section 11, RFC 6874 */
+
+/* S6: IPv6 segment, S6C: IPv6 segment followed by a comma, CS6: comma followed by an IPv6 segment */
+#define S6_DEF "(?(DEFINE)(?<S6>[[:xdigit:]]{1,4})(?<CS6>:(?&S6))(?<S6C>(?&S6):))"
+
+/* No :: shorthand */
+#define IPV6_FULL "(?x: (?&S6C){7} (?&S6) )"
+/* Begins with :: */
+#define IPV6_LEFT "(?x: : (?&CS6){1,7} )"
+/* :: somewhere in the middle - use negative lookahead to make sure there aren't too many colons in total */
+#define IPV6_MID "(?x: (?! (?: [[:xdigit:]]*: ){8} ) (?&S6C){1,6} (?&CS6){1,6} )"
+/* Ends with :: */
+#define IPV6_RIGHT "(?x: (?&S6C){1,7} : )"
+/* Is "::" and nothing more */
+#define IPV6_NULL "(?x: :: )"
+
+/* The same ones for IPv4-embedded notation, without the actual IPv4 part */
+#define IPV6V4_FULL "(?x: (?&S6C){6} )"
+#define IPV6V4_LEFT "(?x: :: (?&S6C){0,5} )" /* includes "::<ipv4>" */
+#define IPV6V4_MID "(?x: (?! (?: [[:xdigit:]]*: ){7} ) (?&S6C){1,4} (?&CS6){1,4} ) :"
+#define IPV6V4_RIGHT "(?x: (?&S6C){1,5} : )"
+
+/* IPV6: An IPv6 address (possibly with an embedded IPv4).
+ * This macro defines both IPV4 and IPV6, since the latter one requires the former. */
+#define IP_DEF IPV4_DEF S6_DEF "(?(DEFINE)(?<IPV6>(?x: (?: " IPV6_NULL " | " IPV6_LEFT " | " IPV6_MID " | " IPV6_RIGHT " | " IPV6_FULL " | (?: " IPV6V4_FULL " | " IPV6V4_LEFT " | " IPV6V4_MID " | " IPV6V4_RIGHT " ) (?&IPV4) ) (?! [.:[:xdigit:]] ) )))"
+
+/* Either an alphanumeric character or dash; or if [negative lookahead] not ASCII
+ * then any graphical Unicode character.
+ * A segment can consist entirely of numbers.
+ * (Note: PCRE doesn't support character class subtraction/intersection.) */
+#define HOSTNAMESEGMENTCHARS_CLASS "(?x: [-[:alnum:]] | (?! [[:ascii:]] ) [[:graph:]] )"
+
+/* A hostname of at least 1 component. The last component cannot be entirely numbers.
+ * E.g. "foo", "example.com", "1234.com", but not "foo.123" */
+#define HOSTNAME1 "(?x: (?: " HOSTNAMESEGMENTCHARS_CLASS "+ \\. )* " HOSTNAMESEGMENTCHARS_CLASS "* (?! [0-9] ) " HOSTNAMESEGMENTCHARS_CLASS "+ )"
+
+/* A hostname of at least 2 components. The last component cannot be entirely numbers.
+ * E.g. "example.com", "1234.com", but not "1234.56" */
+#define HOSTNAME2 "(?x: (?: " HOSTNAMESEGMENTCHARS_CLASS "+ \\.)+ " HOSTNAME1 " )"
+
+/* For URL: Hostname, IPv4, or bracket-enclosed IPv6, e.g. "example.com", "1.2.3.4", "[::1]" */
+#define URL_HOST "(?x: " HOSTNAME1 " | (?&IPV4) | \\[ (?&IPV6) \\] )"
+
+/* For e-mail: Hostname of at least two segments, or bracket-enclosed IPv4 or IPv6, e.g. "example.com", "[1.2.3.4]", "[::1]".
+ * Technically an e-mail with a single-component hostname might be valid on a local network, but let's avoid tons of false positives (e.g. in a typical shell prompt). */
+#define EMAIL_HOST "(?x: " HOSTNAME2 " | \\[ (?: (?&IPV4) | (?&IPV6) ) \\] )"
+
+/* Number between 1 and 65535, with lookahead at the end so that we don't match "6789" in the string "67890",
+ and in turn we don't eventually match "http://host:6789" in "http://host:67890". */
+#define N_1_65535 "(?x: (?: [1-9][0-9]{0,3} | [1-5][0-9]{4} | 6[0-4][0-9]{3} | 65[0-4][0-9]{2} | 655[0-2][0-9] | 6553[0-5] ) (?! [0-9] ) )"
+
+/* Optional colon-prefixed port, e.g. ":1080", "" */
+#define PORT "(?x: \\:" N_1_65535 " )?"
+
+/* Omit the parentheses, see below */
+#define PATHCHARS_CLASS "[-[:alnum:]\\Q_$.+!*,:;@&=?/~#|%'\\E]"
+/* Chars to end a URL. Apostrophe only allowed if there wasn't one in front of the URL, see bug 448044 */
+#define PATHTERM_CLASS "[-[:alnum:]\\Q_$+*:@&=/~#|%'\\E]"
+#define PATHTERM_NOAPOS_CLASS "[-[:alnum:]\\Q_$+*:@&=/~#|%\\E]"
+
+/* Recursive definition of PATH that allows parentheses and square brackets only if balanced, see bug 763980. */
+#define PATH_INNER_DEF "(?(DEFINE)(?<PATH_INNER>(?x: (?: " PATHCHARS_CLASS "* (?: \\( (?&PATH_INNER) \\) | \\[ (?&PATH_INNER) \\] ) )* " PATHCHARS_CLASS "* )))"
+/* Same as above, but the last character (if exists and is not a parenthesis) must be from PATHTERM_CLASS. */
+#define PATH_DEF "(?(DEFINE)(?<PATH>(?x: (?: " PATHCHARS_CLASS "* (?: \\( (?&PATH_INNER) \\) | \\[ (?&PATH_INNER) \\] ) )* (?: " PATHCHARS_CLASS "* (?(<APOS_START>)" PATHTERM_NOAPOS_CLASS "|" PATHTERM_CLASS ") )? )))"
+
+#define URLPATH "(?x: [/?#](?&PATH) )?"
+#define VOIP_PATH "(?x: [;?](?&PATH) )?"
+
+/* Now let's put these fragments together */
+
+#define DEFS APOS_START_DEF IP_DEF PATH_INNER_DEF PATH_DEF
+
+#define REGEX_URL_AS_IS DEFS SCHEME "://" USERPASS URL_HOST PORT URLPATH
+/* TODO: also support file:/etc/passwd */
+#define REGEX_URL_FILE DEFS "(?ix: file:/ (?: / (?: " HOSTNAME1 " )? / )? (?! / ) )(?&PATH)"
+/* Lookbehind so that we don't catch "abc.www.foo.bar", bug 739757. Lookahead for www/ftp for convenience (so that we can reuse HOSTNAME1). */
+#define REGEX_URL_HTTP DEFS "(?<!(?:" HOSTNAMESEGMENTCHARS_CLASS "|[.]))(?=(?i:www|ftp))" HOSTNAME1 PORT URLPATH
+#define REGEX_URL_VOIP DEFS "(?i:h323:|sips?:)" USERPASS URL_HOST PORT VOIP_PATH
+#define REGEX_EMAIL DEFS "(?i:mailto:)?" USER "@" EMAIL_HOST
+#define REGEX_NEWS_MAN "(?i:news:|man:|info:)[-[:alnum:]\\Q^_{|}~!\"#$%&'()*+,./;:=?`\\E]+"
+
+#endif /* !TERMINAL_REGEX_H */
diff --git a/src/terminal-schemas.hh b/src/terminal-schemas.hh
new file mode 100644
index 0000000..61f1305
--- /dev/null
+++ b/src/terminal-schemas.hh
@@ -0,0 +1,105 @@
+/*
+ * Copyright © 2008, 2010 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/>.
+ */
+
+#ifndef TERMINAL_SCHEMAS_H
+#define TERMINAL_SCHEMAS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_SCHEMA_VERSION (3u)
+
+#define TERMINAL_KEYBINDINGS_SCHEMA "org.gnome.Terminal.Legacy.Keybindings"
+#define TERMINAL_PROFILE_SCHEMA "org.gnome.Terminal.Legacy.Profile"
+#define TERMINAL_SETTING_SCHEMA "org.gnome.Terminal.Legacy.Settings"
+#define TERMINAL_SETTINGS_LIST_SCHEMA "org.gnome.Terminal.SettingsList"
+#define TERMINAL_PROFILES_LIST_SCHEMA "org.gnome.Terminal.ProfilesList"
+
+#define TERMINAL_KEYBINDINGS_SCHEMA_PATH "/org/gnome/terminal/legacy/keybindings/"
+
+#define TERMINAL_PROFILE_AUDIBLE_BELL_KEY "audible-bell"
+#define TERMINAL_PROFILE_BOLD_IS_BRIGHT_KEY "bold-is-bright"
+#define TERMINAL_PROFILE_BACKGROUND_COLOR_KEY "background-color"
+#define TERMINAL_PROFILE_BACKSPACE_BINDING_KEY "backspace-binding"
+#define TERMINAL_PROFILE_BOLD_COLOR_KEY "bold-color"
+#define TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY "bold-color-same-as-fg"
+#define TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY "cell-height-scale"
+#define TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY "cell-width-scale"
+#define TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY "cursor-colors-set"
+#define TERMINAL_PROFILE_CURSOR_BACKGROUND_COLOR_KEY "cursor-background-color"
+#define TERMINAL_PROFILE_CURSOR_FOREGROUND_COLOR_KEY "cursor-foreground-color"
+#define TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY "cjk-utf8-ambiguous-width"
+#define TERMINAL_PROFILE_CURSOR_BLINK_MODE_KEY "cursor-blink-mode"
+#define TERMINAL_PROFILE_CURSOR_SHAPE_KEY "cursor-shape"
+#define TERMINAL_PROFILE_CUSTOM_COMMAND_KEY "custom-command"
+#define TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS_KEY "default-size-columns"
+#define TERMINAL_PROFILE_DEFAULT_SIZE_ROWS_KEY "default-size-rows"
+#define TERMINAL_PROFILE_DELETE_BINDING_KEY "delete-binding"
+#define TERMINAL_PROFILE_ENABLE_BIDI_KEY "enable-bidi"
+#define TERMINAL_PROFILE_ENABLE_SHAPING_KEY "enable-shaping"
+#define TERMINAL_PROFILE_ENABLE_SIXEL_KEY "enable-sixel"
+#define TERMINAL_PROFILE_ENCODING_KEY "encoding"
+#define TERMINAL_PROFILE_EXIT_ACTION_KEY "exit-action"
+#define TERMINAL_PROFILE_FONT_KEY "font"
+#define TERMINAL_PROFILE_FOREGROUND_COLOR_KEY "foreground-color"
+#define TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY "highlight-colors-set"
+#define TERMINAL_PROFILE_HIGHLIGHT_BACKGROUND_COLOR_KEY "highlight-background-color"
+#define TERMINAL_PROFILE_HIGHLIGHT_FOREGROUND_COLOR_KEY "highlight-foreground-color"
+#define TERMINAL_PROFILE_LOGIN_SHELL_KEY "login-shell"
+#define TERMINAL_PROFILE_NAME_KEY "name"
+#define TERMINAL_PROFILE_PALETTE_KEY "palette"
+#define TERMINAL_PROFILE_PRESERVE_WORKING_DIRECTORY_KEY "preserve-working-directory"
+#define TERMINAL_PROFILE_REWRAP_ON_RESIZE_KEY "rewrap-on-resize"
+#define TERMINAL_PROFILE_SCROLLBACK_LINES_KEY "scrollback-lines"
+#define TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY "scrollback-unlimited"
+#define TERMINAL_PROFILE_SCROLLBAR_POLICY_KEY "scrollbar-policy"
+#define TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE_KEY "scroll-on-keystroke"
+#define TERMINAL_PROFILE_SCROLL_ON_OUTPUT_KEY "scroll-on-output"
+#define TERMINAL_PROFILE_TEXT_BLINK_MODE_KEY "text-blink-mode"
+#define TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY "use-custom-command"
+#define TERMINAL_PROFILE_USE_SKEY_KEY "use-skey"
+#define TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY "use-system-font"
+#define TERMINAL_PROFILE_USE_THEME_COLORS_KEY "use-theme-colors"
+#define TERMINAL_PROFILE_VISIBLE_NAME_KEY "visible-name"
+#define TERMINAL_PROFILE_WORD_CHAR_EXCEPTIONS_KEY "word-char-exceptions"
+
+#define TERMINAL_SETTING_CONFIRM_CLOSE_KEY "confirm-close"
+#define TERMINAL_SETTING_CONTEXT_INFO_KEY "context-info"
+#define TERMINAL_SETTING_DEFAULT_SHOW_MENUBAR_KEY "default-show-menubar"
+#define TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY "menu-accelerator-enabled"
+#define TERMINAL_SETTING_ENABLE_MNEMONICS_KEY "mnemonics-enabled"
+#define TERMINAL_SETTING_ENABLE_SHORTCUTS_KEY "shortcuts-enabled"
+#define TERMINAL_SETTING_HEADERBAR_KEY "headerbar"
+#define TERMINAL_SETTING_NEW_TERMINAL_MODE_KEY "new-terminal-mode"
+#define TERMINAL_SETTING_NEW_TAB_POSITION_KEY "new-tab-position"
+#define TERMINAL_SETTING_SCHEMA_VERSION "schema-version"
+#define TERMINAL_SETTING_SHELL_INTEGRATION_KEY "shell-integration-enabled"
+#define TERMINAL_SETTING_TAB_POLICY_KEY "tab-policy"
+#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"
+
+#define TERMINAL_PROFILES_PATH_PREFIX "/org/gnome/terminal/legacy/profiles:/"
+
+G_END_DECLS
+
+#endif /* TERMINAL_SCHEMAS_H */
diff --git a/src/terminal-screen-container.cc b/src/terminal-screen-container.cc
new file mode 100644
index 0000000..6d85190
--- /dev/null
+++ b/src/terminal-screen-container.cc
@@ -0,0 +1,392 @@
+/*
+ * Copyright © 2008, 2010, 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-screen-container.hh"
+#include "terminal-debug.hh"
+
+#if 0
+#define USE_SCROLLED_WINDOW
+#endif
+
+#include <gtk/gtk.h>
+
+#define TERMINAL_SCREEN_CONTAINER_GET_PRIVATE(screen_container)(G_TYPE_INSTANCE_GET_PRIVATE ((screen_container), TERMINAL_TYPE_SCREEN_CONTAINER, TerminalScreenContainerPrivate))
+
+struct _TerminalScreenContainerPrivate
+{
+ TerminalScreen *screen;
+#ifdef USE_SCROLLED_WINDOW
+ GtkWidget *scrolled_window;
+#else
+ GtkWidget *hbox;
+ GtkWidget *vscrollbar;
+#endif
+ GtkPolicyType hscrollbar_policy;
+ GtkPolicyType vscrollbar_policy;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SCREEN,
+ PROP_HSCROLLBAR_POLICY,
+ PROP_VSCROLLBAR_POLICY
+};
+
+G_DEFINE_TYPE (TerminalScreenContainer, terminal_screen_container, GTK_TYPE_OVERLAY)
+
+#define TERMINAL_SCREEN_CONTAINER_CSS_NAME "terminal-screen-container"
+
+/* helper functions */
+
+/* Widget class implementation */
+
+static void
+terminal_screen_container_realize (GtkWidget *widget)
+{
+
+ GTK_WIDGET_CLASS (terminal_screen_container_parent_class)->realize (widget);
+
+ /* We need to realize the screen itself too, see issue #203 */
+ TerminalScreenContainer *container = TERMINAL_SCREEN_CONTAINER (widget);
+ TerminalScreenContainerPrivate *priv = container->priv;
+ gtk_widget_realize (GTK_WIDGET (priv->screen));
+}
+
+#ifndef USE_SCROLLED_WINDOW
+
+static void
+terminal_screen_container_style_updated (GtkWidget *widget)
+{
+ TerminalScreenContainer *container = TERMINAL_SCREEN_CONTAINER (widget);
+ TerminalScreenContainerPrivate *priv = container->priv;
+ GtkCornerType corner;
+ gboolean set;
+
+ GTK_WIDGET_CLASS (terminal_screen_container_parent_class)->style_updated (widget);
+
+ gtk_widget_style_get (widget,
+ "window-placement", &corner,
+ "window-placement-set", &set,
+ nullptr);
+
+ if (!set) {
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-scrolled-window-placement", &corner,
+ nullptr);
+ }
+
+ switch (corner) {
+ case GTK_CORNER_TOP_LEFT:
+ case GTK_CORNER_BOTTOM_LEFT:
+ gtk_box_reorder_child (GTK_BOX (priv->hbox), priv->vscrollbar, -1);
+ break;
+ case GTK_CORNER_TOP_RIGHT:
+ case GTK_CORNER_BOTTOM_RIGHT:
+ gtk_box_reorder_child (GTK_BOX (priv->hbox), priv->vscrollbar, 0);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+#endif /* !USE_SCROLLED_WINDOW */
+
+/* Class implementation */
+
+static void
+terminal_screen_container_init (TerminalScreenContainer *container)
+{
+ TerminalScreenContainerPrivate *priv;
+
+ priv = container->priv = TERMINAL_SCREEN_CONTAINER_GET_PRIVATE (container);
+
+ priv->hscrollbar_policy = GTK_POLICY_AUTOMATIC;
+ priv->vscrollbar_policy = GTK_POLICY_AUTOMATIC;
+}
+
+static void
+terminal_screen_container_constructed (GObject *object)
+{
+ TerminalScreenContainer *container = TERMINAL_SCREEN_CONTAINER (object);
+ TerminalScreenContainerPrivate *priv = container->priv;
+
+ G_OBJECT_CLASS (terminal_screen_container_parent_class)->constructed (object);
+
+ g_assert (priv->screen != nullptr);
+
+#ifdef USE_SCROLLED_WINDOW
+{
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+
+ hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (priv->screen));
+ vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->screen));
+
+ priv->scrolled_window = gtk_scrolled_window_new (hadjustment, vadjustment);
+ gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (priv->scrolled_window), FALSE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+ priv->hscrollbar_policy,
+ priv->vscrollbar_policy);
+ gtk_container_add (GTK_CONTAINER (priv->scrolled_window), GTK_WIDGET (priv->screen));
+
+ gtk_container_add (GTK_CONTAINER (container), priv->scrolled_window);
+ gtk_widget_show_all (priv->scrolled_window);
+}
+#else
+ priv->hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+
+ priv->vscrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL,
+ gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (priv->screen)));
+
+ gtk_box_pack_start (GTK_BOX (priv->hbox), GTK_WIDGET (priv->screen), TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (priv->hbox), priv->vscrollbar, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (container), priv->hbox);
+ gtk_widget_show_all (priv->hbox);
+#endif
+
+ _terminal_screen_update_scrollbar (priv->screen);
+}
+
+static void
+terminal_screen_container_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalScreenContainer *container = TERMINAL_SCREEN_CONTAINER (object);
+ TerminalScreenContainerPrivate *priv = container->priv;
+
+ switch (prop_id) {
+ case PROP_SCREEN:
+ break;
+ case PROP_HSCROLLBAR_POLICY:
+ g_value_set_enum (value, priv->hscrollbar_policy);
+ break;
+ case PROP_VSCROLLBAR_POLICY:
+ g_value_set_enum (value, priv->vscrollbar_policy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_screen_container_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalScreenContainer *container = TERMINAL_SCREEN_CONTAINER (object);
+ TerminalScreenContainerPrivate *priv = container->priv;
+
+ switch (prop_id) {
+ case PROP_SCREEN:
+ priv->screen = (TerminalScreen*)g_value_get_object (value);
+ break;
+ case PROP_HSCROLLBAR_POLICY:
+ terminal_screen_container_set_policy (container,
+ GtkPolicyType(g_value_get_enum (value)),
+ priv->vscrollbar_policy);
+ break;
+ case PROP_VSCROLLBAR_POLICY:
+ terminal_screen_container_set_policy (container,
+ priv->hscrollbar_policy,
+ GtkPolicyType(g_value_get_enum (value)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_screen_container_class_init (TerminalScreenContainerClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (gobject_class, sizeof (TerminalScreenContainerPrivate));
+
+ gobject_class->constructed = terminal_screen_container_constructed;
+ gobject_class->get_property = terminal_screen_container_get_property;
+ gobject_class->set_property = terminal_screen_container_set_property;
+
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ widget_class->realize = terminal_screen_container_realize;
+
+#ifndef USE_SCROLLED_WINDOW
+ widget_class->style_updated = terminal_screen_container_style_updated;
+
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_enum ("window-placement", nullptr, nullptr,
+ GTK_TYPE_CORNER_TYPE,
+ GTK_CORNER_BOTTOM_RIGHT,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS)));
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_boolean ("window-placement-set", nullptr, nullptr,
+ FALSE,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS)));
+#endif
+
+ gtk_widget_class_set_css_name(widget_class, TERMINAL_SCREEN_CONTAINER_CSS_NAME);
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SCREEN,
+ g_param_spec_object ("screen", nullptr, nullptr,
+ TERMINAL_TYPE_SCREEN,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS)));
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_HSCROLLBAR_POLICY,
+ g_param_spec_enum ("hscrollbar-policy", nullptr, nullptr,
+ GTK_TYPE_POLICY_TYPE,
+ GTK_POLICY_AUTOMATIC,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_VSCROLLBAR_POLICY,
+ g_param_spec_enum ("vscrollbar-policy", nullptr, nullptr,
+ GTK_TYPE_POLICY_TYPE,
+ GTK_POLICY_AUTOMATIC,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS)));
+}
+
+/* public API */
+
+/**
+ * terminal_screen_container_new:
+ * @screen: a #TerminalScreen
+ *
+ * Returns: a new #TerminalScreenContainer for @screen
+ */
+GtkWidget *
+terminal_screen_container_new (TerminalScreen *screen)
+{
+ return reinterpret_cast<GtkWidget*>
+ (g_object_new (TERMINAL_TYPE_SCREEN_CONTAINER,
+ "screen", screen,
+ nullptr));
+}
+
+/**
+ * terminal_screen_container_get_screen:
+ * @container: a #TerminalScreenContainer
+ *
+ * Returns: @container's #TerminalScreen
+ */
+TerminalScreen *
+terminal_screen_container_get_screen (TerminalScreenContainer *container)
+{
+ if (container == nullptr)
+ return nullptr;
+
+ g_return_val_if_fail (TERMINAL_IS_SCREEN_CONTAINER (container), nullptr);
+
+ return container->priv->screen;
+}
+
+/**
+ * terminal_screen_container_get_from_screen:
+ * @screen: a #TerminalScreenContainerPrivate
+ *
+ * Returns the #TerminalScreenContainer containing @screen.
+ */
+TerminalScreenContainer *
+terminal_screen_container_get_from_screen (TerminalScreen *screen)
+{
+ if (screen == nullptr)
+ return nullptr;
+
+ g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), nullptr);
+
+ return TERMINAL_SCREEN_CONTAINER (gtk_widget_get_ancestor (GTK_WIDGET (screen), TERMINAL_TYPE_SCREEN_CONTAINER));
+}
+
+/**
+ * terminal_screen_container_set_policy:
+ * @container: a #TerminalScreenContainer
+ * @hpolicy: a #GtkPolicyType
+ * @vpolicy: a #GtkPolicyType
+ *
+ * Sets @container's scrollbar policy.
+ */
+void
+terminal_screen_container_set_policy (TerminalScreenContainer *container,
+ GtkPolicyType hpolicy,
+ GtkPolicyType vpolicy)
+{
+ TerminalScreenContainerPrivate *priv;
+ GObject *object;
+
+ g_return_if_fail (TERMINAL_IS_SCREEN_CONTAINER (container));
+
+ object = G_OBJECT (container);
+ priv = container->priv;
+
+ g_object_freeze_notify (object);
+
+ if (priv->hscrollbar_policy != hpolicy) {
+ priv->hscrollbar_policy = hpolicy;
+ g_object_notify (object, "hscrollbar-policy");
+ }
+ if (priv->vscrollbar_policy != vpolicy) {
+ priv->vscrollbar_policy = vpolicy;
+ g_object_notify (object, "vscrollbar-policy");
+ }
+
+#ifdef USE_SCROLLED_WINDOW
+ switch (vpolicy) {
+ case GTK_POLICY_NEVER:
+ vpolicy = GTK_POLICY_EXTERNAL;
+ break;
+ case GTK_POLICY_AUTOMATIC:
+ case GTK_POLICY_ALWAYS:
+ vpolicy = GTK_POLICY_ALWAYS;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), hpolicy, vpolicy);
+#else
+ switch (vpolicy) {
+ case GTK_POLICY_NEVER:
+ gtk_widget_hide (priv->vscrollbar);
+ break;
+ case GTK_POLICY_AUTOMATIC:
+ case GTK_POLICY_ALWAYS:
+ gtk_widget_show (priv->vscrollbar);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+#endif /* USE_SCROLLED_WINDOW */
+
+ g_object_thaw_notify (object);
+}
diff --git a/src/terminal-screen-container.hh b/src/terminal-screen-container.hh
new file mode 100644
index 0000000..6d30e5c
--- /dev/null
+++ b/src/terminal-screen-container.hh
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2008, 2010 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/>.
+ */
+
+#ifndef TERMINAL_SCREEN_CONTAINER_H
+#define TERMINAL_SCREEN_CONTAINER_H
+
+#include <gtk/gtk.h>
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SCREEN_CONTAINER (terminal_screen_container_get_type ())
+#define TERMINAL_SCREEN_CONTAINER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_SCREEN_CONTAINER, TerminalScreenContainer))
+#define TERMINAL_SCREEN_CONTAINER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_SCREEN_CONTAINER, TerminalScreenContainerClass))
+#define TERMINAL_IS_SCREEN_CONTAINER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_SCREEN_CONTAINER))
+#define TERMINAL_IS_SCREEN_CONTAINER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_SCREEN_CONTAINER))
+#define TERMINAL_SCREEN_CONTAINER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_SCREEN_CONTAINER, TerminalScreenContainerClass))
+
+typedef struct _TerminalScreenContainer TerminalScreenContainer;
+typedef struct _TerminalScreenContainerClass TerminalScreenContainerClass;
+typedef struct _TerminalScreenContainerPrivate TerminalScreenContainerPrivate;
+
+struct _TerminalScreenContainer
+{
+ GtkOverlay parent_instance;
+
+ /*< private >*/
+ TerminalScreenContainerPrivate *priv;
+};
+
+struct _TerminalScreenContainerClass
+{
+ GtkOverlayClass parent_class;
+};
+
+GType terminal_screen_container_get_type (void);
+
+GtkWidget *terminal_screen_container_new (TerminalScreen *screen);
+
+TerminalScreen *terminal_screen_container_get_screen (TerminalScreenContainer *container);
+
+TerminalScreenContainer *terminal_screen_container_get_from_screen (TerminalScreen *screen);
+
+void terminal_screen_container_set_policy (TerminalScreenContainer *container,
+ GtkPolicyType hpolicy,
+ GtkPolicyType vpolicy);
+
+G_END_DECLS
+
+#endif /* TERMINAL_SCREEN_CONTAINER_H */
diff --git a/src/terminal-screen.cc b/src/terminal-screen.cc
new file mode 100644
index 0000000..a559f7f
--- /dev/null
+++ b/src/terminal-screen.cc
@@ -0,0 +1,2358 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2007, 2008, 2010, 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-pcre2.hh"
+#include "terminal-regex.hh"
+#include "terminal-screen.hh"
+#include "terminal-client-utils.hh"
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <uuid.h>
+
+#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+#include <sys/sysctl.h>
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "terminal-accels.hh"
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-enums.hh"
+#include "terminal-intl.hh"
+#include "terminal-marshal.h"
+#include "terminal-schemas.hh"
+#include "terminal-screen-container.hh"
+#include "terminal-util.hh"
+#include "terminal-window.hh"
+#include "terminal-info-bar.hh"
+#include "terminal-libgsystem.hh"
+
+#include "eggshell.hh"
+
+#define URL_MATCH_CURSOR_NAME "pointer"
+
+namespace {
+
+typedef struct {
+ volatile int refcount;
+ char **argv; /* as passed */
+ char **exec_argv; /* as processed */
+ char **envv;
+ char *cwd;
+ gboolean as_shell;
+
+ VtePtyFlags pty_flags;
+ GSpawnFlags spawn_flags;
+
+ /* FD passing */
+ GUnixFDList *fd_list;
+ int n_fd_map;
+ int* fd_map;
+
+ /* async exec callback */
+ TerminalScreenExecCallback callback;
+ gpointer callback_data;
+ GDestroyNotify callback_data_destroy_notify;
+
+ /* Cancellable */
+ GCancellable *cancellable;
+} ExecData;
+
+} // anon namespace
+
+typedef struct
+{
+ int tag;
+ TerminalURLFlavor flavor;
+} TagData;
+
+struct _TerminalScreenPrivate
+{
+ char *uuid;
+ gboolean registered; /* D-Bus interface is registered */
+
+ GSettings *profile; /* never nullptr */
+ guint profile_changed_id;
+ guint profile_forgotten_id;
+ int child_pid;
+ GSList *match_tags;
+ gboolean exec_on_realize;
+ guint idle_exec_source;
+ ExecData *exec_data;
+};
+
+enum
+{
+ PROFILE_SET,
+ SHOW_POPUP_MENU,
+ MATCH_CLICKED,
+ CLOSE_SCREEN,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_PROFILE,
+ PROP_TITLE,
+};
+
+enum
+{
+ TARGET_COLOR,
+ TARGET_BGIMAGE,
+ TARGET_RESET_BG,
+ TARGET_MOZ_URL,
+ TARGET_NETSCAPE_URL,
+ TARGET_TAB
+};
+
+static void terminal_screen_constructed (GObject *object);
+static void terminal_screen_dispose (GObject *object);
+static void terminal_screen_finalize (GObject *object);
+static void terminal_screen_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time);
+static void terminal_screen_set_font (TerminalScreen *screen);
+static void terminal_screen_system_font_changed_cb (GSettings *,
+ const char*,
+ TerminalScreen *screen);
+static gboolean terminal_screen_popup_menu (GtkWidget *widget);
+static gboolean terminal_screen_button_press (GtkWidget *widget,
+ GdkEventButton *event);
+static void terminal_screen_child_exited (VteTerminal *terminal,
+ int status);
+
+static void terminal_screen_window_title_changed (VteTerminal *vte_terminal,
+ TerminalScreen *screen);
+
+static void update_color_scheme (TerminalScreen *screen);
+
+static char* terminal_screen_check_hyperlink (TerminalScreen *screen,
+ GdkEvent *event);
+static void terminal_screen_check_extra (TerminalScreen *screen,
+ GdkEvent *event,
+ char **number_info,
+ char **timestamp_info);
+static char* terminal_screen_check_match (TerminalScreen *screen,
+ GdkEvent *event,
+ int *flavor);
+
+static void terminal_screen_show_info_bar (TerminalScreen *screen,
+ GError *error,
+ gboolean show_relaunch);
+
+
+static char**terminal_screen_get_child_environment (TerminalScreen *screen,
+ char **initial_envv,
+ char **path,
+ char **shell);
+
+static gboolean terminal_screen_get_child_command (TerminalScreen *screen,
+ char **exec_argv,
+ const char *path_env,
+ const char *shell_env,
+ gboolean shell,
+ gboolean *preserve_cwd_p,
+ GSpawnFlags *spawn_flags_p,
+ char ***argv_p,
+ GError **err);
+
+static void terminal_screen_queue_idle_exec (TerminalScreen *screen);
+
+static guint signals[LAST_SIGNAL];
+
+typedef struct {
+ const char *pattern;
+ TerminalURLFlavor flavor;
+} TerminalRegexPattern;
+
+static const TerminalRegexPattern url_regex_patterns[] = {
+ { REGEX_URL_AS_IS, FLAVOR_AS_IS },
+ { REGEX_URL_HTTP, FLAVOR_DEFAULT_TO_HTTP },
+ { REGEX_URL_FILE, FLAVOR_AS_IS },
+ { REGEX_URL_VOIP, FLAVOR_VOIP_CALL },
+ { REGEX_EMAIL, FLAVOR_EMAIL },
+ { REGEX_NEWS_MAN, FLAVOR_AS_IS },
+};
+
+static const TerminalRegexPattern extra_regex_patterns[] = {
+ { "(0[Xx][[:xdigit:]]+|[[:digit:]]+)", FLAVOR_NUMBER },
+};
+
+static VteRegex **url_regexes;
+static VteRegex **extra_regexes;
+static TerminalURLFlavor *url_regex_flavors;
+static TerminalURLFlavor *extra_regex_flavors;
+static guint n_url_regexes;
+static guint n_extra_regexes;
+
+/* See bug #697024 */
+#ifndef __linux__
+
+#undef dup3
+#define dup3 fake_dup3
+
+static int
+fake_dup3 (int fd, int fd2, int flags)
+{
+ if (dup2 (fd, fd2) == -1)
+ return -1;
+
+ return fcntl (fd2, F_SETFD, flags);
+}
+#endif /* !__linux__ */
+
+static char*
+strv_to_string (char **strv)
+{
+ return strv ? g_strjoinv (" ", strv) : g_strdup ("(null)");
+}
+
+static char*
+exec_data_to_string (ExecData *data)
+{
+ gs_free char *str1 = nullptr;
+ gs_free char *str2 = nullptr;
+ return data ? g_strdup_printf ("data %p argv:[%s] exec-argv:[%s] envv:%p(%u) as-shell:%s cwd:%s",
+ data,
+ (str1 = strv_to_string (data->argv)),
+ (str2 = strv_to_string (data->exec_argv)),
+ data->envv, data->envv ? g_strv_length (data->envv) : 0,
+ data->as_shell ? "true" : "false",
+ data->cwd)
+ : g_strdup ("(null)");
+}
+
+static ExecData*
+exec_data_new (void)
+{
+ ExecData *data = g_new0 (ExecData, 1);
+ data->refcount = 1;
+
+ return data;
+}
+
+static ExecData *
+exec_data_clone (ExecData *data,
+ gboolean preserve_argv)
+{
+ if (data == nullptr)
+ return nullptr;
+
+ ExecData *clone = exec_data_new ();
+ clone->envv = g_strdupv (data->envv);
+ clone->cwd = g_strdup (data->cwd);
+
+ /* If FDs were passed, cannot repeat argv. Return data only for env and cwd */
+ if (!preserve_argv ||
+ data->fd_list != nullptr) {
+ clone->as_shell = TRUE;
+ return clone;
+ }
+
+ clone->argv = g_strdupv (data->argv);
+ clone->as_shell = data->as_shell;
+
+ return clone;
+}
+
+static void
+exec_data_callback (ExecData *data,
+ GError *error,
+ TerminalScreen *screen)
+{
+ if (data->callback)
+ data->callback (screen, error, data->callback_data);
+}
+
+static ExecData*
+exec_data_ref (ExecData *data)
+{
+ data->refcount++;
+ return data;
+}
+
+static void
+exec_data_unref (ExecData *data)
+{
+ if (data == nullptr)
+ return;
+
+ if (--data->refcount > 0)
+ return;
+
+ g_strfreev (data->argv);
+ g_strfreev (data->exec_argv);
+ g_strfreev (data->envv);
+ g_free (data->cwd);
+ g_clear_object (&data->fd_list);
+ g_free (data->fd_map);
+
+ if (data->callback_data_destroy_notify && data->callback_data)
+ data->callback_data_destroy_notify (data->callback_data);
+
+ g_clear_object (&data->cancellable);
+
+ g_free (data);
+}
+
+GS_DEFINE_CLEANUP_FUNCTION0(ExecData*, _terminal_local_unref_exec_data, exec_data_unref)
+#define terminal_unref_exec_data __attribute__((__cleanup__(_terminal_local_unref_exec_data)))
+
+static void
+terminal_screen_clear_exec_data (TerminalScreen *screen,
+ gboolean cancelled)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+
+ if (priv->exec_data == nullptr)
+ return;
+
+ if (cancelled) {
+ gs_free_error GError *err = nullptr;
+ g_set_error_literal (&err, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+ "Spawning was cancelled");
+ exec_data_callback (priv->exec_data, err, screen);
+ }
+
+ exec_data_unref (priv->exec_data);
+ priv->exec_data = nullptr;
+}
+
+G_DEFINE_TYPE (TerminalScreen, terminal_screen, VTE_TYPE_TERMINAL)
+
+static void
+free_tag_data (TagData *tagdata)
+{
+ g_slice_free (TagData, tagdata);
+}
+
+static void
+precompile_regexes (const TerminalRegexPattern *regex_patterns,
+ guint n_regexes,
+ VteRegex ***regexes,
+ TerminalURLFlavor **regex_flavors)
+{
+ guint i;
+
+ *regexes = g_new0 (VteRegex*, n_regexes);
+ *regex_flavors = g_new0 (TerminalURLFlavor, n_regexes);
+
+ for (i = 0; i < n_regexes; ++i)
+ {
+ GError *error = nullptr;
+
+ (*regexes)[i] = vte_regex_new_for_match (regex_patterns[i].pattern, -1,
+ PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_UCP | PCRE2_MULTILINE,
+ &error);
+ g_assert_no_error (error);
+
+ if (!vte_regex_jit ((*regexes)[i], PCRE2_JIT_COMPLETE, &error) ||
+ !vte_regex_jit ((*regexes)[i], PCRE2_JIT_PARTIAL_SOFT, &error)) {
+ g_printerr ("Failed to JIT regex '%s': %s\n", regex_patterns[i].pattern, error->message);
+ g_clear_error (&error);
+ }
+
+ (*regex_flavors)[i] = regex_patterns[i].flavor;
+ }
+}
+
+static void
+terminal_screen_class_enable_menu_bar_accel_notify_cb (GSettings *settings,
+ const char *key,
+ TerminalScreenClass *klass)
+{
+ static gboolean is_enabled = TRUE; /* the binding is enabled by default since GtkWidgetClass installs it */
+ gboolean enable;
+ GtkBindingSet *binding_set;
+
+ enable = g_settings_get_boolean (settings, key);
+
+ /* Only remove the 'skip' entry when we have added it previously! */
+ if (enable == is_enabled)
+ return;
+
+ is_enabled = enable;
+
+ binding_set = gtk_binding_set_by_class (klass);
+ if (enable)
+ gtk_binding_entry_remove (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
+ else
+ gtk_binding_entry_skip (binding_set, GDK_KEY_F10, GDK_SHIFT_MASK);
+}
+
+static TerminalWindow *
+terminal_screen_get_window (TerminalScreen *screen)
+{
+ GtkWidget *widget = GTK_WIDGET (screen);
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (!gtk_widget_is_toplevel (toplevel))
+ return nullptr;
+
+ return TERMINAL_WINDOW (toplevel);
+}
+
+static void
+terminal_screen_realize (GtkWidget *widget)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (widget);
+
+ GTK_WIDGET_CLASS (terminal_screen_parent_class)->realize (widget);
+
+ terminal_screen_set_font (screen);
+
+ TerminalScreenPrivate *priv = screen->priv;
+ if (priv->exec_on_realize)
+ terminal_screen_queue_idle_exec (screen);
+
+ priv->exec_on_realize = FALSE;
+
+}
+
+static void
+terminal_screen_update_style (TerminalScreen *screen)
+{
+ update_color_scheme (screen);
+ terminal_screen_set_font (screen);
+}
+
+static void
+terminal_screen_style_updated (GtkWidget *widget)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (widget);
+
+ GTK_WIDGET_CLASS (terminal_screen_parent_class)->style_updated (widget);
+
+ terminal_screen_update_style (screen);
+}
+
+static void
+terminal_screen_init (TerminalScreen *screen)
+{
+ const GtkTargetEntry target_table[] = {
+ { (char *) "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, TARGET_TAB },
+ { (char *) "application/x-color", 0, TARGET_COLOR },
+ { (char *) "x-special/gnome-reset-background", 0, TARGET_RESET_BG },
+ { (char *) "text/x-moz-url", 0, TARGET_MOZ_URL },
+ { (char *) "_NETSCAPE_URL", 0, TARGET_NETSCAPE_URL }
+ };
+ VteTerminal *terminal = VTE_TERMINAL (screen);
+ TerminalScreenPrivate *priv;
+ TerminalApp *app;
+ GtkTargetList *target_list;
+ GtkTargetEntry *targets;
+ int n_targets;
+ guint i;
+ uuid_t u;
+ char uuidstr[37];
+
+ priv = screen->priv = G_TYPE_INSTANCE_GET_PRIVATE (screen, TERMINAL_TYPE_SCREEN, TerminalScreenPrivate);
+
+ uuid_generate (u);
+ uuid_unparse (u, uuidstr);
+ priv->uuid = g_strdup (uuidstr);
+
+ vte_terminal_set_mouse_autohide (terminal, TRUE);
+
+ priv->child_pid = -1;
+
+ vte_terminal_set_allow_hyperlink (terminal, TRUE);
+
+ for (i = 0; i < n_url_regexes; ++i)
+ {
+ TagData *tag_data;
+
+ tag_data = g_slice_new (TagData);
+ tag_data->flavor = url_regex_flavors[i];
+ tag_data->tag = vte_terminal_match_add_regex (terminal, url_regexes[i], 0);
+ vte_terminal_match_set_cursor_name (terminal, tag_data->tag, URL_MATCH_CURSOR_NAME);
+
+ priv->match_tags = g_slist_prepend (priv->match_tags, tag_data);
+ }
+
+ /* Setup DND */
+ target_list = gtk_target_list_new (nullptr, 0);
+ gtk_target_list_add_uri_targets (target_list, 0);
+ gtk_target_list_add_text_targets (target_list, 0);
+ gtk_target_list_add_table (target_list, target_table, G_N_ELEMENTS (target_table));
+
+ targets = gtk_target_table_new_from_list (target_list, &n_targets);
+
+ gtk_drag_dest_set (GTK_WIDGET (screen),
+ GtkDestDefaults(GTK_DEST_DEFAULT_MOTION |
+ GTK_DEST_DEFAULT_HIGHLIGHT |
+ GTK_DEST_DEFAULT_DROP),
+ targets, n_targets,
+ GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
+
+ gtk_target_table_free (targets, n_targets);
+ gtk_target_list_unref (target_list);
+
+ g_signal_connect (screen, "window-title-changed",
+ G_CALLBACK (terminal_screen_window_title_changed),
+ screen);
+
+ app = terminal_app_get ();
+ g_signal_connect (terminal_app_get_desktop_interface_settings (app), "changed::" MONOSPACE_FONT_KEY_NAME,
+ G_CALLBACK (terminal_screen_system_font_changed_cb), screen);
+
+}
+
+static void
+terminal_screen_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (object);
+
+ switch (prop_id)
+ {
+ case PROP_PROFILE:
+ g_value_set_object (value, terminal_screen_get_profile (screen));
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, terminal_screen_get_title (screen));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_screen_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (object);
+
+ switch (prop_id)
+ {
+ case PROP_PROFILE:
+ terminal_screen_set_profile (screen, (GSettings*)g_value_get_object (value));
+ break;
+ case PROP_TITLE:
+ /* not writable */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_screen_class_init (TerminalScreenClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ VteTerminalClass *terminal_class = VTE_TERMINAL_CLASS (klass);
+ GSettings *settings;
+
+ object_class->constructed = terminal_screen_constructed;
+ object_class->dispose = terminal_screen_dispose;
+ object_class->finalize = terminal_screen_finalize;
+ object_class->get_property = terminal_screen_get_property;
+ object_class->set_property = terminal_screen_set_property;
+
+ widget_class->realize = terminal_screen_realize;
+ widget_class->style_updated = terminal_screen_style_updated;
+ widget_class->drag_data_received = terminal_screen_drag_data_received;
+ widget_class->button_press_event = terminal_screen_button_press;
+ widget_class->popup_menu = terminal_screen_popup_menu;
+
+ terminal_class->child_exited = terminal_screen_child_exited;
+
+ signals[PROFILE_SET] =
+ g_signal_new (I_("profile-set"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalScreenClass, profile_set),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, G_TYPE_SETTINGS);
+
+ signals[SHOW_POPUP_MENU] =
+ g_signal_new (I_("show-popup-menu"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalScreenClass, show_popup_menu),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ signals[MATCH_CLICKED] =
+ g_signal_new (I_("match-clicked"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalScreenClass, match_clicked),
+ g_signal_accumulator_true_handled, nullptr,
+ _terminal_marshal_BOOLEAN__STRING_INT_UINT,
+ G_TYPE_BOOLEAN,
+ 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT);
+
+ signals[CLOSE_SCREEN] =
+ g_signal_new (I_("close-screen"),
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalScreenClass, close_screen),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_install_property
+ (object_class,
+ PROP_PROFILE,
+ g_param_spec_object ("profile", nullptr, nullptr,
+ G_TYPE_SETTINGS,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB)));
+
+ g_object_class_install_property
+ (object_class,
+ PROP_TITLE,
+ g_param_spec_string ("title", nullptr, nullptr,
+ nullptr,
+ GParamFlags(G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB)));
+
+ g_type_class_add_private (object_class, sizeof (TerminalScreenPrivate));
+
+ n_url_regexes = G_N_ELEMENTS (url_regex_patterns);
+ precompile_regexes (url_regex_patterns, n_url_regexes, &url_regexes, &url_regex_flavors);
+ n_extra_regexes = G_N_ELEMENTS (extra_regex_patterns);
+ precompile_regexes (extra_regex_patterns, n_extra_regexes, &extra_regexes, &extra_regex_flavors);
+
+ /* This fixes bug #329827 */
+ settings = terminal_app_get_global_settings (terminal_app_get ());
+ terminal_screen_class_enable_menu_bar_accel_notify_cb (settings, TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY, klass);
+ g_signal_connect (settings, "changed::" TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY,
+ G_CALLBACK (terminal_screen_class_enable_menu_bar_accel_notify_cb), klass);
+}
+
+static void
+terminal_screen_constructed (GObject *object)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (object);
+ TerminalScreenPrivate *priv = screen->priv;
+
+ G_OBJECT_CLASS (terminal_screen_parent_class)->constructed (object);
+
+ terminal_app_register_screen (terminal_app_get (), screen);
+ priv->registered = TRUE;
+}
+
+static void
+terminal_screen_dispose (GObject *object)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (object);
+ TerminalScreenPrivate *priv = screen->priv;
+ GtkSettings *settings;
+
+ /* Unset child PID so that when an eventual child-exited signal arrives,
+ * we don't emit "close".
+ */
+ priv->child_pid = -1;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (screen));
+ g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
+ 0, 0, nullptr, nullptr,
+ screen);
+
+ if (priv->idle_exec_source != 0)
+ {
+ g_source_remove (priv->idle_exec_source);
+ priv->idle_exec_source = 0;
+ }
+
+ terminal_screen_clear_exec_data (screen, TRUE);
+
+ G_OBJECT_CLASS (terminal_screen_parent_class)->dispose (object);
+
+ /* Unregister *after* chaining up to the parent's dispose,
+ * since that will terminate the child process if there still
+ * is any, and we need to get the dbus signal out
+ * from the TerminalReceiver.
+ */
+ if (priv->registered) {
+ terminal_app_unregister_screen (terminal_app_get (), screen);
+ priv->registered = FALSE;
+ }
+}
+
+static void
+terminal_screen_finalize (GObject *object)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (object);
+ TerminalScreenPrivate *priv = screen->priv;
+
+ g_signal_handlers_disconnect_by_func (terminal_app_get_desktop_interface_settings (terminal_app_get ()),
+ (void*)terminal_screen_system_font_changed_cb,
+ screen);
+
+ terminal_screen_set_profile (screen, nullptr);
+
+ g_slist_free_full (priv->match_tags, (GDestroyNotify) free_tag_data);
+
+ g_free (priv->uuid);
+
+ G_OBJECT_CLASS (terminal_screen_parent_class)->finalize (object);
+}
+
+TerminalScreen *
+terminal_screen_new (GSettings *profile,
+ const char *title,
+ double zoom)
+{
+ g_return_val_if_fail (G_IS_SETTINGS (profile), nullptr);
+
+ TerminalScreen *screen = (TerminalScreen*)g_object_new (TERMINAL_TYPE_SCREEN, nullptr);
+
+ terminal_screen_set_profile (screen, profile);
+
+ vte_terminal_set_size (VTE_TERMINAL (screen),
+ g_settings_get_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS_KEY),
+ g_settings_get_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_ROWS_KEY));
+
+ /* If given an initial title, strip it of control characters and
+ * feed it to the terminal.
+ */
+ if (title != nullptr) {
+ GString *seq;
+ const char *p;
+
+ seq = g_string_new ("\033]0;");
+ for (p = title; *p; p = g_utf8_next_char (p)) {
+ gunichar c = g_utf8_get_char (p);
+ if (c < 0x20 || (c >= 0x7f && c <= 0x9f))
+ continue;
+ else if (c == ';')
+ break;
+
+ g_string_append_unichar (seq, c);
+ }
+ g_string_append (seq, "\033\\");
+
+ vte_terminal_feed (VTE_TERMINAL (screen), seq->str, seq->len);
+ g_string_free (seq, TRUE);
+ }
+
+ vte_terminal_set_font_scale (VTE_TERMINAL (screen), zoom);
+ terminal_screen_set_font (screen);
+
+ return screen;
+}
+
+static gboolean
+terminal_screen_reexec_from_exec_data (TerminalScreen *screen,
+ ExecData *data,
+ char **envv,
+ const char *cwd,
+ GCancellable *cancellable,
+ GError **error)
+{
+ _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) {
+ gs_free char *str = exec_data_to_string (data);
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] reexec_from_data: envv:%p(%u) cwd:%s data:[%s]\n",
+ screen,
+ envv, envv ? g_strv_length (envv) : 0,
+ cwd,
+ str);
+ }
+
+ return terminal_screen_exec (screen,
+ data ? data->argv : nullptr,
+ envv ? envv : data ? data->envv : nullptr,
+ data ? data->as_shell : TRUE,
+ /* If we have command line args, must always pass the cwd from the command line, too */
+ data && data->argv ? data->cwd : cwd ? cwd : data ? data->cwd : nullptr,
+ nullptr /* fd list */, nullptr /* fd array */,
+ nullptr, nullptr, nullptr, /* callback + data + destroy notify */
+ cancellable,
+ error);
+}
+
+gboolean
+terminal_screen_reexec_from_screen (TerminalScreen *screen,
+ TerminalScreen *parent_screen,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE);
+
+ if (parent_screen == nullptr)
+ return TRUE;
+
+ g_return_val_if_fail (TERMINAL_IS_SCREEN (parent_screen), FALSE);
+
+ terminal_unref_exec_data ExecData* data = exec_data_clone (parent_screen->priv->exec_data, FALSE);
+ gs_free char* cwd = terminal_screen_get_current_dir (parent_screen);
+
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] reexec_from_screen: parent:%p cwd:%s\n",
+ screen,
+ parent_screen,
+ cwd);
+
+ return terminal_screen_reexec_from_exec_data (screen,
+ data,
+ nullptr /* envv */,
+ cwd,
+ cancellable,
+ error);
+}
+
+gboolean
+terminal_screen_reexec (TerminalScreen *screen,
+ char **envv,
+ const char *cwd,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE);
+
+
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] reexec: envv:%p(%u) cwd:%s\n",
+ screen,
+ envv, envv ? g_strv_length (envv) : 0,
+ cwd);
+
+ return terminal_screen_reexec_from_exec_data (screen,
+ screen->priv->exec_data,
+ envv,
+ cwd,
+ cancellable,
+ error);
+}
+
+gboolean
+terminal_screen_exec (TerminalScreen *screen,
+ char **argv,
+ char **initial_envv,
+ gboolean as_shell,
+ const char *cwd,
+ GUnixFDList *fd_list,
+ GVariant *fd_array,
+ TerminalScreenExecCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), FALSE);
+ g_return_val_if_fail (cancellable == nullptr || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == nullptr || *error == nullptr, FALSE);
+ g_return_val_if_fail (gtk_widget_get_parent (GTK_WIDGET (screen)) != nullptr, FALSE);
+
+ _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) {
+ gs_free char *argv_str = nullptr;
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] exec: argv:[%s] envv:%p(%u) as-shell:%s cwd:%s\n",
+ screen,
+ (argv_str = strv_to_string(argv)),
+ initial_envv, initial_envv ? g_strv_length (initial_envv) : 0,
+ as_shell ? "true":"false",
+ cwd);
+ }
+
+ TerminalScreenPrivate *priv = screen->priv;
+
+ ExecData *data = exec_data_new ();
+ data->callback = callback;
+ data->callback_data = user_data;
+ data->callback_data_destroy_notify = destroy_notify;
+
+ GError *err = nullptr;
+ if (priv->child_pid != -1) {
+ g_set_error_literal (&err, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Cannot launch a new child process while the terminal is still running another child process");
+
+ terminal_screen_show_info_bar (screen, err, FALSE);
+ g_propagate_error (error, err);
+ exec_data_unref (data); /* frees the callback data */
+ return FALSE;
+ }
+
+ gs_free char *path = nullptr;
+ gs_free char *shell = nullptr;
+ gs_strfreev char **envv = terminal_screen_get_child_environment (screen,
+ initial_envv,
+ &path,
+ &shell);
+
+ gboolean preserve_cwd = FALSE;
+ GSpawnFlags spawn_flags = GSpawnFlags(G_SPAWN_SEARCH_PATH_FROM_ENVP |
+ VTE_SPAWN_NO_PARENT_ENVV);
+ gs_strfreev char **exec_argv = nullptr;
+ if (!terminal_screen_get_child_command (screen,
+ argv,
+ path,
+ shell,
+ as_shell,
+ &preserve_cwd,
+ &spawn_flags,
+ &exec_argv,
+ &err)) {
+ terminal_screen_show_info_bar (screen, err, FALSE);
+ g_propagate_error (error, err);
+ exec_data_unref (data); /* frees the callback data */
+ return FALSE;
+ }
+
+ if (!preserve_cwd) {
+ cwd = g_get_home_dir ();
+ envv = g_environ_unsetenv (envv, "PWD");
+ }
+
+ data->fd_list = (GUnixFDList*)(fd_list ? g_object_ref(fd_list) : nullptr);
+
+ if (fd_array) {
+ g_assert_nonnull(fd_list);
+ int n_fds = g_unix_fd_list_get_length(fd_list);
+
+ gsize fd_array_data_len;
+ const int *fd_array_data = (int const*)g_variant_get_fixed_array (fd_array, &fd_array_data_len, 2 * sizeof (int));
+
+ data->n_fd_map = fd_array_data_len;
+ data->fd_map = g_new (int, data->n_fd_map);
+ for (gsize i = 0; i < fd_array_data_len; i++) {
+ const int fd = fd_array_data[2 * i];
+ const int idx = fd_array_data[2 * i + 1];
+ g_assert_cmpint(idx, >=, 0);
+ g_assert_cmpuint(idx, <, n_fds);
+
+ data->fd_map[idx] = fd;
+ }
+ } else {
+ data->n_fd_map = 0;
+ data->fd_map = nullptr;
+ }
+
+ data->argv = g_strdupv (argv);
+ data->exec_argv = g_strdupv (exec_argv);
+ data->cwd = g_strdup (cwd);
+ data->envv = g_strdupv (envv);
+ data->as_shell = as_shell;
+ data->pty_flags = VTE_PTY_DEFAULT;
+ data->spawn_flags = spawn_flags;
+ data->cancellable = (GCancellable*)(cancellable ? g_object_ref (cancellable) : nullptr);
+
+ terminal_screen_clear_exec_data (screen, TRUE);
+ priv->exec_data = data;
+
+ terminal_screen_queue_idle_exec (screen);
+
+ return TRUE;
+}
+
+const char*
+terminal_screen_get_title (TerminalScreen *screen)
+{
+ return vte_terminal_get_window_title (VTE_TERMINAL (screen));
+}
+
+static void
+terminal_screen_profile_changed_cb (GSettings *profile,
+ const char *prop_name,
+ TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ GObject *object = G_OBJECT (screen);
+ VteTerminal *vte_terminal = VTE_TERMINAL (screen);
+ TerminalWindow *window;
+
+ g_object_freeze_notify (object);
+
+ if ((window = terminal_screen_get_window (screen)))
+ {
+ /* We need these in line for the set_size in
+ * update_on_realize
+ */
+ terminal_window_update_geometry (window);
+ }
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLLBAR_POLICY_KEY))
+ _terminal_screen_update_scrollbar (screen);
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENCODING_KEY))
+ {
+ gs_free char *charset = g_settings_get_string (profile, TERMINAL_PROFILE_ENCODING_KEY);
+ const char *encoding = terminal_util_translate_encoding (charset);
+ if (encoding != nullptr)
+ vte_terminal_set_encoding (vte_terminal, encoding, nullptr);
+ }
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY))
+ {
+ int width;
+
+ width = g_settings_get_enum (profile, TERMINAL_PROFILE_CJK_UTF8_AMBIGUOUS_WIDTH_KEY);
+ vte_terminal_set_cjk_ambiguous_width (vte_terminal, width);
+ }
+
+ if (gtk_widget_get_realized (GTK_WIDGET (screen)) &&
+ (!prop_name ||
+ prop_name == I_(TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_FONT_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY)))
+ terminal_screen_set_font (screen);
+
+ if (!prop_name ||
+ prop_name == I_(TERMINAL_PROFILE_USE_THEME_COLORS_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_FOREGROUND_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_BACKGROUND_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_BOLD_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_CURSOR_BACKGROUND_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_CURSOR_FOREGROUND_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_HIGHLIGHT_BACKGROUND_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_HIGHLIGHT_FOREGROUND_COLOR_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_PALETTE_KEY))
+ update_color_scheme (screen);
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_AUDIBLE_BELL_KEY))
+ vte_terminal_set_audible_bell (vte_terminal, g_settings_get_boolean (profile, TERMINAL_PROFILE_AUDIBLE_BELL_KEY));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE_KEY))
+ vte_terminal_set_scroll_on_keystroke (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_KEYSTROKE_KEY));
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_SCROLL_ON_OUTPUT_KEY))
+ vte_terminal_set_scroll_on_output (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_SCROLL_ON_OUTPUT_KEY));
+ if (!prop_name ||
+ prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_LINES_KEY) ||
+ prop_name == I_(TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY))
+ {
+ glong lines = g_settings_get_boolean (profile, TERMINAL_PROFILE_SCROLLBACK_UNLIMITED_KEY) ?
+ -1 : g_settings_get_int (profile, TERMINAL_PROFILE_SCROLLBACK_LINES_KEY);
+ vte_terminal_set_scrollback_lines (vte_terminal, lines);
+ }
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_BACKSPACE_BINDING_KEY))
+ vte_terminal_set_backspace_binding (vte_terminal,
+ VteEraseBinding(g_settings_get_enum (profile, TERMINAL_PROFILE_BACKSPACE_BINDING_KEY)));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_DELETE_BINDING_KEY))
+ vte_terminal_set_delete_binding (vte_terminal,
+ VteEraseBinding(g_settings_get_enum (profile, TERMINAL_PROFILE_DELETE_BINDING_KEY)));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENABLE_BIDI_KEY))
+ vte_terminal_set_enable_bidi (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_ENABLE_BIDI_KEY));
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENABLE_SHAPING_KEY))
+ vte_terminal_set_enable_shaping (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_ENABLE_SHAPING_KEY));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_ENABLE_SIXEL_KEY))
+ vte_terminal_set_enable_sixel (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_ENABLE_SIXEL_KEY));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_BOLD_IS_BRIGHT_KEY))
+ vte_terminal_set_bold_is_bright (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_BOLD_IS_BRIGHT_KEY));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_BLINK_MODE_KEY))
+ vte_terminal_set_cursor_blink_mode (vte_terminal,
+ VteCursorBlinkMode(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_CURSOR_BLINK_MODE_KEY)));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_CURSOR_SHAPE_KEY))
+ vte_terminal_set_cursor_shape (vte_terminal,
+ VteCursorShape(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_CURSOR_SHAPE_KEY)));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_REWRAP_ON_RESIZE_KEY))
+ vte_terminal_set_rewrap_on_resize (vte_terminal,
+ g_settings_get_boolean (profile, TERMINAL_PROFILE_REWRAP_ON_RESIZE_KEY));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_TEXT_BLINK_MODE_KEY))
+ vte_terminal_set_text_blink_mode (vte_terminal,
+ VteTextBlinkMode(g_settings_get_enum (profile, TERMINAL_PROFILE_TEXT_BLINK_MODE_KEY)));
+
+ if (!prop_name || prop_name == I_(TERMINAL_PROFILE_WORD_CHAR_EXCEPTIONS_KEY))
+ {
+ gs_free char *word_char_exceptions;
+ g_settings_get (profile, TERMINAL_PROFILE_WORD_CHAR_EXCEPTIONS_KEY, "ms", &word_char_exceptions);
+ vte_terminal_set_word_char_exceptions (vte_terminal, word_char_exceptions);
+ }
+
+ g_object_thaw_notify (object);
+}
+
+static void
+update_color_scheme (TerminalScreen *screen)
+{
+ GtkWidget *widget = GTK_WIDGET (screen);
+ TerminalScreenPrivate *priv = screen->priv;
+ GSettings *profile = priv->profile;
+ gs_free GdkRGBA *colors;
+ gsize n_colors;
+ GdkRGBA fg, bg, bold, theme_fg, theme_bg;
+ GdkRGBA cursor_bg, cursor_fg;
+ GdkRGBA highlight_bg, highlight_fg;
+ GdkRGBA *boldp;
+ GdkRGBA *cursor_bgp = nullptr, *cursor_fgp = nullptr;
+ GdkRGBA *highlight_bgp = nullptr, *highlight_fgp = nullptr;
+ GtkStyleContext *context;
+ gboolean use_theme_colors;
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_get_color (context, gtk_style_context_get_state (context), &theme_fg);
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &theme_bg);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+ use_theme_colors = g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_THEME_COLORS_KEY);
+ if (use_theme_colors ||
+ (!terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_FOREGROUND_COLOR_KEY, &fg) ||
+ !terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_BACKGROUND_COLOR_KEY, &bg)))
+ {
+ fg = theme_fg;
+ bg = theme_bg;
+ }
+
+ if (!g_settings_get_boolean (profile, TERMINAL_PROFILE_BOLD_COLOR_SAME_AS_FG_KEY) &&
+ !use_theme_colors &&
+ terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_BOLD_COLOR_KEY, &bold))
+ boldp = &bold;
+ else
+ boldp = nullptr;
+
+ if (g_settings_get_boolean (profile, TERMINAL_PROFILE_CURSOR_COLORS_SET_KEY) &&
+ !use_theme_colors)
+ {
+ if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_CURSOR_BACKGROUND_COLOR_KEY, &cursor_bg))
+ cursor_bgp = &cursor_bg;
+ if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_CURSOR_FOREGROUND_COLOR_KEY, &cursor_fg))
+ cursor_fgp = &cursor_fg;
+ }
+
+ if (g_settings_get_boolean (profile, TERMINAL_PROFILE_HIGHLIGHT_COLORS_SET_KEY) &&
+ !use_theme_colors)
+ {
+ if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_HIGHLIGHT_BACKGROUND_COLOR_KEY, &highlight_bg))
+ highlight_bgp = &highlight_bg;
+ if (terminal_g_settings_get_rgba (profile, TERMINAL_PROFILE_HIGHLIGHT_FOREGROUND_COLOR_KEY, &highlight_fg))
+ highlight_fgp = &highlight_fg;
+ }
+
+ colors = terminal_g_settings_get_rgba_palette (priv->profile, TERMINAL_PROFILE_PALETTE_KEY, &n_colors);
+ vte_terminal_set_colors (VTE_TERMINAL (screen), &fg, &bg,
+ colors, n_colors);
+ vte_terminal_set_color_bold (VTE_TERMINAL (screen), boldp);
+ vte_terminal_set_color_cursor (VTE_TERMINAL (screen), cursor_bgp);
+ vte_terminal_set_color_cursor_foreground (VTE_TERMINAL (screen), cursor_fgp);
+ vte_terminal_set_color_highlight (VTE_TERMINAL (screen), highlight_bgp);
+ vte_terminal_set_color_highlight_foreground (VTE_TERMINAL (screen), highlight_fgp);
+}
+
+static void
+terminal_screen_set_font (TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ GSettings *profile = priv->profile;
+ PangoFontDescription *desc;
+ int size;
+
+ if (g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY))
+ {
+ desc = terminal_app_get_system_font (terminal_app_get ());
+ }
+ else
+ {
+ gs_free char *font;
+ font = g_settings_get_string (profile, TERMINAL_PROFILE_FONT_KEY);
+ desc = pango_font_description_from_string (font);
+ }
+
+ size = pango_font_description_get_size (desc);
+ /* Sanity check */
+ if (size == 0) {
+ if (pango_font_description_get_size_is_absolute (desc))
+ pango_font_description_set_absolute_size (desc, 10);
+ else
+ pango_font_description_set_size (desc, 10);
+ }
+
+ vte_terminal_set_font (VTE_TERMINAL (screen), desc);
+
+ pango_font_description_free (desc);
+
+ vte_terminal_set_cell_width_scale (VTE_TERMINAL (screen),
+ g_settings_get_double (profile, TERMINAL_PROFILE_CELL_WIDTH_SCALE_KEY));
+ vte_terminal_set_cell_height_scale (VTE_TERMINAL (screen),
+ g_settings_get_double (profile, TERMINAL_PROFILE_CELL_HEIGHT_SCALE_KEY));
+}
+
+static void
+terminal_screen_system_font_changed_cb (GSettings *settings,
+ const char *key,
+ TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (screen)))
+ return;
+
+ if (!g_settings_get_boolean (priv->profile, TERMINAL_PROFILE_USE_SYSTEM_FONT_KEY))
+ return;
+
+ terminal_screen_set_font (screen);
+}
+
+void
+terminal_screen_set_profile (TerminalScreen *screen,
+ GSettings *profile)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ GSettings*old_profile;
+
+ old_profile = priv->profile;
+ if (profile == old_profile)
+ return;
+
+ if (priv->profile_changed_id)
+ {
+ g_signal_handler_disconnect (G_OBJECT (priv->profile),
+ priv->profile_changed_id);
+ priv->profile_changed_id = 0;
+ }
+
+ priv->profile = profile;
+ if (profile)
+ {
+ g_object_ref (profile);
+ priv->profile_changed_id =
+ g_signal_connect (profile, "changed",
+ G_CALLBACK (terminal_screen_profile_changed_cb),
+ screen);
+ terminal_screen_profile_changed_cb (profile, nullptr, screen);
+
+ g_signal_emit (G_OBJECT (screen), signals[PROFILE_SET], 0, old_profile);
+ }
+
+ if (old_profile)
+ g_object_unref (old_profile);
+
+ g_object_notify (G_OBJECT (screen), "profile");
+}
+
+GSettings*
+terminal_screen_get_profile (TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+
+ return priv->profile;
+}
+
+GSettings*
+terminal_screen_ref_profile (TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+
+ if (priv->profile != nullptr)
+ return (GSettings*)g_object_ref (priv->profile);
+ return nullptr;
+}
+
+static gboolean
+should_preserve_cwd (TerminalPreserveWorkingDirectory preserve_cwd,
+ const char *path,
+ const char *arg0)
+{
+ switch (preserve_cwd) {
+ case TERMINAL_PRESERVE_WORKING_DIRECTORY_SAFE: {
+ gs_free char *resolved_arg0 = terminal_util_find_program_in_path (path, arg0);
+ return resolved_arg0 != nullptr &&
+ terminal_util_get_is_shell (resolved_arg0);
+ }
+
+ case TERMINAL_PRESERVE_WORKING_DIRECTORY_ALWAYS:
+ return TRUE;
+
+ case TERMINAL_PRESERVE_WORKING_DIRECTORY_NEVER:
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean
+terminal_screen_get_child_command (TerminalScreen *screen,
+ char **argv,
+ const char *path_env,
+ const char *shell_env,
+ gboolean as_shell,
+ gboolean *preserve_cwd_p,
+ GSpawnFlags *spawn_flags_p,
+ char ***exec_argv_p,
+ GError **err)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ GSettings *profile = priv->profile;
+ TerminalPreserveWorkingDirectory preserve_cwd;
+ char **exec_argv;
+
+ g_assert (spawn_flags_p != nullptr && exec_argv_p != nullptr && preserve_cwd_p != nullptr);
+
+ *exec_argv_p = exec_argv = nullptr;
+
+ preserve_cwd = TerminalPreserveWorkingDirectory
+ (g_settings_get_enum (profile, TERMINAL_PROFILE_PRESERVE_WORKING_DIRECTORY_KEY));
+
+ if (argv)
+ {
+ exec_argv = g_strdupv (argv);
+
+ /* argv and cwd come from the command line client, so it must always be used */
+ *preserve_cwd_p = TRUE;
+ *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_SEARCH_PATH_FROM_ENVP);
+ }
+ else if (g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY))
+ {
+ gs_free char *exec_argv_str;
+
+ exec_argv_str = g_settings_get_string (profile, TERMINAL_PROFILE_CUSTOM_COMMAND_KEY);
+ if (!g_shell_parse_argv (exec_argv_str, nullptr, &exec_argv, err))
+ return FALSE;
+
+ *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, exec_argv[0]);
+ *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_SEARCH_PATH_FROM_ENVP);
+ }
+ else if (as_shell)
+ {
+ const char *only_name;
+ char *shell;
+ int argc = 0;
+
+ shell = egg_shell (shell_env);
+
+ only_name = strrchr (shell, '/');
+ if (only_name != nullptr)
+ only_name++;
+ else {
+ only_name = shell;
+ *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_SEARCH_PATH_FROM_ENVP);
+ }
+
+ exec_argv = g_new (char*, 3);
+
+ exec_argv[argc++] = shell;
+
+ if (g_settings_get_boolean (profile, TERMINAL_PROFILE_LOGIN_SHELL_KEY))
+ exec_argv[argc++] = g_strconcat ("-", only_name, nullptr);
+ else
+ exec_argv[argc++] = g_strdup (only_name);
+
+ exec_argv[argc++] = nullptr;
+
+ *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, shell);
+ *spawn_flags_p = GSpawnFlags(*spawn_flags_p | G_SPAWN_FILE_AND_ARGV_ZERO);
+ }
+
+ else
+ {
+ g_set_error_literal (err, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ _("No command supplied nor shell requested"));
+ return FALSE;
+ }
+
+ *exec_argv_p = exec_argv;
+
+ return TRUE;
+}
+
+static gboolean
+remove_prefixed_cb(void* key,
+ void* value,
+ void* user_data)
+{
+ auto const env = reinterpret_cast<char const*>(key);
+ auto const prefix = reinterpret_cast<char const*>(user_data);
+
+ if (terminal_client_get_environment_prefix_filters_is_excluded(env))
+ return false;
+
+ return g_str_has_prefix(env, prefix);
+}
+
+static char**
+terminal_screen_get_child_environment (TerminalScreen *screen,
+ char **initial_envv,
+ char **path,
+ char **shell)
+{
+ TerminalApp *app = terminal_app_get ();
+ char **env;
+ gs_strfreev char** current_environ = nullptr;
+ char *e, *v;
+ GHashTable *env_table;
+ GHashTableIter iter;
+ GPtrArray *retval;
+ guint i;
+
+ env_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ if (initial_envv)
+ env = initial_envv;
+ else {
+ env = current_environ = g_get_environ ();
+ /* Remove this variable which we set in server.c:main() */
+ env = g_environ_unsetenv (env, "G_ENABLE_DIAGNOSTIC");
+ }
+
+ for (i = 0; env[i]; ++i)
+ {
+ v = strchr (env[i], '=');
+ if (v)
+ g_hash_table_replace (env_table, g_strndup (env[i], v - env[i]), g_strdup (v + 1));
+ else
+ g_hash_table_replace (env_table, g_strdup (env[i]), nullptr);
+ }
+
+ /* Remove unwanted env variables */
+ auto const filters = terminal_client_get_environment_filters ();
+ for (i = 0; filters[i]; ++i)
+ g_hash_table_remove (env_table, filters[i]);
+
+ auto const pfilters = terminal_client_get_environment_prefix_filters ();
+ for (i = 0; pfilters[i]; ++i) {
+ g_hash_table_foreach_remove (env_table,
+ GHRFunc(remove_prefixed_cb),
+ (void*)pfilters[i]);
+ }
+
+ terminal_util_add_proxy_env (env_table);
+
+ /* Add gnome-terminal private env vars used to communicate back to g-t-server */
+ GDBusConnection *connection = g_application_get_dbus_connection (G_APPLICATION (app));
+ g_hash_table_replace (env_table, g_strdup (TERMINAL_ENV_SERVICE_NAME),
+ g_strdup (g_dbus_connection_get_unique_name (connection)));
+
+ g_hash_table_replace (env_table, g_strdup (TERMINAL_ENV_SCREEN),
+ terminal_app_dup_screen_object_path (app, screen));
+
+ /* Convert to strv */
+ retval = g_ptr_array_sized_new (g_hash_table_size (env_table));
+ g_hash_table_iter_init (&iter, env_table);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &e, (gpointer *) &v))
+ g_ptr_array_add (retval, g_strdup_printf ("%s=%s", e, v ? v : ""));
+ g_ptr_array_add (retval, nullptr);
+
+ *path = g_strdup ((char const*)g_hash_table_lookup (env_table, "PATH"));
+ *shell = g_strdup ((char const*)g_hash_table_lookup (env_table, "SHELL"));
+
+ g_hash_table_destroy (env_table);
+ return (char **) g_ptr_array_free (retval, FALSE);
+}
+
+enum {
+ RESPONSE_RELAUNCH,
+ RESPONSE_EDIT_PREFERENCES
+};
+
+static void
+info_bar_response_cb (GtkWidget *info_bar,
+ int response,
+ TerminalScreen *screen)
+{
+ gtk_widget_grab_focus (GTK_WIDGET (screen));
+
+ switch (response) {
+ case GTK_RESPONSE_CANCEL:
+ gtk_widget_destroy (info_bar);
+ g_signal_emit (screen, signals[CLOSE_SCREEN], 0);
+ break;
+ case RESPONSE_RELAUNCH:
+ gtk_widget_destroy (info_bar);
+ terminal_screen_reexec (screen, nullptr, nullptr, nullptr, nullptr);
+ break;
+ case RESPONSE_EDIT_PREFERENCES:
+ terminal_app_edit_preferences (terminal_app_get (),
+ terminal_screen_get_profile (screen),
+ "custom-command-entry",
+ gtk_get_current_event_time());
+ break;
+ default:
+ gtk_widget_destroy (info_bar);
+ break;
+ }
+}
+
+static void
+terminal_screen_show_info_bar (TerminalScreen *screen,
+ GError *error,
+ gboolean show_relaunch)
+{
+ GtkWidget *info_bar;
+
+ if (!gtk_widget_get_parent (GTK_WIDGET (screen)))
+ return;
+
+ info_bar = terminal_info_bar_new (GTK_MESSAGE_ERROR,
+ _("_Preferences"), RESPONSE_EDIT_PREFERENCES,
+ !show_relaunch ? nullptr : _("_Relaunch"), RESPONSE_RELAUNCH,
+ nullptr);
+ terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
+ _("There was an error creating the child process for this terminal"));
+ terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
+ "%s", error->message);
+ g_signal_connect (info_bar, "response",
+ G_CALLBACK (info_bar_response_cb), screen);
+
+ gtk_widget_set_halign (info_bar, GTK_ALIGN_FILL);
+ gtk_widget_set_valign (info_bar, GTK_ALIGN_START);
+ gtk_overlay_add_overlay (GTK_OVERLAY (terminal_screen_container_get_from_screen (screen)),
+ info_bar);
+ gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_CANCEL);
+ gtk_widget_show (info_bar);
+}
+
+static void
+spawn_result_cb (VteTerminal *terminal,
+ GPid pid,
+ GError *error,
+ gpointer user_data)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (terminal);
+ ExecData *exec_data = (ExecData*)user_data;
+
+ /* Terminal was destroyed while the spawn operation was in progress; nothing to do. */
+ if (terminal == nullptr)
+ goto out;
+
+ {
+ TerminalScreenPrivate *priv = screen->priv;
+
+ priv->child_pid = pid;
+
+ if (error) {
+ // FIXMEchpe should be unnecessary, vte already does this internally
+ vte_terminal_set_pty (terminal, nullptr);
+
+ gboolean can_reexec = TRUE; /* FIXME */
+ terminal_screen_show_info_bar (screen, error, can_reexec);
+ }
+
+ /* Retain info for reexec, if possible */
+ ExecData *new_exec_data = exec_data_clone (exec_data, TRUE);
+ terminal_screen_clear_exec_data (screen, FALSE);
+ priv->exec_data = new_exec_data;
+ }
+
+out:
+
+ /* Must do this even if the terminal was destroyed */
+ exec_data_callback (exec_data, error, screen);
+
+ exec_data_unref (exec_data);
+}
+
+static gboolean
+idle_exec_cb (TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+
+ priv->idle_exec_source = 0;
+
+ ExecData *data = priv->exec_data;
+ _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) {
+ gs_free char *str = exec_data_to_string (data);
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] now launching the child process: %s\n",
+ screen, str);
+ }
+
+ int n_fds;
+ int *fds;
+ if (data->fd_list) {
+ fds = g_unix_fd_list_steal_fds(data->fd_list, &n_fds);
+ } else {
+ fds = nullptr;
+ n_fds = 0;
+ }
+
+ VteTerminal *terminal = VTE_TERMINAL (screen);
+ vte_terminal_spawn_with_fds_async (terminal,
+ data->pty_flags,
+ data->cwd,
+ (char const* const*)data->exec_argv,
+ (char const* const*)data->envv,
+ fds, n_fds,
+ data->fd_map, data->n_fd_map,
+ data->spawn_flags,
+ nullptr, nullptr, nullptr, /* child setup, data, destroy */
+ -1,
+ data->cancellable,
+ spawn_result_cb,
+ exec_data_ref (data));
+
+ return FALSE; /* don't run again */
+}
+
+static void
+terminal_screen_queue_idle_exec (TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+
+ if (priv->idle_exec_source != 0)
+ return;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (screen))) {
+ priv->exec_on_realize = TRUE;
+ return;
+ }
+
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] scheduling launching the child process on idle\n",
+ screen);
+
+ priv->idle_exec_source = g_idle_add ((GSourceFunc) idle_exec_cb, screen);
+}
+
+static TerminalScreenPopupInfo *
+terminal_screen_popup_info_new (TerminalScreen *screen)
+{
+ TerminalScreenPopupInfo *info;
+
+ info = g_slice_new0 (TerminalScreenPopupInfo);
+ info->ref_count = 1;
+
+ return info;
+}
+
+TerminalScreenPopupInfo *
+terminal_screen_popup_info_ref (TerminalScreenPopupInfo *info)
+{
+ g_return_val_if_fail (info != nullptr, nullptr);
+
+ info->ref_count++;
+ return info;
+}
+
+void
+terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info)
+{
+ g_return_if_fail (info != nullptr);
+
+ if (--info->ref_count > 0)
+ return;
+
+ g_free (info->hyperlink);
+ g_free (info->url);
+ g_free (info->number_info);
+ g_free (info->timestamp_info);
+ g_slice_free (TerminalScreenPopupInfo, info);
+}
+
+static gboolean
+terminal_screen_popup_menu (GtkWidget *widget)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (widget);
+ TerminalScreenPopupInfo *info;
+
+ info = terminal_screen_popup_info_new (screen);
+ info->button = 0;
+ info->timestamp = gtk_get_current_event_time ();
+
+ g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
+ terminal_screen_popup_info_unref (info);
+
+ return TRUE;
+}
+
+static void
+terminal_screen_do_popup (TerminalScreen *screen,
+ GdkEventButton *event,
+ char *hyperlink,
+ char *url,
+ int url_flavor,
+ char *number_info,
+ char *timestamp_info)
+{
+ TerminalScreenPopupInfo *info;
+
+ info = terminal_screen_popup_info_new (screen);
+ info->button = event->button;
+ info->state = event->state & gtk_accelerator_get_default_mod_mask ();
+ info->timestamp = event->time;
+ info->hyperlink = hyperlink; /* adopted */
+ info->url = url; /* adopted */
+ info->url_flavor = TerminalURLFlavor(url_flavor);
+ info->number_info = number_info; /* adopted */
+ info->timestamp_info = timestamp_info; /* adopted */
+
+ g_signal_emit (screen, signals[SHOW_POPUP_MENU], 0, info);
+ terminal_screen_popup_info_unref (info);
+}
+
+static gboolean
+terminal_screen_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (widget);
+ gboolean (* button_press_event) (GtkWidget*, GdkEventButton*) =
+ GTK_WIDGET_CLASS (terminal_screen_parent_class)->button_press_event;
+ gs_free char *hyperlink = nullptr;
+ gs_free char *url = nullptr;
+ int url_flavor = 0;
+ gs_free char *number_info = nullptr;
+ gs_free char *timestamp_info = nullptr;
+ guint state;
+
+ state = event->state & gtk_accelerator_get_default_mod_mask ();
+
+ hyperlink = terminal_screen_check_hyperlink (screen, (GdkEvent*)event);
+ url = terminal_screen_check_match (screen, (GdkEvent*)event, &url_flavor);
+ terminal_screen_check_extra (screen, (GdkEvent*)event, &number_info, &timestamp_info);
+
+ if (hyperlink != nullptr &&
+ (event->button == 1 || event->button == 2) &&
+ (state & GDK_CONTROL_MASK))
+ {
+ gboolean handled = FALSE;
+
+ g_signal_emit (screen, signals[MATCH_CLICKED], 0,
+ hyperlink,
+ FLAVOR_AS_IS,
+ state,
+ &handled);
+ if (handled)
+ return TRUE; /* don't do anything else such as select with the click */
+ }
+
+ if (url != nullptr &&
+ (event->button == 1 || event->button == 2) &&
+ (state & GDK_CONTROL_MASK))
+ {
+ gboolean handled = FALSE;
+
+ g_signal_emit (screen, signals[MATCH_CLICKED], 0,
+ url,
+ url_flavor,
+ state,
+ &handled);
+ if (handled)
+ return TRUE; /* don't do anything else such as select with the click */
+ }
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+ {
+ if (!(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)))
+ {
+ /* on right-click, we should first try to send the mouse event to
+ * the client, and popup only if that's not handled. */
+ if (button_press_event && button_press_event (widget, event))
+ return TRUE;
+
+ terminal_screen_do_popup (screen, event, hyperlink, url, url_flavor, number_info, timestamp_info);
+ hyperlink = nullptr; /* adopted to the popup info */
+ url = nullptr; /* ditto */
+ number_info = nullptr; /* ditto */
+ timestamp_info = nullptr; /* ditto */
+ return TRUE;
+ }
+ else if (!(event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
+ {
+ /* do popup on shift+right-click */
+ terminal_screen_do_popup (screen, event, hyperlink, url, url_flavor, number_info, timestamp_info);
+ hyperlink = nullptr; /* adopted to the popup info */
+ url = nullptr; /* ditto */
+ number_info = nullptr; /* ditto */
+ timestamp_info = nullptr; /* ditto */
+ return TRUE;
+ }
+ }
+
+ /* default behavior is to let the terminal widget deal with it */
+ if (button_press_event)
+ return button_press_event (widget, event);
+
+ return FALSE;
+}
+
+/**
+ * terminal_screen_get_current_dir:
+ * @screen:
+ *
+ * Tries to determine the current working directory of the foreground process
+ * in @screen's PTY.
+ *
+ * Returns: a newly allocated string containing the current working directory,
+ * or %nullptr on failure
+ */
+char *
+terminal_screen_get_current_dir (TerminalScreen *screen)
+{
+ const char *uri;
+
+ uri = vte_terminal_get_current_directory_uri (VTE_TERMINAL (screen));
+ if (uri != nullptr)
+ return g_filename_from_uri (uri, nullptr, nullptr);
+
+ ExecData *data = screen->priv->exec_data;
+ if (data && data->cwd)
+ return g_strdup (data->cwd);
+
+ return nullptr;
+}
+
+static void
+terminal_screen_window_title_changed (VteTerminal *vte_terminal,
+ TerminalScreen *screen)
+{
+ g_object_notify (G_OBJECT (screen), "title");
+}
+
+static void
+terminal_screen_child_exited (VteTerminal *terminal,
+ int status)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (terminal);
+ TerminalScreenPrivate *priv = screen->priv;
+ TerminalExitAction action;
+
+ /* Don't do anything if we don't have a child */
+ if (priv->child_pid == -1)
+ return;
+
+ /* No need to chain up to VteTerminalClass::child_exited since it's nullptr */
+
+ _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
+ "[screen %p] child process exited\n",
+ screen);
+
+ priv->child_pid = -1;
+
+ action = TerminalExitAction(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_EXIT_ACTION_KEY));
+
+ switch (action)
+ {
+ case TERMINAL_EXIT_CLOSE:
+ g_signal_emit (screen, signals[CLOSE_SCREEN], 0);
+ break;
+ case TERMINAL_EXIT_RESTART:
+ terminal_screen_reexec (screen, nullptr, nullptr, nullptr, nullptr);
+ break;
+ case TERMINAL_EXIT_HOLD: {
+ GtkWidget *info_bar;
+
+ info_bar = terminal_info_bar_new (GTK_MESSAGE_INFO,
+ _("_Relaunch"), RESPONSE_RELAUNCH,
+ nullptr);
+ if (WIFEXITED (status)) {
+ terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
+ _("The child process exited normally with status %d."), WEXITSTATUS (status));
+ } else if (WIFSIGNALED (status)) {
+ terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
+ _("The child process was aborted by signal %d."), WTERMSIG (status));
+ } else {
+ terminal_info_bar_format_text (TERMINAL_INFO_BAR (info_bar),
+ _("The child process was aborted."));
+ }
+ g_signal_connect (info_bar, "response",
+ G_CALLBACK (info_bar_response_cb), screen);
+
+ gtk_widget_set_halign (info_bar, GTK_ALIGN_FILL);
+ gtk_widget_set_valign (info_bar, GTK_ALIGN_START);
+ gtk_overlay_add_overlay (GTK_OVERLAY (terminal_screen_container_get_from_screen (screen)),
+ info_bar);
+ gtk_info_bar_set_default_response (GTK_INFO_BAR (info_bar), RESPONSE_RELAUNCH);
+ gtk_widget_show (info_bar);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void
+terminal_screen_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp)
+{
+ TerminalScreen *screen = TERMINAL_SCREEN (widget);
+ TerminalScreenPrivate *priv = screen->priv;
+ const guchar *selection_data_data;
+ GdkAtom selection_data_target;
+ gint selection_data_length, selection_data_format;
+
+ selection_data_data = gtk_selection_data_get_data (selection_data);
+ selection_data_target = gtk_selection_data_get_target (selection_data);
+ selection_data_length = gtk_selection_data_get_length (selection_data);
+ selection_data_format = gtk_selection_data_get_format (selection_data);
+
+#if 0
+ {
+ GList *tmp;
+
+ g_print ("info: %d\n", info);
+ tmp = context->targets;
+ while (tmp != nullptr)
+ {
+ GdkAtom atom = GDK_POINTER_TO_ATOM (tmp->data);
+
+ g_print ("Target: %s\n", gdk_atom_name (atom));
+
+ tmp = tmp->next;
+ }
+
+ g_print ("Chosen target: %s\n", gdk_atom_name (selection_data->target));
+ }
+#endif
+
+ if (gtk_targets_include_uri (&selection_data_target, 1))
+ {
+ gs_strfreev char **uris;
+ gs_free char *text = nullptr;
+ gsize len;
+
+ uris = gtk_selection_data_get_uris (selection_data);
+ if (!uris)
+ return;
+
+ terminal_util_transform_uris_to_quoted_fuse_paths (uris);
+
+ text = terminal_util_concat_uris (uris, &len);
+ terminal_screen_paste_text (screen, text, len);
+ }
+ else if (gtk_targets_include_text (&selection_data_target, 1))
+ {
+ gs_free char *text;
+
+ text = (char *) gtk_selection_data_get_text (selection_data);
+ if (text && text[0])
+ terminal_screen_paste_text (screen, text, -1);
+ }
+ else switch (info)
+ {
+ case TARGET_COLOR:
+ {
+ guint16 *data = (guint16 *)selection_data_data;
+ GdkRGBA color;
+
+ /* We accept drops with the wrong format, since the KDE color
+ * chooser incorrectly drops application/x-color with format 8.
+ * So just check for the data length.
+ */
+ if (selection_data_length != 8)
+ return;
+
+ color.red = (double) data[0] / 65535.;
+ color.green = (double) data[1] / 65535.;
+ color.blue = (double) data[2] / 65535.;
+ color.alpha = 1.;
+ /* FIXME: use opacity from data[3] */
+
+ terminal_g_settings_set_rgba (priv->profile,
+ TERMINAL_PROFILE_BACKGROUND_COLOR_KEY,
+ &color);
+ g_settings_set_boolean (priv->profile, TERMINAL_PROFILE_USE_THEME_COLORS_KEY, FALSE);
+ }
+ break;
+
+ case TARGET_MOZ_URL:
+ {
+ char *utf8_data, *text;
+ char *uris[2];
+ gsize len;
+
+ /* MOZ_URL is in UCS-2 but in format 8. BROKEN!
+ *
+ * The data contains the URL, a \n, then the
+ * title of the web page.
+ *
+ * Note that some producers (e.g. dolphin) delimit with a \r\n
+ * (see issue#293), so we need to handle that, too.
+ */
+ if (selection_data_format != 8 ||
+ selection_data_length == 0 ||
+ (selection_data_length % 2) != 0)
+ return;
+
+ utf8_data = g_utf16_to_utf8 ((const gunichar2*) selection_data_data,
+ selection_data_length / 2,
+ nullptr, nullptr, nullptr);
+ if (!utf8_data)
+ return;
+
+ uris[0] = g_strdelimit(utf8_data, "\r\n", 0);
+ uris[1] = nullptr;
+ terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */
+
+ text = terminal_util_concat_uris (uris, &len);
+ terminal_screen_paste_text (screen, text, len);
+ g_free (text);
+ g_free (uris[0]);
+ }
+ break;
+
+ case TARGET_NETSCAPE_URL:
+ {
+ char *utf8_data, *newline, *text;
+ char *uris[2];
+ gsize len;
+
+ /* The data contains the URL, a \n, then the
+ * title of the web page.
+ */
+ if (selection_data_length < 0 || selection_data_format != 8)
+ return;
+
+ utf8_data = g_strndup ((char *) selection_data_data, selection_data_length);
+ newline = strchr (utf8_data, '\n');
+ if (newline)
+ *newline = '\0';
+
+ uris[0] = utf8_data;
+ uris[1] = nullptr;
+ terminal_util_transform_uris_to_quoted_fuse_paths (uris); /* This may replace uris[0] */
+
+ text = terminal_util_concat_uris (uris, &len);
+ terminal_screen_paste_text (screen, text, len);
+ g_free (text);
+ g_free (uris[0]);
+ }
+ break;
+
+ case TARGET_RESET_BG:
+ g_settings_reset (priv->profile, TERMINAL_PROFILE_BACKGROUND_COLOR_KEY);
+ break;
+
+ case TARGET_TAB:
+ {
+ GtkWidget *container;
+ TerminalScreen *moving_screen;
+ TerminalWindow *source_window;
+ TerminalWindow *dest_window;
+
+ container = *(GtkWidget**) selection_data_data;
+ if (!GTK_IS_WIDGET (container))
+ return;
+
+ moving_screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (container));
+ g_warn_if_fail (TERMINAL_IS_SCREEN (moving_screen));
+ if (!TERMINAL_IS_SCREEN (moving_screen))
+ return;
+
+ source_window = terminal_screen_get_window (moving_screen);
+ dest_window = terminal_screen_get_window (screen);
+ terminal_window_move_screen (source_window, dest_window, moving_screen, -1);
+
+ gtk_drag_finish (context, TRUE, TRUE, timestamp);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+void
+_terminal_screen_update_scrollbar (TerminalScreen *screen)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ TerminalScreenContainer *container;
+ GtkPolicyType vpolicy;
+
+ container = terminal_screen_container_get_from_screen (screen);
+ if (container == nullptr)
+ return;
+
+ vpolicy = GtkPolicyType(g_settings_get_enum (priv->profile, TERMINAL_PROFILE_SCROLLBAR_POLICY_KEY));
+
+ terminal_screen_container_set_policy (container, GTK_POLICY_NEVER, vpolicy);
+}
+
+void
+terminal_screen_get_size (TerminalScreen *screen,
+ int *width_chars,
+ int *height_chars)
+{
+ VteTerminal *terminal = VTE_TERMINAL (screen);
+
+ *width_chars = vte_terminal_get_column_count (terminal);
+ *height_chars = vte_terminal_get_row_count (terminal);
+}
+
+void
+terminal_screen_get_cell_size (TerminalScreen *screen,
+ int *cell_width_pixels,
+ int *cell_height_pixels)
+{
+ VteTerminal *terminal = VTE_TERMINAL (screen);
+
+ *cell_width_pixels = vte_terminal_get_char_width (terminal);
+ *cell_height_pixels = vte_terminal_get_char_height (terminal);
+}
+
+static char*
+terminal_screen_check_hyperlink (TerminalScreen *screen,
+ GdkEvent *event)
+{
+ return vte_terminal_hyperlink_check_event (VTE_TERMINAL (screen), event);
+}
+
+static char*
+terminal_screen_check_match (TerminalScreen *screen,
+ GdkEvent *event,
+ int *flavor)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ GSList *tags;
+ int tag;
+ char *match;
+
+ match = vte_terminal_match_check_event (VTE_TERMINAL (screen), event, &tag);
+ for (tags = priv->match_tags; tags != nullptr; tags = tags->next)
+ {
+ TagData *tag_data = (TagData*) tags->data;
+ if (tag_data->tag == tag)
+ {
+ if (flavor)
+ *flavor = tag_data->flavor;
+ return match;
+ }
+ }
+
+ g_free (match);
+ return nullptr;
+}
+
+static void
+terminal_screen_check_extra (TerminalScreen *screen,
+ GdkEvent *event,
+ char **number_info,
+ char **timestamp_info)
+{
+ guint i;
+ char **matches;
+ gboolean flavor_number_found = FALSE;
+
+ matches = g_newa (char *, n_extra_regexes);
+ memset(matches, 0, sizeof(char*) * n_extra_regexes);
+
+ if (
+ vte_terminal_event_check_regex_simple (VTE_TERMINAL (screen),
+ event,
+ extra_regexes,
+ n_extra_regexes,
+ 0,
+ matches))
+ {
+ for (i = 0; i < n_extra_regexes; i++)
+ {
+ if (matches[i] != nullptr)
+ {
+ /* Store the first match for each flavor, free all the others */
+ switch (extra_regex_flavors[i])
+ {
+ case FLAVOR_NUMBER:
+ if (!flavor_number_found)
+ {
+ *number_info = terminal_util_number_info (matches[i]);
+ *timestamp_info = terminal_util_timestamp_info (matches[i]);
+ flavor_number_found = TRUE;
+ }
+ g_free (matches[i]);
+ break;
+ default:
+ g_free (matches[i]);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * terminal_screen_has_foreground_process:
+ * @screen:
+ * @process_name: (out) (allow-none): the basename of the program, or %nullptr
+ * @cmdline: (out) (allow-none): the full command line, or %nullptr
+ *
+ * Checks whether there's a foreground process running in
+ * this terminal.
+ *
+ * Returns: %TRUE iff there's a foreground process running in @screen
+ */
+gboolean
+terminal_screen_has_foreground_process (TerminalScreen *screen,
+ char **process_name,
+ char **cmdline)
+{
+ TerminalScreenPrivate *priv = screen->priv;
+ gs_free char *command = nullptr;
+ gs_free char *data_buf = nullptr;
+ gs_free char *basename = nullptr;
+ gs_free char *name = nullptr;
+ VtePty *pty;
+ int fd;
+#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
+ int mib[4];
+#else
+ char filename[64];
+#endif
+ char *data;
+ gsize i;
+ gsize len;
+ int fgpid;
+
+ if (priv->child_pid == -1)
+ return FALSE;
+
+ pty = vte_terminal_get_pty (VTE_TERMINAL (screen));
+ if (pty == nullptr)
+ return FALSE;
+
+ fd = vte_pty_get_fd (pty);
+ if (fd == -1)
+ return FALSE;
+
+ fgpid = tcgetpgrp (fd);
+ if (fgpid == -1 || fgpid == priv->child_pid)
+ return FALSE;
+
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_ARGS;
+ mib[3] = fgpid;
+ if (sysctl (mib, G_N_ELEMENTS (mib), nullptr, &len, nullptr, 0) == -1)
+ return TRUE;
+
+ data_buf = (char*)g_malloc0 (len);
+ if (sysctl (mib, G_N_ELEMENTS (mib), data_buf, &len, nullptr, 0) == -1)
+ return TRUE;
+ data = data_buf;
+#elif defined(__OpenBSD__)
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC_ARGS;
+ mib[2] = fgpid;
+ mib[3] = KERN_PROC_ARGV;
+ if (sysctl (mib, G_N_ELEMENTS (mib), nullptr, &len, nullptr, 0) == -1)
+ return TRUE;
+
+ data_buf = (char*)g_malloc0 (len);
+ if (sysctl (mib, G_N_ELEMENTS (mib), data_buf, &len, nullptr, 0) == -1)
+ return TRUE;
+ data = ((char**)data_buf)[0];
+#else
+ g_snprintf (filename, sizeof (filename), "/proc/%d/cmdline", fgpid);
+ if (!g_file_get_contents (filename, &data_buf, &len, nullptr))
+ return TRUE;
+ data = data_buf;
+#endif
+
+ basename = g_path_get_basename (data);
+ if (!basename)
+ return TRUE;
+
+ name = g_filename_to_utf8 (basename, -1, nullptr, nullptr, nullptr);
+ if (!name)
+ return TRUE;
+
+ if (!process_name && !cmdline)
+ return TRUE;
+
+ gs_transfer_out_value (process_name, &name);
+
+ if (len > 0 && data[len - 1] == '\0')
+ len--;
+ for (i = 0; i < len; i++)
+ {
+ if (data[i] == '\0')
+ data[i] = ' ';
+ }
+
+ command = g_filename_to_utf8 (data, -1, nullptr, nullptr, nullptr);
+ if (!command)
+ return TRUE;
+
+ gs_transfer_out_value (cmdline, &command);
+
+ return TRUE;
+}
+
+const char *
+terminal_screen_get_uuid (TerminalScreen *screen)
+{
+ g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), nullptr);
+
+ return screen->priv->uuid;
+}
+
+/**
+ * terminal_screen_paste_text:
+ * @screen:
+ * @text: a NUL-terminated string
+ * @len: length of @text, or -1
+ *
+ * Inserts @text to @terminal as if pasted.
+ */
+void
+terminal_screen_paste_text (TerminalScreen* screen,
+ char const* text,
+ gssize len)
+{
+ g_return_if_fail (text != nullptr);
+ g_return_if_fail (len >= -1);
+
+ /* This is just an API hack until vte 0.69 adds vte_terminal_paste_text_len() */
+ /* Note that @text MUST be NUL-terminated */
+
+ vte_terminal_paste_text (VTE_TERMINAL (screen), text);
+}
diff --git a/src/terminal-screen.hh b/src/terminal-screen.hh
new file mode 100644
index 0000000..ded6b8c
--- /dev/null
+++ b/src/terminal-screen.hh
@@ -0,0 +1,171 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2008, 2010 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/>.
+ */
+
+#ifndef TERMINAL_SCREEN_H
+#define TERMINAL_SCREEN_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <vte/vte.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ FLAVOR_AS_IS,
+ FLAVOR_DEFAULT_TO_HTTP,
+ FLAVOR_VOIP_CALL,
+ FLAVOR_EMAIL,
+ FLAVOR_NUMBER,
+} TerminalURLFlavor;
+
+/* Forward decls */
+typedef struct _TerminalScreenPopupInfo TerminalScreenPopupInfo;
+typedef struct _TerminalWindow TerminalWindow;
+
+#define TERMINAL_TYPE_SCREEN (terminal_screen_get_type ())
+#define TERMINAL_SCREEN(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TERMINAL_TYPE_SCREEN, TerminalScreen))
+#define TERMINAL_SCREEN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TERMINAL_TYPE_SCREEN, TerminalScreenClass))
+#define TERMINAL_IS_SCREEN(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TERMINAL_TYPE_SCREEN))
+#define TERMINAL_IS_SCREEN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TERMINAL_TYPE_SCREEN))
+#define TERMINAL_SCREEN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TERMINAL_TYPE_SCREEN, TerminalScreenClass))
+
+typedef struct _TerminalScreen TerminalScreen;
+typedef struct _TerminalScreenClass TerminalScreenClass;
+typedef struct _TerminalScreenPrivate TerminalScreenPrivate;
+
+struct _TerminalScreen
+{
+ VteTerminal parent_instance;
+
+ TerminalScreenPrivate *priv;
+};
+
+struct _TerminalScreenClass
+{
+ VteTerminalClass parent_class;
+
+ void (* profile_set) (TerminalScreen *screen,
+ GSettings *old_profile);
+ void (* show_popup_menu) (TerminalScreen *screen,
+ TerminalScreenPopupInfo *info);
+ gboolean (* match_clicked) (TerminalScreen *screen,
+ const char *url,
+ int flavor,
+ guint state);
+ void (* close_screen) (TerminalScreen *screen);
+};
+
+GType terminal_screen_get_type (void) G_GNUC_CONST;
+
+const char *terminal_screen_get_uuid (TerminalScreen *screen);
+
+TerminalScreen *terminal_screen_new (GSettings *profile,
+ const char *title,
+ double zoom);
+
+typedef void (* TerminalScreenExecCallback) (TerminalScreen *screen,
+ GError *error,
+ gpointer user_data);
+
+gboolean terminal_screen_exec (TerminalScreen *screen,
+ char **argv,
+ char **envv,
+ gboolean as_shell,
+ const char *cwd,
+ GUnixFDList *fd_list,
+ GVariant *fd_array,
+ TerminalScreenExecCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy_notify,
+ GCancellable *cancellable,
+ GError **error);
+
+
+gboolean terminal_screen_reexec (TerminalScreen *screen,
+ char **envv,
+ const char *cwd,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean terminal_screen_reexec_from_screen (TerminalScreen *screen,
+ TerminalScreen *parent_screen,
+ GCancellable *cancellable,
+ GError **error);
+
+void terminal_screen_set_profile (TerminalScreen *screen,
+ GSettings *profile);
+GSettings* terminal_screen_get_profile (TerminalScreen *screen);
+GSettings* terminal_screen_ref_profile (TerminalScreen *screen);
+
+const char* terminal_screen_get_title (TerminalScreen *screen);
+
+char *terminal_screen_get_current_dir (TerminalScreen *screen);
+
+void terminal_screen_get_size (TerminalScreen *screen,
+ int *width_chars,
+ int *height_chars);
+void terminal_screen_get_cell_size (TerminalScreen *screen,
+ int *width_chars,
+ int *height_chars);
+
+void _terminal_screen_update_scrollbar (TerminalScreen *screen);
+
+void terminal_screen_save_config (TerminalScreen *screen,
+ GKeyFile *key_file,
+ const char *group);
+
+gboolean terminal_screen_has_foreground_process (TerminalScreen *screen,
+ char **process_name,
+ char **cmdline);
+
+/* Allow scales a bit smaller and a bit larger than the usual pango ranges */
+#define TERMINAL_SCALE_XXX_SMALL (PANGO_SCALE_XX_SMALL/1.2)
+#define TERMINAL_SCALE_XXXX_SMALL (TERMINAL_SCALE_XXX_SMALL/1.2)
+#define TERMINAL_SCALE_XXXXX_SMALL (TERMINAL_SCALE_XXXX_SMALL/1.2)
+#define TERMINAL_SCALE_XXX_LARGE (PANGO_SCALE_XX_LARGE*1.2)
+#define TERMINAL_SCALE_XXXX_LARGE (TERMINAL_SCALE_XXX_LARGE*1.2)
+#define TERMINAL_SCALE_XXXXX_LARGE (TERMINAL_SCALE_XXXX_LARGE*1.2)
+#define TERMINAL_SCALE_MINIMUM (TERMINAL_SCALE_XXXXX_SMALL/1.2)
+#define TERMINAL_SCALE_MAXIMUM (TERMINAL_SCALE_XXXXX_LARGE*1.2)
+
+struct _TerminalScreenPopupInfo {
+ int ref_count;
+ char *url;
+ TerminalURLFlavor url_flavor;
+ char *hyperlink;
+ char *number_info;
+ char *timestamp_info;
+ guint button;
+ guint state;
+ guint32 timestamp;
+};
+
+TerminalScreenPopupInfo *terminal_screen_popup_info_ref (TerminalScreenPopupInfo *info);
+
+void terminal_screen_popup_info_unref (TerminalScreenPopupInfo *info);
+
+/* API hack */
+
+void terminal_screen_paste_text (TerminalScreen* screen,
+ char const* text,
+ gssize len);
+
+G_END_DECLS
+
+#endif /* TERMINAL_SCREEN_H */
diff --git a/src/terminal-search-popover.cc b/src/terminal-search-popover.cc
new file mode 100644
index 0000000..73d682f
--- /dev/null
+++ b/src/terminal-search-popover.cc
@@ -0,0 +1,587 @@
+/*
+ * Copyright © 2015 Christian Persch
+ * Copyright © 2005 Paolo Maggi
+ * Copyright © 2010 Red Hat (Red Hat author: Behdad Esfahbod)
+ *
+ * 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 <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "terminal-pcre2.hh"
+#include "terminal-search-popover.hh"
+#include "terminal-intl.hh"
+#include "terminal-window.hh"
+#include "terminal-app.hh"
+#include "terminal-libgsystem.hh"
+
+typedef struct _TerminalSearchPopoverPrivate TerminalSearchPopoverPrivate;
+
+struct _TerminalSearchPopover
+{
+ GtkWindow parent_instance;
+};
+
+struct _TerminalSearchPopoverClass
+{
+ GtkWindowClass parent_class;
+
+ /* Signals */
+ void (* search) (TerminalSearchPopover *popover,
+ gboolean backward);
+};
+
+struct _TerminalSearchPopoverPrivate
+{
+ GtkWidget *search_entry;
+ GtkWidget *search_prev_button;
+ GtkWidget *search_next_button;
+ GtkWidget *reveal_button;
+ GtkWidget *close_button;
+ GtkWidget *revealer;
+ GtkWidget *match_case_checkbutton;
+ GtkWidget *entire_word_checkbutton;
+ GtkWidget *regex_checkbutton;
+ GtkWidget *wrap_around_checkbutton;
+
+ gboolean search_text_changed;
+
+ /* Cached regex */
+ gboolean regex_caseless;
+ char *regex_pattern;
+ VteRegex *regex;
+};
+
+enum {
+ PROP_0,
+ PROP_REGEX,
+ PROP_WRAP_AROUND,
+ LAST_PROP
+};
+
+enum {
+ SEARCH,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static GParamSpec *pspecs[LAST_PROP];
+static GtkListStore *history_store;
+
+G_DEFINE_TYPE_WITH_PRIVATE (TerminalSearchPopover, terminal_search_popover, GTK_TYPE_WINDOW)
+
+#define PRIV(obj) ((TerminalSearchPopoverPrivate *) terminal_search_popover_get_instance_private ((TerminalSearchPopover *)(obj)))
+
+/* history */
+
+#define HISTORY_MIN_ITEM_LEN (3)
+#define HISTORY_LENGTH (10)
+
+static gboolean
+history_enabled (void)
+{
+ gboolean enabled;
+
+ /* not quite an exact setting for this, but close enough… */
+ g_object_get (gtk_settings_get_default (), "gtk-recent-files-enabled", &enabled, nullptr);
+ if (!enabled)
+ return FALSE;
+
+ if (history_store == nullptr) {
+ history_store = gtk_list_store_new (1, G_TYPE_STRING);
+ g_object_set_data_full (G_OBJECT (terminal_app_get ()), "search-history-store",
+ history_store, (GDestroyNotify) g_object_unref);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+history_remove_item (const char *text)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (history_store);
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ return FALSE;
+
+ do {
+ gs_free gchar *item_text;
+
+ gtk_tree_model_get (model, &iter, 0, &item_text, -1);
+
+ if (item_text != nullptr && strcmp (item_text, text) == 0) {
+ gtk_list_store_remove (history_store, &iter);
+ return TRUE;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ return FALSE;
+}
+
+static void
+history_clamp (int max)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ /* -1 because TreePath counts from 0 */
+ path = gtk_tree_path_new_from_indices (max - 1, -1);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (history_store), &iter, path))
+ while (1)
+ if (!gtk_list_store_remove (history_store, &iter))
+ break;
+
+ gtk_tree_path_free (path);
+}
+
+static void
+history_insert_item (const char *text)
+{
+ GtkTreeIter iter;
+
+ if (!history_enabled () || text == nullptr)
+ return;
+
+ if (g_utf8_strlen (text, -1) <= HISTORY_MIN_ITEM_LEN)
+ return;
+
+ /* remove the text from the store if it was already
+ * present. If it wasn't, clamp to max history - 1
+ * before inserting the new row, otherwise appending
+ * would not work */
+ if (!history_remove_item (text))
+ history_clamp (HISTORY_LENGTH - 1);
+
+ gtk_list_store_insert_with_values (history_store, &iter, 0,
+ 0, text,
+ -1);
+}
+
+/* helper functions */
+
+static void
+update_sensitivity (TerminalSearchPopover *popover)
+{
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+ gboolean can_search;
+
+ can_search = priv->regex != nullptr;
+
+ gtk_widget_set_sensitive (priv->search_prev_button, can_search);
+ gtk_widget_set_sensitive (priv->search_next_button, can_search);
+}
+
+static void
+perform_search (TerminalSearchPopover *popover,
+ gboolean backward)
+{
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+ if (priv->regex == nullptr)
+ return;
+
+ /* Add to search history */
+ if (priv->search_text_changed) {
+ const char *search_text;
+
+ search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
+ history_insert_item (search_text);
+
+ priv->search_text_changed = FALSE;
+ }
+
+ g_signal_emit (popover, signals[SEARCH], 0, backward);
+}
+
+static void
+previous_match_cb (GtkWidget *widget,
+ TerminalSearchPopover *popover)
+{
+ perform_search (popover, TRUE);
+}
+
+static void
+next_match_cb (GtkWidget *widget,
+ TerminalSearchPopover *popover)
+{
+ perform_search (popover, FALSE);
+}
+
+static void
+close_clicked_cb (GtkWidget *widget,
+ GtkWidget *popover)
+{
+ gtk_widget_hide (popover);
+}
+
+static void
+search_button_clicked_cb (GtkWidget *button,
+ TerminalSearchPopover *popover)
+{
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+ perform_search (popover, button == priv->search_prev_button);
+}
+
+static gboolean
+key_press_cb (GtkWidget *popover,
+ GdkEventKey *event,
+ gpointer user_data G_GNUC_UNUSED)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ gtk_widget_hide (popover);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+update_regex (TerminalSearchPopover *popover)
+{
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+ const char *search_text;
+ gboolean caseless;
+ gs_free char *pattern;
+ gs_free_error GError *error = nullptr;
+
+ search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
+
+ caseless = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->match_case_checkbutton));
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->regex_checkbutton))) {
+ pattern = g_strdup (search_text);
+ } else {
+ pattern = g_regex_escape_string (search_text, -1);
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->entire_word_checkbutton))) {
+ char *new_pattern;
+ new_pattern = g_strdup_printf ("\\b%s\\b", pattern);
+ g_free (pattern);
+ pattern = new_pattern;
+ }
+
+ if (priv->regex_caseless == caseless &&
+ g_strcmp0 (priv->regex_pattern, pattern) == 0)
+ return;
+
+ if (priv->regex) {
+ vte_regex_unref (priv->regex);
+ }
+
+ g_clear_pointer (&priv->regex_pattern, g_free);
+
+ /* FIXME: if comping the regex fails, show the error message somewhere */
+ if (search_text[0] != '\0') {
+ guint32 compile_flags;
+
+ compile_flags = PCRE2_UTF | PCRE2_NO_UTF_CHECK | PCRE2_UCP | PCRE2_MULTILINE;
+ if (caseless)
+ compile_flags |= PCRE2_CASELESS;
+
+ priv->regex = vte_regex_new_for_search (pattern, -1, compile_flags, &error);
+ if (priv->regex != nullptr &&
+ (!vte_regex_jit (priv->regex, PCRE2_JIT_COMPLETE, nullptr) ||
+ !vte_regex_jit (priv->regex, PCRE2_JIT_PARTIAL_SOFT, nullptr))) {
+ }
+
+ if (priv->regex != nullptr)
+ gs_transfer_out_value (&priv->regex_pattern, &pattern);
+ } else {
+ priv->regex = nullptr;
+ }
+
+ priv->regex_caseless = caseless;
+
+ update_sensitivity (popover);
+
+ g_object_notify_by_pspec (G_OBJECT (popover), pspecs[PROP_REGEX]);
+}
+
+static void
+search_text_changed_cb (GtkToggleButton *button,
+ TerminalSearchPopover *popover)
+{
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+ update_regex (popover);
+ priv->search_text_changed = TRUE;
+}
+
+static void
+search_parameters_changed_cb (GtkToggleButton *button,
+ TerminalSearchPopover *popover)
+{
+ update_regex (popover);
+}
+
+static void
+wrap_around_toggled_cb (GtkToggleButton *button,
+ TerminalSearchPopover *popover)
+{
+ g_object_notify_by_pspec (G_OBJECT (popover), pspecs[PROP_WRAP_AROUND]);
+}
+
+/* public functions */
+
+/* Class implementation */
+
+static void
+terminal_search_popover_grab_focus (GtkWidget *widget)
+{
+ TerminalSearchPopover *popover = TERMINAL_SEARCH_POPOVER (widget);
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+ gtk_widget_grab_focus (priv->search_entry);
+}
+
+static void
+terminal_search_popover_init (TerminalSearchPopover *popover)
+{
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+ GtkWidget *widget = GTK_WIDGET (popover);
+
+ priv->regex_pattern = 0;
+ priv->regex_caseless = TRUE;
+
+ gtk_widget_init_template (widget);
+
+ /* Make the search entry reasonably wide */
+ gtk_widget_set_size_request (priv->search_entry, 300, -1);
+
+ /* Add entry completion with history */
+#if 0
+ g_object_set (G_OBJECT (priv->search_entry),
+ "model", history_store,
+ "entry-text-column", 0,
+ nullptr);
+#endif
+
+ if (history_enabled ()) {
+ gs_unref_object GtkEntryCompletion *completion;
+
+ completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (history_store));
+ gtk_entry_completion_set_text_column (completion, 0);
+ gtk_entry_completion_set_minimum_key_length (completion, HISTORY_MIN_ITEM_LEN);
+ gtk_entry_completion_set_popup_completion (completion, FALSE);
+ gtk_entry_completion_set_inline_completion (completion, TRUE);
+ gtk_entry_set_completion (GTK_ENTRY (priv->search_entry), completion);
+ }
+
+#if 0
+ gtk_popover_set_default_widget (GTK_POPOVER (popover), priv->search_prev_button);
+#else
+ GtkWindow *window = GTK_WINDOW (popover);
+ gtk_window_set_default (window, priv->search_prev_button);
+#endif
+
+ g_signal_connect (priv->search_entry, "previous-match", G_CALLBACK (previous_match_cb), popover);
+ g_signal_connect (priv->search_entry, "next-match", G_CALLBACK (next_match_cb), popover);
+
+ g_signal_connect (priv->search_prev_button, "clicked", G_CALLBACK (search_button_clicked_cb), popover);
+ g_signal_connect (priv->search_next_button, "clicked", G_CALLBACK (search_button_clicked_cb), popover);
+
+ g_signal_connect (priv->close_button, "clicked", G_CALLBACK (close_clicked_cb), popover);
+
+ g_object_bind_property (priv->reveal_button, "active",
+ priv->revealer, "reveal-child",
+ G_BINDING_DEFAULT);
+
+ update_sensitivity (popover);
+
+ g_signal_connect (priv->search_entry, "search-changed", G_CALLBACK (search_text_changed_cb), popover);
+ g_signal_connect (priv->match_case_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), popover);
+ g_signal_connect (priv->entire_word_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), popover);
+ g_signal_connect (priv->regex_checkbutton, "toggled", G_CALLBACK (search_parameters_changed_cb), popover);
+
+ g_signal_connect (priv->wrap_around_checkbutton, "toggled", G_CALLBACK (wrap_around_toggled_cb), popover);
+
+ g_signal_connect (popover, "key-press-event", G_CALLBACK (key_press_cb), nullptr);
+
+ if (terminal_app_get_dialog_use_headerbar (terminal_app_get ())) {
+ GtkWidget *headerbar;
+
+ headerbar = (GtkWidget*)g_object_new (GTK_TYPE_HEADER_BAR,
+ "title", gtk_window_get_title (window),
+ "has-subtitle", FALSE,
+ "show-close-button", TRUE,
+ "visible", TRUE,
+ nullptr);
+ gtk_style_context_add_class (gtk_widget_get_style_context (headerbar),
+ "default-decoration");
+ gtk_window_set_titlebar (window, headerbar);
+ }
+}
+
+static void
+terminal_search_popover_finalize (GObject *object)
+{
+ TerminalSearchPopover *popover = TERMINAL_SEARCH_POPOVER (object);
+ TerminalSearchPopoverPrivate *priv = PRIV (popover);
+
+ if (priv->regex) {
+ vte_regex_unref (priv->regex);
+ }
+
+ g_free (priv->regex_pattern);
+
+ G_OBJECT_CLASS (terminal_search_popover_parent_class)->finalize (object);
+}
+
+static void
+terminal_search_popover_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalSearchPopover *popover = TERMINAL_SEARCH_POPOVER (object);
+
+ switch (prop_id) {
+ case PROP_REGEX:
+ g_value_set_boxed (value, terminal_search_popover_get_regex (popover));
+ break;
+ case PROP_WRAP_AROUND:
+ g_value_set_boolean (value, terminal_search_popover_get_wrap_around (popover));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_search_popover_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id) {
+ case PROP_REGEX:
+ case PROP_WRAP_AROUND:
+ /* not writable */
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_search_popover_class_init (TerminalSearchPopoverClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->finalize = terminal_search_popover_finalize;
+ gobject_class->get_property = terminal_search_popover_get_property;
+ gobject_class->set_property = terminal_search_popover_set_property;
+
+ widget_class->grab_focus = terminal_search_popover_grab_focus;
+
+ signals[SEARCH] =
+ g_signal_new (I_("search"),
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalSearchPopoverClass, search),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_BOOLEAN);
+
+ pspecs[PROP_REGEX] =
+ g_param_spec_boxed ("regex", nullptr, nullptr,
+ VTE_TYPE_REGEX,
+ GParamFlags(G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ pspecs[PROP_WRAP_AROUND] =
+ g_param_spec_boolean ("wrap-around", nullptr, nullptr,
+ FALSE,
+ GParamFlags(G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_properties (gobject_class, G_N_ELEMENTS (pspecs), pspecs);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/terminal/ui/search-popover.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, search_entry);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, search_prev_button);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, search_next_button);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, reveal_button);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, close_button);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, revealer);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, match_case_checkbutton);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, entire_word_checkbutton);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, regex_checkbutton);
+ gtk_widget_class_bind_template_child_private (widget_class, TerminalSearchPopover, wrap_around_checkbutton);
+}
+
+/* public API */
+
+/**
+ * terminal_search_popover_new:
+ *
+ * Returns: a new #TerminalSearchPopover
+ */
+TerminalSearchPopover *
+terminal_search_popover_new (GtkWidget *relative_to_widget)
+{
+ return reinterpret_cast<TerminalSearchPopover*>
+ (g_object_new (TERMINAL_TYPE_SEARCH_POPOVER,
+#if 0
+ "relative-to", relative_to_widget,
+#else
+ "transient-for", gtk_widget_get_toplevel (relative_to_widget),
+#endif
+ nullptr));
+}
+
+/**
+ * terminal_search_popover_get_regex:
+ * @popover: a #TerminalSearchPopover
+ *
+ * Returns: (transfer none): the search regex, or %nullptr
+ */
+VteRegex *
+terminal_search_popover_get_regex (TerminalSearchPopover *popover)
+{
+ g_return_val_if_fail (TERMINAL_IS_SEARCH_POPOVER (popover), nullptr);
+
+ return PRIV (popover)->regex;
+}
+
+/**
+ * terminal_search_popover_get_wrap_around:
+ * @popover: a #TerminalSearchPopover
+ *
+ * Returns: (transfer none): whether search should wrap around
+ */
+gboolean
+terminal_search_popover_get_wrap_around (TerminalSearchPopover *popover)
+{
+ g_return_val_if_fail (TERMINAL_IS_SEARCH_POPOVER (popover), FALSE);
+
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (PRIV (popover)->wrap_around_checkbutton));
+}
diff --git a/src/terminal-search-popover.hh b/src/terminal-search-popover.hh
new file mode 100644
index 0000000..b7af128
--- /dev/null
+++ b/src/terminal-search-popover.hh
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2008 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/>.
+ */
+
+#ifndef TERMINAL_SEARCH_POPOVER_H
+#define TERMINAL_SEARCH_POPOVER_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SEARCH_POPOVER (terminal_search_popover_get_type ())
+#define TERMINAL_SEARCH_POPOVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_SEARCH_POPOVER, TerminalSearchPopover))
+#define TERMINAL_SEARCH_POPOVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_SEARCH_POPOVER, TerminalSearchPopoverClass))
+#define TERMINAL_IS_SEARCH_POPOVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_SEARCH_POPOVER))
+#define TERMINAL_IS_SEARCH_POPOVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_SEARCH_POPOVER))
+#define TERMINAL_SEARCH_POPOVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_SEARCH_POPOVER, TerminalSearchPopoverClass))
+
+typedef struct _TerminalSearchPopover TerminalSearchPopover;
+typedef struct _TerminalSearchPopoverClass TerminalSearchPopoverClass;
+
+GType terminal_search_popover_get_type (void);
+
+TerminalSearchPopover *terminal_search_popover_new (GtkWidget *relative_to_widget);
+
+VteRegex *
+ terminal_search_popover_get_regex (TerminalSearchPopover *popover);
+
+gboolean terminal_search_popover_get_wrap_around (TerminalSearchPopover *popover);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_SEARCH_POPOVER_H */
diff --git a/src/terminal-search-provider.cc b/src/terminal-search-provider.cc
new file mode 100644
index 0000000..deb5dc1
--- /dev/null
+++ b/src/terminal-search-provider.cc
@@ -0,0 +1,380 @@
+/*
+ * Copyright © 2013, 2014 Red Hat, Inc.
+ *
+ * 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 <string.h>
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-screen-container.hh"
+#include "terminal-search-provider.hh"
+#include "terminal-search-provider-gdbus-generated.h"
+#include "terminal-window.hh"
+
+struct _TerminalSearchProvider
+{
+ GObject parent;
+
+ TerminalSearchProvider2 *skeleton;
+};
+
+struct _TerminalSearchProviderClass
+{
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (TerminalSearchProvider, terminal_search_provider, G_TYPE_OBJECT)
+
+static char *
+normalize_casefold_and_unaccent (const char *str)
+{
+ gs_free char *casefolded = nullptr, *normalized = nullptr;
+ char *retval = nullptr;
+
+ if (str == nullptr)
+ goto out;
+
+ normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL_COMPOSE);
+ casefolded = g_utf8_casefold (normalized, -1);
+ retval = g_str_to_ascii (casefolded, nullptr);
+
+ out:
+ return retval;
+}
+
+static char **
+normalize_casefold_and_unaccent_terms (const char* const *terms)
+{
+ char **casefolded_terms;
+ guint i, n;
+
+ n = g_strv_length ((char **) terms);
+ casefolded_terms = g_new (char *, n + 1);
+
+ for (i = 0; i < n; i++)
+ casefolded_terms[i] = normalize_casefold_and_unaccent (terms[i]);
+ casefolded_terms[n] = nullptr;
+
+ return casefolded_terms;
+}
+
+static gboolean
+match_terms (const char *str,
+ const char* const *terms)
+{
+ gs_free char *casefolded_str = nullptr;
+ gboolean matches = TRUE;
+ guint i;
+
+ if (str == nullptr)
+ {
+ matches = FALSE;
+ goto out;
+ }
+
+ casefolded_str = normalize_casefold_and_unaccent (str);
+ for (i = 0; terms[i] != nullptr; i++)
+ {
+ if (strstr (casefolded_str, terms[i]) == nullptr)
+ {
+ matches = FALSE;
+ break;
+ }
+ }
+
+ out:
+ return matches;
+}
+
+static gboolean
+handle_get_initial_result_set_cb (TerminalSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *const *terms,
+ gpointer user_data)
+{
+ GList *l, *screens = nullptr, *windows;
+ gs_unref_ptrarray GPtrArray *results;
+ TerminalApp *app;
+ gs_strfreev char **casefolded_terms = nullptr;
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "GetInitialResultSet started\n");
+
+ app = terminal_app_get ();
+ windows = gtk_application_get_windows (GTK_APPLICATION (app));
+ for (l = windows; l != nullptr; l = l->next)
+ {
+ TerminalWindow *window = (TerminalWindow*)(l->data);
+ GList *c, *containers;
+
+ if (!TERMINAL_IS_WINDOW (l->data))
+ continue;
+
+ containers = terminal_window_list_screen_containers (window);
+ for (c = containers; c != nullptr; c = c->next)
+ {
+ TerminalScreenContainer *container = TERMINAL_SCREEN_CONTAINER (c->data);
+ TerminalScreen *screen;
+
+ screen = terminal_screen_container_get_screen (container);
+ screens = g_list_prepend (screens, screen);
+ }
+ }
+
+ casefolded_terms = normalize_casefold_and_unaccent_terms (terms);
+ results = g_ptr_array_new_with_free_func (g_free);
+
+ for (l = screens; l != nullptr; l = l->next)
+ {
+ TerminalScreen *screen = TERMINAL_SCREEN (l->data);
+ gs_free char *cmdline = nullptr, *process = nullptr;
+ const char *cwd, *title;
+
+ cwd = vte_terminal_get_current_directory_uri (VTE_TERMINAL (screen));
+ title = terminal_screen_get_title (screen);
+ terminal_screen_has_foreground_process (screen, &process, &cmdline);
+ if (match_terms (cwd, (const char *const *) casefolded_terms) ||
+ match_terms (title, (const char *const *) casefolded_terms) ||
+ match_terms (process, (const char *const *) casefolded_terms) ||
+ match_terms (cmdline, (const char *const *) casefolded_terms))
+ {
+ const char *uuid;
+
+ uuid = terminal_screen_get_uuid (screen);
+ g_ptr_array_add (results, g_strdup (uuid));
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "Search hit: %s\n", uuid);
+ }
+ }
+
+ g_ptr_array_add (results, nullptr);
+ terminal_search_provider2_complete_get_initial_result_set (skeleton,
+ invocation,
+ (const char *const *) results->pdata);
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "GetInitialResultSet completed\n");
+ return TRUE;
+}
+
+static gboolean
+handle_get_subsearch_result_set_cb (TerminalSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *const *previous_results,
+ const char *const *terms,
+ gpointer user_data)
+{
+ gs_unref_ptrarray GPtrArray *results;
+ TerminalApp *app;
+ gs_strfreev char **casefolded_terms = nullptr;
+ guint i;
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "GetSubsearchResultSet started\n");
+
+ app = terminal_app_get ();
+ casefolded_terms = normalize_casefold_and_unaccent_terms (terms);
+ results = g_ptr_array_new_with_free_func (g_free);
+
+ for (i = 0; previous_results[i] != nullptr; i++)
+ {
+ TerminalScreen *screen;
+ gs_free char *cmdline = nullptr, *process = nullptr;
+ const char *cwd, *title;
+
+ screen = terminal_app_get_screen_by_uuid (app, previous_results[i]);
+ if (screen == nullptr)
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "Not a screen: %s\n", previous_results[i]);
+ continue;
+ }
+
+ cwd = vte_terminal_get_current_directory_uri (VTE_TERMINAL (screen));
+ title = terminal_screen_get_title (screen);
+ terminal_screen_has_foreground_process (screen, &process, &cmdline);
+ if (match_terms (cwd, (const char *const *) casefolded_terms) ||
+ match_terms (title, (const char *const *) casefolded_terms) ||
+ match_terms (process, (const char *const *) casefolded_terms) ||
+ match_terms (cmdline, (const char *const *) casefolded_terms))
+ {
+ g_ptr_array_add (results, g_strdup (previous_results[i]));
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "Search hit: %s\n", previous_results[i]);
+ }
+ }
+
+ g_ptr_array_add (results, nullptr);
+ terminal_search_provider2_complete_get_subsearch_result_set (skeleton,
+ invocation,
+ (const char *const *) results->pdata);
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "GetSubsearchResultSet completed\n");
+ return TRUE;
+}
+
+static gboolean
+handle_get_result_metas_cb (TerminalSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *const *results,
+ gpointer user_data)
+{
+ GVariantBuilder builder;
+ TerminalApp *app;
+ guint i;
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "GetResultMetas started\n");
+
+ app = terminal_app_get ();
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+ for (i = 0; results[i] != nullptr; i++)
+ {
+ TerminalScreen *screen;
+ const char *title;
+ gs_free char *escaped_text = nullptr;
+ gs_free char *text = nullptr;
+
+ screen = terminal_app_get_screen_by_uuid (app, results[i]);
+ if (screen == nullptr)
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "Not a screen: %s\n", results[i]);
+ continue;
+ }
+
+ title = terminal_screen_get_title (screen);
+ if (terminal_screen_has_foreground_process (screen, nullptr, nullptr)) {
+ VteTerminal *terminal = VTE_TERMINAL (screen);
+ long cursor_row;
+
+ vte_terminal_get_cursor_position (terminal, nullptr, &cursor_row);
+ text = vte_terminal_get_text_range (terminal,
+ MAX(0, cursor_row - 1),
+ 0,
+ cursor_row + 1,
+ vte_terminal_get_column_count (terminal) - 1,
+ nullptr, nullptr, nullptr);
+ }
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder, "{sv}", "id", g_variant_new_string (results[i]));
+ g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string (title));
+ if (text != nullptr)
+ {
+ escaped_text = g_markup_escape_text (text, -1);
+ g_variant_builder_add (&builder, "{sv}", "description", g_variant_new_string (escaped_text));
+ }
+ g_variant_builder_close (&builder);
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "Meta for %s: %s\n", results[i], title);
+ }
+
+ terminal_search_provider2_complete_get_result_metas (skeleton, invocation, g_variant_builder_end (&builder));
+
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "GetResultMetas completed\n");
+
+ return TRUE;
+}
+
+static gboolean
+handle_activate_result_cb (TerminalSearchProvider2 *skeleton,
+ GDBusMethodInvocation *invocation,
+ const char *identifier,
+ const char* const *terms,
+ guint timestamp,
+ gpointer user_data)
+{
+ GtkWidget *toplevel;
+ TerminalApp *app;
+ TerminalScreen *screen;
+
+ app = terminal_app_get ();
+ screen = terminal_app_get_screen_by_uuid (app, identifier);
+ if (screen == nullptr)
+ goto out;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (screen));
+ if (!gtk_widget_is_toplevel (toplevel))
+ goto out;
+
+ terminal_window_switch_screen (TERMINAL_WINDOW (toplevel), screen);
+ gtk_window_present_with_time (GTK_WINDOW (toplevel), timestamp);
+ _terminal_debug_print (TERMINAL_DEBUG_SEARCH, "ActivateResult: %s\n", identifier);
+
+ out:
+ terminal_search_provider2_complete_activate_result (skeleton, invocation);
+ return TRUE;
+}
+
+static void
+terminal_search_provider_init (TerminalSearchProvider *provider)
+{
+ provider->skeleton = terminal_search_provider2_skeleton_new ();
+
+ g_signal_connect (provider->skeleton, "handle-get-initial-result-set",
+ G_CALLBACK (handle_get_initial_result_set_cb), provider);
+ g_signal_connect (provider->skeleton, "handle-get-subsearch-result-set",
+ G_CALLBACK (handle_get_subsearch_result_set_cb), provider);
+ g_signal_connect (provider->skeleton, "handle-get-result-metas",
+ G_CALLBACK (handle_get_result_metas_cb), provider);
+ g_signal_connect (provider->skeleton, "handle-activate-result",
+ G_CALLBACK (handle_activate_result_cb), provider);
+}
+
+static void
+terminal_search_provider_dispose (GObject *object)
+{
+ TerminalSearchProvider *provider = TERMINAL_SEARCH_PROVIDER (object);
+
+ g_clear_object (&provider->skeleton);
+
+ G_OBJECT_CLASS (terminal_search_provider_parent_class)->dispose (object);
+}
+
+static void
+terminal_search_provider_class_init (TerminalSearchProviderClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->dispose = terminal_search_provider_dispose;
+}
+
+TerminalSearchProvider *
+terminal_search_provider_new (void)
+{
+ return reinterpret_cast<TerminalSearchProvider*>
+ (g_object_new (TERMINAL_TYPE_SEARCH_PROVIDER, nullptr));
+}
+
+gboolean
+terminal_search_provider_dbus_register (TerminalSearchProvider *provider,
+ GDBusConnection *connection,
+ const char *object_path,
+ GError **error)
+{
+ return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (provider->skeleton),
+ connection,
+ object_path,
+ error);
+}
+
+void
+terminal_search_provider_dbus_unregister (TerminalSearchProvider *provider,
+ GDBusConnection *connection,
+ const char *object_path)
+{
+ if (g_dbus_interface_skeleton_has_connection (G_DBUS_INTERFACE_SKELETON (provider->skeleton), connection))
+ g_dbus_interface_skeleton_unexport_from_connection (G_DBUS_INTERFACE_SKELETON (provider->skeleton),
+ connection);
+}
diff --git a/src/terminal-search-provider.hh b/src/terminal-search-provider.hh
new file mode 100644
index 0000000..719bd3e
--- /dev/null
+++ b/src/terminal-search-provider.hh
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef TERMINAL_SEARCH_PROVIDER_H
+#define TERMINAL_SEARCH_PROVIDER_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SEARCH_PROVIDER (terminal_search_provider_get_type ())
+#define TERMINAL_SEARCH_PROVIDER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TERMINAL_TYPE_SEARCH_PROVIDER, TerminalSearchProvider))
+#define TERMINAL_SEARCH_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TERMINAL_TYPE_SEARCH_PROVIDER, TerminalSearchProviderClass))
+#define TERMINAL_IS_SEARCH_PROVIDER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TERMINAL_TYPE_SEARCH_PROVIDER))
+#define TERMINAL_IS_SEARCH_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TERMINAL_TYPE_SEARCH_PROVIDER))
+#define TERMINAL_SEARCH_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TERMINAL_TYPE_SEARCH_PROVIDER, TerminalSearchProviderClass))
+
+typedef struct _TerminalSearchProvider TerminalSearchProvider;
+typedef struct _TerminalSearchProviderClass TerminalSearchProviderClass;
+
+GType terminal_search_provider_get_type (void);
+
+TerminalSearchProvider *terminal_search_provider_new (void);
+
+gboolean terminal_search_provider_dbus_register (TerminalSearchProvider *provider,
+ GDBusConnection *connection,
+ const char *object_path,
+ GError **error);
+
+void terminal_search_provider_dbus_unregister (TerminalSearchProvider *provider,
+ GDBusConnection *connection,
+ const char *object_path);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_SEARCH_PROVIDER_H */
diff --git a/src/terminal-settings-bridge-backend.cc b/src/terminal-settings-bridge-backend.cc
new file mode 100644
index 0000000..4e950de
--- /dev/null
+++ b/src/terminal-settings-bridge-backend.cc
@@ -0,0 +1,563 @@
+/*
+ * Copyright © 2008, 2010, 2011, 2022 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"
+#define G_SETTINGS_ENABLE_BACKEND
+
+#include <cassert>
+
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-settings-bridge-backend.hh"
+#include "terminal-settings-utils.hh"
+#include "terminal-settings-bridge-generated.h"
+
+#include <gio/gio.h>
+#include <gio/gsettingsbackend.h>
+
+struct _TerminalSettingsBridgeBackend {
+ GSettingsBackend parent_instance;
+
+ TerminalSettingsBridge* bridge;
+ GCancellable* cancellable;
+
+ GHashTable* cache;
+};
+
+struct _TerminalSettingsBridgeBackendClass {
+ GSettingsBackendClass parent_class;
+};
+
+enum {
+ PROP_SETTINGS_BRIDGE = 1,
+};
+
+#define PRIORITY (10000)
+
+// _g_io_modules_ensure_extension_points_registered() is not public,
+// so just ensure a type that does this call on registration, which
+// all of the glib-internal settings backends do. However as an added
+// complication, none of their get_type() functions are public. So
+// instead we need to create an object using the public function and
+// immediately delete it again.
+// However, this *still* does not work; the
+// g_null_settings_backend_new() call prints the warning about a non-
+// registered extension point even though its get_type() function calls
+// _g_io_modules_ensure_extension_points_registered() immediately before.
+// Therefore we can only use this backend when creating a GSettings
+// ourself, by explicitly passing it at that time.
+
+G_DEFINE_TYPE_WITH_CODE(TerminalSettingsBridgeBackend,
+ terminal_settings_bridge_backend,
+ G_TYPE_SETTINGS_BACKEND,
+ // _g_io_modules_ensure_extension_points_registered();
+ // { gs_unref_object auto dummy = g_null_settings_backend_new(); }
+ //
+ // g_io_extension_point_implement(G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
+ // g_define_type_id, "bridge", PRIORITY)
+);
+
+// Note that since D-Bus doesn't support maybe values, we use arrays
+// with either zero or one item to send/receive a maybe.
+// If we get more than one item, just use the first one.
+
+/* helper functions */
+
+template<typename T>
+inline constexpr auto
+IMPL(T* that) noexcept
+{
+ return reinterpret_cast<TerminalSettingsBridgeBackend*>(that);
+}
+
+typedef struct {
+ GVariant* value;
+ bool value_set;
+ bool writable;
+ bool writable_set;
+} CacheEntry;
+
+static auto
+cache_entry_new(void)
+{
+ return g_new0(CacheEntry, 1);
+}
+
+static void
+cache_entry_free(CacheEntry* e) noexcept
+{
+ if (e->value)
+ g_variant_unref(e->value);
+ g_free(e);
+}
+
+static auto
+cache_lookup_entry(TerminalSettingsBridgeBackend* impl,
+ char const* key) noexcept
+{
+ return reinterpret_cast<CacheEntry*>(g_hash_table_lookup(impl->cache, key));
+}
+
+static auto
+cache_ensure_entry(TerminalSettingsBridgeBackend* impl,
+ char const* key) noexcept
+{
+ g_hash_table_insert(impl->cache, g_strdup(key), cache_entry_new());
+ return cache_lookup_entry(impl, key);
+}
+
+static void
+cache_insert_value(TerminalSettingsBridgeBackend* impl,
+ char const* key,
+ GVariant* value) noexcept
+{
+ auto const ce = cache_ensure_entry(impl, key);
+ g_clear_pointer(&ce->value, g_variant_unref);
+ ce->value = value ? g_variant_ref(value) : nullptr;
+ ce->value_set = true;
+}
+
+static void
+cache_insert_writable(TerminalSettingsBridgeBackend* impl,
+ char const* key,
+ bool writable) noexcept
+{
+ auto const ce = cache_ensure_entry(impl, key);
+ ce->writable = writable;
+ ce->writable_set = true;
+}
+
+static void
+cache_remove_path(TerminalSettingsBridgeBackend* impl,
+ char const* path) noexcept
+{
+ auto iter = GHashTableIter{};
+ g_hash_table_iter_init(&iter, impl->cache);
+ void* keyp = nullptr;
+ void* valuep = nullptr;
+ while (g_hash_table_iter_next(&iter, &keyp, &valuep)) {
+ auto const key = reinterpret_cast<char const*>(keyp);
+ if (g_str_has_prefix(key, path)) {
+ auto ce = reinterpret_cast<CacheEntry*>(valuep);
+ g_clear_pointer(&ce->value, g_variant_unref);
+ ce->value_set = false;
+ }
+ }
+}
+
+static void
+cache_remove_value(TerminalSettingsBridgeBackend* impl,
+ char const* key) noexcept
+{
+ auto const ce = cache_ensure_entry(impl, key);
+ g_clear_pointer(&ce->value, g_variant_unref);
+ ce->value_set = false;
+}
+
+static void
+cache_remove_writable(TerminalSettingsBridgeBackend* impl,
+ char const* key) noexcept
+{
+ auto const ce = cache_ensure_entry(impl, key);
+ ce->writable_set = false;
+}
+
+/* GSettingsBackend class implementation */
+
+static GPermission*
+terminal_settings_bridge_backend_get_permission(GSettingsBackend* backend,
+ char const* path) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::get_permission\n");
+
+ return g_simple_permission_new(true);
+}
+
+static gboolean
+terminal_settings_bridge_backend_get_writable(GSettingsBackend* backend,
+ char const* key) noexcept
+{
+ auto const impl = IMPL(backend);
+
+ auto const ce = cache_lookup_entry(impl, key);
+ if (ce && ce->writable_set)
+ return ce->writable;
+
+ auto writable = gboolean{false};
+ auto const r =
+ terminal_settings_bridge_call_get_writable_sync(impl->bridge,
+ key,
+ &writable,
+ impl->cancellable,
+ nullptr);
+
+ if (r)
+ cache_insert_writable(impl, key, writable);
+ else
+ cache_remove_writable(impl, key);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::get_writable key %s success %d writable %d\n",
+ key, r, writable);
+
+ return writable;
+}
+
+static GVariant*
+terminal_settings_bridge_backend_read(GSettingsBackend* backend,
+ char const* key,
+ GVariantType const* type,
+ gboolean default_value) noexcept
+{
+ if (default_value)
+ return nullptr;
+
+ auto const impl = IMPL(backend);
+ auto const ce = cache_lookup_entry(impl, key);
+ if (ce && ce->value_set)
+ return ce->value ? g_variant_ref(ce->value) : nullptr;
+
+ gs_unref_variant GVariant* rv = nullptr;
+ auto r =
+ terminal_settings_bridge_call_read_sync(impl->bridge,
+ key,
+ g_variant_type_peek_string(type),
+ default_value,
+ &rv,
+ impl->cancellable,
+ nullptr);
+
+ auto const value = r ? terminal_g_variant_unwrap(rv) : nullptr;
+
+ if (r && value && !g_variant_is_of_type(value, type)) {
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::read key %s got type %s expected type %s\n",
+ key,
+ g_variant_get_type_string(value),
+ g_variant_type_peek_string(type));
+
+ g_clear_pointer(&value, g_variant_unref);
+ r = false;
+ }
+
+ if (r)
+ cache_insert_value(impl, key, value);
+ else
+ cache_remove_value(impl, key);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::read key %s success %d value %s\n",
+ key, r, value ? g_variant_print(value, true) : "(null)");
+
+ return value;
+}
+
+static GVariant*
+terminal_settings_bridge_backend_read_user_value(GSettingsBackend* backend,
+ char const* key,
+ GVariantType const* type) noexcept
+{
+ auto const impl = IMPL(backend);
+
+ auto const ce = cache_lookup_entry(impl, key);
+ if (ce && ce->value_set)
+ return ce->value ? g_variant_ref(ce->value) : nullptr;
+
+ gs_unref_variant GVariant* rv = nullptr;
+ auto r =
+ terminal_settings_bridge_call_read_user_value_sync(impl->bridge,
+ key,
+ g_variant_type_peek_string(type),
+ &rv,
+ impl->cancellable,
+ nullptr);
+
+ auto const value = r ? terminal_g_variant_unwrap(rv) : nullptr;
+
+ if (r && value && !g_variant_is_of_type(value, type)) {
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::read_user_value key %s got type %s expected type %s\n",
+ key,
+ g_variant_get_type_string(value),
+ g_variant_type_peek_string(type));
+
+ g_clear_pointer(&value, g_variant_unref);
+ r = false;
+ }
+
+ if (r)
+ cache_insert_value(impl, key, value);
+ else
+ cache_remove_value(impl, key);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::read_user_value key %s success %d value %s\n",
+ key, r, value ? g_variant_print(value, true) : "(null)");
+
+ return value;
+}
+
+static void
+terminal_settings_bridge_backend_reset(GSettingsBackend* backend,
+ char const* key,
+ void* tag) noexcept
+{
+ auto const impl = IMPL(backend);
+ auto const r =
+ terminal_settings_bridge_call_reset_sync(impl->bridge,
+ key,
+ impl->cancellable,
+ nullptr);
+
+ cache_remove_value(impl, key);
+
+ g_settings_backend_changed(backend, key, tag);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::reset key %s success %d\n",
+ key, r);
+}
+
+static void
+terminal_settings_bridge_backend_sync(GSettingsBackend* backend) noexcept
+{
+ auto const impl = IMPL(backend);
+ terminal_settings_bridge_call_sync_sync(impl->bridge,
+ impl->cancellable,
+ nullptr);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::sync\n");
+}
+
+static void
+terminal_settings_bridge_backend_subscribe(GSettingsBackend* backend,
+ char const* name) noexcept
+{
+ auto const impl = IMPL(backend);
+ terminal_settings_bridge_call_subscribe_sync(impl->bridge,
+ name,
+ impl->cancellable,
+ nullptr);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::subscribe name %s\n", name);
+}
+
+static void
+terminal_settings_bridge_backend_unsubscribe(GSettingsBackend* backend,
+ char const* name) noexcept
+{
+ auto const impl = IMPL(backend);
+ terminal_settings_bridge_call_unsubscribe_sync(impl->bridge,
+ name,
+ impl->cancellable,
+ nullptr);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::unsubscribe name %s\n", name);
+}
+
+static gboolean
+terminal_settings_bridge_backend_write(GSettingsBackend* backend,
+ char const* key,
+ GVariant* value,
+ void* tag) noexcept
+{
+ auto const impl = IMPL(backend);
+
+ gs_unref_variant auto holder = g_variant_ref_sink(value);
+
+ auto success = gboolean{false};
+ auto const r =
+ terminal_settings_bridge_call_write_sync(impl->bridge,
+ key,
+ terminal_g_variant_wrap(value),
+ &success,
+ impl->cancellable,
+ nullptr);
+
+ cache_insert_value(impl, key, value);
+
+ g_settings_backend_changed(backend, key, tag);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::write key %s value %s success %d\n",
+ key, value ? g_variant_print(value, true) : "(null)", r);
+
+ return r && success;
+}
+
+static gboolean
+terminal_settings_bridge_backend_write_tree(GSettingsBackend* backend,
+ GTree* tree,
+ void* tag) noexcept
+{
+ auto const impl = IMPL(backend);
+
+ gs_free char* path_prefix = nullptr;
+ gs_free char const** keys = nullptr;
+ gs_free GVariant** values = nullptr;
+ g_settings_backend_flatten_tree(tree,
+ &path_prefix,
+ &keys,
+ &values);
+
+ auto builder = GVariantBuilder{};
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a(smv)"));
+ for (auto i = 0; keys[i]; ++i) {
+ gs_unref_variant auto value = values[i] ? g_variant_ref_sink(values[i]) : nullptr;
+
+ g_variant_builder_add(&builder,
+ "(smv)",
+ keys[i],
+ value ? g_variant_new_variant(value) : nullptr);
+
+ gs_free auto wkey = g_strconcat(path_prefix, keys[i], nullptr);
+ // Directory reset?
+ if (g_str_has_suffix(wkey, "/")) {
+ g_warn_if_fail(!value);
+ cache_remove_path(impl, wkey);
+ } else {
+ cache_insert_value(impl, wkey, value);
+ }
+ }
+
+ auto const tree_value = terminal_g_variant_wrap(g_variant_builder_end(&builder));
+
+ auto success = gboolean{false};
+ auto const r =
+ terminal_settings_bridge_call_write_tree_sync(impl->bridge,
+ path_prefix,
+ tree_value, // consumed
+ &success,
+ impl->cancellable,
+ nullptr);
+
+ g_settings_backend_changed_tree(backend, tree, tag);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::write_tree success %d\n",
+ r);
+
+ return r && success;
+}
+
+/* GObject class implementation */
+
+static void
+terminal_settings_bridge_backend_init(TerminalSettingsBridgeBackend* backend) /* noexcept */
+{
+ auto const impl = IMPL(backend);
+
+ // Note that unfortunately it appears to be impossible to receive all
+ // change notifications from a GSettingsBackend directly, so we cannot
+ // get forwarded change notifications from the bridge. Instead, we have
+ // to cache written values (since the actual write happens delayed in
+ // the remote backend and the next read may still return the old value
+ // otherwise).
+ impl->cache = g_hash_table_new_full(g_str_hash,
+ g_str_equal,
+ g_free,
+ GDestroyNotify(cache_entry_free));
+}
+
+static void
+terminal_settings_bridge_backend_constructed(GObject* object) noexcept
+{
+ G_OBJECT_CLASS(terminal_settings_bridge_backend_parent_class)->constructed(object);
+
+ auto const impl = IMPL(object);
+ assert(impl->bridge);
+}
+
+static void
+terminal_settings_bridge_backend_finalize(GObject* object) noexcept
+{
+ auto const impl = IMPL(object);
+ g_clear_pointer(&impl->cache, g_hash_table_unref);
+ g_clear_object(&impl->cancellable);
+ g_clear_object(&impl->bridge);
+
+ G_OBJECT_CLASS(terminal_settings_bridge_backend_parent_class)->finalize(object);
+}
+
+static void
+terminal_settings_bridge_backend_set_property(GObject* object,
+ guint prop_id,
+ GValue const* value,
+ GParamSpec* pspec) noexcept
+{
+ auto const impl = IMPL(object);
+
+ switch (prop_id) {
+ case PROP_SETTINGS_BRIDGE:
+ impl->bridge = TERMINAL_SETTINGS_BRIDGE(g_value_dup_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_settings_bridge_backend_class_init(TerminalSettingsBridgeBackendClass* klass) /* noexcept */
+{
+ auto const gobject_class = G_OBJECT_CLASS(klass);
+ gobject_class->constructed = terminal_settings_bridge_backend_constructed;
+ gobject_class->finalize = terminal_settings_bridge_backend_finalize;
+ gobject_class->set_property = terminal_settings_bridge_backend_set_property;
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SETTINGS_BRIDGE,
+ g_param_spec_object("settings-bridge", nullptr, nullptr,
+ TERMINAL_TYPE_SETTINGS_BRIDGE,
+ GParamFlags(G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS)));
+
+ auto const backend_class = G_SETTINGS_BACKEND_CLASS(klass);
+ backend_class->get_permission = terminal_settings_bridge_backend_get_permission;
+ backend_class->get_writable = terminal_settings_bridge_backend_get_writable;
+ backend_class->read = terminal_settings_bridge_backend_read;
+ backend_class->read_user_value = terminal_settings_bridge_backend_read_user_value;
+ backend_class->reset = terminal_settings_bridge_backend_reset;
+ backend_class->subscribe = terminal_settings_bridge_backend_subscribe;
+ backend_class->sync = terminal_settings_bridge_backend_sync;
+ backend_class->unsubscribe = terminal_settings_bridge_backend_unsubscribe;
+ backend_class->write = terminal_settings_bridge_backend_write;
+ backend_class->write_tree = terminal_settings_bridge_backend_write_tree;
+}
+
+/* public API */
+
+/**
+ * terminal_settings_bridge_backend_new:
+ * @bridge: a #TerminalSettingsBridge
+ *
+ * Returns: (transfer full): a new #TerminalSettingsBridgeBackend for @bridge
+ */
+GSettingsBackend*
+terminal_settings_bridge_backend_new(TerminalSettingsBridge* bridge)
+{
+ return reinterpret_cast<GSettingsBackend*>
+ (g_object_new (TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND,
+ "settings-bridge", bridge,
+ nullptr));
+}
diff --git a/src/terminal-settings-bridge-backend.hh b/src/terminal-settings-bridge-backend.hh
new file mode 100644
index 0000000..2b7c188
--- /dev/null
+++ b/src/terminal-settings-bridge-backend.hh
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2008, 2010, 2022 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/>.
+ */
+
+#pragma once
+
+#include "terminal-settings-bridge-generated.h"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND (terminal_settings_bridge_backend_get_type ())
+#define TERMINAL_SETTINGS_BRIDGE_BACKEND(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, TerminalSettingsBridgeBackend))
+#define TERMINAL_SETTINGS_BRIDGE_BACKEND_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, TerminalSettingsBridgeBackendClass))
+#define TERMINAL_IS_SETTINGS_BRIDGE_BACKEND(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND))
+#define TERMINAL_IS_SETTINGS_BRIDGE_BACKEND_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND))
+#define TERMINAL_SETTINGS_BRIDGE_BACKEND_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_SETTINGS_BRIDGE_BACKEND, TerminalSettingsBridgeBackendClass))
+
+typedef struct _TerminalSettingsBridgeBackend TerminalSettingsBridgeBackend;
+typedef struct _TerminalSettingsBridgeBackendClass TerminalSettingsBridgeBackendClass;
+
+GType terminal_settings_bridge_backend_get_type(void);
+
+GSettingsBackend* terminal_settings_bridge_backend_new(TerminalSettingsBridge* bridge);
+
+G_END_DECLS
diff --git a/src/terminal-settings-bridge-impl.cc b/src/terminal-settings-bridge-impl.cc
new file mode 100644
index 0000000..da26e38
--- /dev/null
+++ b/src/terminal-settings-bridge-impl.cc
@@ -0,0 +1,402 @@
+/*
+ * Copyright © 2008, 2010, 2011, 2022 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"
+#define G_SETTINGS_ENABLE_BACKEND
+
+#include <cassert>
+
+#include "terminal-settings-bridge-impl.hh"
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+#include "terminal-settings-utils.hh"
+#include "terminal-settings-bridge-generated.h"
+
+#include <gio/gio.h>
+#include <gio/gsettingsbackend.h>
+
+enum {
+ PROP_SETTINGS_BACKEND = 1,
+};
+
+struct _TerminalSettingsBridgeImpl {
+ TerminalSettingsBridgeSkeleton parent_instance;
+
+ GSettingsBackend* backend;
+ void* tag;
+};
+
+struct _TerminalSettingsBridgeImplClass {
+ TerminalSettingsBridgeSkeletonClass parent_class;
+};
+
+/* helper functions */
+
+template<typename T>
+static inline constexpr auto
+IMPL(T* that) noexcept
+{
+ return reinterpret_cast<TerminalSettingsBridgeImpl*>(that);
+}
+
+static GVariantType*
+type_from_string(GDBusMethodInvocation* invocation,
+ char const* type) noexcept
+{
+ if (!g_variant_type_string_is_valid(type)) {
+ g_dbus_method_invocation_return_error(invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid type: %s",
+ type);
+ return nullptr;
+ }
+
+ return g_variant_type_new(type);
+}
+
+static auto
+value(GDBusMethodInvocation* invocation,
+ char const* format,
+ ...) noexcept
+{
+ va_list args;
+ va_start(args, format);
+ auto const v = g_variant_new_va(format, nullptr, &args);
+ va_end(args);
+ g_dbus_method_invocation_return_value(invocation, v);
+ return true;
+}
+
+static auto
+nothing(GDBusMethodInvocation* invocation) noexcept
+{
+ return value(invocation, "()");
+}
+
+static auto
+novalue(GDBusMethodInvocation* invocation) noexcept
+{
+ g_dbus_method_invocation_return_error_literal(invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "No value");
+ return true;
+}
+
+static auto
+success(GDBusMethodInvocation* invocation,
+ bool v = true) noexcept
+{
+ return value(invocation, "(b)", v);
+}
+
+/* TerminalSettingsBridge interface implementation */
+
+static gboolean
+terminal_settings_bridge_impl_get_permission(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* path) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::get_permission path %s\n",
+ path);
+
+ return novalue(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_get_writable(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* key) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::get_writable key %s\n",
+ key);
+
+ auto const impl = IMPL(object);
+ auto const v = terminal_g_settings_backend_get_writable(impl->backend, key);
+ return success(invocation, v);
+}
+
+static gboolean
+terminal_settings_bridge_impl_read(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* key,
+ char const* type,
+ gboolean default_value) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::read key %s type %s default %d\n",
+ key, type, default_value);
+
+ gs_free_variant_type auto vtype = type_from_string(invocation, type);
+ if (!vtype)
+ return true;
+
+ auto const impl = IMPL(object);
+ gs_unref_variant auto v = terminal_g_settings_backend_read(impl->backend,
+ key,
+ vtype,
+ default_value);
+ return value(invocation, "(@ay)", terminal_g_variant_wrap(v));
+}
+
+static gboolean
+terminal_settings_bridge_impl_read_user_value(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* key,
+ char const* type) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::read_user_value key %s type %s\n",
+ key, type);
+
+ gs_free_variant_type auto vtype = type_from_string(invocation, type);
+ if (!vtype)
+ return true;
+
+ auto const impl = IMPL(object);
+ gs_unref_variant auto v = terminal_g_settings_backend_read_user_value(impl->backend,
+ key,
+ vtype);
+ return value(invocation, "(@ay)", terminal_g_variant_wrap(v));
+}
+
+static gboolean
+terminal_settings_bridge_impl_reset(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* key) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::reset key %s\n",
+ key);
+
+ auto const impl = IMPL(object);
+ terminal_g_settings_backend_reset(impl->backend, key, impl->tag);
+ return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_subscribe(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* name) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::subscribe name %s\n",
+ name);
+
+ auto const impl = IMPL(object);
+ terminal_g_settings_backend_subscribe(impl->backend, name);
+ return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_sync(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::sync\n");
+
+ auto const impl = IMPL(object);
+ terminal_g_settings_backend_sync(impl->backend);
+ return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_unsubscribe(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* name) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::unsubscribe name %s\n",
+ name);
+
+ auto const impl = IMPL(object);
+ terminal_g_settings_backend_unsubscribe(impl->backend, name);
+ return nothing(invocation);
+}
+
+static gboolean
+terminal_settings_bridge_impl_write(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* key,
+ GVariant* value) noexcept
+{
+ auto const impl = IMPL(object);
+ gs_unref_variant auto v = terminal_g_variant_unwrap(value);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::write key %s value %s\n",
+ key, v ? g_variant_print(v, true): "(null)");
+
+ auto const r = terminal_g_settings_backend_write(impl->backend,
+ key,
+ v,
+ impl->tag);
+ return success(invocation, r);
+}
+
+static gboolean
+terminal_settings_bridge_impl_write_tree(TerminalSettingsBridge* object,
+ GDBusMethodInvocation* invocation,
+ char const* path_prefix,
+ GVariant* variant) noexcept
+{
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::write_tree path-prefix %s\n",
+ path_prefix);
+
+ gs_unref_variant auto tree_value = terminal_g_variant_unwrap(variant);
+ if (!tree_value ||
+ !g_variant_is_of_type(tree_value, G_VARIANT_TYPE("a(smv)"))) {
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge impl ::write_tree got type %s expected type a(smv)\n",
+ tree_value ? g_variant_get_type_string(tree_value) : "(null)");
+
+ g_dbus_method_invocation_return_error
+ (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid type: got type \"%s\" expected type \"a(smv)\"",
+ tree_value ? g_variant_get_type_string(tree_value) : "(null)");
+ return true;
+ }
+
+ auto const tree = terminal_g_settings_backend_create_tree();
+
+ auto iter = GVariantIter{};
+ g_variant_iter_init(&iter, tree_value);
+
+ char const* key = nullptr;
+ GVariant* value = nullptr;
+ while (g_variant_iter_loop(&iter, "(&smv)", &key, &value)) {
+ g_tree_insert(tree,
+ g_strconcat(path_prefix, key, nullptr), // adopts
+ value ? g_variant_get_variant(value) : nullptr);
+ }
+
+ auto const impl = IMPL(object);
+ auto const v = terminal_g_settings_backend_write_tree(impl->backend,
+ tree,
+ impl->tag);
+
+ g_tree_unref(tree);
+ return success(invocation, v);
+}
+
+static void
+terminal_settings_bridge_impl_iface_init(TerminalSettingsBridgeIface* iface) noexcept
+{
+ iface->handle_get_permission = terminal_settings_bridge_impl_get_permission;
+ iface->handle_get_writable = terminal_settings_bridge_impl_get_writable;
+ iface->handle_read = terminal_settings_bridge_impl_read;
+ iface->handle_read_user_value = terminal_settings_bridge_impl_read_user_value;
+ iface->handle_reset = terminal_settings_bridge_impl_reset;
+ iface->handle_subscribe = terminal_settings_bridge_impl_subscribe;
+ iface->handle_sync= terminal_settings_bridge_impl_sync;
+ iface->handle_unsubscribe = terminal_settings_bridge_impl_unsubscribe;
+ iface->handle_write = terminal_settings_bridge_impl_write;
+ iface->handle_write_tree = terminal_settings_bridge_impl_write_tree;
+}
+
+/* GObject class implementation */
+
+G_DEFINE_TYPE_WITH_CODE(TerminalSettingsBridgeImpl,
+ terminal_settings_bridge_impl,
+ TERMINAL_TYPE_SETTINGS_BRIDGE_SKELETON,
+ G_IMPLEMENT_INTERFACE(TERMINAL_TYPE_SETTINGS_BRIDGE,
+ terminal_settings_bridge_impl_iface_init));
+
+static void
+terminal_settings_bridge_impl_init(TerminalSettingsBridgeImpl* impl) /* noexcept */
+{
+ impl->tag = &impl->tag;
+}
+
+static void
+terminal_settings_bridge_impl_constructed(GObject* object) noexcept
+{
+ G_OBJECT_CLASS(terminal_settings_bridge_impl_parent_class)->constructed(object);
+
+ auto const impl = IMPL(object);
+ assert(impl->backend);
+}
+
+static void
+terminal_settings_bridge_impl_finalize(GObject* object) noexcept
+{
+ auto const impl = IMPL(object);
+ g_clear_object(&impl->backend);
+
+ G_OBJECT_CLASS(terminal_settings_bridge_impl_parent_class)->finalize(object);
+}
+
+static void
+terminal_settings_bridge_impl_set_property(GObject* object,
+ guint prop_id,
+ GValue const* value,
+ GParamSpec* pspec) noexcept
+{
+ auto const impl = IMPL(object);
+
+ switch (prop_id) {
+ case PROP_SETTINGS_BACKEND:
+ impl->backend = G_SETTINGS_BACKEND(g_value_dup_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_settings_bridge_impl_class_init(TerminalSettingsBridgeImplClass* klass) /* noexcept */
+{
+ auto const gobject_class = G_OBJECT_CLASS(klass);
+ gobject_class->constructed = terminal_settings_bridge_impl_constructed;
+ gobject_class->finalize = terminal_settings_bridge_impl_finalize;
+ gobject_class->set_property = terminal_settings_bridge_impl_set_property;
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SETTINGS_BACKEND,
+ g_param_spec_object("settings-backend", nullptr, nullptr,
+ G_TYPE_SETTINGS_BACKEND,
+ GParamFlags(G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS)));
+}
+
+/* public API */
+
+/**
+* terminal_settings_bridge_impl_new:
+* @backend: a #GSettingsBackend
+*
+* Returns: (transfer full): a new #TerminalSettingsBridgeImpl for @backend
+ */
+TerminalSettingsBridgeImpl*
+terminal_settings_bridge_impl_new(GSettingsBackend* backend)
+{
+ return reinterpret_cast<TerminalSettingsBridgeImpl*>
+ (g_object_new (TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL,
+ "settings-backend", backend,
+ nullptr));
+}
diff --git a/src/terminal-settings-bridge-impl.hh b/src/terminal-settings-bridge-impl.hh
new file mode 100644
index 0000000..ab8881f
--- /dev/null
+++ b/src/terminal-settings-bridge-impl.hh
@@ -0,0 +1,38 @@
+/*
+ * Copyright © 2008, 2010, 2022 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL (terminal_settings_bridge_impl_get_type ())
+#define TERMINAL_SETTINGS_BRIDGE_IMPL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL, TerminalSettingsBridgeImpl))
+#define TERMINAL_SETTINGS_BRIDGE_IMPL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL, TerminalSettingsBridgeImplClass))
+#define TERMINAL_IS_SETTINGS_BRIDGE_IMPL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL))
+#define TERMINAL_IS_SETTINGS_BRIDGE_IMPL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL))
+#define TERMINAL_SETTINGS_BRIDGE_IMPL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_SETTINGS_BRIDGE_IMPL, TerminalSettingsBridgeImplClass))
+
+typedef struct _TerminalSettingsBridgeImpl TerminalSettingsBridgeImpl;
+typedef struct _TerminalSettingsBridgeImplClass TerminalSettingsBridgeImplClass;
+
+GType terminal_settings_bridge_impl_get_type(void);
+
+TerminalSettingsBridgeImpl* terminal_settings_bridge_impl_new(GSettingsBackend* backend);
+
+G_END_DECLS
diff --git a/src/terminal-settings-list.cc b/src/terminal-settings-list.cc
new file mode 100644
index 0000000..8907643
--- /dev/null
+++ b/src/terminal-settings-list.cc
@@ -0,0 +1,930 @@
+/*
+ * Copyright © 2013 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "terminal-settings-list.hh"
+#include "terminal-client-utils.hh"
+
+#include <string.h>
+#include <uuid.h>
+
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
+#include "terminal-type-builtins.hh"
+#include "terminal-settings-utils.hh"
+#include "terminal-schemas.hh"
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+
+struct _TerminalSettingsList {
+ GSettings parent;
+
+ GSettingsBackend* settings_backend;
+ GSettingsSchemaSource* schema_source;
+ char *path;
+ char *child_schema_id;
+
+ char **uuids;
+ char *default_uuid;
+
+ GHashTable *children;
+
+ TerminalSettingsListFlags flags;
+};
+
+struct _TerminalSettingsListClass {
+ GSettingsClass parent;
+
+ void (* children_changed) (TerminalSettingsList *list);
+ void (* default_changed) (TerminalSettingsList *list);
+};
+
+enum {
+ PROP_SCHEMA_SOURCE = 1,
+ PROP_CHILD_SCHEMA_ID,
+ PROP_FLAGS
+};
+
+enum {
+ SIGNAL_CHILDREN_CHANGED,
+ SIGNAL_DEFAULT_CHANGED,
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+static void
+strv_printerr (char **strv)
+{
+ char **p;
+
+ if (strv == nullptr) {
+ g_printerr ("(null)");
+ return;
+ }
+
+ for (p = strv; *p; p++)
+ g_printerr ("%s'%s'", p != strv ? ", " : "", *p);
+}
+
+static char **
+strv_sort (char **strv)
+{
+ // FIXMEchpe
+ return strv;
+}
+
+static gboolean
+strv_equal (char **a,
+ char **b)
+{
+ char **e, **f;
+
+ if (a == nullptr || b == nullptr)
+ return a == b;
+
+ for (e = a, f = b; *e && *f; e++, f++) {
+ if (!g_str_equal (*e, *f))
+ return FALSE;
+ }
+
+ return *e == *f;
+}
+
+static int
+strv_find (char **strv,
+ const char *str)
+{
+ int i;
+
+ if (strv == nullptr || str == nullptr)
+ return -1;
+
+ for (i = 0; strv[i]; i++) {
+ if (!g_str_equal (strv[i], str))
+ continue;
+
+ return i;
+ }
+
+ return -1;
+}
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+static char **
+strv_dupv_insert (char **strv,
+ const char *str)
+{
+ char **nstrv, **p, **q;
+
+ if (strv == nullptr) {
+ char *s[2] = { (char *) str, nullptr };
+ return g_strdupv (s);
+ }
+
+ /* Is it already in the list? */
+ for (p = strv; *p; p++)
+ if (g_str_equal (*p, str))
+ return g_strdupv (strv);
+
+ /* Not found; append */
+ nstrv = g_new (char *, (p - strv) + 2);
+ for (p = strv, q = nstrv; *p; p++, q++)
+ *q = g_strdup (*p);
+ *q++ = g_strdup (str);
+ *q = nullptr;
+
+ return strv_sort (nstrv);
+}
+
+static char **
+strv_dupv_remove (char **strv,
+ const char *str)
+{
+ char **nstrv, **p, **q;
+
+ if (strv == nullptr)
+ return nullptr;
+
+ nstrv = g_strdupv (strv);
+ for (p = q = nstrv; *p; p++) {
+ if (!g_str_equal (*p, str))
+ *q++ = *p;
+ else
+ g_free (*p);
+ }
+ *q = nullptr;
+
+ return nstrv;
+}
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
+gboolean
+terminal_settings_list_valid_uuid (const char *str)
+{
+ uuid_t u;
+
+ if (str == nullptr)
+ return FALSE;
+
+ return uuid_parse ((char *) str, u) == 0;
+}
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+static char *
+new_list_entry (void)
+{
+ uuid_t u;
+ char name[37];
+
+ uuid_generate (u);
+ uuid_unparse (u, name);
+
+ return g_strdup (name);
+}
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
+static gboolean
+validate_list (TerminalSettingsList *list,
+ char **entries)
+{
+ gboolean allow_empty = (list->flags & TERMINAL_SETTINGS_LIST_FLAG_ALLOW_EMPTY) != 0;
+ guint i;
+
+ if (entries == nullptr)
+ return allow_empty;
+
+ for (i = 0; entries[i]; i++) {
+ if (!terminal_settings_list_valid_uuid (entries[i]))
+ return FALSE;
+ }
+
+ return (i > 0) || allow_empty;
+}
+
+static gboolean
+list_map_func (GVariant *value,
+ gpointer *result,
+ gpointer user_data)
+{
+ TerminalSettingsList *list = (TerminalSettingsList*)user_data;
+ gs_strfreev char **entries;
+
+ entries = strv_sort (g_variant_dup_strv (value, nullptr));
+
+ if (validate_list (list, entries)) {
+ gs_transfer_out_value(result, &entries);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static char*
+path_new (TerminalSettingsList *list,
+ char const* uuid)
+{
+ return g_strdup_printf ("%s:%s/", list->path, uuid);
+}
+
+static GSettings *
+terminal_settings_list_ref_child_internal (TerminalSettingsList *list,
+ const char *uuid)
+{
+ GSettings *child;
+ gs_free char *path = nullptr;
+
+ if (strv_find (list->uuids, uuid) == -1)
+ return nullptr;
+
+ _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
+ "%s UUID %s\n", G_STRFUNC, uuid);
+
+ child = (GSettings*)g_hash_table_lookup (list->children, uuid);
+ if (child)
+ goto done;
+
+ path = path_new (list, uuid);
+ child = terminal_g_settings_new_with_path(list->settings_backend,
+ list->schema_source,
+ list->child_schema_id,
+ path);
+ g_hash_table_insert (list->children, g_strdup (uuid), child /* adopted */);
+
+ done:
+ return (GSettings*)g_object_ref(child);
+}
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+static char *
+terminal_settings_list_add_child_internal (TerminalSettingsList *list,
+ const char *uuid,
+ const char *name)
+{
+
+ auto const new_uuid = new_list_entry();
+ _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
+ "%s NEW UUID %s\n", G_STRFUNC, new_uuid);
+
+ gs_free auto path = path_new(list, uuid);
+ gs_free auto new_path = path_new(list, new_uuid);
+
+ auto tree = terminal_g_settings_backend_create_tree();
+ terminal_g_settings_backend_clone_schema(list->settings_backend,
+ list->schema_source,
+ list->child_schema_id,
+ path,
+ new_path,
+ tree);
+ if (name) {
+ g_tree_insert(tree,
+ g_strconcat(new_path, TERMINAL_PROFILE_VISIBLE_NAME_KEY, nullptr), // transfer
+ g_variant_take_ref(g_variant_new_string(name))); // transfer
+ }
+
+#ifdef ENABLE_DEBUG
+ _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_SETTINGS_LIST) {
+ g_printerr("Cloning schema %s from %s -> %s\n", list->child_schema_id, path, new_path);
+ terminal_g_settings_backend_print_tree(tree);
+ }
+#endif
+
+ auto const tag = &list;
+ (void)terminal_g_settings_backend_write_tree(list->settings_backend, tree, tag);
+ g_tree_unref(tree);
+
+ gs_strfreev auto new_uuids = strv_dupv_insert(list->uuids, new_uuid);
+ g_settings_set_strv(&list->parent, TERMINAL_SETTINGS_LIST_LIST_KEY,
+ (char const* const*)new_uuids);
+
+ return new_uuid;
+}
+
+static void
+terminal_settings_list_remove_child_internal (TerminalSettingsList *list,
+ const char *uuid)
+{
+ gs_strfreev char **new_uuids;
+
+ _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
+ "%s UUID %s\n", G_STRFUNC, uuid);
+
+ new_uuids = strv_dupv_remove (list->uuids, uuid);
+
+ if ((new_uuids == nullptr || new_uuids[0] == nullptr) &&
+ (list->flags & TERMINAL_SETTINGS_LIST_FLAG_ALLOW_EMPTY) == 0)
+ return;
+
+ g_settings_set_strv (&list->parent, TERMINAL_SETTINGS_LIST_LIST_KEY, (const char * const *) new_uuids);
+
+ if (list->default_uuid != nullptr &&
+ g_str_equal (list->default_uuid, uuid))
+ g_settings_set_string (&list->parent, TERMINAL_SETTINGS_LIST_DEFAULT_KEY, "");
+
+ /* Now we unset all keys under the child */
+ gs_free auto path = path_new(list, uuid);
+ terminal_g_settings_backend_erase_path(list->settings_backend,
+ list->schema_source,
+ list->child_schema_id,
+ path);
+}
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
+static void
+terminal_settings_list_update_list (TerminalSettingsList *list)
+{
+ char **uuids, *uuid;
+ GSettings *child;
+ GHashTable *new_children;
+ guint i;
+ gboolean changed;
+
+ uuids = (char**)g_settings_get_mapped (&list->parent,
+ TERMINAL_SETTINGS_LIST_LIST_KEY,
+ list_map_func, list);
+
+ _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_SETTINGS_LIST) {
+ g_printerr ("%s: current UUIDs [", G_STRFUNC);
+ strv_printerr (list->uuids);
+ g_printerr ("]\n new UUIDs [");
+ strv_printerr (uuids);
+ g_printerr ("]\n");
+ }
+
+ if (strv_equal (uuids, list->uuids) &&
+ ((list->flags & TERMINAL_SETTINGS_LIST_FLAG_HAS_DEFAULT) == 0 ||
+ strv_find (list->uuids, list->default_uuid) != -1)) {
+ g_strfreev (uuids);
+ return;
+ }
+
+ new_children = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ if (uuids) {
+ for (i = 0; uuids[i] != nullptr; i++) {
+ uuid = uuids[i];
+
+ child = (GSettings*)g_hash_table_lookup (list->children, uuid);
+
+ if (child) {
+ g_object_ref (child);
+ g_hash_table_remove (list->children, uuid);
+ g_hash_table_insert (new_children, g_strdup (uuid), child /* adopted */);
+ }
+ }
+
+ changed = !strv_equal (uuids, list->uuids);
+ } else {
+ changed = g_strv_length (list->uuids) != 0;
+ }
+
+ g_hash_table_unref (list->children);
+ list->children = new_children;
+
+ g_strfreev (list->uuids);
+ list->uuids = uuids; /* adopts */
+
+ if (changed)
+ g_signal_emit (list, signals[SIGNAL_CHILDREN_CHANGED], 0);
+}
+
+static void
+terminal_settings_list_update_default (TerminalSettingsList *list)
+{
+ if ((list->flags & TERMINAL_SETTINGS_LIST_FLAG_HAS_DEFAULT) == 0)
+ return;
+
+ g_free (list->default_uuid);
+ list->default_uuid = g_settings_get_string (&list->parent,
+ TERMINAL_SETTINGS_LIST_DEFAULT_KEY);
+
+ _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
+ "%s new default UUID %s\n", G_STRFUNC, list->default_uuid);
+
+ g_signal_emit (list, signals[SIGNAL_DEFAULT_CHANGED], 0);
+}
+
+G_DEFINE_TYPE (TerminalSettingsList, terminal_settings_list, G_TYPE_SETTINGS);
+
+static void
+terminal_settings_list_changed (GSettings *list_settings,
+ const char *key)
+{
+ TerminalSettingsList *list = TERMINAL_SETTINGS_LIST (list_settings);
+
+ _terminal_debug_print (TERMINAL_DEBUG_SETTINGS_LIST,
+ "%s key %s", G_STRFUNC, key ? key : "(null)");
+
+ if (key == nullptr ||
+ g_str_equal (key, TERMINAL_SETTINGS_LIST_LIST_KEY)) {
+ terminal_settings_list_update_list (list);
+ terminal_settings_list_update_default (list);
+ }
+
+ if (key == nullptr)
+ return;
+
+ if (g_str_equal (key, TERMINAL_SETTINGS_LIST_DEFAULT_KEY)) {
+ terminal_settings_list_update_default (list);
+ }
+}
+
+static void
+terminal_settings_list_init (TerminalSettingsList *list)
+{
+ list->flags = TERMINAL_SETTINGS_LIST_FLAG_NONE;
+}
+
+static void
+terminal_settings_list_constructed (GObject *object)
+{
+ TerminalSettingsList *list = TERMINAL_SETTINGS_LIST (object);
+
+ G_OBJECT_CLASS (terminal_settings_list_parent_class)->constructed (object);
+
+ g_object_get(object, "backend", &list->settings_backend, nullptr);
+ g_assert(list->settings_backend);
+ g_assert(list->schema_source);
+
+ g_assert (list->schema_source != nullptr);
+ g_assert (list->child_schema_id != nullptr);
+
+ g_object_get (object, "path", &list->path, nullptr);
+
+ list->children = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ terminal_settings_list_changed (&list->parent, nullptr);
+}
+
+static void
+terminal_settings_list_finalize (GObject *object)
+{
+ TerminalSettingsList *list = TERMINAL_SETTINGS_LIST (object);
+
+ g_free (list->path);
+ g_free (list->child_schema_id);
+ g_strfreev (list->uuids);
+ g_free (list->default_uuid);
+ g_hash_table_unref (list->children);
+ g_settings_schema_source_unref(list->schema_source);
+ g_clear_object(&list->settings_backend);
+
+ G_OBJECT_CLASS (terminal_settings_list_parent_class)->finalize (object);
+}
+
+static void
+terminal_settings_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalSettingsList *list = TERMINAL_SETTINGS_LIST (object);
+
+ switch (prop_id) {
+ case PROP_SCHEMA_SOURCE: {
+ auto const schema_source = reinterpret_cast<GSettingsSchemaSource*>(g_value_get_boxed(value));
+ list->schema_source = g_settings_schema_source_ref(schema_source);
+ break;
+ }
+ case PROP_CHILD_SCHEMA_ID:
+ list->child_schema_id = g_value_dup_string (value);
+ break;
+ case PROP_FLAGS:
+ list->flags = TerminalSettingsListFlags(g_value_get_flags (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_settings_list_class_init (TerminalSettingsListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GSettingsClass *settings_class = G_SETTINGS_CLASS (klass);
+
+ object_class->set_property = terminal_settings_list_set_property;
+ object_class->constructed = terminal_settings_list_constructed;
+ object_class->finalize = terminal_settings_list_finalize;
+
+ /**
+ * TerminalSettingsList:child-schema-id:
+ *
+ * The name of the schema of the children of this list.
+ */
+ g_object_class_install_property (object_class, PROP_SCHEMA_SOURCE,
+ g_param_spec_boxed("schema-source", nullptr, nullptr,
+ G_TYPE_SETTINGS_SCHEMA_SOURCE,
+ GParamFlags(G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS)));
+
+ /**
+ * TerminalSettingsList:child-schema-id:
+ *
+ * The name of the schema of the children of this list.
+ */
+ g_object_class_install_property (object_class, PROP_CHILD_SCHEMA_ID,
+ g_param_spec_string ("child-schema-id", nullptr, nullptr,
+ nullptr,
+ GParamFlags(G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS)));
+
+ /**
+ * TerminalSettingsList:flags:
+ *
+ * Flags from #TerminalSettingsListFlags.
+ */
+ g_object_class_install_property (object_class, PROP_FLAGS,
+ g_param_spec_flags ("flags", nullptr,nullptr,
+ TERMINAL_TYPE_SETTINGS_LIST_FLAGS,
+ TERMINAL_SETTINGS_LIST_FLAG_NONE,
+ GParamFlags(G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS)));
+
+ /**
+ * TerminalSettingsList::children-changed:
+ * @list: the object on which the signal was emitted
+ *
+ * The "children-changed" signal is emitted when the list of children
+ * has potentially changed.
+ */
+ signals[SIGNAL_CHILDREN_CHANGED] =
+ g_signal_new ("children-changed", TERMINAL_TYPE_SETTINGS_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalSettingsListClass, children_changed),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * TerminalSettingsList::default-changed:
+ * @list: the object on which the signal was emitted
+ *
+ * The "default-changed" signal is emitted when the default child
+ * has potentially changed.
+ */
+ signals[SIGNAL_DEFAULT_CHANGED] =
+ g_signal_new ("default-changed", TERMINAL_TYPE_SETTINGS_LIST,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalSettingsListClass, default_changed),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ settings_class->changed = terminal_settings_list_changed;
+}
+
+
+/**
+ * terminal_settings_list_new:
+ * @backend: (nullable): a #GSettingsBackend, or %NULL
+ * @schema_source: a #GSettingsSchemaSource
+ * @path: the settings path for the list
+ * @schema_id: the schema of the list, equal to or derived from "org.gnome.Terminal.SettingsList"
+ * @child_schema_id: the schema of the list children
+ * @flags: list flags
+ *
+ * Returns: (transfer full): the newly created #TerminalSettingsList
+ */
+TerminalSettingsList *
+terminal_settings_list_new (GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source,
+ const char *path,
+ const char *schema_id,
+ const char *child_schema_id,
+ TerminalSettingsListFlags flags)
+{
+ g_return_val_if_fail (backend == nullptr || G_IS_SETTINGS_BACKEND (backend), nullptr);
+ g_return_val_if_fail (schema_source != nullptr, nullptr);
+ g_return_val_if_fail (path != nullptr, nullptr);
+ g_return_val_if_fail (schema_id != nullptr, nullptr);
+ g_return_val_if_fail (child_schema_id != nullptr, nullptr);
+ g_return_val_if_fail (g_str_has_suffix (path, ":/"), nullptr);
+
+ gs_unref_settings_schema auto schema =
+ g_settings_schema_source_lookup(schema_source, schema_id, true);
+ g_assert_nonnull(schema);
+
+ return reinterpret_cast<TerminalSettingsList*>(g_object_new (TERMINAL_TYPE_SETTINGS_LIST,
+ "backend", backend,
+ "schema-source", schema_source,
+ "settings-schema", schema,
+ "child-schema-id", child_schema_id,
+ "path", path,
+ "flags", flags,
+ nullptr));
+}
+
+/**
+ * terminal_settings_list_dupv_children:
+ * @list: a #TerminalSettingsList
+ *
+ * Returns: (transfer full): the UUIDs of the children in the settings list, or %nullptr
+ * if the list is empty
+ */
+char **
+terminal_settings_list_dupv_children (TerminalSettingsList *list)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+
+ return g_strdupv (list->uuids);
+}
+
+/**
+ * terminal_settings_list_dup_default_child:
+ * @list: a #TerminalSettingsList
+ *
+ * Returns: (transfer full): the UUID of the default child in the settings list
+ */
+char *
+terminal_settings_list_dup_default_child (TerminalSettingsList *list)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+
+ if ((list->flags & TERMINAL_SETTINGS_LIST_FLAG_HAS_DEFAULT) == 0)
+ return nullptr;
+
+ if ((strv_find (list->uuids, list->default_uuid)) != -1)
+ return g_strdup (list->default_uuid);
+
+ /* Just randomly designate the first child as default, but don't write that
+ * to the settings.
+ */
+ if (list->uuids == nullptr || list->uuids[0] == nullptr) {
+ g_warn_if_fail ((list->flags & TERMINAL_SETTINGS_LIST_FLAG_ALLOW_EMPTY));
+ return nullptr;
+ }
+
+ return g_strdup (list->uuids[0]);
+}
+
+/**
+ * terminal_settings_list_has_child:
+ * @list: a #TerminalSettingsList
+ * @uuid: the UUID of a list child
+ *
+ * Returns: %TRUE iff the child with @uuid exists
+ */
+gboolean
+terminal_settings_list_has_child (TerminalSettingsList *list,
+ const char *uuid)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), FALSE);
+ g_return_val_if_fail (terminal_settings_list_valid_uuid (uuid), FALSE);
+
+ return strv_find (list->uuids, uuid) != -1;
+}
+
+/**
+ * terminal_settings_list_ref_child:
+ * @list: a #TerminalSettingsList
+ * @uuid: the UUID of a list child
+ *
+ * Returns the child #GSettings for the list child with UUID @uuid, or %nullptr
+ * if @list has no such child.
+ *
+ * Returns: (transfer full): a reference to the #GSettings for the child, or %nullptr
+ */
+GSettings *
+terminal_settings_list_ref_child (TerminalSettingsList *list,
+ const char *uuid)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+ g_return_val_if_fail (terminal_settings_list_valid_uuid (uuid), nullptr);
+
+ return terminal_settings_list_ref_child_internal (list, uuid);
+}
+
+/**
+ * terminal_settings_list_ref_children:
+ * @list: a #TerminalSettingsList
+ *
+ * Returns the list of children #GSettings or @list.
+ *
+ * Returns: (transfer full): a list of child #GSettings of @list
+ */
+GList *
+terminal_settings_list_ref_children (TerminalSettingsList *list)
+{
+ GList *l;
+ guint i;
+
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+
+ if (list->uuids == nullptr)
+ return nullptr;
+
+ l = nullptr;
+ for (i = 0; list->uuids[i]; i++)
+ l = g_list_prepend (l, terminal_settings_list_ref_child (list, list->uuids[i]));
+
+ return g_list_reverse (l);
+}
+
+/**
+ * terminal_settings_list_ref_default_child:
+ * @list: a #TerminalSettingsList
+ *
+ * Returns the default child #GSettings for the list, or %nullptr if @list has no
+ * children.
+ *
+ * Returns: (transfer full): a reference to the #GSettings for the default child, or %nullptr
+ */
+GSettings *
+terminal_settings_list_ref_default_child (TerminalSettingsList *list)
+{
+ gs_free char *uuid = nullptr;
+
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+
+ uuid = terminal_settings_list_dup_default_child (list);
+ if (uuid == nullptr)
+ return nullptr;
+
+ return terminal_settings_list_ref_child_internal (list, uuid);
+}
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+/**
+ * terminal_settings_list_add_child:
+ * @list: a #TerminalSettingsList
+ * @name: the name of the new profile
+ *
+ * Adds a new child to the list, and returns a reference to its #GSettings.
+ *
+ * Returns: (transfer full): the UUID of new child
+ */
+char *
+terminal_settings_list_add_child (TerminalSettingsList *list,
+ const char *name)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+
+ return terminal_settings_list_add_child_internal (list, nullptr, name);
+}
+
+/**
+ * terminal_settings_list_clone_child:
+ * @list: a #TerminalSettingsList
+ * @uuid: the UUID of the child to clone
+ * @name: the name of the new child
+ *
+ * Adds a new child to the list, and returns a reference to its #GSettings.
+ * All keys of the new child will have the same value as @uuid's.
+ *
+ * Returns: (transfer full): the UUID of new child
+ */
+char *
+terminal_settings_list_clone_child (TerminalSettingsList *list,
+ const char *uuid,
+ const char *name)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+ g_return_val_if_fail (terminal_settings_list_valid_uuid (uuid), nullptr);
+
+ return terminal_settings_list_add_child_internal (list, uuid, name);
+}
+
+/**
+ * terminal_settings_list_remove_child:
+ * @list: a #TerminalSettingsList
+ * @uuid: the UUID of a list child
+ *
+ * Removes the child with UUID @uuid from the list.
+ */
+void
+terminal_settings_list_remove_child (TerminalSettingsList *list,
+ const char *uuid)
+{
+ g_return_if_fail (TERMINAL_IS_SETTINGS_LIST (list));
+ g_return_if_fail (terminal_settings_list_valid_uuid (uuid));
+
+ terminal_settings_list_remove_child_internal (list, uuid);
+}
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
+/**
+ * terminal_settings_list_dup_uuid_from_child:
+ * @list: a #TerminalSettingsList
+ * @child: a #GSettings of a child in the list
+ *
+ * Returns the UUID of @child in the list, or %nullptr if @child is not in the list.
+ *
+ * Returns: (transfer full): the UUID of the child in the settings list, or %nullptr
+ */
+char *
+terminal_settings_list_dup_uuid_from_child (TerminalSettingsList *list,
+ GSettings *child)
+{
+ gs_free char *path;
+ char *p;
+
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), nullptr);
+
+ g_object_get (child, "path", &path, nullptr);
+ g_return_val_if_fail (g_str_has_prefix (path, list->path), nullptr);
+
+ p = path + strlen (list->path);
+ g_return_val_if_fail (p[0] == ':', nullptr);
+ p++;
+ g_return_val_if_fail (strlen (p) == 37, nullptr);
+ g_return_val_if_fail (p[36] == '/', nullptr);
+ p[36] = '\0';
+ g_assert (terminal_settings_list_valid_uuid (p));
+
+ return g_strdup (p);
+}
+
+/**
+ * terminal_settings_list_get_set_default_child:
+ * @list: a #TerminalSettingsList
+ * @uuid: the UUID of a child in the list
+ *
+ * Sets @uuid as the default child.
+ */
+void
+terminal_settings_list_set_default_child (TerminalSettingsList *list,
+ const char *uuid)
+{
+ g_return_if_fail (TERMINAL_IS_SETTINGS_LIST (list));
+ g_return_if_fail (terminal_settings_list_valid_uuid (uuid));
+
+ if (!terminal_settings_list_has_child (list, uuid))
+ return;
+
+ g_settings_set_string (&list->parent, TERMINAL_SETTINGS_LIST_DEFAULT_KEY, uuid);
+}
+
+/**
+ * terminal_settings_list_foreach_child:
+ * @list: a #TerminalSettingsList
+ * @callback: a #TerminalSettingsListForeachFunc
+ * @user_data: user data for @callback
+ *
+ * Calls @callback for each child of @list.
+ *
+ * NOTE: No changes to @list must be made from @callback.
+ */
+void
+terminal_settings_list_foreach_child (TerminalSettingsList *list,
+ TerminalSettingsListForeachFunc callback,
+ gpointer user_data)
+{
+ g_return_if_fail (TERMINAL_IS_SETTINGS_LIST (list));
+ g_return_if_fail (callback);
+
+ for (char **p = list->uuids; *p; p++) {
+ const char *uuid = *p;
+ gs_unref_object GSettings *child = terminal_settings_list_ref_child_internal (list, uuid);
+ if (child != nullptr)
+ callback (list, uuid, child, user_data);
+ }
+}
+
+/**
+ * terminal_settings_list_foreach_child:
+ * @list: a #TerminalSettingsList
+ *
+ * Returns: the number of children of @list.
+ */
+guint
+terminal_settings_list_get_n_children (TerminalSettingsList *list)
+{
+ g_return_val_if_fail (TERMINAL_IS_SETTINGS_LIST (list), 0);
+
+ return g_hash_table_size (list->children);
+}
diff --git a/src/terminal-settings-list.hh b/src/terminal-settings-list.hh
new file mode 100644
index 0000000..de99766
--- /dev/null
+++ b/src/terminal-settings-list.hh
@@ -0,0 +1,91 @@
+/*
+ * Copyright © 2013 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/>.
+ */
+
+#ifndef TERMINAL_SETTINGS_LIST_H
+#define TERMINAL_SETTINGS_LIST_H
+
+#include <gio/gio.h>
+
+#include "terminal-enums.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_SETTINGS_LIST (terminal_settings_list_get_type ())
+#define TERMINAL_SETTINGS_LIST(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TERMINAL_TYPE_SETTINGS_LIST, TerminalSettingsList))
+#define TERMINAL_SETTINGS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TERMINAL_TYPE_SETTINGS_LIST, TerminalSettingsListClass))
+#define TERMINAL_IS_SETTINGS_LIST(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TERMINAL_TYPE_SETTINGS_LIST))
+#define TERMINAL_IS_SETTINGS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TERMINAL_TYPE_SETTINGS_LIST))
+#define TERMINAL_SETTINGS_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TERMINAL_TYPE_SETTINGS_LIST, TerminalSettingsListClass))
+
+typedef struct _TerminalSettingsList TerminalSettingsList;
+typedef struct _TerminalSettingsListClass TerminalSettingsListClass;
+
+GType terminal_settings_list_get_type (void);
+
+TerminalSettingsList *terminal_settings_list_new (GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source,
+ const char *path,
+ const char *schema_id,
+ const char *child_schema_id,
+ TerminalSettingsListFlags flags);
+
+char **terminal_settings_list_dupv_children (TerminalSettingsList *list);
+
+GList *terminal_settings_list_ref_children (TerminalSettingsList *list);
+
+gboolean terminal_settings_list_has_child (TerminalSettingsList *list,
+ const char *uuid);
+
+GSettings *terminal_settings_list_ref_child (TerminalSettingsList *list,
+ const char *uuid);
+
+char *terminal_settings_list_add_child (TerminalSettingsList *list,
+ const char *name);
+
+char *terminal_settings_list_clone_child (TerminalSettingsList *list,
+ const char *uuid,
+ const char *name);
+
+void terminal_settings_list_remove_child (TerminalSettingsList *list,
+ const char *uuid);
+
+char *terminal_settings_list_dup_uuid_from_child (TerminalSettingsList *list,
+ GSettings *child);
+
+GSettings *terminal_settings_list_ref_default_child (TerminalSettingsList *list);
+
+char *terminal_settings_list_dup_default_child (TerminalSettingsList *list);
+
+void terminal_settings_list_set_default_child (TerminalSettingsList *list,
+ const char *uuid);
+
+typedef void (* TerminalSettingsListForeachFunc) (TerminalSettingsList *list,
+ const char *uuid,
+ GSettings *child,
+ gpointer user_data);
+
+void terminal_settings_list_foreach_child (TerminalSettingsList *list,
+ TerminalSettingsListForeachFunc callback,
+ gpointer user_data);
+
+guint terminal_settings_list_get_n_children (TerminalSettingsList *list);
+
+gboolean terminal_settings_list_valid_uuid (const char *str);
+
+G_END_DECLS
+
+#endif /* TERMINAL_SETTINGS_LIST_H */
diff --git a/src/terminal-settings-utils.cc b/src/terminal-settings-utils.cc
new file mode 100644
index 0000000..a40ab5e
--- /dev/null
+++ b/src/terminal-settings-utils.cc
@@ -0,0 +1,992 @@
+/*
+ * Copyright © 2008, 2010, 2011, 2022 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"
+#define G_SETTINGS_ENABLE_BACKEND
+
+#include <gio/gsettingsbackend.h>
+
+#include "terminal-settings-utils.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-debug.hh"
+#include "terminal-libgsystem.hh"
+
+#ifdef ENABLE_DEBUG
+
+static gboolean
+settings_change_event_cb(GSettings* settings,
+ void* keys,
+ int n_keys,
+ void* data)
+{
+ gs_free char* schema_id = nullptr;
+ gs_free char* path = nullptr;
+ g_object_get(settings,
+ "schema-id", &schema_id,
+ "path", &path,
+ nullptr);
+
+ auto const qkeys = reinterpret_cast<GQuark*>(keys);
+ for (auto i = 0; i < n_keys; ++i) {
+ auto key = g_quark_to_string(qkeys[i]);
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::change-event schema %s path %s key %s\n",
+ schema_id, path, key);
+ }
+
+ return false; // propagate
+}
+
+static gboolean
+settings_writable_change_event_cb(GSettings* settings,
+ char const* key,
+ void* data)
+{
+ gs_free char* schema_id = nullptr;
+ gs_free char* path = nullptr;
+ g_object_get(settings,
+ "schema-id", &schema_id,
+ "path", &path,
+ nullptr);
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Bridge backend ::writeable-change-event schema %s path %s key %s\n",
+ schema_id, path, key);
+
+ return false; // propagate
+}
+
+#endif /* ENABLE_DEBUG */
+
+GSettings*
+terminal_g_settings_new_with_path (GSettingsBackend* backend,
+ GSettingsSchemaSource* source,
+ char const* schema_id,
+ char const* path)
+{
+ gs_unref_settings_schema GSettingsSchema* schema =
+ g_settings_schema_source_lookup(source,
+ schema_id,
+ TRUE /* recursive */);
+ g_assert_nonnull(schema);
+
+ auto const settings = g_settings_new_full(schema,
+ backend,
+ path);
+
+#ifdef ENABLE_DEBUG
+ _TERMINAL_DEBUG_IF(TERMINAL_DEBUG_BRIDGE) {
+
+ _terminal_debug_print(TERMINAL_DEBUG_BRIDGE,
+ "Creating GSettings for schema %s at %s with backend %s\n",
+ schema_id, path,
+ backend ? G_OBJECT_TYPE_NAME(backend) : "(default)");
+
+ if (backend != nullptr &&
+ g_str_equal(G_OBJECT_TYPE_NAME(backend), "TerminalSettingsBridgeBackend")) {
+ g_signal_connect(settings,
+ "change-event",
+ G_CALLBACK(settings_change_event_cb),
+ nullptr);
+ g_signal_connect(settings,
+ "writable-change-event",
+ G_CALLBACK(settings_writable_change_event_cb),
+ nullptr);
+ }
+ }
+#endif /* ENABLE_DEBUG */
+
+ return settings;
+}
+
+GSettings*
+terminal_g_settings_new(GSettingsBackend* backend,
+ GSettingsSchemaSource* source,
+ char const* schema_id)
+{
+ return terminal_g_settings_new_with_path(backend, source, schema_id, nullptr);
+}
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+void
+terminal_g_settings_backend_clone_schema(GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source,
+ char const* schema_id,
+ char const* path,
+ char const* new_path,
+ GTree* tree)
+{
+ gs_unref_settings_schema auto schema =
+ g_settings_schema_source_lookup(schema_source, schema_id, true);
+ if (schema == nullptr) [[unlikely]] // This shouldn't really happen ever
+ return;
+
+ gs_strfreev auto keys = g_settings_schema_list_keys(schema);
+
+ for (auto i = 0; keys[i]; ++i) {
+ gs_unref_settings_schema_key auto schema_key =
+ g_settings_schema_get_key(schema, keys[i]);
+
+ gs_free auto rkey = g_strconcat(path, keys[i], nullptr);
+ auto const value =
+ terminal_g_settings_backend_read(backend,
+ rkey,
+ g_settings_schema_key_get_value_type(schema_key),
+ false);
+
+ if (value) {
+ g_tree_insert(tree,
+ g_strconcat(new_path, keys[i], nullptr), // transfer
+ value); // transfer
+ }
+ }
+}
+
+gboolean
+terminal_g_settings_backend_erase_path(GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source,
+ char const* schema_id,
+ char const* path)
+
+{
+ // We want to erase all keys below @path, not just keys we wrote ourself
+ // or that are (currently) in a known schema. DConf supports this kind of
+ // 'directory reset' by writing a NULL value for the non-key @path (i.e.
+ // which ends in a slash). However, neither g_settings_backend_reset() nor
+ // g_settings_backend_write() accept a non-key path, and the latter
+ // doesn't accept NULL values anyway. g_settings_backend_write_tree()
+ // does allow NULL values, and the DConf backend works fine with this and
+ // performs the directory reset, however it also (as is a documented
+ // requirement) calls g_settings_backend_changed_tree() which chokes on
+ // such a tree containing a non-key path.
+ //
+ // We could:
+ // 1. Just do nothing, i.e. leave the deleted settings lying around.
+ // 2. Fix glib. However, getting any improvements to gsettings into glib
+ // seems almost impossible at this point.
+ // 3. Interpose a fixed g_settings_backend_changed_tree() that works
+ // with these non-key paths. This will work with out-of-tree
+ // settings backends like DConf. However, this will *not* work with
+ // the settings backends inside libgio, like the memory and keyfile
+ // backends, due to -Bsymbolic_functions.
+ // 4. At least reset those keys we know might exists, i.e. those in
+ // the schema.
+ //
+ // Since I don't like 1, 2 is impossible, and 3 is too hacky, let's at least
+ // do 4.
+
+#if 0
+ // This is how this function would ideally work if glib was fixed (option 2 above)
+ auto tree = terminal_g_settings_backend_create_tree();
+ g_tree_insert(tree, g_strdup(path), nullptr);
+ auto const tag = &backend;
+ auto const r = terminal_g_settings_backend_write_tree(backend, tree, tag);
+ g_tree_unref(tree);
+#endif
+
+ gs_unref_settings_schema auto schema =
+ g_settings_schema_source_lookup(schema_source, schema_id, true);
+ if (schema == nullptr) [[unlikely]] // This shouldn't really happen ever
+ return false;
+
+ auto tree = terminal_g_settings_backend_create_tree();
+ gs_strfreev auto keys = g_settings_schema_list_keys(schema);
+
+ for (auto i = 0; keys[i]; ++i) {
+ g_tree_insert(tree,
+ g_strconcat(path, keys[i], nullptr), // transfer
+ nullptr); // reset key
+ }
+
+ auto const tag = &backend;
+ auto const r = terminal_g_settings_backend_write_tree(backend, tree, tag);
+ g_tree_unref(tree);
+ return r;
+}
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
+
+#define TERMINAL_SCHEMA_VERIFIER_ERROR (g_quark_from_static_string("TerminalSchemaVerifier"))
+
+typedef enum {
+ TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
+ TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
+ TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
+ TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
+ TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
+ TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
+} TerminalSchemaVerifierError;
+
+static gboolean
+strv_contains(char const* const* strv,
+ char const* str)
+{
+ if (strv == nullptr)
+ return FALSE;
+
+ for (size_t i = 0; strv[i]; i++) {
+ if (g_str_equal (strv[i], str))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+schema_key_range_compatible(GSettingsSchema* source_schema,
+ GSettingsSchemaKey* source_key,
+ char const* key,
+ GSettingsSchemaKey* reference_key,
+ GError** error)
+{
+ gs_unref_variant GVariant* source_range =
+ g_settings_schema_key_get_range(source_key);
+ gs_unref_variant GVariant* reference_range =
+ g_settings_schema_key_get_range(reference_key);
+
+ char const* source_type = nullptr;
+ gs_unref_variant GVariant* source_data = nullptr;
+ g_variant_get(source_range, "(&sv)", &source_type, &source_data);
+
+ char const* reference_type = nullptr;
+ gs_unref_variant GVariant* reference_data = nullptr;
+ g_variant_get(reference_range, "(&sv)", &reference_type, &reference_data);
+
+ if (!g_str_equal(source_type, reference_type)) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
+ "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
+ g_settings_schema_get_id(source_schema),
+ key, source_type, reference_type);
+ return FALSE;
+ }
+
+ if (g_str_equal(reference_type, "type"))
+ ; /* no constraints; this is fine */
+ else if (g_str_equal(reference_type, "enum")) {
+ size_t source_values_len = 0;
+ gs_free char const** source_values = g_variant_get_strv(source_data, &source_values_len);
+
+ size_t reference_values_len = 0;
+ gs_free char const** reference_values = g_variant_get_strv(reference_data, &reference_values_len);
+
+ /* Check that every enum value in source is valid according to the reference */
+ for (size_t i = 0; i < source_values_len; ++i) {
+ if (!strv_contains(reference_values, source_values[i])) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
+ "Schema \"%s\" key \"%s\" has enum value \"%s\" not in reference schema",
+ g_settings_schema_get_id(source_schema),
+ key, source_values[i]);
+ return FALSE;
+ }
+ }
+ } else if (g_str_equal(reference_type, "flags")) {
+ /* Our schemas don't use flags. If that changes, need to implement this! */
+ g_assert_not_reached();
+ } else if (g_str_equal(reference_type, "range")) {
+ if (!g_variant_is_of_type(source_data,
+ g_variant_get_type(reference_data))) {
+ char const* source_type_str = g_variant_get_type_string(source_data);
+ char const* reference_type_str = g_variant_get_type_string(reference_data);
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
+ "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
+ g_settings_schema_get_id(source_schema),
+ key, source_type_str, reference_type_str);
+ return FALSE;
+ }
+
+ gs_unref_variant GVariant* reference_min = nullptr;
+ gs_unref_variant GVariant* reference_max = nullptr;
+ g_variant_get(reference_data, "(**)", &reference_min, &reference_max);
+
+ gs_unref_variant GVariant* source_min = nullptr;
+ gs_unref_variant GVariant* source_max = nullptr;
+ g_variant_get(source_data, "(**)", &source_min, &source_max);
+
+ /* The source interval must be contained within the reference interval */
+ if (g_variant_compare(source_min, reference_min) < 0 ||
+ g_variant_compare(source_max, reference_max) > 0) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
+ "Schema \"%s\" key \"%s\" has range interval not contained in reference range interval",
+ g_settings_schema_get_id(source_schema), key);
+ return FALSE;
+ }
+ } else {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
+ "Schema \"%s\" key \"%s\" has unknown range type \"%s\"",
+ g_settings_schema_get_id(source_schema),
+ key, reference_type);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+schema_verify_key(GSettingsSchema* source_schema,
+ char const* key,
+ GSettingsSchema* reference_schema,
+ GError** error)
+{
+ if (!g_settings_schema_has_key(source_schema, key)) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
+ "Schema \"%s\" has missing key \"%s\"",
+ g_settings_schema_get_id(source_schema), key);
+ return FALSE;
+ }
+
+ gs_unref_settings_schema_key GSettingsSchemaKey* source_key =
+ g_settings_schema_get_key(source_schema, key);
+ g_assert_nonnull(source_key);
+
+ gs_unref_settings_schema_key GSettingsSchemaKey* reference_key =
+ g_settings_schema_get_key(reference_schema, key);
+ g_assert_nonnull(reference_key);
+
+ GVariantType const* source_type = g_settings_schema_key_get_value_type(source_key);
+ GVariantType const* reference_type = g_settings_schema_key_get_value_type(reference_key);
+ if (!g_variant_type_equal(source_type, reference_type)) {
+ gs_free char* source_type_str = g_variant_type_dup_string(source_type);
+ gs_free char* reference_type_str = g_variant_type_dup_string(reference_type);
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
+ "Schema \"%s\" has type \"%s\" but reference type is \"%s\"",
+ g_settings_schema_get_id(source_schema),
+ source_type_str, reference_type_str);
+ return FALSE;
+ }
+
+ gs_unref_variant GVariant* source_default = g_settings_schema_key_get_default_value(source_key);
+ if (!g_settings_schema_key_range_check(reference_key, source_default)) {
+ gs_free char* source_value_str = g_variant_print(source_default, TRUE);
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
+ "Schema \"%s\" default value \"%s\" does not conform to reference schema",
+ g_settings_schema_get_id(source_schema), source_value_str);
+ return FALSE;
+ }
+
+ if (!schema_key_range_compatible(source_schema,
+ source_key,
+ key,
+ reference_key,
+ error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+schema_verify_child(GSettingsSchema* source_schema,
+ char const* child_name,
+ GSettingsSchema* reference_schema,
+ GError** error)
+{
+ /* Should verify the child's schema ID is as expected and exists in
+ * the source, but there appears to be no API to get the schema ID of
+ * the child.
+ *
+ * We work around this missing verification by never calling
+ * g_settings_get_child() and instead always constructing the child
+ * GSettings directly; and the existence and correctness of that
+ * schema is verified by the per-schema checks.
+ */
+
+ return TRUE;
+}
+
+static gboolean
+schema_verify(GSettingsSchema* source_schema,
+ GSettingsSchema* reference_schema,
+ GError** error)
+{
+ /* Verify path */
+ char const* source_path = g_settings_schema_get_path(source_schema);
+ char const* reference_path = g_settings_schema_get_path(reference_schema);
+ if (g_strcmp0(source_path, reference_path) != 0) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
+ "Schema \"%s\" has path \"%s\" but reference path is \"%s\"",
+ g_settings_schema_get_id(source_schema),
+ source_path ? source_path : "(null)",
+ reference_path ? reference_path : "(null)");
+ return FALSE;
+ }
+
+ /* Verify keys */
+ gs_strfreev char** keys = g_settings_schema_list_keys(reference_schema);
+ if (keys) {
+ for (int i = 0; keys[i]; ++i) {
+ if (!schema_verify_key(source_schema,
+ keys[i],
+ reference_schema,
+ error))
+ return FALSE;
+ }
+ }
+
+ /* Verify child schemas */
+ gs_strfreev char** source_children = g_settings_schema_list_children(source_schema);
+ gs_strfreev char** reference_children = g_settings_schema_list_children(reference_schema);
+ if (reference_children) {
+ for (size_t i = 0; reference_children[i]; ++i) {
+ if (!strv_contains((char const* const*)source_children, reference_children[i])) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
+ "Schema \"%s\" has missing child \"%s\"",
+ g_settings_schema_get_id(source_schema),
+ reference_children[i]);
+ return FALSE;
+ }
+
+ if (!schema_verify_child(source_schema,
+ reference_children[i],
+ reference_schema,
+ error))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+schemas_source_verify_schema_by_name(GSettingsSchemaSource* source,
+ char const* schema_name,
+ GSettingsSchemaSource* reference_source,
+ GError** error)
+{
+ gs_unref_settings_schema GSettingsSchema* source_schema =
+ g_settings_schema_source_lookup(source, schema_name, TRUE /* recursive */);
+
+ if (!source_schema) {
+ g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+ TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
+ "Schema \"%s\" is missing", schema_name);
+ return FALSE;
+ }
+
+ gs_unref_settings_schema GSettingsSchema* reference_schema =
+ g_settings_schema_source_lookup(reference_source,
+ schema_name,
+ FALSE /* recursive */);
+ g_assert_nonnull(reference_schema);
+
+ return schema_verify(source_schema,
+ reference_schema,
+ error);
+}
+
+static gboolean
+schemas_source_verify_schemas(GSettingsSchemaSource* source,
+ char const* const* schemas,
+ GSettingsSchemaSource* reference_source,
+ GError** error)
+{
+ if (!schemas)
+ return TRUE;
+
+ for (int i = 0; schemas[i]; ++i) {
+ if (!schemas_source_verify_schema_by_name(source,
+ schemas[i],
+ reference_source,
+ error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+schemas_source_verify(GSettingsSchemaSource* source,
+ GSettingsSchemaSource* reference_source,
+ GError** error)
+{
+ gs_strfreev char** reloc_schemas = nullptr;
+ gs_strfreev char** nonreloc_schemas = nullptr;
+
+ g_settings_schema_source_list_schemas(reference_source,
+ FALSE /* recursive */,
+ &reloc_schemas,
+ &nonreloc_schemas);
+
+ if (!schemas_source_verify_schemas(source,
+ (char const* const*)reloc_schemas,
+ reference_source,
+ error))
+ return FALSE;
+
+ if (!schemas_source_verify_schemas(source,
+ (char const* const*)nonreloc_schemas,
+ reference_source,
+ error))
+ return FALSE;
+
+ return TRUE;
+}
+
+GSettingsSchemaSource*
+terminal_g_settings_schema_source_get_default(void)
+{
+ GSettingsSchemaSource* default_source = g_settings_schema_source_get_default();
+
+ gs_free auto schema_dir =
+ terminal_client_get_directory_uninstalled(
+#if defined(TERMINAL_SERVER)
+ TERM_LIBEXECDIR,
+#elif defined(TERMINAL_PREFERENCES)
+ TERM_LIBEXECDIR,
+#elif defined(TERMINAL_CLIENT)
+ TERM_BINDIR,
+#else
+#error Need to define installed location
+#endif
+ TERM_PKGLIBDIR,
+ "gschemas.compiled",
+ GFileTest(0));
+
+ gs_free_error GError* error = nullptr;
+ GSettingsSchemaSource* reference_source =
+ g_settings_schema_source_new_from_directory(schema_dir,
+ nullptr /* parent source */,
+ FALSE /* trusted */,
+ &error);
+ if (!reference_source) {
+ /* Can only use the installed schemas, or abort here. */
+ g_printerr("Failed to load reference schemas: %s\n"
+ "Using unverified installed schemas.\n",
+ error->message);
+
+ return g_settings_schema_source_ref(default_source);
+ }
+
+ if (!schemas_source_verify(default_source, reference_source, &error)) {
+ g_printerr("Installed schemas failed verification: %s\n"
+ "Falling back to built-in reference schemas.\n",
+ error->message);
+
+ return reference_source; /* transfer */
+ }
+
+ /* Installed schemas verified; use them. */
+ g_settings_schema_source_unref(reference_source);
+ return g_settings_schema_source_ref(default_source);
+}
+
+// BEGIN copied from glib/gio/gsettingsbackend.c
+
+/*
+ * Copyright © 2009, 2010 Codethink Limited
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ * Matthias Clasen <mclasen@redhat.com>
+ */
+
+static void
+variant_unref0(void* data)
+{
+ if (data)
+ g_variant_unref(reinterpret_cast<GVariant*>(data));
+}
+
+static int
+compare_string(void const* a,
+ void const* b,
+ void* closure)
+{
+ return strcmp(reinterpret_cast<char const*>(a),
+ reinterpret_cast<char const*>(b));
+}
+
+/*
+ * terminal_g_settings_backend_create_tree:
+ *
+ * This is a convenience function for creating a tree that is compatible
+ * with terminal_g_settings_backend_write(). It merely calls g_tree_new_full()
+ * with strcmp(), g_free() and g_variant_unref().
+ *
+ * Returns: (transfer full): a new #GTree
+ */
+GTree*
+terminal_g_settings_backend_create_tree(void)
+{
+ return g_tree_new_full(compare_string, nullptr,
+ g_free,
+ variant_unref0);
+}
+
+#ifdef ENABLE_DEBUG
+
+static gboolean
+print_tree(void* key,
+ void* value,
+ void* closure)
+{
+ g_printerr(" %s => %s\n",
+ reinterpret_cast<char const*>(key),
+ value ? g_variant_print(reinterpret_cast<GVariant*>(value), true): "(null)");
+
+ return false; // continue
+}
+
+void
+terminal_g_settings_backend_print_tree(GTree* tree)
+{
+ g_printerr("Settings tree: [\n");
+ g_tree_foreach(tree, print_tree, nullptr);
+ g_printerr("]\n");
+}
+
+#endif /* ENABLE_DEBUG */
+
+#if defined(TERMINAL_SERVER) || defined(TERMINAL_PREFERENCES)
+
+/*
+ * g_settings_backend_read:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the key to read
+ * @expected_type: a #GVariantType
+ * @default_value: if the default value should be returned
+ *
+ * Reads a key. This call will never block.
+ *
+ * If the key exists, the value associated with it will be returned.
+ * If the key does not exist, %nullptr will be returned.
+ *
+ * The returned value will be of the type given in @expected_type. If
+ * the backend stored a value of a different type then %nullptr will be
+ * returned.
+ *
+ * If @default_value is %TRUE then this gets the default value from the
+ * backend (ie: the one that the backend would contain if
+ * g_settings_reset() were called).
+ *
+ * Returns: (nullable) (transfer full): the value that was read, or %nullptr
+ */
+GVariant*
+terminal_g_settings_backend_read(GSettingsBackend* backend,
+ char const* key,
+ GVariantType const* expected_type,
+ gboolean default_value)
+{
+ auto value = G_SETTINGS_BACKEND_GET_CLASS(backend)->read (backend,
+ key,
+ expected_type,
+ default_value);
+
+ if (value)
+ value = g_variant_take_ref(value);
+
+ if (value && !g_variant_is_of_type(value, expected_type)) [[unlikely]] {
+ g_clear_pointer(&value, g_variant_unref);
+ }
+
+ return value;
+}
+
+/*
+ * terminal_g_settings_backend_read_user_value:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the key to read
+ * @expected_type: a #GVariantType
+ *
+ * Reads the 'user value' of a key.
+ *
+ * This is the value of the key that the user has control over and has
+ * set for themselves. Put another way: if the user did not set the
+ * value for themselves, then this will return %nullptr(even if the
+ * sysadmin has provided a default value).
+ *
+ * Returns:(nullable)(transfer full): the value that was read, or %nullptr
+ */
+GVariant*
+terminal_g_settings_backend_read_user_value(GSettingsBackend* backend,
+ char const*key,
+ GVariantType const* expected_type)
+{
+ auto value = G_SETTINGS_BACKEND_GET_CLASS(backend)->read_user_value(backend,
+ key,
+ expected_type);
+
+ if (value)
+ value = g_variant_take_ref(value);
+
+ if (value && !g_variant_is_of_type(value, expected_type)) [[unlikely]] {
+ g_clear_pointer(&value, g_variant_unref);
+ }
+
+ return value;
+}
+
+/*
+ * terminal_g_settings_backend_write:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the name of the key
+ * @value: a #GVariant value to write to this key
+ * @origin_tag: the origin tag
+ *
+ * Writes exactly one key.
+ *
+ * This call does not fail. During this call a
+ * #GSettingsBackend::changed signal will be emitted if the value of the
+ * key has changed. The updated key value will be visible to any signal
+ * callbacks.
+ *
+ * One possible method that an implementation might deal with failures is
+ * to emit a second "changed" signal(either during this call, or later)
+ * to indicate that the affected keys have suddenly "changed back" to their
+ * old values.
+ *
+ * If @value has a floating reference, it will be sunk.
+ *
+ * Returns: %TRUE if the write succeeded, %FALSE if the key was not writable
+ */
+gboolean
+terminal_g_settings_backend_write(GSettingsBackend* backend,
+ char const* key,
+ GVariant* value,
+ void* origin_tag)
+{
+ g_variant_ref_sink(value);
+ auto const success = G_SETTINGS_BACKEND_GET_CLASS(backend)->write(backend,
+ key,
+ value,
+ origin_tag);
+ g_variant_unref(value);
+
+ return success;
+}
+
+/*
+ * terminal_g_settings_backend_write_tree:
+ * @backend: a #GSettingsBackend implementation
+ * @tree: a #GTree containing key-value pairs to write
+ * @origin_tag: the origin tag
+ *
+ * Writes one or more keys. This call will never block.
+ *
+ * The key of each item in the tree is the key name to write to and the
+ * value is a #GVariant to write. The proper type of #GTree for this
+ * call can be created with terminal_g_settings_backend_create_tree(). This call
+ * might take a reference to the tree; you must not modified the #GTree
+ * after passing it to this call.
+ *
+ * This call does not fail. During this call a #GSettingsBackend::changed
+ * signal will be emitted if any keys have been changed. The new values of
+ * all updated keys will be visible to any signal callbacks.
+ *
+ * One possible method that an implementation might deal with failures is
+ * to emit a second "changed" signal(either during this call, or later)
+ * to indicate that the affected keys have suddenly "changed back" to their
+ * old values.
+ */
+gboolean
+terminal_g_settings_backend_write_tree(GSettingsBackend* backend,
+ GTree* tree,
+ void* origin_tag)
+{
+ return G_SETTINGS_BACKEND_GET_CLASS(backend)->write_tree(backend,
+ tree,
+ origin_tag);
+}
+
+/*
+ * terminal_g_settings_backend_reset:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the name of a key
+ * @origin_tag: the origin tag
+ *
+ * "Resets" the named key to its "default" value(ie: after system-wide
+ * defaults, mandatory keys, etc. have been taken into account) or possibly
+ * unsets it.
+ */
+void
+terminal_g_settings_backend_reset(GSettingsBackend*backend,
+ char const* key,
+ void* origin_tag)
+{
+ G_SETTINGS_BACKEND_GET_CLASS(backend)->reset(backend, key, origin_tag);
+}
+
+/*
+ * terminal_g_settings_backend_get_writable:
+ * @backend: a #GSettingsBackend implementation
+ * @key: the name of a key
+ *
+ * Finds out if a key is available for writing to. This is the
+ * interface through which 'lockdown' is implemented. Locked down
+ * keys will have %FALSE returned by this call.
+ *
+ * You should not write to locked-down keys, but if you do, the
+ * implementation will deal with it.
+ *
+ * Returns: %TRUE if the key is writable
+ */
+gboolean
+terminal_g_settings_backend_get_writable(GSettingsBackend* backend,
+ char const* key)
+{
+ return G_SETTINGS_BACKEND_GET_CLASS(backend)->get_writable(backend, key);
+}
+
+/*
+ * terminal_g_settings_backend_unsubscribe:
+ * @backend: a #GSettingsBackend
+ * @name: a key or path to subscribe to
+ *
+ * Reverses the effect of a previous call to
+ * terminal_g_settings_backend_subscribe().
+ */
+void
+terminal_g_settings_backend_unsubscribe(GSettingsBackend* backend,
+ const char* name)
+{
+ G_SETTINGS_BACKEND_GET_CLASS(backend)->unsubscribe(backend, name);
+}
+
+/*
+ * terminal_g_settings_backend_subscribe:
+ * @backend: a #GSettingsBackend
+ * @name: a key or path to subscribe to
+ *
+ * Requests that change signals be emitted for events on @name.
+ */
+void
+terminal_g_settings_backend_subscribe(GSettingsBackend* backend,
+ char const* name)
+{
+ G_SETTINGS_BACKEND_GET_CLASS(backend)->subscribe(backend, name);
+}
+
+/*
+ * terminal_g_settings_backend_get_permission:
+ * @backend: a #GSettingsBackend
+ * @path: a path
+ *
+ * Gets the permission object associated with writing to keys below
+ * @path on @backend.
+ *
+ * If this is not implemented in the backend, then a %TRUE
+ * #GSimplePermission is returned.
+ *
+ * Returns:(not nullable)(transfer full): a non-%nullptr #GPermission.
+ * Free with g_object_unref()
+ */
+GPermission*
+terminal_g_settings_backend_get_permission(GSettingsBackend* backend,
+ char const* path)
+{
+ auto const klass = G_SETTINGS_BACKEND_GET_CLASS(backend);
+ if (klass->get_permission)
+ return klass->get_permission(backend, path);
+
+ return g_simple_permission_new(TRUE);
+}
+
+/*
+ * terminal_g_settings_backend_sync_default:
+ *
+ * Syncs.
+ */
+void
+terminal_g_settings_backend_sync(GSettingsBackend* backend)
+{
+ auto const klass = G_SETTINGS_BACKEND_GET_CLASS(backend);
+ if (klass->sync)
+ klass->sync(backend);
+}
+
+// END copied from glib
+
+// Note: Since D-Bus/GDBus does not support GVariant maybe types (not even
+// on private peer-to-peer connections), we need to wrap the variants
+// for transport over the bus. The format is a "mv" variant whose inner
+// value is the variant to transport, or Nothing for a nullptr GVariant.
+// We then transport that variant in serialised form as a byte array over
+// the bus.
+
+/*
+ * terminal_g_variant_wrap:
+ * @variant: (nullable): a #GVariant, or %NULL
+ *
+ * Wraps @variant for transport over D-Bus.
+ * if @variant is floating, it is consumed.
+ *
+ * Returns: (transfer full): a floating variant wrapping @variant
+ */
+GVariant*
+terminal_g_variant_wrap(GVariant* variant)
+{
+ auto const value = variant ? g_variant_new_variant(variant) : nullptr;
+ auto const maybe = g_variant_new_maybe(G_VARIANT_TYPE_VARIANT, value);
+
+ return g_variant_new_from_data(G_VARIANT_TYPE("ay"),
+ g_variant_get_data(maybe),
+ g_variant_get_size(maybe),
+ false, // trusted
+ GDestroyNotify(g_variant_unref),
+ g_variant_ref_sink(maybe)); // adopts
+}
+
+/*
+ * terminal_g_variant_unwrap:
+ * @variant: a "ay" #GVariant
+ *
+ * Unwraps a variant transported over D-Bus.
+ * If @variant is floating, it is NOT consumed.
+ *
+ * Returns: (transfer full): a non-floating variant unwrapping @variant
+ */
+GVariant*
+terminal_g_variant_unwrap(GVariant* variant)
+{
+ g_return_val_if_fail(g_variant_is_of_type(variant, G_VARIANT_TYPE("ay")), nullptr);
+
+ gs_unref_bytes auto bytes = g_variant_get_data_as_bytes(variant);
+ gs_unref_variant auto maybe = g_variant_take_ref(g_variant_new_from_bytes(G_VARIANT_TYPE("mv"), bytes, false));
+ gs_unref_variant auto value = g_variant_get_maybe(maybe);
+ return value ? g_variant_get_variant(value) : nullptr;
+}
+
+
+#endif /* TERMINAL_SERVER || TERMINAL_PREFERENCES */
diff --git a/src/terminal-settings-utils.hh b/src/terminal-settings-utils.hh
new file mode 100644
index 0000000..b34afb3
--- /dev/null
+++ b/src/terminal-settings-utils.hh
@@ -0,0 +1,114 @@
+/*
+ * Copyright © 2008, 2010, 2022 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/>.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+GSettings* terminal_g_settings_new (GSettingsBackend* backend,
+ GSettingsSchemaSource* source,
+ char const* schema_id);
+
+GSettings* terminal_g_settings_new_with_path (GSettingsBackend* backend,
+ GSettingsSchemaSource* source,
+ char const* schema_id,
+ char const* path);
+
+void terminal_g_settings_backend_clone_schema(GSettingsBackend* backend,
+ GSettingsSchemaSource*schema_source,
+ char const* schema_id,
+ char const* path,
+ char const* new_path,
+ GTree* tree);
+
+gboolean terminal_g_settings_backend_erase_path(GSettingsBackend* backend,
+ GSettingsSchemaSource* schema_source,
+ char const* schema_id,
+ char const* path);
+
+GTree* terminal_g_settings_backend_create_tree(void);
+
+void terminal_g_settings_backend_print_tree(GTree* tree);
+
+GSettingsSchemaSource* terminal_g_settings_schema_source_get_default(void);
+
+GTree* terminal_g_settings_backend_create_tree(void);
+
+// BEGIN copied from glib/gio/gsettingsbackendinternal.h
+
+/*
+ * Copyright © 2009, 2010 Codethink Limited
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Ryan Lortie <desrt@desrt.ca>
+ * Matthias Clasen <mclasen@redhat.com>
+ */
+
+GPermission* terminal_g_settings_backend_get_permission(GSettingsBackend* backend,
+ char const*path);
+
+gboolean terminal_g_settings_backend_get_writable(GSettingsBackend* backend,
+ const char* key);
+
+GVariant* terminal_g_settings_backend_read(GSettingsBackend* backend,
+ char const* key,
+ GVariantType const* expected_type,
+ gboolean default_value);
+
+GVariant* terminal_g_settings_backend_read_user_value(GSettingsBackend* backend,
+ char const* key,
+ GVariantType const* expected_type);
+
+void terminal_g_settings_backend_reset(GSettingsBackend* backend,
+ char const* key,
+ void* origin_tag);
+
+void terminal_g_settings_backend_subscribe(GSettingsBackend* backend,
+ const char* name);
+
+void terminal_g_settings_backend_sync(GSettingsBackend* backend);
+
+void terminal_g_settings_backend_unsubscribe(GSettingsBackend* backend,
+ const char* name);
+
+gboolean terminal_g_settings_backend_write(GSettingsBackend* backend,
+ char const* key,
+ GVariant* value,
+ void* origin_tag);
+
+gboolean terminal_g_settings_backend_write_tree(GSettingsBackend* backend,
+ GTree* tree,
+ void* origin_tag);
+
+// END copied from glib
+
+GVariant* terminal_g_variant_wrap(GVariant* variant);
+
+GVariant* terminal_g_variant_unwrap(GVariant* variant);
diff --git a/src/terminal-tab-label.cc b/src/terminal-tab-label.cc
new file mode 100644
index 0000000..88af5d4
--- /dev/null
+++ b/src/terminal-tab-label.cc
@@ -0,0 +1,395 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2007, 2008 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 <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "terminal-intl.hh"
+#include "terminal-tab-label.hh"
+#include "terminal-icon-button.hh"
+#include "terminal-window.hh"
+
+#define TERMINAL_TAB_LABEL_GET_PRIVATE(tab_label)(G_TYPE_INSTANCE_GET_PRIVATE ((tab_label), TERMINAL_TYPE_TAB_LABEL, TerminalTabLabelPrivate))
+
+#define SPACING (4)
+
+struct _TerminalTabLabelPrivate
+{
+ TerminalScreen *screen;
+ GtkWidget *label;
+ GtkWidget *close_button;
+ gboolean bold;
+ GtkPositionType tab_pos;
+};
+
+enum
+{
+ PROP_0,
+ PROP_SCREEN
+};
+
+enum
+{
+ CLOSE_BUTTON_CLICKED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (TerminalTabLabel, terminal_tab_label, GTK_TYPE_BOX);
+
+/* helper functions */
+
+static void
+close_button_clicked_cb (GtkWidget *widget,
+ TerminalTabLabel *tab_label)
+{
+ g_signal_emit (tab_label, signals[CLOSE_BUTTON_CLICKED], 0);
+}
+
+static void
+sync_tab_label (TerminalScreen *screen,
+ GParamSpec *pspec,
+ GtkWidget *label)
+{
+ GtkWidget *hbox;
+ const char *title;
+ TerminalWindow *window;
+
+ title = terminal_screen_get_title (screen);
+ hbox = gtk_widget_get_parent (label);
+
+ gtk_label_set_text (GTK_LABEL (label),
+ title && title[0] ? title : _("Terminal"));
+
+ gtk_widget_set_tooltip_text (hbox, title);
+
+ /* This call updates the window size: bug 732588.
+ * FIXMEchpe: This is probably a GTK+ bug, should get them fix it.
+ */
+ window = TERMINAL_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (label),
+ TERMINAL_TYPE_WINDOW));
+ if (window != nullptr)
+ terminal_window_update_size (window);
+}
+
+static void
+notify_tab_pos_cb (GtkNotebook *notebook,
+ GParamSpec *pspec G_GNUC_UNUSED,
+ TerminalTabLabel *label)
+{
+ TerminalTabLabelPrivate *priv = label->priv;
+ GtkPositionType pos;
+
+ pos = gtk_notebook_get_tab_pos (notebook);
+ if (pos == priv->tab_pos)
+ return;
+
+ priv->tab_pos = pos;
+
+ switch (pos) {
+ case GTK_POS_LEFT:
+ case GTK_POS_RIGHT:
+ gtk_widget_hide (priv->close_button);
+ break;
+ case GTK_POS_TOP:
+ case GTK_POS_BOTTOM:
+ gtk_widget_show (priv->close_button);
+ break;
+ }
+}
+
+/* public functions */
+
+/* Class implementation */
+
+static void
+terminal_tab_label_parent_set (GtkWidget *widget,
+ GtkWidget *old_parent)
+{
+ GtkWidget *parent;
+ void (* parent_set) (GtkWidget *, GtkWidget *) = GTK_WIDGET_CLASS (terminal_tab_label_parent_class)->parent_set;
+
+ if (GTK_IS_NOTEBOOK (old_parent)) {
+ g_signal_handlers_disconnect_by_func (old_parent,
+ (void*)notify_tab_pos_cb,
+ widget);
+ }
+
+ if (parent_set)
+ parent_set (widget, old_parent);
+
+ parent = gtk_widget_get_parent (widget);
+ if (GTK_IS_NOTEBOOK (parent)) {
+ notify_tab_pos_cb (GTK_NOTEBOOK (parent), nullptr, TERMINAL_TAB_LABEL (widget));
+ g_signal_connect (parent, "notify::tab-pos",
+ G_CALLBACK (notify_tab_pos_cb), widget);
+ }
+}
+
+static void
+terminal_tab_label_get_preferred_width (GtkWidget *widget,
+ int *minimum_width,
+ int *natural_width)
+{
+ TerminalTabLabel *tab_label = TERMINAL_TAB_LABEL (widget);
+ TerminalTabLabelPrivate *priv = tab_label->priv;
+
+ if (priv->tab_pos == GTK_POS_LEFT ||
+ priv->tab_pos == GTK_POS_RIGHT) {
+ if (natural_width)
+ *natural_width = 160;
+ if (minimum_width)
+ *minimum_width = 160;
+ }
+ else
+ GTK_WIDGET_CLASS (terminal_tab_label_parent_class)->get_preferred_width (widget, minimum_width, natural_width);
+}
+
+static void
+terminal_tab_label_init (TerminalTabLabel *tab_label)
+{
+ TerminalTabLabelPrivate *priv;
+
+ priv = tab_label->priv = TERMINAL_TAB_LABEL_GET_PRIVATE (tab_label);
+
+ priv->tab_pos = (GtkPositionType) -1; /* invalid */
+}
+
+static void
+terminal_tab_label_constructed (GObject *object)
+{
+ TerminalTabLabel *tab_label = TERMINAL_TAB_LABEL (object);
+ TerminalTabLabelPrivate *priv = tab_label->priv;
+ GtkWidget *hbox, *label, *close_button;
+
+ G_OBJECT_CLASS (terminal_tab_label_parent_class)->constructed (object);
+
+ hbox = GTK_WIDGET (tab_label);
+
+ g_assert (priv->screen != nullptr);
+
+ gtk_box_set_spacing (GTK_BOX (hbox), SPACING);
+
+ priv->label = label = gtk_label_new (nullptr);
+ gtk_widget_set_halign (label, GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (label, GTK_ALIGN_BASELINE);
+ gtk_widget_set_margin_start (label, 0);
+ gtk_widget_set_margin_end (label, 0);
+ gtk_widget_set_margin_top (label, 0);
+ gtk_widget_set_margin_bottom (label, 0);
+
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
+
+ gtk_box_set_center_widget (GTK_BOX (hbox), label);
+
+ priv->close_button = close_button = terminal_close_button_new ();
+ gtk_widget_set_tooltip_text (close_button, _("Close tab"));
+ gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
+
+ sync_tab_label (priv->screen, nullptr, label);
+ g_signal_connect (priv->screen, "notify::title",
+ G_CALLBACK (sync_tab_label), label);
+
+ g_signal_connect (close_button, "clicked",
+ G_CALLBACK (close_button_clicked_cb), tab_label);
+
+ gtk_widget_show_all (hbox);
+}
+
+static void
+terminal_tab_label_dispose (GObject *object)
+{
+ TerminalTabLabel *tab_label = TERMINAL_TAB_LABEL (object);
+ TerminalTabLabelPrivate *priv = tab_label->priv;
+
+ if (priv->screen != nullptr) {
+ g_signal_handlers_disconnect_by_func (priv->screen,
+ (void*)sync_tab_label,
+ priv->label);
+ g_object_unref (priv->screen);
+ priv->screen = nullptr;
+ }
+
+ G_OBJECT_CLASS (terminal_tab_label_parent_class)->dispose (object);
+}
+
+static void
+terminal_tab_label_finalize (GObject *object)
+{
+// TerminalTabLabel *tab_label = TERMINAL_TAB_LABEL (object);
+
+ G_OBJECT_CLASS (terminal_tab_label_parent_class)->finalize (object);
+}
+
+static void
+terminal_tab_label_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalTabLabel *tab_label = TERMINAL_TAB_LABEL (object);
+
+ switch (prop_id) {
+ case PROP_SCREEN:
+ g_value_set_object (value, terminal_tab_label_get_screen (tab_label));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_tab_label_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ TerminalTabLabel *tab_label = TERMINAL_TAB_LABEL (object);
+ TerminalTabLabelPrivate *priv = tab_label->priv;
+
+ switch (prop_id) {
+ case PROP_SCREEN:
+ priv->screen = (TerminalScreen*)g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+terminal_tab_label_class_init (TerminalTabLabelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->constructed = terminal_tab_label_constructed;
+ gobject_class->dispose = terminal_tab_label_dispose;
+ gobject_class->finalize = terminal_tab_label_finalize;
+ gobject_class->get_property = terminal_tab_label_get_property;
+ gobject_class->set_property = terminal_tab_label_set_property;
+
+ widget_class->parent_set = terminal_tab_label_parent_set;
+ widget_class->get_preferred_width = terminal_tab_label_get_preferred_width;
+
+ signals[CLOSE_BUTTON_CLICKED] =
+ g_signal_new (I_("close-button-clicked"),
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (TerminalTabLabelClass, close_button_clicked),
+ nullptr, nullptr,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SCREEN,
+ g_param_spec_object ("screen", nullptr, nullptr,
+ TERMINAL_TYPE_SCREEN,
+ GParamFlags(G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_CONSTRUCT_ONLY)));
+
+ g_type_class_add_private (gobject_class, sizeof (TerminalTabLabelPrivate));
+}
+
+/* public API */
+
+/**
+ * terminal_tab_label_new:
+ * @screen: a #TerminalScreen
+ *
+ * Returns: a new #TerminalTabLabel for @screen
+ */
+GtkWidget *
+terminal_tab_label_new (TerminalScreen *screen)
+{
+ return reinterpret_cast<GtkWidget*>
+ (g_object_new (TERMINAL_TYPE_TAB_LABEL,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "screen", screen,
+ nullptr));
+}
+
+/**
+ * terminal_tab_label_set_bold:
+ * @tab_label: a #TerminalTabLabel
+ * @bold: whether to enable label bolding
+ *
+ * Sets the tab label text bold, or unbolds it.
+ */
+void
+terminal_tab_label_set_bold (TerminalTabLabel *tab_label,
+ gboolean bold)
+{
+ TerminalTabLabelPrivate *priv = tab_label->priv;
+ PangoAttrList *attr_list;
+ PangoAttribute *weight_attr;
+ gboolean free_list = FALSE;
+
+ bold = bold != FALSE;
+ if (priv->bold == bold)
+ return;
+
+ priv->bold = bold;
+
+ attr_list = gtk_label_get_attributes (GTK_LABEL (priv->label));
+ if (!attr_list) {
+ attr_list = pango_attr_list_new ();
+ free_list = TRUE;
+ }
+
+ if (bold)
+ weight_attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+ else
+ weight_attr = pango_attr_weight_new (PANGO_WEIGHT_NORMAL);
+
+ /* gtk_label_get_attributes() returns the label's internal list,
+ * which we're probably not supposed to modify directly.
+ * It seems to work ok however.
+ */
+ pango_attr_list_change (attr_list, weight_attr);
+
+ gtk_label_set_attributes (GTK_LABEL (priv->label), attr_list);
+
+ if (free_list)
+ pango_attr_list_unref (attr_list);
+}
+
+/**
+ * terminal_tab_label_get_screen:
+ * @tab_label: a #TerminalTabLabel
+ *
+ * Returns: (transfer none): the #TerminalScreen for @tab_label
+ */
+TerminalScreen *
+terminal_tab_label_get_screen (TerminalTabLabel *tab_label)
+{
+ g_return_val_if_fail (TERMINAL_IS_TAB_LABEL (tab_label), nullptr);
+
+ return tab_label->priv->screen;
+}
diff --git a/src/terminal-tab-label.hh b/src/terminal-tab-label.hh
new file mode 100644
index 0000000..91b7e61
--- /dev/null
+++ b/src/terminal-tab-label.hh
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2008 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/>.
+ */
+
+#ifndef TERMINAL_TAB_LABEL_H
+#define TERMINAL_TAB_LABEL_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_TAB_LABEL (terminal_tab_label_get_type ())
+#define TERMINAL_TAB_LABEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TERMINAL_TYPE_TAB_LABEL, TerminalTabLabel))
+#define TERMINAL_TAB_LABEL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), TERMINAL_TYPE_TAB_LABEL, TerminalTabLabelClass))
+#define TERMINAL_IS_TAB_LABEL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TERMINAL_TYPE_TAB_LABEL))
+#define TERMINAL_IS_TAB_LABEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), TERMINAL_TYPE_TAB_LABEL))
+#define TERMINAL_TAB_LABEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TERMINAL_TYPE_TAB_LABEL, TerminalTabLabelClass))
+
+typedef struct _TerminalTabLabel TerminalTabLabel;
+typedef struct _TerminalTabLabelClass TerminalTabLabelClass;
+typedef struct _TerminalTabLabelPrivate TerminalTabLabelPrivate;
+
+struct _TerminalTabLabel
+{
+ GtkBox parent_instance;
+
+ /*< private >*/
+ TerminalTabLabelPrivate *priv;
+};
+
+struct _TerminalTabLabelClass
+{
+ GtkBoxClass parent_class;
+
+ /* Signals */
+ void (* close_button_clicked) (TerminalTabLabel *tab_label);
+};
+
+GType terminal_tab_label_get_type (void);
+
+GtkWidget * terminal_tab_label_new (TerminalScreen *screen);
+
+void terminal_tab_label_set_bold (TerminalTabLabel *tab_label,
+ gboolean bold);
+
+TerminalScreen *terminal_tab_label_get_screen (TerminalTabLabel *tab_label);
+
+G_END_DECLS
+
+#endif /* !TERMINAL_TAB_LABEL_H */
diff --git a/src/terminal-type-builtins.cc.template b/src/terminal-type-builtins.cc.template
new file mode 100644
index 0000000..00c0276
--- /dev/null
+++ b/src/terminal-type-builtins.cc.template
@@ -0,0 +1,45 @@
+/*** BEGIN file-header ***/
+#include <config.h>
+
+#include "terminal-type-builtins.hh"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+
+#include "@filename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static gsize g_define_type_id__ = 0;
+
+ if (g_once_init_enter (&g_define_type_id__)) {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, nullptr, nullptr }
+ };
+ GType g_define_type_id = \
+ g_@type@_register_static (/* g_intern_static_string */ ("@EnumName@"), values);
+
+ g_once_init_leave (&g_define_type_id__, g_define_type_id);
+ }
+
+ return g_define_type_id__;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+
+/*** END file-tail ***/
diff --git a/src/terminal-type-builtins.hh.template b/src/terminal-type-builtins.hh.template
new file mode 100644
index 0000000..c454d05
--- /dev/null
+++ b/src/terminal-type-builtins.hh.template
@@ -0,0 +1,25 @@
+/*** BEGIN file-header ***/
+#ifndef TERMINAL_TYPE_BUILTINS_H
+#define TERMINAL_TYPE_BUILTINS_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void);
+#define TERMINAL_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+
+G_END_DECLS
+
+#endif /* !TERMINAL_TYPE_BUILTINS_H */
+/*** END file-tail ***/
diff --git a/src/terminal-util.cc b/src/terminal-util.cc
new file mode 100644
index 0000000..3ee8f51
--- /dev/null
+++ b/src/terminal-util.cc
@@ -0,0 +1,1945 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2008, 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <langinfo.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <gdesktop-enums.h>
+
+#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"
+#include "terminal-libgsystem.hh"
+
+/**
+ * terminal_util_show_error_dialog:
+ * @transient_parent: parent of the future dialog window;
+ * @weap_ptr: pointer to a #Widget pointer, to control the population.
+ * @error: a #GError, or %nullptr
+ * @message_format: printf() style format string
+ *
+ * Create a #GtkMessageDialog window with the message, and present it, handling its buttons.
+ * If @weap_ptr is not #nullptr, only create the dialog if <literal>*weap_ptr</literal> is #nullptr
+ * (and in that * case, set @weap_ptr to be a weak pointer to the new dialog), otherwise just
+ * present <literal>*weak_ptr</literal>. Note that in this last case, the message <emph>will</emph>
+ * be changed.
+ */
+void
+terminal_util_show_error_dialog (GtkWindow *transient_parent,
+ GtkWidget **weak_ptr,
+ GError *error,
+ const char *message_format,
+ ...)
+{
+ gs_free char *message;
+ va_list args;
+
+ if (message_format)
+ {
+ va_start (args, message_format);
+ message = g_strdup_vprintf (message_format, args);
+ va_end (args);
+ }
+ else message = nullptr;
+
+ if (weak_ptr == nullptr || *weak_ptr == nullptr)
+ {
+ GtkWidget *dialog;
+ dialog = gtk_message_dialog_new (transient_parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ message ? "%s" : nullptr,
+ message);
+
+ if (error != nullptr)
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), nullptr);
+
+ if (weak_ptr != nullptr)
+ {
+ *weak_ptr = dialog;
+ g_object_add_weak_pointer (G_OBJECT (dialog), (void**)weak_ptr);
+ }
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_show_all (dialog);
+ }
+ else
+ {
+ g_return_if_fail (GTK_IS_MESSAGE_DIALOG (*weak_ptr));
+
+ /* Sucks that there's no direct accessor for "text" property */
+ g_object_set (G_OBJECT (*weak_ptr), "text", message, nullptr);
+
+ gtk_window_present (GTK_WINDOW (*weak_ptr));
+ }
+}
+
+static gboolean
+open_url (GtkWindow *parent,
+ const char *uri,
+ guint32 user_time,
+ GError **error)
+{
+ GdkScreen *screen;
+ gs_free char *uri_fixed;
+
+ if (parent)
+ screen = gtk_widget_get_screen (GTK_WIDGET (parent));
+ else
+ screen = gdk_screen_get_default ();
+
+ uri_fixed = terminal_util_uri_fixup (uri, error);
+ if (uri_fixed == nullptr)
+ return FALSE;
+
+ return gtk_show_uri (screen, uri_fixed, user_time, error);
+}
+
+void
+terminal_util_show_help (const char *topic)
+{
+ gs_free_error GError *error = nullptr;
+ gs_free char *uri;
+
+ if (topic) {
+ uri = g_strdup_printf ("help:gnome-terminal/%s", topic);
+ } else {
+ uri = g_strdup ("help:gnome-terminal");
+ }
+
+ if (!open_url (nullptr, uri, gtk_get_current_event_time (), &error))
+ {
+ terminal_util_show_error_dialog (nullptr, nullptr, error,
+ _("There was an error displaying help"));
+ }
+}
+
+#define ABOUT_GROUP "About"
+#define ABOUT_URL "https://wiki.gnome.org/Apps/Terminal"
+#define EMAILIFY(string) (g_strdelimit ((string), "%", '@'))
+
+void
+terminal_util_show_about (void)
+{
+ static const char copyright[] =
+ "Copyright © 2002–2004 Havoc Pennington\n"
+ "Copyright © 2003–2004, 2007 Mariano Suárez-Alvarez\n"
+ "Copyright © 2006 Guilherme de S. Pastore\n"
+ "Copyright © 2007–2019 Christian Persch\n"
+ "Copyright © 2013–2019 Egmont Koblinger";
+ char *licence_text;
+ GKeyFile *key_file;
+ GBytes *bytes;
+ const guint8 *data;
+ gsize data_len;
+ GError *error = nullptr;
+ char **authors, **contributors, **artists, **documenters, **array_strv;
+ gsize n_authors = 0, n_contributors = 0, n_artists = 0, n_documenters = 0 , i;
+ GPtrArray *array;
+ gs_free char *comment;
+ gs_free char *version;
+ gs_free char *vte_version;
+ GtkWindow *dialog;
+
+ bytes = g_resources_lookup_data (TERMINAL_RESOURCES_PATH_PREFIX "/ui/terminal.about",
+ G_RESOURCE_LOOKUP_FLAGS_NONE,
+ &error);
+ g_assert_no_error (error);
+
+ data = (guint8 const*)g_bytes_get_data (bytes, &data_len);
+ key_file = g_key_file_new ();
+ g_key_file_load_from_data (key_file, (const char *) data, data_len, GKeyFileFlags(0), &error);
+ g_assert_no_error (error);
+
+ authors = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Authors", &n_authors, nullptr);
+ contributors = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Contributors", &n_contributors, nullptr);
+ artists = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Artists", &n_artists, nullptr);
+ documenters = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Documenters", &n_documenters, nullptr);
+
+ g_key_file_free (key_file);
+ g_bytes_unref (bytes);
+
+ array = g_ptr_array_new ();
+
+ for (i = 0; i < n_authors; ++i)
+ g_ptr_array_add (array, EMAILIFY (authors[i]));
+ g_free (authors); /* strings are now owned by the array */
+
+ if (n_contributors > 0)
+ {
+ g_ptr_array_add (array, g_strdup (""));
+ g_ptr_array_add (array, g_strdup (_("Contributors:")));
+ for (i = 0; i < n_contributors; ++i)
+ g_ptr_array_add (array, EMAILIFY (contributors[i]));
+ }
+ g_free (contributors); /* strings are now owned by the array */
+
+ g_ptr_array_add (array, nullptr);
+ array_strv = (char **) g_ptr_array_free (array, FALSE);
+
+ for (i = 0; i < n_artists; ++i)
+ artists[i] = EMAILIFY (artists[i]);
+ for (i = 0; i < n_documenters; ++i)
+ documenters[i] = EMAILIFY (documenters[i]);
+
+ licence_text = terminal_util_get_licence_text ();
+
+ /* gnome 40 corresponds to g-t 3.40.x. After that, gnome version
+ * increases by 1 while the g-t minor version increases by 2 between
+ * stable releases.
+ */
+ auto const gnome_version = 40 + (TERMINAL_MINOR_VERSION - 40 + 1) / 2;
+ version = g_strdup_printf (_("Version %s for GNOME %d"),
+ VERSION,
+ gnome_version);
+
+ vte_version = g_strdup_printf (_("Using VTE version %u.%u.%u"),
+ vte_get_major_version (),
+ vte_get_minor_version (),
+ vte_get_micro_version ());
+
+ comment = g_strdup_printf("%s\n%s %s",
+ _("A terminal emulator for the GNOME desktop"),
+ vte_version,
+ vte_get_features ());
+
+ dialog = (GtkWindow*)g_object_new (GTK_TYPE_ABOUT_DIALOG,
+ /* Hold the application while the window is shown */
+ "application", terminal_app_get (),
+ "program-name", _("GNOME Terminal"),
+ "copyright", copyright,
+ "comments", comment,
+ "version", version,
+ "authors", array_strv,
+ "artists", artists,
+ "documenters", documenters,
+ "license", licence_text,
+ "wrap-license", TRUE,
+ "website", ABOUT_URL,
+ "translator-credits", _("translator-credits"),
+ "logo-icon-name", GNOME_TERMINAL_ICON_NAME,
+ nullptr);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), nullptr);
+ gtk_window_present (dialog);
+
+ g_strfreev (array_strv);
+ g_strfreev (artists);
+ g_strfreev (documenters);
+ g_free (licence_text);
+}
+
+/* sets accessible name and description for the widget */
+
+void
+terminal_util_set_atk_name_description (GtkWidget *widget,
+ const char *name,
+ const char *desc)
+{
+ AtkObject *obj;
+
+ obj = gtk_widget_get_accessible (widget);
+
+ if (obj == nullptr)
+ {
+ g_warning ("%s: for some reason widget has no GtkAccessible",
+ G_STRFUNC);
+ return;
+ }
+
+ if (!GTK_IS_ACCESSIBLE (obj))
+ return; /* This means GAIL is not loaded so we have the NoOp accessible */
+
+ g_return_if_fail (GTK_IS_ACCESSIBLE (obj));
+ if (desc)
+ atk_object_set_description (obj, desc);
+ if (name)
+ atk_object_set_name (obj, name);
+}
+
+void
+terminal_util_open_url (GtkWidget *parent,
+ const char *orig_url,
+ TerminalURLFlavor flavor,
+ guint32 user_time)
+{
+ gs_free_error GError *error = nullptr;
+ gs_free char *uri = nullptr;
+
+ g_return_if_fail (orig_url != nullptr);
+
+ switch (flavor)
+ {
+ case FLAVOR_DEFAULT_TO_HTTP:
+ uri = g_strdup_printf ("http://%s", orig_url);
+ break;
+ case FLAVOR_EMAIL:
+ if (g_ascii_strncasecmp ("mailto:", orig_url, 7) != 0)
+ uri = g_strdup_printf ("mailto:%s", orig_url);
+ else
+ uri = g_strdup (orig_url);
+ break;
+ case FLAVOR_VOIP_CALL:
+ case FLAVOR_AS_IS:
+ uri = g_strdup (orig_url);
+ break;
+ default:
+ uri = nullptr;
+ g_assert_not_reached ();
+ }
+
+ if (!open_url (GTK_WINDOW (parent), uri, user_time, &error))
+ {
+ terminal_util_show_error_dialog (GTK_WINDOW (parent), nullptr, error,
+ _("Could not open the address “%s”"),
+ uri);
+ }
+}
+
+/**
+ * terminal_util_transform_uris_to_quoted_fuse_paths:
+ * @uris:
+ *
+ * Transforms those URIs in @uris to shell-quoted paths that point to
+ * GIO fuse paths.
+ */
+void
+terminal_util_transform_uris_to_quoted_fuse_paths (char **uris)
+{
+ guint i;
+
+ if (!uris)
+ return;
+
+ for (i = 0; uris[i]; ++i)
+ {
+ gs_unref_object GFile *file;
+ gs_free char *path;
+
+ file = g_file_new_for_uri (uris[i]);
+
+ path = g_file_get_path (file);
+ if (path)
+ {
+ char *quoted;
+
+ quoted = g_shell_quote (path);
+ g_free (uris[i]);
+
+ uris[i] = quoted;
+ }
+ }
+}
+
+char *
+terminal_util_concat_uris (char **uris,
+ gsize *length)
+{
+ GString *string;
+ gsize len;
+ guint i;
+
+ len = 0;
+ for (i = 0; uris[i]; ++i)
+ len += strlen (uris[i]) + 1;
+
+ if (length)
+ *length = len;
+
+ string = g_string_sized_new (len + 1);
+ for (i = 0; uris[i]; ++i)
+ {
+ g_string_append (string, uris[i]);
+ g_string_append_c (string, ' ');
+ }
+
+ return g_string_free (string, FALSE);
+}
+
+char *
+terminal_util_get_licence_text (void)
+{
+ const gchar *license[] = {
+ N_("GNOME 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."),
+ N_("GNOME 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."),
+ N_("You should have received a copy of the GNU General Public License "
+ "along with GNOME Terminal. If not, see <http://www.gnu.org/licenses/>.")
+ };
+
+ return g_strjoin ("\n\n", _(license[0]), _(license[1]), _(license[2]), nullptr);
+}
+
+static void
+main_object_destroy_cb (GtkWidget *widget)
+{
+ g_object_set_data (G_OBJECT (widget), "builder", nullptr);
+}
+
+GtkBuilder *
+terminal_util_load_widgets_resource (const char *path,
+ const char *main_object_name,
+ const char *object_name,
+ ...)
+{
+ GtkBuilder *builder;
+ GError *error = nullptr;
+ va_list args;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, path, &error);
+ g_assert_no_error (error);
+
+ va_start (args, object_name);
+
+ while (object_name) {
+ GObject **objectptr;
+
+ objectptr = va_arg (args, GObject**);
+ *objectptr = gtk_builder_get_object (builder, object_name);
+ if (!*objectptr)
+ g_error ("Failed to fetch object \"%s\" from resource \"%s\"\n", object_name, path);
+
+ object_name = va_arg (args, const char*);
+ }
+
+ va_end (args);
+
+ if (main_object_name) {
+ GObject *main_object;
+ GtkWidget *action_area;
+
+ main_object = gtk_builder_get_object (builder, main_object_name);
+ g_object_set_data_full (main_object, "builder", g_object_ref (builder), (GDestroyNotify) g_object_unref);
+ g_signal_connect (main_object, "destroy", G_CALLBACK (main_object_destroy_cb), nullptr);
+
+ /* Fixup dialogue padding, #735242 */
+ if (GTK_IS_DIALOG (main_object) &&
+ (action_area = (GtkWidget *) gtk_builder_get_object (builder, "dialog-action-area"))) {
+ gtk_widget_set_margin_start (action_area, 5);
+ gtk_widget_set_margin_end (action_area, 5);
+ gtk_widget_set_margin_top (action_area, 5);
+ gtk_widget_set_margin_bottom (action_area, 5);
+ }
+ }
+ return builder;
+}
+
+void
+terminal_util_load_objects_resource (const char *path,
+ const char *object_name,
+ ...)
+{
+ gs_unref_object GtkBuilder *builder;
+ GError *error = nullptr;
+ va_list args;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, path, &error);
+ g_assert_no_error (error);
+
+ va_start (args, object_name);
+
+ while (object_name) {
+ GObject **objectptr;
+
+ objectptr = va_arg (args, GObject**);
+ *objectptr = gtk_builder_get_object (builder, object_name);
+ if (*objectptr)
+ g_object_ref (*objectptr);
+ else
+ g_error ("Failed to fetch object \"%s\" from resource \"%s\"\n", object_name, path);
+
+ object_name = va_arg (args, const char*);
+ }
+
+ va_end (args);
+}
+
+gboolean
+terminal_util_dialog_response_on_delete (GtkWindow *widget)
+{
+ gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
+ return TRUE;
+}
+
+void
+terminal_util_dialog_focus_widget (GtkBuilder *builder,
+ const char *widget_name)
+{
+ GtkWidget *widget, *page, *page_parent;
+
+ if (widget_name == nullptr)
+ return;
+
+ widget = GTK_WIDGET (gtk_builder_get_object (builder, widget_name));
+ if (widget == nullptr)
+ return;
+
+ page = widget;
+ while (page != nullptr &&
+ (page_parent = gtk_widget_get_parent (page)) != nullptr &&
+ !GTK_IS_NOTEBOOK (page_parent))
+ page = page_parent;
+
+ page_parent = gtk_widget_get_parent (page);
+ if (page != nullptr && GTK_IS_NOTEBOOK (page_parent)) {
+ GtkNotebook *notebook;
+
+ notebook = GTK_NOTEBOOK (page_parent);
+ gtk_notebook_set_current_page (notebook, gtk_notebook_page_num (notebook, page));
+ }
+
+ if (gtk_widget_is_sensitive (widget))
+ gtk_widget_grab_focus (widget);
+}
+
+/* Proxy stuff */
+
+/*
+ * set_proxy_env:
+ * @env_table: a #GHashTable
+ * @key: the env var name
+ * @value: the env var value
+ *
+ * Adds @value for @key to @env_table, taking care to never overwrite an
+ * existing value for @key. @value is consumed.
+ */
+static void
+set_proxy_env (GHashTable *env_table,
+ const char *key,
+ char *value /* consumed */)
+{
+ char *key1 = nullptr, *key2 = nullptr;
+ char *value1 = nullptr, *value2 = nullptr;
+
+ if (!value)
+ return;
+
+ if (g_hash_table_lookup (env_table, key) == nullptr)
+ key1 = g_strdup (key);
+
+ key2 = g_ascii_strup (key, -1);
+ if (g_hash_table_lookup (env_table, key) != nullptr)
+ {
+ g_free (key2);
+ key2 = nullptr;
+ }
+
+ if (key1 && key2)
+ {
+ value1 = value;
+ value2 = g_strdup (value);
+ }
+ else if (key1)
+ value1 = value;
+ else if (key2)
+ value2 = value;
+ else
+ g_free (value);
+
+ if (key1)
+ g_hash_table_replace (env_table, key1, value1);
+ if (key2)
+ g_hash_table_replace (env_table, key2, value2);
+}
+
+static void
+setup_proxy_env (TerminalApp* app,
+ TerminalProxyProtocol protocol,
+ const char *proxy_scheme,
+ const char *env_name,
+ GHashTable *env_table)
+{
+ GString *buf;
+ gs_free char *host;
+ int port;
+
+ gboolean is_http = (protocol == TERMINAL_PROXY_HTTP);
+
+ GSettings *child_settings = terminal_app_get_proxy_settings_for_protocol(app, protocol);
+
+ host = g_settings_get_string (child_settings, "host");
+ port = g_settings_get_int (child_settings, "port");
+ if (host[0] == '\0' || port == 0)
+ return;
+
+ buf = g_string_sized_new (64);
+
+ g_string_append_printf (buf, "%s://", proxy_scheme);
+
+ if (is_http &&
+ g_settings_get_boolean (child_settings, "use-authentication"))
+ {
+ gs_free char *user;
+
+ user = g_settings_get_string (child_settings, "authentication-user");
+ if (user[0])
+ {
+ gs_free char *password;
+
+ g_string_append_uri_escaped (buf, user, nullptr, TRUE);
+
+ password = g_settings_get_string (child_settings, "authentication-password");
+ if (password[0])
+ {
+ g_string_append_c (buf, ':');
+ g_string_append_uri_escaped (buf, password, nullptr, TRUE);
+ }
+ g_string_append_c (buf, '@');
+ }
+ }
+
+ g_string_append_printf (buf, "%s:%d/", host, port);
+ set_proxy_env (env_table, env_name, g_string_free (buf, FALSE));
+}
+
+static void
+setup_ignore_proxy_env (GSettings *proxy_settings,
+ GHashTable *env_table)
+{
+ GString *buf;
+ gs_strfreev char **ignore;
+ int i;
+
+ g_settings_get (proxy_settings, "ignore-hosts", "^as", &ignore);
+ if (ignore == nullptr)
+ return;
+
+ buf = g_string_sized_new (64);
+ for (i = 0; ignore[i] != nullptr; ++i)
+ {
+ if (buf->len)
+ g_string_append_c (buf, ',');
+ g_string_append (buf, ignore[i]);
+ }
+
+ set_proxy_env (env_table, "no_proxy", g_string_free (buf, FALSE));
+}
+
+/**
+ * terminal_util_add_proxy_env:
+ * @env_table: a #GHashTable
+ *
+ * Adds the proxy env variables to @env_table.
+ */
+void
+terminal_util_add_proxy_env (GHashTable *env_table)
+{
+ auto const app = terminal_app_get();
+ auto const proxy_settings = terminal_app_get_proxy_settings(app);
+ auto const mode = GDesktopProxyMode(g_settings_get_enum (proxy_settings, "mode"));
+
+ if (mode == G_DESKTOP_PROXY_MODE_MANUAL)
+ {
+ setup_proxy_env (app, TERMINAL_PROXY_HTTP, "http", "http_proxy", env_table);
+ /* Even though it's https, the proxy scheme is 'http'. See bug #624440. */
+ setup_proxy_env (app, TERMINAL_PROXY_HTTPS, "http", "https_proxy", env_table);
+ /* Even though it's ftp, the proxy scheme is 'http'. See bug #624440. */
+ setup_proxy_env (app, TERMINAL_PROXY_FTP, "http", "ftp_proxy", env_table);
+ setup_proxy_env (app, TERMINAL_PROXY_SOCKS, "socks", "all_proxy", env_table);
+ setup_ignore_proxy_env (proxy_settings, env_table);
+ }
+ else if (mode == G_DESKTOP_PROXY_MODE_AUTO)
+ {
+ /* Not supported */
+ }
+}
+
+/**
+ * terminal_util_get_etc_shells:
+ *
+ * Returns: (transfer full) the contents of /etc/shells
+ */
+char **
+terminal_util_get_etc_shells (void)
+{
+ GError *err = nullptr;
+ gsize len;
+ gs_free char *contents = nullptr;
+ char *str, *nl, *end;
+ GPtrArray *arr;
+
+ if (!g_file_get_contents ("/etc/shells", &contents, &len, &err) || len == 0) {
+ /* Defaults as per man:getusershell(3) */
+ char *default_shells[3] = {
+ (char*) "/bin/sh",
+ (char*) "/bin/csh",
+ nullptr
+ };
+ return g_strdupv (default_shells);
+ }
+
+ arr = g_ptr_array_new ();
+ str = contents;
+ end = contents + len;
+ while (str < end && (nl = strchr (str, '\n')) != nullptr) {
+ if (str != nl) /* non-empty? */
+ g_ptr_array_add (arr, g_strndup (str, nl - str));
+ str = nl + 1;
+ }
+ /* Anything non-empty left? */
+ if (str < end && str[0])
+ g_ptr_array_add (arr, g_strdup (str));
+
+ g_ptr_array_add (arr, nullptr);
+ return (char **) g_ptr_array_free (arr, FALSE);
+}
+
+/**
+ * terminal_util_get_is_shell:
+ * @command: a string
+ *
+ * Returns wether @command is a valid shell as defined by the contents of /etc/shells.
+ *
+ * Returns: whether @command is a shell
+ */
+gboolean
+terminal_util_get_is_shell (const char *command)
+{
+ gs_strfreev char **shells;
+ guint i;
+
+ shells = terminal_util_get_etc_shells ();
+ if (shells == nullptr)
+ return FALSE;
+
+ for (i = 0; shells[i]; i++)
+ if (g_str_equal (command, shells[i]))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+s_to_rgba (GVariant *variant,
+ gpointer *result,
+ gpointer user_data)
+{
+ GdkRGBA *color = (GdkRGBA*)user_data;
+ const char *str;
+
+ if (variant == nullptr) {
+ /* Fallback */
+ *result = nullptr;
+ return TRUE;
+ }
+
+ g_variant_get (variant, "&s", &str);
+ if (!gdk_rgba_parse (color, str))
+ return FALSE;
+
+ color->alpha = 1.0;
+ *result = color;
+ return TRUE;
+}
+
+/**
+ * terminal_g_settings_get_rgba:
+ * @settings: a #GSettings
+ * @key: a valid key in @settings of type "s"
+ * @color: location to store the parsed color
+ *
+ * Gets a color from @key in @settings.
+ *
+ * Returns: @color if parsing succeeded, or %nullptr otherwise
+ */
+const GdkRGBA *
+terminal_g_settings_get_rgba (GSettings *settings,
+ const char *key,
+ GdkRGBA *color)
+{
+ g_return_val_if_fail (color != nullptr, FALSE);
+
+ return (GdkRGBA const*)g_settings_get_mapped (settings, key,
+ s_to_rgba,
+ color);
+}
+
+/**
+ * terminal_g_settings_set_rgba:
+ * @settings: a #GSettings
+ * @key: a valid key in @settings of type "s"
+ * @color: a #GdkRGBA
+ *
+ * Sets a color in @key in @settings.
+ */
+void
+terminal_g_settings_set_rgba (GSettings *settings,
+ const char *key,
+ const GdkRGBA *color)
+{
+ gs_free char *str;
+
+ str = gdk_rgba_to_string (color);
+ g_settings_set_string (settings, key, str);
+}
+
+static gboolean
+as_to_rgba_palette (GVariant *variant,
+ gpointer *result,
+ gpointer user_data)
+{
+ gsize *n_colors = (gsize*)user_data;
+ gs_free GdkRGBA *colors = nullptr;
+ gsize n = 0;
+ GVariantIter iter;
+ const char *str;
+ gsize i;
+
+ /* Fallback */
+ if (variant == nullptr)
+ goto out;
+
+ g_variant_iter_init (&iter, variant);
+ n = g_variant_iter_n_children (&iter);
+ colors = g_new (GdkRGBA, n);
+
+ i = 0;
+ while (g_variant_iter_next (&iter, "&s", &str)) {
+ if (!gdk_rgba_parse (&colors[i++], str)) {
+ return FALSE;
+ }
+ }
+
+ out:
+ gs_transfer_out_value (result, &colors);
+ if (n_colors)
+ *n_colors = n;
+
+ return TRUE;
+}
+
+/**
+ * terminal_g_settings_get_rgba_palette:
+ * @settings: a #GSettings
+ * @key: a valid key in @settings or type "s"
+ * @n_colors: (allow-none): location to store the number of palette entries, or %nullptr
+ *
+ * Returns: (transfer full):
+ */
+GdkRGBA *
+terminal_g_settings_get_rgba_palette (GSettings *settings,
+ const char *key,
+ gsize *n_colors)
+{
+ return (GdkRGBA*)g_settings_get_mapped (settings, key,
+ as_to_rgba_palette,
+ n_colors);
+}
+
+void
+terminal_g_settings_set_rgba_palette (GSettings *settings,
+ const char *key,
+ const GdkRGBA *colors,
+ gsize n_colors)
+{
+ gs_strfreev char **strv;
+ gsize i;
+
+ strv = g_new (char *, n_colors + 1);
+ for (i = 0; i < n_colors; ++i)
+ strv[i] = gdk_rgba_to_string (&colors[i]);
+ strv[n_colors] = nullptr;
+
+ g_settings_set (settings, key, "^as", strv);
+}
+
+static void
+mnemonic_label_set_sensitive_cb (GtkWidget *widget,
+ GParamSpec *pspec,
+ GtkWidget *label)
+{
+ gtk_widget_set_sensitive (label, gtk_widget_get_sensitive (widget));
+}
+
+/**
+ * terminal_util_bind_mnemonic_label_sensitivity:
+ * @container: a #GtkContainer
+ */
+void
+terminal_util_bind_mnemonic_label_sensitivity (GtkWidget *widget)
+{
+ GList *list, *l;
+
+ list = gtk_widget_list_mnemonic_labels (widget);
+ for (l = list; l != nullptr; l = l->next) {
+ GtkWidget *label = (GtkWidget*)l->data;
+
+ if (gtk_widget_is_ancestor (label, widget))
+ continue;
+
+#if 0
+ g_print ("Widget %s has mnemonic label %s\n",
+ gtk_buildable_get_name (GTK_BUILDABLE (widget)),
+ gtk_buildable_get_name (GTK_BUILDABLE (label)));
+#endif
+
+ mnemonic_label_set_sensitive_cb (widget, nullptr, label);
+ g_signal_connect (widget, "notify::sensitive",
+ G_CALLBACK (mnemonic_label_set_sensitive_cb),
+ label);
+ }
+ g_list_free (list);
+
+ if (GTK_IS_CONTAINER (widget))
+ gtk_container_foreach (GTK_CONTAINER (widget),
+ /* See #96 for double casting. */
+ (GtkCallback) (GCallback) terminal_util_bind_mnemonic_label_sensitivity,
+ nullptr);
+}
+
+/*
+ * "1234567", "'", 3 -> "1'234'567"
+ */
+static char *
+add_separators (const char *in, const char *sep, int groupby)
+{
+ int inlen, outlen, seplen, firstgrouplen;
+ char *out, *ret;
+
+ if (in[0] == '\0')
+ return g_strdup("");
+
+ inlen = strlen(in);
+ seplen = strlen(sep);
+ outlen = inlen + (inlen - 1) / groupby * seplen;
+ ret = out = (char*)g_malloc(outlen + 1);
+
+ firstgrouplen = (inlen - 1) % groupby + 1;
+ memcpy(out, in, firstgrouplen);
+ in += firstgrouplen;
+ out += firstgrouplen;
+
+ while (*in != '\0') {
+ memcpy(out, sep, seplen);
+ out += seplen;
+ memcpy(out, in, groupby);
+ in += groupby;
+ out += groupby;
+ }
+
+ g_assert(out - ret == outlen);
+ *out = '\0';
+ return ret;
+}
+
+/**
+ * terminal_util_number_info:
+ * @str: a dec or hex number as string
+ *
+ * Returns: (transfer full): Useful info about @str, or %nullptr if it's too large
+ */
+char *
+terminal_util_number_info (const char *str)
+{
+ gs_free char *decstr = nullptr;
+ gs_free char *hextmp = nullptr;
+ gs_free char *hexstr = nullptr;
+ gs_free char *magnitudestr = nullptr;
+ gboolean exact = TRUE;
+ gboolean hex = FALSE;
+ const char *thousep;
+
+ /* Deliberately not handle octal */
+ if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
+ str += 2;
+ hex = TRUE;
+ }
+
+ errno = 0;
+ char* end;
+ gint64 num = g_ascii_strtoull(str, &end, hex ? 16 : 10);
+ if (errno || str == end || num == -1)
+ return nullptr;
+
+ /* No use in dec-hex conversion for so small numbers */
+ if (num < 10) {
+ return nullptr;
+ }
+
+ /* Group the decimal digits */
+ thousep = nl_langinfo(THOUSEP);
+ if (thousep[0] != '\0') {
+ /* If thousep is nonempty, use printf's magic which can handle
+ more complex separating logics, e.g. 2+2+2+3 for some locales */
+ decstr = g_strdup_printf("%'" G_GINT64_FORMAT, num);
+ } else {
+ /* If, however, thousep is empty, override it with a space so that we
+ do always group the digits (that's the whole point of this feature;
+ the choice of space guarantees not conflicting with the decimal separator) */
+ gs_free char *tmp = g_strdup_printf("%" G_GINT64_FORMAT, num);
+ thousep = " ";
+ decstr = add_separators(tmp, thousep, 3);
+ }
+
+ /* Group the hex digits by 4 using the same nonempty separator */
+ hextmp = g_strdup_printf("%" G_GINT64_MODIFIER "x", (guint64)(num));
+ hexstr = add_separators(hextmp, thousep, 4);
+
+ /* Find out the human-readable magnitude, e.g. 15.99 Mi */
+ if (num >= 1024) {
+ int power = 0;
+ while (num >= 1024 * 1024) {
+ power++;
+ if (num % 1024 != 0)
+ exact = FALSE;
+ num /= 1024;
+ }
+ /* Show 2 fraction digits, always rounding downwards. Printf rounds floats to the nearest representable value,
+ so do the calculation with integers until we get 100-fold the desired value, and then switch to float. */
+ if (100 * num % 1024 != 0)
+ exact = FALSE;
+ num = 100 * num / 1024;
+ magnitudestr = g_strdup_printf(" %s %.2f %ci", exact ? "=" : "≈", (double) num / 100, "KMGTPE"[power]);
+ } else {
+ magnitudestr = g_strdup("");
+ }
+
+ return g_strdup_printf(hex ? "0x%2$s = %1$s%3$s" : "%s = 0x%s%s", decstr, hexstr, magnitudestr);
+}
+
+/**
+ * terminal_util_timestamp_info:
+ * @str: a dec or hex number as string
+ *
+ * Returns: (transfer full): Formatted localtime if @str is decimal and looks like a timestamp, or %nullptr
+ */
+char *
+terminal_util_timestamp_info (const char *str)
+{
+ /* Bail out on hex numbers */
+ if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
+ return nullptr;
+ }
+
+ /* Deliberately not handle octal */
+ errno = 0;
+ char* end;
+ gint64 num = g_ascii_strtoull (str, &end, 10);
+ if (errno || end == str || num == -1)
+ return nullptr;
+
+ /* Java uses Unix time in milliseconds. */
+ if (num >= 1000000000000 && num <= 1999999999999)
+ num /= 1000;
+
+ /* Fun: use inclusive interval so you can right-click on these numbers
+ * and check the human-readable time in gnome-terminal.
+ * (They're Sep 9 2001 and May 18 2033 by the way.) */
+ if (num < 1000000000 || num > 1999999999)
+ return nullptr;
+
+ gs_unref_date_time GDateTime* date = g_date_time_new_from_unix_local (num);
+ if (date == nullptr)
+ return nullptr;
+
+ return g_date_time_format(date, "%c");
+}
+
+/**
+ * terminal_util_uri_fixup:
+ * @uri: The URI to verify and maybe fixup
+ * @error: a #GError that is returned in case of errors
+ *
+ * Checks if gnome-terminal should attempt to handle the given URI,
+ * and rewrites if necessary.
+ *
+ * Currently URIs of "file://some-other-host/..." are refused because
+ * GIO (e.g. gtk_show_uri()) silently strips off the remote hostname
+ * and opens the local counterpart which is incorrect and misleading.
+ *
+ * Furthermore, once the hostname is verified, it is stripped off to
+ * avoid potential confusion around short hostname vs. fqdn, and to
+ * work around bug 781800 (LibreOffice bug 107461).
+ *
+ * Returns: The possibly rewritten URI if gnome-terminal should attempt
+ * to handle it, nullptr if it should refuse to handle.
+ */
+char *
+terminal_util_uri_fixup (const char *uri,
+ GError **error)
+{
+ gs_free char *filename;
+ gs_free char *hostname;
+
+ filename = g_filename_from_uri (uri, &hostname, nullptr);
+ if (filename != nullptr &&
+ hostname != nullptr &&
+ hostname[0] != '\0') {
+ /* "file" scheme and nonempty hostname */
+ if (g_ascii_strcasecmp (hostname, "localhost") == 0 ||
+ g_ascii_strcasecmp (hostname, g_get_host_name()) == 0) {
+ /* hostname corresponds to localhost */
+ char const *slash1, *slash2, *slash3;
+
+ /* We shouldn't enter this branch in case of URIs like
+ * "file:/etc/passwd", but just in case we do, or encounter
+ * something else unexpected, leave the URI unchanged. */
+ slash1 = strchr(uri, '/');
+ if (slash1 == nullptr)
+ return g_strdup (uri);
+
+ slash2 = slash1 + 1;
+ if (*slash2 != '/')
+ return g_strdup (uri);
+
+ slash3 = strchr(slash2 + 1, '/');
+ if (slash3 == nullptr)
+ return g_strdup (uri);
+
+ return g_strdup_printf("%.*s%s",
+ (int) (slash2 + 1 - uri),
+ uri,
+ slash3);
+ } else {
+ /* hostname refers to another host (e.g. the OSC 8 escape sequence
+ * was correctly emitted by a utility inside an ssh session) */
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("“file” scheme with remote hostname not supported"));
+ return nullptr;
+ }
+ } else {
+ /* "file" scheme without hostname, or some other scheme */
+ return g_strdup (uri);
+ }
+}
+
+/**
+ * terminal_util_hyperlink_uri_label:
+ * @uri: a URI
+ *
+ * Formats @uri to be displayed in a tooltip.
+ * Performs URI-decoding and converts IDN hostname to UTF-8.
+ *
+ * Returns: (transfer full): The human readable URI as plain text
+ */
+char *terminal_util_hyperlink_uri_label (const char *uri)
+{
+ gs_free char *unesc = nullptr;
+ gboolean replace_hostname;
+
+ if (uri == nullptr)
+ return nullptr;
+
+ unesc = g_uri_unescape_string(uri, nullptr);
+ if (unesc == nullptr)
+ unesc = g_strdup(uri);
+
+ if (g_ascii_strncasecmp(unesc, "ftp://", 6) == 0 ||
+ g_ascii_strncasecmp(unesc, "http://", 7) == 0 ||
+ g_ascii_strncasecmp(unesc, "https://", 8) == 0) {
+ gs_free char *unidn = nullptr;
+ char *hostname = strchr(unesc, '/') + 2;
+ char *hostname_end = strchrnul(hostname, '/');
+ char save = *hostname_end;
+ *hostname_end = '\0';
+ unidn = g_hostname_to_unicode(hostname);
+ replace_hostname = unidn != nullptr && g_ascii_strcasecmp(unidn, hostname) != 0;
+ *hostname_end = save;
+ if (replace_hostname) {
+ char *new_unesc = g_strdup_printf("%.*s%s%s",
+ (int) (hostname - unesc),
+ unesc,
+ unidn,
+ hostname_end);
+ g_free(unesc);
+ unesc = new_unesc;
+ }
+ }
+
+ return g_utf8_make_valid (unesc, -1);
+}
+
+#define TERMINAL_CACHE_DIR "gnome-terminal"
+#define TERMINAL_PRINT_SETTINGS_FILENAME "print-settings.ini"
+#define TERMINAL_PRINT_SETTINGS_GROUP_NAME "Print Settings"
+#define TERMINAL_PAGE_SETUP_GROUP_NAME "Page Setup"
+
+#define KEYFILE_FLAGS_FOR_LOAD GKeyFileFlags(G_KEY_FILE_NONE)
+#define KEYFILE_FLAGS_FOR_SAVE GKeyFileFlags(G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS)
+
+static char *
+get_cache_dir (void)
+{
+ return g_build_filename (g_get_user_cache_dir (), TERMINAL_CACHE_DIR, nullptr);
+}
+
+static gboolean
+ensure_cache_dir (void)
+{
+ gs_free char *cache_dir;
+ int r;
+
+ cache_dir = get_cache_dir ();
+ errno = 0;
+ r = g_mkdir_with_parents (cache_dir, 0700);
+ if (r == -1 && errno != EEXIST) {
+ auto const errsv = errno;
+ g_printerr ("Failed to create cache dir: %s\n", g_strerror(errsv));
+ }
+ return r == 0;
+}
+
+static char *
+get_cache_filename (const char *filename)
+{
+ gs_free char *cache_dir = get_cache_dir ();
+ return g_build_filename (cache_dir, filename, nullptr);
+}
+
+static GKeyFile *
+load_cache_keyfile (const char *filename,
+ GKeyFileFlags flags,
+ gboolean ignore_error)
+{
+ gs_free char *path;
+ GKeyFile *keyfile;
+
+ path = get_cache_filename (filename);
+ keyfile = g_key_file_new ();
+ if (g_key_file_load_from_file (keyfile, path, flags, nullptr) || ignore_error)
+ return keyfile;
+
+ g_key_file_unref (keyfile);
+ return nullptr;
+}
+
+static void
+save_cache_keyfile (GKeyFile *keyfile,
+ const char *filename)
+{
+ gs_free char *path = nullptr;
+ gs_free char *data = nullptr;
+ gsize len = 0;
+
+ if (!ensure_cache_dir ())
+ return;
+
+ data = g_key_file_to_data (keyfile, &len, nullptr);
+ if (data == nullptr || len == 0)
+ return;
+
+ path = get_cache_filename (filename);
+
+ /* Ignore errors */
+ GError *err = nullptr;
+ if (!g_file_set_contents (path, data, len, &err)) {
+ g_printerr ("Error saving print settings: %s\n", err->message);
+ g_error_free (err);
+ }
+}
+
+static void
+keyfile_remove_keys (GKeyFile *keyfile,
+ const char *group_name,
+ ...)
+{
+ va_list args;
+ const char *key;
+
+ va_start (args, group_name);
+ while ((key = va_arg (args, const char *)) != nullptr) {
+ g_key_file_remove_key (keyfile, group_name, key, nullptr);
+ }
+ va_end (args);
+}
+
+/**
+ * terminal_util_load_print_settings:
+ *
+ * Loads the saved print settings, if any.
+ */
+void
+terminal_util_load_print_settings (GtkPrintSettings **settings,
+ GtkPageSetup **page_setup)
+{
+ gs_unref_key_file GKeyFile *keyfile = load_cache_keyfile (TERMINAL_PRINT_SETTINGS_FILENAME,
+ KEYFILE_FLAGS_FOR_LOAD,
+ FALSE);
+ if (keyfile == nullptr) {
+ *settings = nullptr;
+ *page_setup = nullptr;
+ return;
+ }
+
+ /* Ignore errors */
+ *settings = gtk_print_settings_new_from_key_file (keyfile,
+ TERMINAL_PRINT_SETTINGS_GROUP_NAME,
+ nullptr);
+ *page_setup = gtk_page_setup_new_from_key_file (keyfile,
+ TERMINAL_PAGE_SETUP_GROUP_NAME,
+ nullptr);
+}
+
+/**
+ * terminal_util_save_print_settings:
+ * @settings: (allow-none): a #GtkPrintSettings
+ * @page_setup: (allow-none): a #GtkPageSetup
+ *
+ * Saves the print settings.
+ */
+void
+terminal_util_save_print_settings (GtkPrintSettings *settings,
+ GtkPageSetup *page_setup)
+{
+ gs_unref_key_file GKeyFile *keyfile = nullptr;
+
+ keyfile = load_cache_keyfile (TERMINAL_PRINT_SETTINGS_FILENAME,
+ KEYFILE_FLAGS_FOR_SAVE,
+ TRUE);
+ g_assert (keyfile != nullptr);
+
+ if (settings != nullptr)
+ gtk_print_settings_to_key_file (settings, keyfile,
+ TERMINAL_PRINT_SETTINGS_GROUP_NAME);
+
+ /* Some keys are not desirable to persist; remove these.
+ * This list comes from evince.
+ */
+ keyfile_remove_keys (keyfile,
+ TERMINAL_PRINT_SETTINGS_GROUP_NAME,
+ GTK_PRINT_SETTINGS_COLLATE,
+ GTK_PRINT_SETTINGS_NUMBER_UP,
+ GTK_PRINT_SETTINGS_N_COPIES,
+ GTK_PRINT_SETTINGS_OUTPUT_URI,
+ GTK_PRINT_SETTINGS_PAGE_RANGES,
+ GTK_PRINT_SETTINGS_PAGE_SET,
+ GTK_PRINT_SETTINGS_PRINT_PAGES,
+ GTK_PRINT_SETTINGS_REVERSE,
+ GTK_PRINT_SETTINGS_SCALE,
+ nullptr);
+
+ if (page_setup != nullptr)
+ gtk_page_setup_to_key_file (page_setup, keyfile,
+ TERMINAL_PAGE_SETUP_GROUP_NAME);
+
+ /* Some keys are not desirable to persist; remove these.
+ * This list comes from evince.
+ */
+ keyfile_remove_keys (keyfile,
+ TERMINAL_PAGE_SETUP_GROUP_NAME,
+ "page-setup-orientation",
+ "page-setup-margin-bottom",
+ "page-setup-margin-left",
+ "page-setup-margin-right",
+ "page-setup-margin-top",
+ nullptr);
+
+ save_cache_keyfile (keyfile, TERMINAL_PRINT_SETTINGS_FILENAME);
+}
+
+/*
+ * terminal_util_translate_encoding:
+ * @encoding: the encoding name
+ *
+ * Translates old encoding name to the one supported by ICU, or
+ * to %nullptr if the encoding is not known to ICU.
+ *
+ * Returns: (transfer none): the translated encoding, or %nullptr if
+ * not translation was possible.
+ */
+const char*
+terminal_util_translate_encoding (const char *encoding)
+{
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ if (vte_get_encoding_supported (encoding))
+ return encoding;
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ /* ICU knows (or has aliases for) most of the old names, except the following */
+ struct {
+ const char *name;
+ const char *replacement;
+ } translations[] = {
+ { "ARMSCII-8", nullptr }, /* apparently not supported by ICU */
+ { "GEORGIAN-PS", nullptr }, /* no idea which charset this even is */
+ { "ISO-IR-111", nullptr }, /* ISO-IR-111 refers to ECMA-94, but that
+ * standard does not contain cyrillic letters.
+ * ECMA-94 refers to ECMA-113 (ISO-IR-144),
+ * whose assignment differs greatly from ISO-IR-111,
+ * so it cannot be that either.
+ */
+ /* All the MAC_* charsets appear to be unknown to even glib iconv, so
+ * why did we have them in our list in the first place?
+ */
+ { "MAC_DEVANAGARI", nullptr }, /* apparently not supported by ICU */
+ { "MAC_FARSI", nullptr }, /* apparently not supported by ICU */
+ { "MAC_GREEK", "x-MacGreek" },
+ { "MAC_GUJARATI", nullptr }, /* apparently not supported by ICU */
+ { "MAC_GURMUKHI", nullptr }, /* apparently not supported by ICU */
+ { "MAC_ICELANDIC", nullptr }, /* apparently not supported by ICU */
+ { "MAC_ROMANIAN", "x-macroman" }, /* not sure this is the right one */
+ { "MAC_TURKISH", "x-MacTurkish" },
+ { "MAC_UKRAINIAN", "x-MacUkraine" },
+
+ { "TCVN", nullptr }, /* apparently not supported by ICU */
+ { "UHC", "cp949" },
+ { "VISCII", nullptr }, /* apparently not supported by ICU */
+
+ /* ISO-2022-* are known to ICU, but they simply cannot work in vte as
+ * I/O encoding, so don't even try.
+ */
+ { "ISO-2022-JP", nullptr },
+ { "ISO-2022-KR", nullptr },
+ };
+
+ const char *replacement = nullptr;
+ for (guint i = 0; i < G_N_ELEMENTS (translations); ++i) {
+ if (g_str_equal (encoding, translations[i].name)) {
+ replacement = translations[i].replacement;
+ break;
+ }
+ }
+
+ return replacement;
+}
+
+/* BEGIN code copied from glib
+ *
+ * Copyright (C) 1995-1998 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * Code originally under LGPL2+; used and modified here under GPL3+
+ * Changes:
+ * Remove win32 support.
+ * Make @program nullable.
+ * Use @path instead of getenv("PATH").
+ * Use strchrnul
+ */
+
+/**
+ * terminal_util_find_program_in_path:
+ * @path: (type filename) (nullable): the search path (delimited by G_SEARCHPATH_SEPARATOR)
+ * @program: (type filename) (nullable): the programme to find in @path
+ *
+ * Like g_find_program_in_path(), but uses @path instead of the
+ * PATH environment variable as the search path.
+ *
+ * Returns: (type filename) (transfer full) (nullable): a newly allocated
+ * string containing the full path to @program, or %nullptr if @program
+ * could not be found in @path.
+ */
+char *
+terminal_util_find_program_in_path (const char *path,
+ const char *program)
+{
+ const gchar *p;
+ gchar *name, *freeme;
+ gsize len;
+ gsize pathlen;
+
+ if (program == nullptr)
+ return nullptr;
+
+ /* If it is an absolute path, or a relative path including subdirectories,
+ * don't look in PATH.
+ */
+ if (g_path_is_absolute (program)
+ || strchr (program, G_DIR_SEPARATOR) != nullptr
+ )
+ {
+ if (g_file_test (program, G_FILE_TEST_IS_EXECUTABLE) &&
+ !g_file_test (program, G_FILE_TEST_IS_DIR))
+ return g_strdup (program);
+ else
+ return nullptr;
+ }
+
+ if (path == nullptr)
+ {
+ /* There is no 'PATH' in the environment. The default
+ * search path in GNU libc is the current directory followed by
+ * the path 'confstr' returns for '_CS_PATH'.
+ */
+
+ /* In GLib we put . last, for security, and don't use the
+ * unportable confstr(); UNIX98 does not actually specify
+ * what to search if PATH is unset. POSIX may, dunno.
+ */
+
+ path = "/bin:/usr/bin:.";
+ }
+
+ len = strlen (program) + 1;
+ pathlen = strlen (path);
+ freeme = name = (char*)g_malloc (pathlen + len + 1);
+
+ /* Copy the file name at the top, including '\0' */
+ memcpy (name + pathlen + 1, program, len);
+ name = name + pathlen;
+ /* And add the slash before the filename */
+ *name = G_DIR_SEPARATOR;
+
+ p = path;
+ do
+ {
+ char *startp;
+
+ path = p;
+ p = strchrnul (path, G_SEARCHPATH_SEPARATOR);
+
+ if (p == path)
+ /* Two adjacent colons, or a colon at the beginning or the end
+ * of 'PATH' means to search the current directory.
+ */
+ startp = name + 1;
+ else
+ startp = (char*)memcpy (name - (p - path), path, p - path);
+
+ if (g_file_test (startp, G_FILE_TEST_IS_EXECUTABLE) &&
+ !g_file_test (startp, G_FILE_TEST_IS_DIR))
+ {
+ gchar *ret;
+ ret = g_strdup (startp);
+ g_free (freeme);
+ return ret;
+ }
+ }
+ while (*p++ != '\0');
+
+ g_free (freeme);
+ return nullptr;
+}
+
+/* END code copied from glib */
+
+/*
+ * terminal_util_check_envv:
+ * @strv:
+ *
+ * Validates that each element is of the form 'KEY=VALUE'.
+ */
+gboolean
+terminal_util_check_envv(char const* const* strv)
+{
+ if (!strv)
+ return TRUE;
+
+ for (int i = 0; strv[i]; ++i) {
+ const char *str = strv[i];
+ const char *equal = strchr(str, '=');
+ if (equal == nullptr || equal == str)
+ return FALSE;
+ }
+
+ 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 (optional) 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 && try_exec[0]) {
+ _terminal_debug_print(TERMINAL_DEBUG_DEFAULT,
+ "Desktop file \"%s\" has no TryExec field.\n",
+ file);
+
+ // TryExec may be an abolute path, or be searched in $PATH
+ gs_free char* exec_path = nullptr;
+ if (g_path_is_absolute(try_exec))
+ exec_path = g_strdup(try_exec);
+ else
+ exec_path = g_find_program_in_path(try_exec);
+
+ auto const exists = exec_path != nullptr &&
+ g_file_test(exec_path, GFileTest(G_FILE_TEST_IS_EXECUTABLE));
+
+ _terminal_debug_print(TERMINAL_DEBUG_DEFAULT,
+ "Desktop file \"%s\" is %sinstalled (TryExec).\n",
+ file, exists ? "" : "not ");
+
+ if (!exists)
+ return false;
+ } else {
+ // TryExec is not present. We could fall back to parsing the Exec
+ // key and look if its first argument points to an executable that
+ // exists on the system, but that may also fail if the desktop file
+ // is DBusActivatable=true in which case we would need to find
+ // out if the D-Bus service corresponding to the name of the desktop
+ // file (without the .desktop extension) is activatable.
+ }
+
+ return true;
+}
+
+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
new file mode 100644
index 0000000..3770c85
--- /dev/null
+++ b/src/terminal-util.hh
@@ -0,0 +1,120 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2008, 2010 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/>.
+ */
+
+#ifndef TERMINAL_UTIL_H
+#define TERMINAL_UTIL_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void terminal_util_show_error_dialog (GtkWindow *transient_parent,
+ GtkWidget **weap_ptr,
+ GError *error,
+ const char *message_format, ...) G_GNUC_PRINTF(4, 5);
+
+void terminal_util_show_help (const char *topic);
+
+void terminal_util_show_about (void);
+
+void terminal_util_set_labelled_by (GtkWidget *widget,
+ GtkLabel *label);
+void terminal_util_set_atk_name_description (GtkWidget *widget,
+ const char *name,
+ const char *desc);
+
+void terminal_util_open_url (GtkWidget *parent,
+ const char *orig_url,
+ TerminalURLFlavor flavor,
+ guint32 user_time);
+
+void terminal_util_transform_uris_to_quoted_fuse_paths (char **uris);
+
+char *terminal_util_concat_uris (char **uris,
+ gsize *length);
+
+char *terminal_util_get_licence_text (void);
+
+GtkBuilder *terminal_util_load_widgets_resource (const char *path,
+ const char *main_object_name,
+ const char *object_name,
+ ...);
+
+void terminal_util_load_objects_resource (const char *path,
+ const char *object_name,
+ ...);
+
+void terminal_util_dialog_focus_widget (GtkBuilder *builder,
+ const char *widget_name);
+
+gboolean terminal_util_dialog_response_on_delete (GtkWindow *widget);
+
+void terminal_util_add_proxy_env (GHashTable *env_table);
+
+char **terminal_util_get_etc_shells (void);
+
+gboolean terminal_util_get_is_shell (const char *command);
+
+const GdkRGBA *terminal_g_settings_get_rgba (GSettings *settings,
+ const char *key,
+ GdkRGBA *rgba);
+void terminal_g_settings_set_rgba (GSettings *settings,
+ const char *key,
+ const GdkRGBA *rgba);
+
+GdkRGBA *terminal_g_settings_get_rgba_palette (GSettings *settings,
+ const char *key,
+ gsize *n_colors);
+void terminal_g_settings_set_rgba_palette (GSettings *settings,
+ const char *key,
+ const GdkRGBA *colors,
+ gsize n_colors);
+
+void terminal_util_bind_mnemonic_label_sensitivity (GtkWidget *widget);
+
+char *terminal_util_number_info (const char *str);
+char *terminal_util_timestamp_info (const char *str);
+
+char *terminal_util_uri_fixup (const char *uri,
+ GError **error);
+
+char *terminal_util_hyperlink_uri_label (const char *str);
+
+void terminal_util_load_print_settings (GtkPrintSettings **settings,
+ GtkPageSetup **page_setup);
+
+void terminal_util_save_print_settings (GtkPrintSettings *settings,
+ GtkPageSetup *page_setup);
+
+const char *terminal_util_translate_encoding (const char *encoding);
+
+char *terminal_util_find_program_in_path (const char *path,
+ const char *program);
+
+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-version.hh.in b/src/terminal-version.hh.in
new file mode 100644
index 0000000..154a3a1
--- /dev/null
+++ b/src/terminal-version.hh.in
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2009 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/>.
+ */
+
+#if !defined (__TERMINAL_TERMINAL_H_INSIDE__) && !defined (TERMINAL_COMPILATION)
+#error "Only <terminal/terminal.h> can be included directly."
+#endif
+
+#ifndef TERMINAL_VERSION_H
+#define TERMINAL_VERSION_H
+
+#define TERMINAL_MAJOR_VERSION (@TERMINAL_MAJOR_VERSION@)
+#define TERMINAL_MINOR_VERSION (@TERMINAL_MINOR_VERSION@)
+#define TERMINAL_MICRO_VERSION (@TERMINAL_MICRO_VERSION@)
+
+#define TERMINAL_CHECK_VERSION(major,minor,micro) \
+ (TERMINAL_MAJOR_VERSION > (major) || \
+ (TERMINAL_MAJOR_VERSION == (major) && TERMINAL_MINOR_VERSION > (minor)) || \
+ (TERMINAL_MAJOR_VERSION == (major) && TERMINAL_MINOR_VERSION == (minor) && TERMINAL_MICRO_VERSION >= (micro)))
+
+#endif /* !TERMINAL_VERSION_H */
diff --git a/src/terminal-window.cc b/src/terminal-window.cc
new file mode 100644
index 0000000..36eb7ce
--- /dev/null
+++ b/src/terminal-window.cc
@@ -0,0 +1,3369 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2007, 2008, 2009, 2011, 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 <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+#include <uuid.h>
+
+#include "terminal-app.hh"
+#include "terminal-debug.hh"
+#include "terminal-enums.hh"
+#include "terminal-headerbar.hh"
+#include "terminal-icon-button.hh"
+#include "terminal-intl.hh"
+#include "terminal-mdi-container.hh"
+#include "terminal-menu-button.hh"
+#include "terminal-notebook.hh"
+#include "terminal-schemas.hh"
+#include "terminal-screen-container.hh"
+#include "terminal-search-popover.hh"
+#include "terminal-tab-label.hh"
+#include "terminal-util.hh"
+#include "terminal-window.hh"
+#include "terminal-libgsystem.hh"
+
+struct _TerminalWindowPrivate
+{
+ char *uuid;
+
+ GtkClipboard *clipboard;
+
+ TerminalScreenPopupInfo *popup_info;
+
+ GtkWidget *menubar;
+ TerminalMdiContainer *mdi_container;
+ GtkWidget *main_vbox;
+ GtkWidget* ask_default_infobar;
+ TerminalScreen *active_screen;
+
+ /* Size of a character cell in pixels */
+ int old_char_width;
+ int old_char_height;
+
+ /* Width and height added to the actual terminal grid by "chrome" inside
+ * what was traditionally the X11 window: menu bar, title bar,
+ * style-provided padding. This must be included when resizing the window
+ * and also included in geometry hints. */
+ int old_chrome_width;
+ int old_chrome_height;
+
+ /* Width and height added to the window by client-side decorations.
+ * This must be included in geometry hints but must not be included when
+ * resizing the window. */
+ int old_csd_width;
+ int old_csd_height;
+
+ /* Width and height of the padding around the geometry widget. */
+ int old_padding_width;
+ int old_padding_height;
+
+ void *old_geometry_widget; /* only used for pointer value as it may be freed */
+
+ /* For restoring hints after unmaximizing etc */
+ GdkGeometry hints;
+ GdkWindowState window_state;
+
+ GtkWidget *confirm_close_dialog;
+ TerminalSearchPopover *search_popover;
+
+ guint use_default_menubar_visibility : 1;
+
+ guint disposed : 1;
+ guint present_on_insert : 1;
+
+ guint realized : 1;
+};
+
+#define TERMINAL_WINDOW_CSS_NAME "terminal-window"
+
+#define MIN_WIDTH_CHARS 4
+#define MIN_HEIGHT_CHARS 1
+
+#if 1
+/*
+ * We don't want to enable content saving until vte supports it async.
+ * So we disable this code for stable versions.
+ */
+#include "terminal-version.hh"
+
+#if (TERMINAL_MINOR_VERSION & 1) != 0
+#define ENABLE_SAVE
+#else
+#undef ENABLE_SAVE
+#endif
+#endif
+
+/* See bug #789356 and issue gnome-terminal#129*/
+static inline constexpr auto
+window_state_is_snapped(GdkWindowState state) noexcept
+{
+ return (state & (GDK_WINDOW_STATE_FULLSCREEN |
+ GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_BOTTOM_TILED |
+ GDK_WINDOW_STATE_LEFT_TILED |
+ GDK_WINDOW_STATE_RIGHT_TILED |
+ GDK_WINDOW_STATE_TOP_TILED |
+ GDK_WINDOW_STATE_TILED)) != 0;
+}
+
+static void terminal_window_dispose (GObject *object);
+static void terminal_window_finalize (GObject *object);
+static gboolean terminal_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event);
+
+static gboolean terminal_window_delete_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data);
+
+static gboolean notebook_button_press_cb (GtkWidget *notebook,
+ GdkEventButton *event,
+ TerminalWindow *window);
+static gboolean notebook_popup_menu_cb (GtkWidget *notebook,
+ TerminalWindow *window);
+static void mdi_screen_switched_cb (TerminalMdiContainer *container,
+ TerminalScreen *old_active_screen,
+ TerminalScreen *screen,
+ TerminalWindow *window);
+static void mdi_screen_added_cb (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ TerminalWindow *window);
+static void mdi_screen_removed_cb (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ TerminalWindow *window);
+static void mdi_screens_reordered_cb (TerminalMdiContainer *container,
+ TerminalWindow *window);
+static void screen_close_request_cb (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ TerminalWindow *window);
+
+/* Menu action callbacks */
+static gboolean find_larger_zoom_factor (double *zoom);
+static gboolean find_smaller_zoom_factor (double *zoom);
+static void terminal_window_update_zoom_sensitivity (TerminalWindow *window);
+static void terminal_window_update_search_sensitivity (TerminalScreen *screen,
+ TerminalWindow *window);
+static void terminal_window_update_paste_sensitivity (TerminalWindow *window);
+
+static void terminal_window_show (GtkWidget *widget);
+
+static gboolean confirm_close_window_or_tab (TerminalWindow *window,
+ TerminalScreen *screen);
+
+G_DEFINE_TYPE (TerminalWindow, terminal_window, GTK_TYPE_APPLICATION_WINDOW)
+
+/* Zoom helpers */
+
+static const double zoom_factors[] = {
+ TERMINAL_SCALE_MINIMUM,
+ TERMINAL_SCALE_XXXXX_SMALL,
+ TERMINAL_SCALE_XXXX_SMALL,
+ TERMINAL_SCALE_XXX_SMALL,
+ PANGO_SCALE_XX_SMALL,
+ PANGO_SCALE_X_SMALL,
+ PANGO_SCALE_SMALL,
+ PANGO_SCALE_MEDIUM,
+ PANGO_SCALE_LARGE,
+ PANGO_SCALE_X_LARGE,
+ PANGO_SCALE_XX_LARGE,
+ TERMINAL_SCALE_XXX_LARGE,
+ TERMINAL_SCALE_XXXX_LARGE,
+ TERMINAL_SCALE_XXXXX_LARGE,
+ TERMINAL_SCALE_MAXIMUM
+};
+
+static gboolean
+find_larger_zoom_factor (double *zoom)
+{
+ double current = *zoom;
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (zoom_factors); ++i)
+ {
+ /* Find a font that's larger than this one */
+ if ((zoom_factors[i] - current) > 1e-6)
+ {
+ *zoom = zoom_factors[i];
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+find_smaller_zoom_factor (double *zoom)
+{
+ double current = *zoom;
+ int i;
+
+ i = (int) G_N_ELEMENTS (zoom_factors) - 1;
+ while (i >= 0)
+ {
+ /* Find a font that's smaller than this one */
+ if ((current - zoom_factors[i]) > 1e-6)
+ {
+ *zoom = zoom_factors[i];
+ return TRUE;
+ }
+
+ --i;
+ }
+
+ return FALSE;
+}
+
+static inline GSimpleAction *
+lookup_action (TerminalWindow *window,
+ const char *name)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (window), name);
+ g_return_val_if_fail (action != nullptr, nullptr);
+
+ return G_SIMPLE_ACTION (action);
+}
+
+/* Context menu helpers */
+
+/* We don't want context menus to show accelerators.
+ * Setting the menu's accel group and/or accel path to nullptr
+ * unfortunately doesn't hide accelerators; we need to walk
+ * the menu items and remove the accelerators on each,
+ * manually.
+ */
+static void
+popup_menu_remove_accelerators (GtkWidget *menu)
+{
+ gs_free_list GList *menu_items;
+ GList *l;
+
+ menu_items = gtk_container_get_children (GTK_CONTAINER (menu));
+ for (l = menu_items; l != nullptr; l = l ->next) {
+ GtkMenuItem *item = (GtkMenuItem*) (l->data);
+ GtkWidget *label, *submenu;
+
+ if (!GTK_IS_MENU_ITEM (item))
+ continue;
+
+ if (GTK_IS_ACCEL_LABEL ((label = gtk_bin_get_child (GTK_BIN (item)))))
+ gtk_accel_label_set_accel (GTK_ACCEL_LABEL (label), 0, GdkModifierType(0));
+
+ /* Recurse into submenus */
+ if ((submenu = gtk_menu_item_get_submenu (item)))
+ popup_menu_remove_accelerators (submenu);
+ }
+}
+
+/* Because we're using gtk_menu_attach_to_widget(), the attach
+ * widget holds a strong reference to the menu, causing it not to
+ * be automatically destroyed once popped down. So we need to
+ * detach the menu from the attach widget manually, which will
+ * cause the menu to be destroyed. We cannot do so in the
+ * "deactivate" handler however, since that causes the menu
+ * item activation to be lost. The "selection-done" signal
+ * appears to be the right place.
+ */
+
+static void
+popup_menu_destroy_cb (GtkWidget *menu,
+ gpointer user_data)
+{
+ /* g_printerr ("Menu %p destroyed!\n", menu); */
+}
+
+static void
+popup_menu_selection_done_cb (GtkMenu *menu,
+ gpointer user_data)
+{
+ g_signal_handlers_disconnect_by_func(menu,
+ (void*)popup_menu_selection_done_cb,
+ user_data);
+
+ /* g_printerr ("selection-done %p\n", menu); */
+
+ /* This will remove the ref from the attach widget widget, and destroy the menu */
+ if (gtk_menu_get_attach_widget (menu) != nullptr)
+ gtk_menu_detach (menu);
+}
+
+static void
+popup_menu_detach_cb (GtkWidget *attach_widget,
+ GtkMenu *menu)
+{
+ gtk_menu_shell_deactivate (GTK_MENU_SHELL (menu));
+}
+
+static GtkWidget *
+context_menu_new (GMenuModel *menu,
+ GtkWidget *widget)
+{
+ GtkWidget *popup_menu;
+
+ popup_menu = gtk_menu_new_from_model (menu);
+ gtk_style_context_add_class (gtk_widget_get_style_context (popup_menu),
+ GTK_STYLE_CLASS_CONTEXT_MENU);
+ gtk_menu_attach_to_widget (GTK_MENU (popup_menu), widget,
+ (GtkMenuDetachFunc)popup_menu_detach_cb);
+
+ popup_menu_remove_accelerators (popup_menu);
+
+ /* Staggered destruction */
+ g_signal_connect (popup_menu, "selection-done",
+ G_CALLBACK (popup_menu_selection_done_cb), widget);
+ g_signal_connect (popup_menu, "destroy",
+ G_CALLBACK (popup_menu_destroy_cb), widget);
+
+ return popup_menu;
+}
+
+/* GAction callbacks */
+
+static void
+action_new_terminal_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalApp *app;
+ TerminalSettingsList *profiles_list;
+ gs_unref_object GSettings *profile = nullptr;
+ gboolean can_toggle = FALSE;
+
+ g_assert (TERMINAL_IS_WINDOW (window));
+
+ app = terminal_app_get ();
+
+ const char *mode_str, *uuid_str;
+ g_variant_get (parameter, "(&s&s)", &mode_str, &uuid_str);
+
+ TerminalNewTerminalMode mode;
+ if (g_str_equal (mode_str, "tab"))
+ mode = TERMINAL_NEW_TERMINAL_MODE_TAB;
+ else if (g_str_equal (mode_str, "window"))
+ mode = TERMINAL_NEW_TERMINAL_MODE_WINDOW;
+ else if (g_str_equal (mode_str, "tab-default")) {
+ mode = TERMINAL_NEW_TERMINAL_MODE_TAB;
+ can_toggle = TRUE;
+ } else {
+ mode = TerminalNewTerminalMode(g_settings_get_enum (terminal_app_get_global_settings (app),
+ TERMINAL_SETTING_NEW_TERMINAL_MODE_KEY));
+ can_toggle = TRUE;
+ }
+
+ if (can_toggle) {
+ GdkEvent *event = gtk_get_current_event ();
+ if (event != nullptr) {
+ GdkModifierType modifiers;
+
+ if ((gdk_event_get_state (event, &modifiers) &&
+ (modifiers & gtk_accelerator_get_default_mod_mask () & GDK_CONTROL_MASK))) {
+ /* Invert */
+ if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW)
+ mode = TERMINAL_NEW_TERMINAL_MODE_TAB;
+ else
+ mode = TERMINAL_NEW_TERMINAL_MODE_WINDOW;
+ }
+ gdk_event_free (event);
+ }
+ }
+
+ TerminalScreen *parent_screen = priv->active_screen;
+
+ profiles_list = terminal_app_get_profiles_list (app);
+ if (g_str_equal (uuid_str, "current"))
+ profile = terminal_screen_ref_profile (parent_screen);
+ else if (g_str_equal (uuid_str, "default"))
+ profile = terminal_settings_list_ref_default_child (profiles_list);
+ else
+ profile = terminal_settings_list_ref_child (profiles_list, uuid_str);
+
+ if (profile == nullptr)
+ return;
+
+ if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW)
+ window = terminal_window_new (G_APPLICATION (app));
+
+ TerminalScreen *screen = terminal_screen_new (profile,
+ nullptr /* title */,
+ 1.0);
+
+ /* Now add the new screen to the window */
+ terminal_window_add_screen (window, screen, -1);
+ terminal_window_switch_screen (window, screen);
+ gtk_widget_grab_focus (GTK_WIDGET (screen));
+
+ /* Start child process, if possible by using the same args as the parent screen */
+ terminal_screen_reexec_from_screen (screen, parent_screen, nullptr, nullptr);
+
+ if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW)
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+#ifdef ENABLE_SAVE
+
+static void
+save_contents_dialog_on_response (GtkDialog *dialog,
+ int response_id,
+ gpointer user_data)
+{
+ VteTerminal *terminal = (VteTerminal*)user_data;
+ GtkWindow *parent;
+ gs_free gchar *filename_uri = nullptr;
+ gs_unref_object GFile *file = nullptr;
+ GOutputStream *stream;
+ gs_free_error GError *error = nullptr;
+
+ if (response_id != GTK_RESPONSE_ACCEPT)
+ {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ return;
+ }
+
+ parent = (GtkWindow*) gtk_widget_get_ancestor (GTK_WIDGET (terminal), GTK_TYPE_WINDOW);
+ filename_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ if (filename_uri == nullptr)
+ return;
+
+ file = g_file_new_for_uri (filename_uri);
+ stream = G_OUTPUT_STREAM (g_file_replace (file, nullptr, FALSE, G_FILE_CREATE_NONE, nullptr, &error));
+
+ if (stream)
+ {
+ /* XXX
+ * FIXME
+ * This is a sync operation.
+ * Should be replaced with the async version when vte implements that.
+ */
+ vte_terminal_write_contents_sync (terminal, stream,
+ VTE_WRITE_DEFAULT,
+ nullptr, &error);
+ g_object_unref (stream);
+ }
+
+ if (error)
+ {
+ terminal_util_show_error_dialog (parent, nullptr, error,
+ "%s", _("Could not save contents"));
+ }
+}
+
+static void
+action_save_contents_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ GtkWidget *dialog = nullptr;
+ TerminalWindowPrivate *priv = window->priv;
+ VteTerminal *terminal;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ terminal = VTE_TERMINAL (priv->active_screen);
+ g_return_if_fail (VTE_IS_TERMINAL (terminal));
+
+ dialog = gtk_file_chooser_dialog_new (_("Save as…"),
+ GTK_WINDOW(window),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Save"), GTK_RESPONSE_ACCEPT,
+ nullptr);
+
+ gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window));
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (save_contents_dialog_on_response), terminal);
+ g_signal_connect (dialog, "delete_event", G_CALLBACK (terminal_util_dialog_response_on_delete), nullptr);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+#endif /* ENABLE_SAVE */
+
+#ifdef ENABLE_PRINT
+
+static void
+print_begin_cb (GtkPrintOperation *op,
+ GtkPrintContext *context,
+ TerminalApp *app)
+{
+ GtkPrintSettings *settings;
+ GtkPageSetup *page_setup;
+
+ /* Don't save if the print dialogue was cancelled */
+ if (gtk_print_operation_get_status(op) == GTK_PRINT_STATUS_FINISHED_ABORTED)
+ return;
+
+ settings = gtk_print_operation_get_print_settings (op);
+ page_setup = gtk_print_operation_get_default_page_setup (op);
+ terminal_util_save_print_settings (settings, page_setup);
+}
+
+static void
+print_done_cb (GtkPrintOperation *op,
+ GtkPrintOperationResult result,
+ TerminalWindow *window)
+{
+ if (result != GTK_PRINT_OPERATION_RESULT_ERROR)
+ return;
+
+ /* FIXME: show error */
+}
+
+static void
+action_print_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ gs_unref_object GtkPrintSettings *settings = nullptr;
+ gs_unref_object GtkPageSetup *page_setup = nullptr;
+ gs_unref_object GtkPrintOperation *op = nullptr;
+ gs_free_error GError *error = nullptr;
+ GtkPrintOperationResult result;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ op = vte_print_operation_new (VTE_TERMINAL (priv->active_screen),
+ VTE_PRINT_OPERATION_DEFAULT /* flags */);
+ if (op == nullptr)
+ return;
+
+ terminal_util_load_print_settings (&settings, &page_setup);
+ if (settings != nullptr)
+ gtk_print_operation_set_print_settings (op, settings);
+ if (page_setup != nullptr)
+ gtk_print_operation_set_default_page_setup (op, page_setup);
+
+ g_signal_connect (op, "begin-print", G_CALLBACK (print_begin_cb), window);
+ g_signal_connect (op, "done", G_CALLBACK (print_done_cb), window);
+
+ /* FIXME: show progress better */
+
+ result = gtk_print_operation_run (op,
+ /* this is the only supported one: */
+ GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+ GTK_WINDOW (window),
+ &error);
+ /* VtePrintOperation always runs async */
+ g_assert_cmpint (result, ==, GTK_PRINT_OPERATION_RESULT_IN_PROGRESS);
+}
+
+#endif /* ENABLE_PRINT */
+
+#ifdef ENABLE_EXPORT
+
+static void
+action_export_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ gs_unref_object VteExportOperation *op = nullptr;
+ gs_free_error GError *error = nullptr;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ op = vte_export_operation_new (VTE_TERMINAL (priv->active_screen),
+ TRUE /* interactive */,
+ VTE_EXPORT_FORMAT_ASK /* allow user to choose export format */,
+ nullptr, nullptr /* GSettings & key to load/store default directory from, FIXME */,
+ nullptr, nullptr /* progress callback & user data, FIXME */);
+ if (op == nullptr)
+ return;
+
+ /* FIXME: show progress better */
+
+ vte_export_operation_run_async (op, GTK_WINDOW (window), nullptr /* cancellable */);
+}
+
+#endif /* ENABLE_EXPORT */
+
+static void
+action_close_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalScreen *screen;
+ const char *mode_str;
+
+ g_assert_nonnull (parameter);
+ g_variant_get (parameter, "&s", &mode_str);
+
+ if (g_str_equal (mode_str, "tab"))
+ screen = priv->active_screen;
+ else if (g_str_equal (mode_str, "window"))
+ screen = nullptr;
+ else
+ return;
+
+ if (confirm_close_window_or_tab (window, screen))
+ return;
+
+ if (screen)
+ terminal_window_remove_screen (window, screen);
+ else
+ gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+static void
+action_copy_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ const char *format_str;
+ VteFormat format;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ g_assert_nonnull (parameter);
+ g_variant_get (parameter, "&s", &format_str);
+
+ if (g_str_equal (format_str, "text"))
+ format = VTE_FORMAT_TEXT;
+ else if (g_str_equal (format_str, "html"))
+ format = VTE_FORMAT_HTML;
+ else
+ return;
+
+ vte_terminal_copy_clipboard_format (VTE_TERMINAL (priv->active_screen), format);
+}
+
+/* Clipboard helpers */
+
+typedef struct {
+ GWeakRef screen_weak_ref;
+} PasteData;
+
+static void
+clipboard_uris_received_cb (GtkClipboard *clipboard,
+ /* const */ char **uris,
+ PasteData *data)
+{
+ gs_unref_object TerminalScreen *screen = nullptr;
+
+ if (uris != nullptr && uris[0] != nullptr &&
+ (screen = (TerminalScreen*)g_weak_ref_get (&data->screen_weak_ref))) {
+ gs_free char *text;
+ gsize len;
+
+ /* This potentially modifies the strings in |uris| but that's ok */
+ terminal_util_transform_uris_to_quoted_fuse_paths (uris);
+ text = terminal_util_concat_uris (uris, &len);
+
+ terminal_screen_paste_text (screen, text, len);
+ }
+
+ g_weak_ref_clear (&data->screen_weak_ref);
+ g_slice_free (PasteData, data);
+}
+
+static void
+request_clipboard_contents_for_paste (TerminalWindow *window,
+ gboolean paste_as_uris)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GdkAtom *targets;
+ int n_targets;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ targets = terminal_app_get_clipboard_targets (terminal_app_get (),
+ priv->clipboard,
+ &n_targets);
+ if (targets == nullptr)
+ return;
+
+ if (paste_as_uris && gtk_targets_include_uri (targets, n_targets)) {
+ PasteData *data = g_slice_new (PasteData);
+ g_weak_ref_init (&data->screen_weak_ref, priv->active_screen);
+
+ gtk_clipboard_request_uris (priv->clipboard,
+ (GtkClipboardURIReceivedFunc) clipboard_uris_received_cb,
+ data);
+ return;
+ } else if (gtk_targets_include_text (targets, n_targets)) {
+ vte_terminal_paste_clipboard (VTE_TERMINAL (priv->active_screen));
+ }
+}
+
+static void
+action_paste_text_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ request_clipboard_contents_for_paste (window, FALSE);
+}
+
+static void
+action_paste_uris_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ request_clipboard_contents_for_paste (window, TRUE);
+}
+
+static void
+action_select_all_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_select_all (VTE_TERMINAL (priv->active_screen));
+}
+
+static void
+action_reset_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ g_assert_nonnull (parameter);
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_reset (VTE_TERMINAL (priv->active_screen),
+ TRUE,
+ g_variant_get_boolean (parameter));
+}
+
+static void
+tab_switch_relative (TerminalWindow *window,
+ int change)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ int n_screens, value;
+
+ n_screens = terminal_mdi_container_get_n_screens (priv->mdi_container);
+ value = terminal_mdi_container_get_active_screen_num (priv->mdi_container) + change;
+
+ gboolean keynav_wrap_around;
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)),
+ "gtk-keynav-wrap-around", &keynav_wrap_around,
+ nullptr);
+ if (keynav_wrap_around) {
+ if (value < 0)
+ value += n_screens;
+ else if (value >= n_screens)
+ value -= n_screens;
+ }
+
+ if (value < 0 || value >= n_screens)
+ return;
+
+ g_action_change_state (G_ACTION (lookup_action (window, "active-tab")),
+ g_variant_new_int32 (value));
+}
+
+static void
+action_tab_switch_left_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ tab_switch_relative (window, -1);
+}
+
+static void
+action_tab_switch_right_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ tab_switch_relative (window, 1);
+}
+
+static void
+action_tab_move_left_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ int change;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ change = gtk_widget_get_direction (GTK_WIDGET (window)) == GTK_TEXT_DIR_RTL ? 1 : -1;
+ terminal_mdi_container_reorder_screen (priv->mdi_container,
+ terminal_mdi_container_get_active_screen (priv->mdi_container),
+ change);
+}
+
+static void
+action_tab_move_right_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ int change;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ change = gtk_widget_get_direction (GTK_WIDGET (window)) == GTK_TEXT_DIR_RTL ? -1 : 1;
+ terminal_mdi_container_reorder_screen (priv->mdi_container,
+ terminal_mdi_container_get_active_screen (priv->mdi_container),
+ change);
+}
+
+static void
+action_zoom_in_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ double zoom;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ zoom = vte_terminal_get_font_scale (VTE_TERMINAL (priv->active_screen));
+ if (!find_larger_zoom_factor (&zoom))
+ return;
+
+ vte_terminal_set_font_scale (VTE_TERMINAL (priv->active_screen), zoom);
+ terminal_window_update_zoom_sensitivity (window);
+}
+
+static void
+action_zoom_out_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ double zoom;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ zoom = vte_terminal_get_font_scale (VTE_TERMINAL (priv->active_screen));
+ if (!find_smaller_zoom_factor (&zoom))
+ return;
+
+ vte_terminal_set_font_scale (VTE_TERMINAL (priv->active_screen), zoom);
+ terminal_window_update_zoom_sensitivity (window);
+}
+
+static void
+action_zoom_normal_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_set_font_scale (VTE_TERMINAL (priv->active_screen), PANGO_SCALE_MEDIUM);
+ terminal_window_update_zoom_sensitivity (window);
+}
+
+static void
+action_tab_detach_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalApp *app;
+ TerminalWindow *new_window;
+ TerminalScreen *screen;
+ char geometry[32];
+ int width, height;
+
+ app = terminal_app_get ();
+
+ screen = priv->active_screen;
+
+ terminal_screen_get_size (screen, &width, &height);
+ g_snprintf (geometry, sizeof (geometry), "%dx%d", width, height);
+
+ new_window = terminal_window_new (G_APPLICATION (app));
+
+ terminal_window_move_screen (window, new_window, screen, -1);
+
+ terminal_window_parse_geometry (new_window, geometry);
+
+ gtk_window_present_with_time (GTK_WINDOW (new_window), gtk_get_current_event_time ());
+}
+
+static void
+action_help_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ terminal_util_show_help (nullptr);
+}
+
+static void
+action_about_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ terminal_util_show_about ();
+}
+
+static void
+action_edit_preferences_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ terminal_app_edit_preferences (terminal_app_get (),
+ terminal_screen_get_profile (priv->active_screen),
+ nullptr,
+ gtk_get_current_event_time());
+}
+
+static void
+action_size_to_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ guint width, height;
+
+ g_assert_nonnull (parameter);
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ g_variant_get (parameter, "(uu)", &width, &height);
+ if (width < MIN_WIDTH_CHARS || height < MIN_HEIGHT_CHARS ||
+ width > 256 || height > 256)
+ return;
+
+ vte_terminal_set_size (VTE_TERMINAL (priv->active_screen), width, height);
+ terminal_window_update_size (window);
+}
+
+static void
+action_open_match_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalScreenPopupInfo *info = priv->popup_info;
+
+ if (info == nullptr)
+ return;
+ if (info->url == nullptr)
+ return;
+
+ terminal_util_open_url (GTK_WIDGET (window), info->url, info->url_flavor,
+ gtk_get_current_event_time ());
+}
+
+static void
+action_copy_match_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalScreenPopupInfo *info = priv->popup_info;
+
+ if (info == nullptr)
+ return;
+ if (info->url == nullptr)
+ return;
+
+ gtk_clipboard_set_text (priv->clipboard, info->url, -1);
+}
+
+static void
+action_open_hyperlink_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalScreenPopupInfo *info = priv->popup_info;
+
+ if (info == nullptr)
+ return;
+ if (info->hyperlink == nullptr)
+ return;
+
+ terminal_util_open_url (GTK_WIDGET (window), info->hyperlink, FLAVOR_AS_IS,
+ gtk_get_current_event_time ());
+}
+
+static void
+action_copy_hyperlink_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalScreenPopupInfo *info = priv->popup_info;
+
+ if (info == nullptr)
+ return;
+ if (info->hyperlink == nullptr)
+ return;
+
+ gtk_clipboard_set_text (priv->clipboard, info->hyperlink, -1);
+}
+
+static void
+action_enter_fullscreen_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ g_action_group_change_action_state (G_ACTION_GROUP (window), "fullscreen",
+ g_variant_new_boolean (TRUE));
+}
+
+static void
+action_leave_fullscreen_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ g_action_group_change_action_state (G_ACTION_GROUP (window), "fullscreen",
+ g_variant_new_boolean (FALSE));
+}
+
+static void
+action_inspector_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gtk_window_set_interactive_debugging (TRUE);
+}
+
+static void
+search_popover_search_cb (TerminalSearchPopover *popover,
+ gboolean backward,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (G_UNLIKELY (priv->active_screen == nullptr))
+ return;
+
+ if (backward)
+ vte_terminal_search_find_previous (VTE_TERMINAL (priv->active_screen));
+ else
+ vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen));
+}
+
+static void
+search_popover_notify_regex_cb (TerminalSearchPopover *popover,
+ GParamSpec *pspec G_GNUC_UNUSED,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ VteRegex *regex;
+
+ if (G_UNLIKELY (priv->active_screen == nullptr))
+ return;
+
+ regex = terminal_search_popover_get_regex (popover);
+ vte_terminal_search_set_regex (VTE_TERMINAL (priv->active_screen), regex, 0);
+
+ terminal_window_update_search_sensitivity (priv->active_screen, window);
+}
+
+static void
+search_popover_notify_wrap_around_cb (TerminalSearchPopover *popover,
+ GParamSpec *pspec G_GNUC_UNUSED,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ gboolean wrap;
+
+ if (G_UNLIKELY (priv->active_screen == nullptr))
+ return;
+
+ wrap = terminal_search_popover_get_wrap_around (popover);
+ vte_terminal_search_set_wrap_around (VTE_TERMINAL (priv->active_screen), wrap);
+}
+
+static void
+action_find_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (G_UNLIKELY(priv->active_screen == nullptr))
+ return;
+
+ if (priv->search_popover != nullptr) {
+ search_popover_notify_regex_cb (priv->search_popover, nullptr, window);
+ search_popover_notify_wrap_around_cb (priv->search_popover, nullptr, window);
+
+ gtk_window_present_with_time (GTK_WINDOW (priv->search_popover),
+ gtk_get_current_event_time ());
+ gtk_widget_grab_focus (GTK_WIDGET (priv->search_popover));
+ return;
+ }
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ priv->search_popover = terminal_search_popover_new (GTK_WIDGET (window));
+
+ g_signal_connect (priv->search_popover, "search", G_CALLBACK (search_popover_search_cb), window);
+
+ search_popover_notify_regex_cb (priv->search_popover, nullptr, window);
+ g_signal_connect (priv->search_popover, "notify::regex", G_CALLBACK (search_popover_notify_regex_cb), window);
+
+ search_popover_notify_wrap_around_cb (priv->search_popover, nullptr, window);
+ g_signal_connect (priv->search_popover, "notify::wrap-around", G_CALLBACK (search_popover_notify_wrap_around_cb), window);
+
+ g_signal_connect (priv->search_popover, "destroy", G_CALLBACK (gtk_widget_destroyed), &priv->search_popover);
+
+ gtk_window_present_with_time (GTK_WINDOW (priv->search_popover), gtk_get_current_event_time ());
+ gtk_widget_grab_focus (GTK_WIDGET (priv->search_popover));
+}
+
+static void
+action_find_forward_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_search_find_next (VTE_TERMINAL (priv->active_screen));
+}
+
+static void
+action_find_backward_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_search_find_previous (VTE_TERMINAL (priv->active_screen));
+}
+
+static void
+action_find_clear_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_search_set_regex (VTE_TERMINAL (priv->active_screen), nullptr, 0);
+ vte_terminal_unselect_all (VTE_TERMINAL (priv->active_screen));
+}
+
+static void
+action_shadow_activate_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ gs_free char *param = g_variant_print(parameter, TRUE);
+
+ _terminal_debug_print (TERMINAL_DEBUG_ACCELS,
+ "Window %p shadow action activated for %s\n",
+ window, param);
+
+ /* We make sure in terminal-accels to always install the keybinding
+ * for the real action first, so that it's first in line for activation.
+ * That means we can make this here a NOP, instead of forwarding the
+ * activation to the shadowed action.
+ */
+}
+
+static void
+action_menubar_visible_state_cb (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ gboolean active;
+
+ active = g_variant_get_boolean (state);
+ terminal_window_set_menubar_visible (window, active); /* this also sets the action state */
+}
+
+static void
+action_fullscreen_state_cb (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (window)))
+ return;
+
+ if (g_variant_get_boolean (state))
+ gtk_window_fullscreen (GTK_WINDOW (window));
+ else
+ gtk_window_unfullscreen (GTK_WINDOW (window));
+
+ /* The window-state-changed callback will update the action's actual state */
+}
+
+static void
+action_read_only_state_cb (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ g_assert_nonnull (state);
+
+ g_simple_action_set_state (action, state);
+
+ terminal_window_update_paste_sensitivity (window);
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ vte_terminal_set_input_enabled (VTE_TERMINAL (priv->active_screen),
+ !g_variant_get_boolean (state));
+}
+
+static void
+action_profile_state_cb (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalSettingsList *profiles_list;
+ const gchar *uuid;
+ gs_unref_object GSettings *profile;
+
+ g_assert_nonnull (state);
+
+ uuid = g_variant_get_string (state, nullptr);
+ profiles_list = terminal_app_get_profiles_list (terminal_app_get ());
+ profile = terminal_settings_list_ref_child (profiles_list, uuid);
+ if (profile == nullptr)
+ return;
+
+ g_simple_action_set_state (action, state);
+
+ terminal_screen_set_profile (priv->active_screen, profile);
+}
+
+static void
+action_active_tab_set_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+ int value, n_screens;
+
+ g_assert_nonnull (parameter);
+
+ n_screens = terminal_mdi_container_get_n_screens (priv->mdi_container);
+
+ value = g_variant_get_int32 (parameter);
+ if (value < 0)
+ value += n_screens;
+ if (value < 0 || value >= n_screens)
+ return;
+
+ g_action_change_state (G_ACTION (action), g_variant_new_int32 (value));
+}
+
+static void
+action_active_tab_state_cb (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ TerminalWindow *window = (TerminalWindow*)user_data;
+ TerminalWindowPrivate *priv = window->priv;
+
+ g_assert_nonnull (state);
+
+ g_simple_action_set_state (action, state);
+
+ terminal_mdi_container_set_active_screen_num (priv->mdi_container, g_variant_get_int32 (state));
+}
+
+/* Menubar mnemonics & accel settings handling */
+
+static void
+enable_menubar_accel_changed_cb (GSettings *settings,
+ const char *key,
+ GtkSettings *gtk_settings)
+{
+ if (g_settings_get_boolean (settings, key))
+ gtk_settings_reset_property (gtk_settings, "gtk-menu-bar-accel");
+ else
+ g_object_set (gtk_settings, "gtk-menu-bar-accel", nullptr, nullptr);
+}
+
+/* The menubar is shown by the app, and the use of mnemonics (e.g. Alt+F for File) is toggled.
+ * The mnemonic modifier is per window, so it doesn't affect the Find or Preferences windows.
+ * If the menubar is shown by the shell, a non-mnemonic variant of the menu is loaded instead
+ * in terminal-app.c. See over there for further details. */
+static void
+enable_mnemonics_changed_cb (GSettings *settings,
+ const char *key,
+ TerminalWindow *window)
+{
+ gboolean enabled = g_settings_get_boolean (settings, key);
+
+ if (enabled)
+ gtk_window_set_mnemonic_modifier (GTK_WINDOW (window), GDK_MOD1_MASK);
+ else
+ gtk_window_set_mnemonic_modifier (GTK_WINDOW (window), GdkModifierType(GDK_MODIFIER_MASK & ~GDK_RELEASE_MASK));
+}
+
+static void
+app_setting_notify_destroy_cb (GtkSettings *gtk_settings)
+{
+ g_signal_handlers_disconnect_by_func (terminal_app_get_global_settings (terminal_app_get ()),
+ (void*)enable_menubar_accel_changed_cb,
+ gtk_settings);
+}
+
+/* utility functions */
+
+static int
+find_tab_num_at_pos (GtkNotebook *notebook,
+ int screen_x,
+ int screen_y)
+{
+ GtkPositionType tab_pos;
+ int page_num = 0;
+ GtkNotebook *nb = GTK_NOTEBOOK (notebook);
+ GtkWidget *page;
+ GtkAllocation tab_allocation;
+
+ tab_pos = gtk_notebook_get_tab_pos (GTK_NOTEBOOK (notebook));
+
+ while ((page = gtk_notebook_get_nth_page (nb, page_num)))
+ {
+ GtkWidget *tab;
+ int max_x, max_y, x_root, y_root;
+
+ tab = gtk_notebook_get_tab_label (nb, page);
+ g_return_val_if_fail (tab != nullptr, -1);
+
+ if (!gtk_widget_get_mapped (GTK_WIDGET (tab)))
+ {
+ page_num++;
+ continue;
+ }
+
+ gdk_window_get_origin (gtk_widget_get_window (tab), &x_root, &y_root);
+
+ gtk_widget_get_allocation (tab, &tab_allocation);
+ max_x = x_root + tab_allocation.x + tab_allocation.width;
+ max_y = y_root + tab_allocation.y + tab_allocation.height;
+
+ if ((tab_pos == GTK_POS_TOP || tab_pos == GTK_POS_BOTTOM) && screen_x <= max_x)
+ return page_num;
+
+ if ((tab_pos == GTK_POS_LEFT || tab_pos == GTK_POS_RIGHT) && screen_y <= max_y)
+ return page_num;
+
+ page_num++;
+ }
+
+ return -1;
+}
+
+static void
+terminal_window_update_set_profile_menu_active_profile (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GSettings *new_active_profile;
+ TerminalSettingsList *profiles_list;
+ char *uuid;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ new_active_profile = terminal_screen_get_profile (priv->active_screen);
+
+ profiles_list = terminal_app_get_profiles_list (terminal_app_get ());
+ uuid = terminal_settings_list_dup_uuid_from_child (profiles_list, new_active_profile);
+
+ g_simple_action_set_state (lookup_action (window, "profile"),
+ g_variant_new_take_string (uuid));
+}
+
+static void
+terminal_window_update_terminal_menu (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ gboolean read_only = !vte_terminal_get_input_enabled (VTE_TERMINAL (priv->active_screen));
+ g_simple_action_set_state (lookup_action (window, "read-only"),
+ g_variant_new_boolean (read_only));
+}
+
+/* Actions stuff */
+
+static void
+terminal_window_update_copy_sensitivity (TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ gboolean can_copy;
+
+ if (screen != priv->active_screen)
+ return;
+
+ can_copy = vte_terminal_get_has_selection (VTE_TERMINAL (screen));
+ g_simple_action_set_enabled (lookup_action (window, "copy"), can_copy);
+}
+
+static void
+terminal_window_update_zoom_sensitivity (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalScreen *screen;
+
+ screen = priv->active_screen;
+ if (screen == nullptr)
+ return;
+
+ double v;
+ double zoom = v = vte_terminal_get_font_scale (VTE_TERMINAL (screen));
+ g_simple_action_set_enabled (lookup_action (window, "zoom-in"),
+ find_larger_zoom_factor (&v));
+
+ v = zoom;
+ g_simple_action_set_enabled (lookup_action (window, "zoom-out"),
+ find_smaller_zoom_factor (&v));
+}
+
+static void
+terminal_window_update_search_sensitivity (TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (screen != priv->active_screen)
+ return;
+
+ gboolean can_search = vte_terminal_search_get_regex (VTE_TERMINAL (screen)) != nullptr;
+
+ g_simple_action_set_enabled (lookup_action (window, "find-forward"), can_search);
+ g_simple_action_set_enabled (lookup_action (window, "find-backward"), can_search);
+ g_simple_action_set_enabled (lookup_action (window, "find-clear"), can_search);
+}
+
+static void
+clipboard_targets_changed_cb (TerminalApp *app,
+ GtkClipboard *clipboard,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (clipboard != priv->clipboard)
+ return;
+
+ terminal_window_update_paste_sensitivity (window);
+}
+
+static void
+terminal_window_update_paste_sensitivity (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ GdkAtom *targets;
+ int n_targets;
+ targets = terminal_app_get_clipboard_targets (terminal_app_get(), priv->clipboard, &n_targets);
+
+ gboolean can_paste;
+ gboolean can_paste_uris;
+ if (n_targets) {
+ can_paste = gtk_targets_include_text (targets, n_targets);
+ can_paste_uris = gtk_targets_include_uri (targets, n_targets);
+ } else {
+ can_paste = can_paste_uris = FALSE;
+ }
+
+ gs_unref_variant GVariant *ro_state = g_action_get_state (g_action_map_lookup_action (G_ACTION_MAP (window), "read-only"));
+ gboolean read_only = g_variant_get_boolean (ro_state);
+
+ g_simple_action_set_enabled (lookup_action (window, "paste-text"), can_paste && !read_only);
+ g_simple_action_set_enabled (lookup_action (window, "paste-uris"), can_paste_uris && !read_only);
+}
+
+static void
+screen_resize_window_cb (TerminalScreen *screen,
+ guint columns,
+ guint rows,
+ TerminalWindow* window)
+{
+ auto const priv = window->priv;
+
+ if (priv->realized &&
+ window_state_is_snapped(priv->window_state))
+ return;
+
+ vte_terminal_set_size (VTE_TERMINAL (priv->active_screen), columns, rows);
+
+ if (screen == priv->active_screen)
+ terminal_window_update_size (window);
+}
+
+static void
+terminal_window_update_tabs_actions_sensitivity (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->disposed)
+ return;
+
+ int num_pages = terminal_mdi_container_get_n_screens (priv->mdi_container);
+ int page_num = terminal_mdi_container_get_active_screen_num (priv->mdi_container);
+
+ gboolean not_only = num_pages > 1;
+ gboolean not_first = page_num > 0;
+ gboolean not_last = page_num + 1 < num_pages;
+
+ gboolean not_first_lr, not_last_lr;
+ if (gtk_widget_get_direction (GTK_WIDGET (window)) == GTK_TEXT_DIR_RTL) {
+ not_first_lr = not_last;
+ not_last_lr = not_first;
+ } else {
+ not_first_lr = not_first;
+ not_last_lr = not_last;
+ }
+
+ /* Hide the tabs menu in single-tab windows */
+ g_simple_action_set_enabled (lookup_action (window, "tabs-menu"), not_only);
+
+ /* Disable shadowing of MDI actions in SDI windows */
+ g_simple_action_set_enabled (lookup_action (window, "shadow-mdi"), not_only);
+
+ /* Disable tab switching (and all its shortcuts) in SDI windows */
+ g_simple_action_set_enabled (lookup_action (window, "active-tab"), not_only);
+
+ /* Set the active tab */
+ g_simple_action_set_state (lookup_action (window, "active-tab"),
+ g_variant_new_int32 (page_num));
+
+ /* Keynav wraps around? See bug #92139 */
+ gboolean keynav_wrap_around;
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)),
+ "gtk-keynav-wrap-around", &keynav_wrap_around,
+ nullptr);
+
+ gboolean wrap = keynav_wrap_around && not_only;
+ g_simple_action_set_enabled (lookup_action (window, "tab-switch-left"), not_first || wrap);
+ g_simple_action_set_enabled (lookup_action (window, "tab-switch-right"), not_last || wrap);
+ g_simple_action_set_enabled (lookup_action (window, "tab-move-left"), not_first_lr || wrap);
+ g_simple_action_set_enabled (lookup_action (window, "tab-move-right"), not_last_lr || wrap);
+ g_simple_action_set_enabled (lookup_action (window, "tab-detach"), not_only);
+}
+
+static GtkNotebook *
+handle_tab_droped_on_desktop (GtkNotebook *source_notebook,
+ GtkWidget *container,
+ gint x,
+ gint y,
+ gpointer data G_GNUC_UNUSED)
+{
+ TerminalWindow *source_window;
+ TerminalWindow *new_window;
+ TerminalWindowPrivate *new_priv;
+
+ source_window = TERMINAL_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source_notebook)));
+ g_return_val_if_fail (TERMINAL_IS_WINDOW (source_window), nullptr);
+
+ new_window = terminal_window_new (G_APPLICATION (terminal_app_get ()));
+ new_priv = new_window->priv;
+ new_priv->present_on_insert = TRUE;
+
+ return GTK_NOTEBOOK (new_priv->mdi_container);
+}
+
+/* Terminal screen popup menu handling */
+
+static void
+remove_popup_info (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->popup_info != nullptr)
+ {
+ terminal_screen_popup_info_unref (priv->popup_info);
+ priv->popup_info = nullptr;
+ }
+}
+
+static void
+screen_popup_menu_selection_done_cb (GtkWidget *popup,
+ GtkWidget *window)
+{
+ g_signal_handlers_disconnect_by_func
+ (popup, (void*)screen_popup_menu_selection_done_cb, window);
+
+ GtkWidget *attach_widget = gtk_menu_get_attach_widget (GTK_MENU (popup));
+ if (attach_widget != window || !TERMINAL_IS_WINDOW (attach_widget))
+ return;
+
+ remove_popup_info (TERMINAL_WINDOW (attach_widget));
+}
+
+static void
+screen_show_popup_menu_cb (TerminalScreen *screen,
+ TerminalScreenPopupInfo *info,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalApp *app = terminal_app_get ();
+
+ if (screen != priv->active_screen)
+ return;
+
+ remove_popup_info (window);
+ priv->popup_info = terminal_screen_popup_info_ref (info);
+
+ gs_unref_object GMenu *menu = g_menu_new ();
+
+ /* Hyperlink section */
+ if (info->hyperlink != nullptr) {
+ gs_unref_object GMenu *section1 = g_menu_new ();
+
+ g_menu_append (section1, _("Open _Hyperlink"), "win.open-hyperlink");
+ g_menu_append (section1, _("Copy Hyperlink _Address"), "win.copy-hyperlink");
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section1));
+ }
+ /* Matched link section */
+ else if (info->url != nullptr) {
+ gs_unref_object GMenu *section2 = g_menu_new ();
+
+ const char *open_label = nullptr, *copy_label = nullptr;
+ switch (info->url_flavor) {
+ case FLAVOR_EMAIL:
+ open_label = _("Send Mail _To…");
+ copy_label = _("Copy Mail _Address");
+ break;
+ case FLAVOR_VOIP_CALL:
+ open_label = _("Call _To…");
+ copy_label = _("Copy Call _Address ");
+ break;
+ case FLAVOR_AS_IS:
+ case FLAVOR_DEFAULT_TO_HTTP:
+ default:
+ open_label = _("_Open Link");
+ copy_label = _("Copy _Link");
+ break;
+ }
+
+ g_menu_append (section2, open_label, "win.open-match");
+ g_menu_append (section2, copy_label, "win.copy-match");
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section2));
+ }
+
+ /* Info section */
+ gs_strfreev char** citems = g_settings_get_strv (terminal_app_get_global_settings (terminal_app_get ()),
+ TERMINAL_SETTING_CONTEXT_INFO_KEY);
+
+ gs_unref_object GMenu *section3 = g_menu_new ();
+
+ for (int i = 0; citems[i] != nullptr; ++i) {
+ const char *citem = citems[i];
+
+ if (g_str_equal (citem, "numbers") &&
+ info->number_info != nullptr) {
+ /* Non-existent action will make this item insensitive */
+ gs_unref_object GMenuItem *item3 = g_menu_item_new (info->number_info, "win.notexist");
+ g_menu_append_item (section3, item3);
+ }
+
+ if (g_str_equal (citem, "timestamps") &&
+ info->timestamp_info != nullptr) {
+ /* Non-existent action will make this item insensitive */
+ gs_unref_object GMenuItem *item3 = g_menu_item_new (info->timestamp_info, "win.notexist");
+ g_menu_append_item (section3, item3);
+ }
+ }
+
+ if (g_menu_model_get_n_items(G_MENU_MODEL (section3)) > 0)
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section3));
+
+ /* Clipboard section */
+ gs_unref_object GMenu *section4 = g_menu_new ();
+
+ g_menu_append (section4, _("_Copy"), "win.copy::text");
+ g_menu_append (section4, _("Copy as _HTML"), "win.copy::html");
+ g_menu_append (section4, _("_Paste"), "win.paste-text");
+ if (g_action_get_enabled (G_ACTION (lookup_action (window, "paste-uris"))))
+ g_menu_append (section4, _("Paste as _Filenames"), "win.paste-uris");
+
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section4));
+
+ /* Profile and property section */
+ gs_unref_object GMenu *section5 = g_menu_new ();
+ g_menu_append (section5, _("Read-_Only"), "win.read-only");
+
+ GMenuModel *profiles_menu = terminal_app_get_profile_section (app);
+ if (profiles_menu != nullptr && g_menu_model_get_n_items (profiles_menu) > 1) {
+ gs_unref_object GMenu *submenu5 = g_menu_new ();
+ g_menu_append_section (submenu5, nullptr, profiles_menu);
+
+ gs_unref_object GMenuItem *item5 = g_menu_item_new (_("P_rofiles"), nullptr);
+ g_menu_item_set_submenu (item5, G_MENU_MODEL (submenu5));
+ g_menu_append_item (section5, item5);
+ }
+
+ g_menu_append (section5, _("_Preferences"), "win.edit-preferences");
+
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section5));
+
+ /* New Terminal section */
+ gs_unref_object GMenu *section6 = g_menu_new ();
+ if (terminal_app_get_menu_unified (app)) {
+ gs_unref_object GMenuItem *item6 = g_menu_item_new (_("New _Terminal"), nullptr);
+ g_menu_item_set_action_and_target (item6, "win.new-terminal",
+ "(ss)", "default", "current");
+ g_menu_append_item (section6, item6);
+ } else {
+ gs_unref_object GMenuItem *item61 = g_menu_item_new (_("New _Window"), nullptr);
+ g_menu_item_set_action_and_target (item61, "win.new-terminal",
+ "(ss)", "window", "current");
+ g_menu_append_item (section6, item61);
+ gs_unref_object GMenuItem *item62 = g_menu_item_new (_("New _Tab"), nullptr);
+ g_menu_item_set_action_and_target (item62, "win.new-terminal",
+ "(ss)", "tab", "current");
+ g_menu_append_item (section6, item62);
+ }
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section6));
+
+ /* Window section */
+ gs_unref_object GMenu *section7 = g_menu_new ();
+
+ /* Only show this if the WM doesn't show the menubar */
+ if (g_action_get_enabled (G_ACTION (lookup_action (window, "menubar-visible"))))
+ g_menu_append (section7, _("Show _Menubar"), "win.menubar-visible");
+ if (g_action_get_enabled (G_ACTION (lookup_action (window, "leave-fullscreen"))))
+ g_menu_append (section7, _("L_eave Full Screen"), "win.leave-fullscreen");
+
+ g_menu_append_section (menu, nullptr, G_MENU_MODEL (section7));
+
+ /* Now create the popup menu and show it */
+ GtkWidget *popup_menu = context_menu_new (G_MENU_MODEL (menu), GTK_WIDGET (window));
+
+ /* Remove the popup info after the menu is done */
+ g_signal_connect (popup_menu, "selection-done",
+ G_CALLBACK (screen_popup_menu_selection_done_cb), window);
+
+ gtk_menu_popup (GTK_MENU (popup_menu), nullptr, nullptr,
+ nullptr, nullptr,
+ info->button,
+ info->timestamp);
+
+ if (info->button == 0)
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (popup_menu), FALSE);
+}
+
+static gboolean
+screen_match_clicked_cb (TerminalScreen *screen,
+ const char *url,
+ int url_flavor,
+ guint state,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (screen != priv->active_screen)
+ return FALSE;
+
+ gtk_widget_grab_focus (GTK_WIDGET (screen));
+ terminal_util_open_url (GTK_WIDGET (window), url, TerminalURLFlavor(url_flavor),
+ gtk_get_current_event_time ());
+
+ return TRUE;
+}
+
+static void
+screen_close_cb (TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ terminal_window_remove_screen (window, screen);
+}
+
+static void
+notebook_update_tabs_menu_cb (GtkMenuButton *button,
+ TerminalWindow *window)
+{
+ gs_unref_object GMenu *menu;
+ gs_free_list GList *tabs;
+ GList *t;
+ int i;
+
+ menu = g_menu_new ();
+ tabs = terminal_window_list_screen_containers (window);
+
+ for (t = tabs, i = 0; t != nullptr; t = t->next, i++) {
+ TerminalScreenContainer *container = (TerminalScreenContainer*)t->data;
+ TerminalScreen *screen = terminal_screen_container_get_screen (container);
+ gs_unref_object GMenuItem *item;
+ const char *title;
+
+ if (t->next == nullptr) {
+ /* Last entry. If it has no dedicated shortcut "Switch to Tab N",
+ * display the accel of "Switch to Last Tab". */
+ GtkApplication *app = GTK_APPLICATION (g_application_get_default ());
+ gs_free gchar *detailed_action = g_strdup_printf("win.active-tab(%d)", i);
+ gs_strfreev gchar **accels = gtk_application_get_accels_for_action (app, detailed_action);
+ if (accels[0] == nullptr)
+ i = -1;
+ }
+
+ title = terminal_screen_get_title (screen);
+
+ item = g_menu_item_new (title && title[0] ? title : _("Terminal"), nullptr);
+ g_menu_item_set_action_and_target (item, "win.active-tab", "i", i);
+ g_menu_append_item (menu, item);
+ }
+
+ gtk_menu_button_set_menu_model (button, G_MENU_MODEL (menu));
+
+ /* Need this so the menu is positioned correctly */
+ gtk_widget_set_halign (GTK_WIDGET (gtk_menu_button_get_popup (button)), GTK_ALIGN_END);
+}
+
+static void
+terminal_window_fill_notebook_action_box (TerminalWindow *window,
+ gboolean add_new_tab_button)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GtkWidget *box, *new_tab_button, *tabs_menu_button;
+
+ box = terminal_notebook_get_action_box (TERMINAL_NOTEBOOK (priv->mdi_container), GTK_PACK_END);
+
+ /* Create the NewTerminal button */
+ if (add_new_tab_button)
+ {
+ new_tab_button = terminal_icon_button_new ("tab-new-symbolic");
+ gtk_actionable_set_action_name (GTK_ACTIONABLE (new_tab_button), "win.new-terminal");
+ gtk_actionable_set_action_target (GTK_ACTIONABLE (new_tab_button), "(ss)", "tab", "current");
+ gtk_box_pack_start (GTK_BOX (box), new_tab_button, FALSE, FALSE, 0);
+ gtk_widget_show (new_tab_button);
+ }
+
+ /* Create Tabs menu button */
+ tabs_menu_button = terminal_menu_button_new ();
+ g_signal_connect (tabs_menu_button, "update-menu",
+ G_CALLBACK (notebook_update_tabs_menu_cb), window);
+ gtk_box_pack_start (GTK_BOX (box), tabs_menu_button, FALSE, FALSE, 0);
+ gtk_menu_button_set_align_widget (GTK_MENU_BUTTON (tabs_menu_button), box);
+ 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
+terminal_window_realize (GtkWidget *widget)
+{
+ TerminalWindow *window = TERMINAL_WINDOW (widget);
+ TerminalWindowPrivate *priv = window->priv;
+ GtkAllocation widget_allocation;
+
+ gtk_widget_get_allocation (widget, &widget_allocation);
+
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] realize, size %d : %d at (%d, %d)\n",
+ widget,
+ widget_allocation.width, widget_allocation.height,
+ widget_allocation.x, widget_allocation.y);
+
+ GTK_WIDGET_CLASS (terminal_window_parent_class)->realize (widget);
+
+ /* Now that we've been realized, we should know precisely how large the
+ * client-side decorations are going to be. Recalculate the geometry hints,
+ * export them to the windowing system, and resize the window accordingly. */
+ priv->realized = TRUE;
+ terminal_window_update_size (window);
+}
+
+static gboolean
+terminal_window_state_event (GtkWidget *widget,
+ GdkEventWindowState *event)
+{
+ auto const window_state_event =
+ GTK_WIDGET_CLASS(terminal_window_parent_class)->window_state_event;
+ auto const window = TERMINAL_WINDOW(widget);
+ auto const priv = window->priv;
+
+ priv->window_state = event->new_window_state;
+
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "Window state changed mask %x old state %x new state %x\n",
+ unsigned(event->changed_mask),
+ unsigned(priv->window_state),
+ unsigned(event->new_window_state));
+
+ if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
+ {
+ auto const is_fullscreen = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0;
+
+ g_simple_action_set_state (lookup_action (window, "fullscreen"),
+ g_variant_new_boolean (is_fullscreen));
+ g_simple_action_set_enabled (lookup_action (window, "leave-fullscreen"),
+ is_fullscreen);
+ g_simple_action_set_enabled (lookup_action (window, "size-to"),
+ !is_fullscreen);
+ }
+
+ if (window_state_is_snapped(event->changed_mask))
+ {
+ if (window_state_is_snapped(event->new_window_state))
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "Disapplying geometry hints entering snapped state\n");
+ gtk_window_set_geometry_hints(GTK_WINDOW(widget), nullptr, nullptr,
+ GdkWindowHints(0));
+ }
+ else
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "Reapplying geometry hints after leaving snapped state\n");
+ gtk_window_set_geometry_hints (GTK_WINDOW (window),
+ nullptr,
+ &priv->hints,
+ GdkWindowHints(GDK_HINT_RESIZE_INC |
+ GDK_HINT_MIN_SIZE |
+ GDK_HINT_BASE_SIZE));
+ }
+ }
+
+ if (window_state_event)
+ return window_state_event (widget, event);
+
+ return false;
+}
+
+static void
+terminal_window_screen_update (TerminalWindow *window,
+ GdkScreen *screen)
+{
+ GSettings *settings;
+ GtkSettings *gtk_settings;
+
+ if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (screen), "GT::HasSettingsConnection")))
+ return;
+
+ settings = terminal_app_get_global_settings (terminal_app_get ());
+ gtk_settings = gtk_settings_get_for_screen (screen);
+
+ g_object_set_data_full (G_OBJECT (screen), "GT::HasSettingsConnection",
+ gtk_settings,
+ (GDestroyNotify) app_setting_notify_destroy_cb);
+
+ g_settings_bind (settings,
+ TERMINAL_SETTING_ENABLE_SHORTCUTS_KEY,
+ gtk_settings,
+ "gtk-enable-accels",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET));
+
+ enable_menubar_accel_changed_cb (settings,
+ TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY,
+ gtk_settings);
+ g_signal_connect (settings, "changed::" TERMINAL_SETTING_ENABLE_MENU_BAR_ACCEL_KEY,
+ G_CALLBACK (enable_menubar_accel_changed_cb),
+ gtk_settings);
+}
+
+static void
+terminal_window_screen_changed (GtkWidget *widget,
+ GdkScreen *previous_screen)
+{
+ TerminalWindow *window = TERMINAL_WINDOW (widget);
+ void (* screen_changed) (GtkWidget *, GdkScreen *) =
+ GTK_WIDGET_CLASS (terminal_window_parent_class)->screen_changed;
+ GdkScreen *screen;
+
+ if (screen_changed)
+ screen_changed (widget, previous_screen);
+
+ screen = gtk_widget_get_screen (widget);
+ if (previous_screen == screen)
+ return;
+
+ if (!screen)
+ return;
+
+ terminal_window_screen_update (window, screen);
+}
+
+static void
+terminal_window_init (TerminalWindow *window)
+{
+ const GActionEntry action_entries[] = {
+ /* Actions without state */
+ { "about", action_about_cb, nullptr, nullptr, nullptr },
+ { "close", action_close_cb, "s", nullptr, nullptr },
+ { "copy", action_copy_cb, "s", nullptr, nullptr },
+ { "copy-hyperlink", action_copy_hyperlink_cb, nullptr, nullptr, nullptr },
+ { "copy-match", action_copy_match_cb, nullptr, nullptr, nullptr },
+ { "edit-preferences", action_edit_preferences_cb, nullptr, nullptr, nullptr },
+ { "enter-fullscreen", action_enter_fullscreen_cb, nullptr, nullptr, nullptr },
+ { "find", action_find_cb, nullptr, nullptr, nullptr },
+ { "find-backward", action_find_backward_cb, nullptr, nullptr, nullptr },
+ { "find-clear", action_find_clear_cb, nullptr, nullptr, nullptr },
+ { "find-forward", action_find_forward_cb, nullptr, nullptr, nullptr },
+ { "help", action_help_cb, nullptr, nullptr, nullptr },
+ { "inspector", action_inspector_cb, nullptr, nullptr, nullptr },
+ { "leave-fullscreen", action_leave_fullscreen_cb, nullptr, nullptr, nullptr },
+ { "new-terminal", action_new_terminal_cb, "(ss)", nullptr, nullptr },
+ { "open-match", action_open_match_cb, nullptr, nullptr, nullptr },
+ { "open-hyperlink", action_open_hyperlink_cb, nullptr, nullptr, nullptr },
+ { "paste-text", action_paste_text_cb, nullptr, nullptr, nullptr },
+ { "paste-uris", action_paste_uris_cb, nullptr, nullptr, nullptr },
+ { "reset", action_reset_cb, "b", nullptr, nullptr },
+ { "select-all", action_select_all_cb, nullptr, nullptr, nullptr },
+ { "size-to", action_size_to_cb, "(uu)", nullptr, nullptr },
+ { "tab-detach", action_tab_detach_cb, nullptr, nullptr, nullptr },
+ { "tab-move-left", action_tab_move_left_cb, nullptr, nullptr, nullptr },
+ { "tab-move-right", action_tab_move_right_cb, nullptr, nullptr, nullptr },
+ { "tab-switch-left", action_tab_switch_left_cb, nullptr, nullptr, nullptr },
+ { "tab-switch-right", action_tab_switch_right_cb, nullptr, nullptr, nullptr },
+ { "tabs-menu", nullptr, nullptr, nullptr, nullptr },
+ { "zoom-in", action_zoom_in_cb, nullptr, nullptr, nullptr },
+ { "zoom-normal", action_zoom_normal_cb, nullptr, nullptr, nullptr },
+ { "zoom-out", action_zoom_out_cb, nullptr, nullptr, nullptr },
+#ifdef ENABLE_EXPORT
+ { "export", action_export_cb, nullptr, nullptr, nullptr },
+#endif
+#ifdef ENABLE_PRINT
+ { "print", action_print_cb, nullptr, nullptr, nullptr },
+#endif
+#ifdef ENABLE_SAVE
+ { "save-contents", action_save_contents_cb, nullptr, nullptr, nullptr },
+#endif
+
+ /* Shadow actions for keybinding comsumption, see comment in terminal-accels.c */
+ { "shadow", action_shadow_activate_cb, "s", nullptr, nullptr },
+ { "shadow-mdi", action_shadow_activate_cb, "s", nullptr, nullptr },
+
+ /* Actions with state */
+ { "active-tab", action_active_tab_set_cb, "i", "@i 0", action_active_tab_state_cb },
+ { "header-menu", nullptr /* toggles state */, nullptr, "false", nullptr },
+ { "fullscreen", nullptr /* toggles state */, nullptr, "false", action_fullscreen_state_cb },
+ { "menubar-visible", nullptr /* toggles state */, nullptr, "true", action_menubar_visible_state_cb },
+ { "profile", nullptr /* changes state */, "s", "''", action_profile_state_cb },
+ { "read-only", nullptr /* toggles state */, nullptr, "false", action_read_only_state_cb },
+ };
+ TerminalWindowPrivate *priv;
+ TerminalApp *app;
+ GSettings *gtk_debug_settings;
+ GtkWindowGroup *window_group;
+ // GtkAccelGroup *accel_group;
+ uuid_t u;
+ char uuidstr[37], role[64];
+ gboolean shell_shows_menubar;
+ gboolean use_headerbar;
+ GSimpleAction *action;
+
+ app = terminal_app_get ();
+
+ priv = window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window, TERMINAL_TYPE_WINDOW, TerminalWindowPrivate);
+
+ gtk_widget_init_template (GTK_WIDGET (window));
+
+ uuid_generate (u);
+ uuid_unparse (u, uuidstr);
+ priv->uuid = g_strdup (uuidstr);
+
+ g_signal_connect (G_OBJECT (window), "delete_event",
+ G_CALLBACK(terminal_window_delete_event),
+ nullptr);
+
+ use_headerbar = terminal_app_get_use_headerbar (app);
+ if (use_headerbar) {
+ GtkWidget *headerbar;
+
+ headerbar = terminal_headerbar_new ();
+ gtk_window_set_titlebar (GTK_WINDOW (window), headerbar);
+ }
+
+ gtk_window_set_title (GTK_WINDOW (window), _("Terminal"));
+
+ priv->active_screen = nullptr;
+
+ priv->main_vbox = gtk_bin_get_child (GTK_BIN (window));
+
+ priv->mdi_container = TERMINAL_MDI_CONTAINER (terminal_notebook_new ());
+
+ g_signal_connect (priv->mdi_container, "screen-close-request",
+ G_CALLBACK (screen_close_request_cb), window);
+
+ g_signal_connect_after (priv->mdi_container, "screen-switched",
+ G_CALLBACK (mdi_screen_switched_cb), window);
+ g_signal_connect_after (priv->mdi_container, "screen-added",
+ G_CALLBACK (mdi_screen_added_cb), window);
+ g_signal_connect_after (priv->mdi_container, "screen-removed",
+ G_CALLBACK (mdi_screen_removed_cb), window);
+ g_signal_connect_after (priv->mdi_container, "screens-reordered",
+ G_CALLBACK (mdi_screens_reordered_cb), window);
+
+ g_signal_connect_swapped (priv->mdi_container, "notify::tab-pos",
+ G_CALLBACK (terminal_window_update_geometry), window);
+ g_signal_connect_swapped (priv->mdi_container, "notify::show-tabs",
+ G_CALLBACK (terminal_window_update_geometry), window);
+
+ /* FIXME hack hack! */
+ if (GTK_IS_NOTEBOOK (priv->mdi_container)) {
+ g_signal_connect (priv->mdi_container, "button-press-event",
+ G_CALLBACK (notebook_button_press_cb), window);
+ g_signal_connect (priv->mdi_container, "popup-menu",
+ G_CALLBACK (notebook_popup_menu_cb), window);
+ g_signal_connect (priv->mdi_container, "create-window",
+ G_CALLBACK (handle_tab_droped_on_desktop), window);
+ }
+
+ gtk_box_pack_end (GTK_BOX (priv->main_vbox), GTK_WIDGET (priv->mdi_container), TRUE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (priv->mdi_container));
+
+ priv->old_char_width = -1;
+ priv->old_char_height = -1;
+
+ priv->old_chrome_width = -1;
+ priv->old_chrome_height = -1;
+ priv->old_csd_width = -1;
+ priv->old_csd_height = -1;
+ priv->old_padding_width = -1;
+ priv->old_padding_height = -1;
+
+ priv->old_geometry_widget = nullptr;
+
+ /* GAction setup */
+ g_action_map_add_action_entries (G_ACTION_MAP (window),
+ action_entries, G_N_ELEMENTS (action_entries),
+ window);
+
+ g_simple_action_set_enabled (lookup_action (window, "leave-fullscreen"), FALSE);
+
+ GSettings *global_settings = terminal_app_get_global_settings (app);
+ enable_mnemonics_changed_cb (global_settings, TERMINAL_SETTING_ENABLE_MNEMONICS_KEY, window);
+ g_signal_connect (global_settings, "changed::" TERMINAL_SETTING_ENABLE_MNEMONICS_KEY,
+ G_CALLBACK (enable_mnemonics_changed_cb), window);
+
+ /* Hide "menubar-visible" when the menubar is shown by the shell */
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (window)),
+ "gtk-shell-shows-menubar", &shell_shows_menubar,
+ nullptr);
+ if (shell_shows_menubar) {
+ g_simple_action_set_enabled (lookup_action (window, "menubar-visible"), FALSE);
+ } else {
+ priv->menubar = gtk_menu_bar_new_from_model (terminal_app_get_menubar (app));
+ gtk_box_pack_start (GTK_BOX (priv->main_vbox),
+ priv->menubar,
+ FALSE, FALSE, 0);
+
+ terminal_window_set_menubar_visible (window, !use_headerbar);
+ 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);
+ if (gtk_debug_settings != nullptr)
+ g_settings_bind (gtk_debug_settings,
+ "enable-inspector-keybinding",
+ action,
+ "enabled",
+ GSettingsBindFlags(G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY));
+ else
+ g_simple_action_set_enabled (action, FALSE);
+
+ priv->clipboard = gtk_widget_get_clipboard (GTK_WIDGET (window), GDK_SELECTION_CLIPBOARD);
+ clipboard_targets_changed_cb (app, priv->clipboard, window);
+ g_signal_connect (app, "clipboard-targets-changed",
+ G_CALLBACK (clipboard_targets_changed_cb), window);
+
+ terminal_window_fill_notebook_action_box (window, !use_headerbar);
+
+ /* We have to explicitly call this, since screen-changed is NOT
+ * emitted for the toplevel the first time!
+ */
+ terminal_window_screen_update (window, gtk_widget_get_screen (GTK_WIDGET (window)));
+
+ window_group = gtk_window_group_new ();
+ gtk_window_group_add_window (window_group, GTK_WINDOW (window));
+ g_object_unref (window_group);
+
+ g_snprintf (role, sizeof (role), "gnome-terminal-window-%s", uuidstr);
+ gtk_window_set_role (GTK_WINDOW (window), role);
+}
+
+static void
+terminal_window_style_updated (GtkWidget *widget)
+{
+ TerminalWindow *window = TERMINAL_WINDOW (widget);
+
+ GTK_WIDGET_CLASS (terminal_window_parent_class)->style_updated (widget);
+
+ terminal_window_update_size (window);
+}
+
+static void
+terminal_window_class_init (TerminalWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = terminal_window_dispose;
+ object_class->finalize = terminal_window_finalize;
+
+ widget_class->show = terminal_window_show;
+ widget_class->realize = terminal_window_realize;
+ widget_class->window_state_event = terminal_window_state_event;
+ widget_class->screen_changed = terminal_window_screen_changed;
+ widget_class->style_updated = terminal_window_style_updated;
+
+ GtkWindowClass *window_klass;
+ GtkBindingSet *binding_set;
+
+ window_klass = (GtkWindowClass*)g_type_class_ref (GTK_TYPE_WINDOW);
+ binding_set = gtk_binding_set_by_class (window_klass);
+ gtk_binding_entry_skip (binding_set, GDK_KEY_I, GdkModifierType(GDK_CONTROL_MASK|GDK_SHIFT_MASK));
+ gtk_binding_entry_skip (binding_set, GDK_KEY_D, GdkModifierType(GDK_CONTROL_MASK|GDK_SHIFT_MASK));
+ g_type_class_unref (window_klass);
+
+ g_type_class_add_private (object_class, sizeof (TerminalWindowPrivate));
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/terminal/ui/window.ui");
+
+ gtk_widget_class_set_css_name(widget_class, TERMINAL_WINDOW_CSS_NAME);
+}
+
+static void
+terminal_window_dispose (GObject *object)
+{
+ TerminalWindow *window = TERMINAL_WINDOW (object);
+ TerminalWindowPrivate *priv = window->priv;
+ TerminalApp *app = terminal_app_get ();
+
+ if (!priv->disposed) {
+ GSettings *global_settings = terminal_app_get_global_settings (app);
+ g_signal_handlers_disconnect_by_func (global_settings,
+ (void*)enable_mnemonics_changed_cb,
+ window);
+ }
+
+ priv->disposed = TRUE;
+
+ if (priv->clipboard != nullptr) {
+ g_signal_handlers_disconnect_by_func (app,
+ (void*)clipboard_targets_changed_cb,
+ window);
+ 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)
+ {
+ g_signal_handlers_disconnect_matched (priv->search_popover, G_SIGNAL_MATCH_DATA,
+ 0, 0, nullptr, nullptr, window);
+ gtk_widget_destroy (GTK_WIDGET (priv->search_popover));
+ priv->search_popover = nullptr;
+ }
+
+ G_OBJECT_CLASS (terminal_window_parent_class)->dispose (object);
+}
+
+static void
+terminal_window_finalize (GObject *object)
+{
+ TerminalWindow *window = TERMINAL_WINDOW (object);
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->confirm_close_dialog)
+ gtk_dialog_response (GTK_DIALOG (priv->confirm_close_dialog),
+ GTK_RESPONSE_DELETE_EVENT);
+
+ g_free (priv->uuid);
+
+ G_OBJECT_CLASS (terminal_window_parent_class)->finalize (object);
+}
+
+static gboolean
+terminal_window_delete_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ return confirm_close_window_or_tab (TERMINAL_WINDOW (widget), nullptr);
+}
+
+static void
+terminal_window_show (GtkWidget *widget)
+{
+ TerminalWindow *window = TERMINAL_WINDOW (widget);
+ TerminalWindowPrivate *priv = window->priv;
+ GtkAllocation widget_allocation;
+
+ gtk_widget_get_allocation (widget, &widget_allocation);
+
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] show, size %d : %d at (%d, %d)\n",
+ widget,
+ widget_allocation.width, widget_allocation.height,
+ widget_allocation.x, widget_allocation.y);
+
+ /* Because of the unexpected reentrancy caused by adding the tab to the notebook
+ * showing the TerminalWindow, we can get here when the first page has been
+ * added but not yet set current. By setting the page current, we get the
+ * right size when we first show the window */
+ if (GTK_IS_NOTEBOOK (priv->mdi_container) &&
+ gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->mdi_container)) == -1)
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->mdi_container), 0);
+
+ if (priv->active_screen != nullptr)
+ {
+ /* At this point, we have our GdkScreen, and hence the right
+ * font size, so we can go ahead and size the window. */
+ terminal_window_update_size (window);
+ }
+
+ GTK_WIDGET_CLASS (terminal_window_parent_class)->show (widget);
+}
+
+TerminalWindow*
+terminal_window_new (GApplication *app)
+{
+ return reinterpret_cast<TerminalWindow*>
+ (g_object_new (TERMINAL_TYPE_WINDOW,
+ "application", app,
+ "show-menubar", FALSE,
+ nullptr));
+}
+
+static void
+profile_set_cb (TerminalScreen *screen,
+ GSettings *old_profile,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (window)))
+ return;
+
+ if (screen != priv->active_screen)
+ return;
+
+ terminal_window_update_set_profile_menu_active_profile (window);
+}
+
+static void
+sync_screen_title (TerminalScreen *screen,
+ GParamSpec *psepc,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ const char *title;
+
+ if (screen != priv->active_screen)
+ return;
+
+ title = terminal_screen_get_title (screen);
+ gtk_window_set_title (GTK_WINDOW (window),
+ title && title[0] ? title : _("Terminal"));
+}
+
+static void
+screen_font_any_changed_cb (TerminalScreen *screen,
+ GParamSpec *psepc,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (window)))
+ return;
+
+ if (screen != priv->active_screen)
+ return;
+
+ terminal_window_update_size (window);
+}
+
+static void
+screen_hyperlink_hover_uri_changed (TerminalScreen *screen,
+ const char *uri,
+ const GdkRectangle *bbox G_GNUC_UNUSED,
+ TerminalWindow *window G_GNUC_UNUSED)
+{
+ gs_free char *label = nullptr;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (screen)))
+ return;
+
+ label = terminal_util_hyperlink_uri_label (uri);
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (screen), label);
+}
+
+/* MDI container callbacks */
+
+static void
+screen_close_request_cb (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ if (confirm_close_window_or_tab (window, screen))
+ return;
+
+ terminal_window_remove_screen (window, screen);
+}
+
+int
+terminal_window_get_active_screen_num (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ return terminal_mdi_container_get_active_screen_num (priv->mdi_container);
+}
+
+void
+terminal_window_add_screen (TerminalWindow *window,
+ TerminalScreen *screen,
+ int position)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GtkWidget *old_window;
+
+ old_window = gtk_widget_get_toplevel (GTK_WIDGET (screen));
+ if (gtk_widget_is_toplevel (old_window) &&
+ TERMINAL_IS_WINDOW (old_window) &&
+ TERMINAL_WINDOW (old_window)== window)
+ return;
+
+ if (TERMINAL_IS_WINDOW (old_window))
+ terminal_window_remove_screen (TERMINAL_WINDOW (old_window), screen);
+
+ if (position == -1) {
+ GSettings *global_settings = terminal_app_get_global_settings (terminal_app_get ());
+ TerminalNewTabPosition position_pref = TerminalNewTabPosition
+ (g_settings_get_enum (global_settings,
+ TERMINAL_SETTING_NEW_TAB_POSITION_KEY));
+ switch (position_pref) {
+ case TERMINAL_NEW_TAB_POSITION_NEXT:
+ position = terminal_window_get_active_screen_num (window) + 1;
+ break;
+
+ default:
+ case TERMINAL_NEW_TAB_POSITION_LAST:
+ position = -1;
+ break;
+ }
+ }
+
+ terminal_mdi_container_add_screen (priv->mdi_container, screen, position);
+}
+
+void
+terminal_window_remove_screen (TerminalWindow *window,
+ TerminalScreen *screen)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ terminal_mdi_container_remove_screen (priv->mdi_container, screen);
+}
+
+void
+terminal_window_move_screen (TerminalWindow *source_window,
+ TerminalWindow *dest_window,
+ TerminalScreen *screen,
+ int dest_position)
+{
+ TerminalScreenContainer *screen_container;
+
+ g_return_if_fail (TERMINAL_IS_WINDOW (source_window));
+ g_return_if_fail (TERMINAL_IS_WINDOW (dest_window));
+ g_return_if_fail (TERMINAL_IS_SCREEN (screen));
+ g_return_if_fail (gtk_widget_get_toplevel (GTK_WIDGET (screen)) == GTK_WIDGET (source_window));
+ g_return_if_fail (dest_position >= -1);
+
+ screen_container = terminal_screen_container_get_from_screen (screen);
+ g_assert (TERMINAL_IS_SCREEN_CONTAINER (screen_container));
+
+ /* We have to ref the screen container as well as the screen,
+ * because otherwise removing the screen container from the source
+ * window's notebook will cause the container and its containing
+ * screen to be gtk_widget_destroy()ed!
+ */
+ g_object_ref_sink (screen_container);
+ g_object_ref_sink (screen);
+ terminal_window_remove_screen (source_window, screen);
+
+ /* Now we can safely remove the screen from the container and let the container die */
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (screen))), GTK_WIDGET (screen));
+ g_object_unref (screen_container);
+
+ terminal_window_add_screen (dest_window, screen, dest_position);
+ terminal_mdi_container_set_active_screen (dest_window->priv->mdi_container, screen);
+ g_object_unref (screen);
+}
+
+GList*
+terminal_window_list_screen_containers (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ return terminal_mdi_container_list_screen_containers (priv->mdi_container);
+}
+
+void
+terminal_window_set_menubar_visible (TerminalWindow *window,
+ gboolean setting)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ if (priv->menubar == nullptr)
+ return;
+
+ /* it's been set now, so don't override when adding a screen.
+ * this side effect must happen before we short-circuit below.
+ */
+ priv->use_default_menubar_visibility = FALSE;
+
+ g_simple_action_set_state (lookup_action (window, "menubar-visible"),
+ g_variant_new_boolean (setting));
+
+ g_object_set (priv->menubar, "visible", setting, nullptr);
+
+ /* FIXMEchpe: use gtk_widget_get_realized instead? */
+ if (priv->active_screen)
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] setting size after toggling menubar visibility\n",
+ window);
+
+ terminal_window_update_size (window);
+ }
+}
+
+GtkWidget *
+terminal_window_get_mdi_container (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ g_return_val_if_fail (TERMINAL_IS_WINDOW (window), nullptr);
+
+ return GTK_WIDGET (priv->mdi_container);
+}
+
+void
+terminal_window_update_size (TerminalWindow *window)
+{
+ auto const priv = window->priv;
+ int grid_width, grid_height;
+ int pixel_width, pixel_height;
+
+ if (priv->realized &&
+ window_state_is_snapped(priv->window_state))
+ {
+ /* Don't adjust the size of maximized or tiled (snapped, half-maximized)
+ * windows: if we do, there will be ugly gaps of up to 1 character cell
+ * around otherwise tiled windows. */
+ return;
+ }
+
+ if (!priv->active_screen)
+ return;
+
+ /* be sure our geometry is up-to-date */
+ terminal_window_update_geometry (window);
+
+ terminal_screen_get_size (priv->active_screen, &grid_width, &grid_height);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] size is %dx%d cells of %dx%d px\n",
+ window, grid_width, grid_height,
+ priv->old_char_width, priv->old_char_height);
+
+ /* the "old" struct members were updated by update_geometry */
+ pixel_width = priv->old_chrome_width + grid_width * priv->old_char_width;
+ pixel_height = priv->old_chrome_height + grid_height * priv->old_char_height;
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] %dx%d + %dx%d = %dx%d\n",
+ window, grid_width * priv->old_char_width,
+ grid_height * priv->old_char_height,
+ priv->old_chrome_width, priv->old_chrome_height,
+ pixel_width, pixel_height);
+
+ gtk_window_resize (GTK_WINDOW (window), pixel_width, pixel_height);
+}
+
+void
+terminal_window_switch_screen (TerminalWindow *window,
+ TerminalScreen *screen)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ terminal_mdi_container_set_active_screen (priv->mdi_container, screen);
+}
+
+TerminalScreen*
+terminal_window_get_active (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ return terminal_mdi_container_get_active_screen (priv->mdi_container);
+}
+
+static void
+notebook_show_context_menu (TerminalWindow *window,
+ GdkEvent *event,
+ guint button,
+ guint32 timestamp)
+{
+ /* Load the UI */
+ gs_unref_object GMenu *menu;
+ terminal_util_load_objects_resource ("/org/gnome/terminal/ui/notebook-menu.ui",
+ "notebook-popup", &menu,
+ nullptr);
+
+ GtkWidget *popup_menu = context_menu_new (G_MENU_MODEL (menu), GTK_WIDGET (window));
+
+ gtk_widget_set_halign (popup_menu, GTK_ALIGN_START);
+ gtk_menu_popup (GTK_MENU (popup_menu), nullptr, nullptr,
+ nullptr, nullptr,
+ button, timestamp);
+
+ if (button == 0)
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (popup_menu), FALSE);
+}
+
+static gboolean
+notebook_button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ TerminalWindow *window)
+{
+ GtkNotebook *notebook = GTK_NOTEBOOK (widget);
+ int tab_clicked;
+
+ if (event->type != GDK_BUTTON_PRESS ||
+ event->button != GDK_BUTTON_SECONDARY ||
+ (event->state & gtk_accelerator_get_default_mod_mask ()) != 0)
+ return FALSE;
+
+ tab_clicked = find_tab_num_at_pos (notebook, event->x_root, event->y_root);
+ if (tab_clicked < 0)
+ return FALSE;
+
+ /* switch to the page the mouse is over */
+ gtk_notebook_set_current_page (notebook, tab_clicked);
+
+ notebook_show_context_menu (window, (GdkEvent*)event, event->button, event->time);
+ return TRUE;
+}
+
+static gboolean
+notebook_popup_menu_cb (GtkWidget *widget,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GtkWidget *focus_widget;
+
+ focus_widget = gtk_window_get_focus (GTK_WINDOW (window));
+ /* Only respond if the notebook is the actual focus */
+ if (focus_widget != GTK_WIDGET (priv->mdi_container))
+ return FALSE;
+
+ notebook_show_context_menu (window, nullptr, 0, gtk_get_current_event_time ());
+ return TRUE;
+}
+
+static void
+mdi_screen_switched_cb (TerminalMdiContainer *container,
+ TerminalScreen *old_active_screen,
+ TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ int old_grid_width, old_grid_height;
+
+ _terminal_debug_print (TERMINAL_DEBUG_MDI,
+ "[window %p] MDI: screen-switched old %p new %p\n",
+ window, old_active_screen, screen);
+
+ if (priv->disposed)
+ return;
+
+ if (screen == nullptr || old_active_screen == screen)
+ return;
+
+ if (priv->search_popover != nullptr)
+ gtk_widget_hide (GTK_WIDGET (priv->search_popover));
+
+ _terminal_debug_print (TERMINAL_DEBUG_MDI,
+ "[window %p] MDI: setting active tab to screen %p (old active screen %p)\n",
+ window, screen, priv->active_screen);
+
+ if (old_active_screen != nullptr && screen != nullptr) {
+ terminal_screen_get_size (old_active_screen, &old_grid_width, &old_grid_height);
+
+ /* This is so that we maintain the same grid */
+ vte_terminal_set_size (VTE_TERMINAL (screen), old_grid_width, old_grid_height);
+ }
+
+ priv->active_screen = screen;
+
+ /* Override menubar setting if it wasn't restored from session */
+ if (priv->use_default_menubar_visibility)
+ {
+ gboolean setting =
+ g_settings_get_boolean (terminal_app_get_global_settings (terminal_app_get ()),
+ TERMINAL_SETTING_DEFAULT_SHOW_MENUBAR_KEY);
+
+ terminal_window_set_menubar_visible (window, setting);
+ }
+
+ sync_screen_title (screen, nullptr, window);
+
+ /* set size of window to current grid size */
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] setting size after flipping notebook pages\n",
+ window);
+ terminal_window_update_size (window);
+
+ terminal_window_update_tabs_actions_sensitivity (window);
+ terminal_window_update_terminal_menu (window);
+ terminal_window_update_set_profile_menu_active_profile (window);
+ terminal_window_update_copy_sensitivity (screen, window);
+ terminal_window_update_zoom_sensitivity (window);
+ terminal_window_update_search_sensitivity (screen, window);
+ terminal_window_update_paste_sensitivity (window);
+}
+
+static void
+mdi_screen_added_cb (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ int pages;
+
+ _terminal_debug_print (TERMINAL_DEBUG_MDI,
+ "[window %p] MDI: screen %p inserted\n",
+ window, screen);
+
+ g_signal_connect (G_OBJECT (screen),
+ "profile-set",
+ G_CALLBACK (profile_set_cb),
+ window);
+
+ /* FIXME: only connect on the active screen, not all screens! */
+ g_signal_connect (screen, "notify::title",
+ G_CALLBACK (sync_screen_title), window);
+ g_signal_connect (screen, "notify::font-desc",
+ G_CALLBACK (screen_font_any_changed_cb), window);
+ g_signal_connect (screen, "notify::font-scale",
+ G_CALLBACK (screen_font_any_changed_cb), window);
+ g_signal_connect (screen, "notify::cell-height-scale",
+ G_CALLBACK (screen_font_any_changed_cb), window);
+ g_signal_connect (screen, "notify::cell-width-scale",
+ G_CALLBACK (screen_font_any_changed_cb), window);
+ g_signal_connect (screen, "selection-changed",
+ G_CALLBACK (terminal_window_update_copy_sensitivity), window);
+ g_signal_connect (screen, "hyperlink-hover-uri-changed",
+ G_CALLBACK (screen_hyperlink_hover_uri_changed), window);
+
+ g_signal_connect (screen, "show-popup-menu",
+ G_CALLBACK (screen_show_popup_menu_cb), window);
+ g_signal_connect (screen, "match-clicked",
+ G_CALLBACK (screen_match_clicked_cb), window);
+ g_signal_connect (screen, "resize-window",
+ G_CALLBACK (screen_resize_window_cb), window);
+
+ g_signal_connect (screen, "close-screen",
+ G_CALLBACK (screen_close_cb), window);
+
+ terminal_window_update_tabs_actions_sensitivity (window);
+ terminal_window_update_search_sensitivity (screen, window);
+ terminal_window_update_paste_sensitivity (window);
+
+#if 0
+ /* FIXMEchpe: wtf is this doing? */
+
+ /* If we have an active screen, match its size and zoom */
+ if (priv->active_screen)
+ {
+ int current_width, current_height;
+ double scale;
+
+ terminal_screen_get_size (priv->active_screen, &current_width, &current_height);
+ vte_terminal_set_size (VTE_TERMINAL (screen), current_width, current_height);
+
+ scale = terminal_screen_get_font_scale (priv->active_screen);
+ terminal_screen_set_font_scale (screen, scale);
+ }
+#endif
+
+ if (priv->present_on_insert)
+ {
+ gtk_window_present_with_time (GTK_WINDOW (window), gtk_get_current_event_time ());
+ priv->present_on_insert = FALSE;
+ }
+
+ pages = terminal_mdi_container_get_n_screens (container);
+ if (pages == 2)
+ {
+ terminal_window_update_size (window);
+ }
+}
+
+static void
+mdi_screen_removed_cb (TerminalMdiContainer *container,
+ TerminalScreen *screen,
+ TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ int pages;
+
+ if (priv->disposed)
+ return;
+
+ _terminal_debug_print (TERMINAL_DEBUG_MDI,
+ "[window %p] MDI: screen %p removed\n",
+ window, screen);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (screen),
+ (void*)profile_set_cb,
+ window);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (screen),
+ (void*)sync_screen_title,
+ window);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (screen),
+ (void*)screen_font_any_changed_cb,
+ window);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (screen),
+ (void*)terminal_window_update_copy_sensitivity,
+ window);
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (screen),
+ (void*)screen_hyperlink_hover_uri_changed,
+ window);
+
+ g_signal_handlers_disconnect_by_func (screen,
+ (void*)screen_show_popup_menu_cb,
+ window);
+
+ g_signal_handlers_disconnect_by_func (screen,
+ (void*)screen_match_clicked_cb,
+ window);
+ g_signal_handlers_disconnect_by_func (screen,
+ (void*)screen_resize_window_cb,
+ window);
+
+ g_signal_handlers_disconnect_by_func (screen,
+ (void*)screen_close_cb,
+ window);
+
+ /* We already got a switch-page signal whose handler sets the active tab to the
+ * new active tab, unless this screen was the only one in the notebook, so
+ * priv->active_tab is valid here.
+ */
+
+ pages = terminal_mdi_container_get_n_screens (container);
+ if (pages == 0)
+ {
+ priv->active_screen = nullptr;
+
+ /* That was the last tab in the window; close it. */
+ gtk_widget_destroy (GTK_WIDGET (window));
+ return;
+ }
+
+ terminal_window_update_tabs_actions_sensitivity (window);
+ terminal_window_update_search_sensitivity (screen, window);
+
+ if (pages == 1)
+ {
+ TerminalScreen *active_screen = terminal_mdi_container_get_active_screen (container);
+ gtk_widget_grab_focus (GTK_WIDGET(active_screen)); /* bug 742422 */
+
+ terminal_window_update_size (window);
+ }
+}
+
+static void
+mdi_screens_reordered_cb (TerminalMdiContainer *container,
+ TerminalWindow *window)
+{
+ terminal_window_update_tabs_actions_sensitivity (window);
+}
+
+gboolean
+terminal_window_parse_geometry (TerminalWindow *window,
+ const char *geometry)
+{
+ TerminalWindowPrivate *priv = window->priv;
+
+ /* gtk_window_parse_geometry() needs to have the right base size
+ * and width/height increment to compute the window size from
+ * the geometry.
+ */
+ terminal_window_update_geometry (window);
+
+ if (!gtk_window_parse_geometry (GTK_WINDOW (window), geometry))
+ return FALSE;
+
+ /* We won't actually get allocated at the size parsed out of the
+ * geometry until the window is shown. If terminal_window_update_size()
+ * is called between now and then, that could result in us getting
+ * snapped back to the old grid size. So we need to immediately
+ * update the size of the active terminal to grid size from the
+ * geometry.
+ */
+ if (priv->active_screen)
+ {
+ int grid_width, grid_height;
+
+ /* After parse_geometry(), the default size is in units of the
+ * width/height increment, not a pixel size */
+ gtk_window_get_default_size (GTK_WINDOW (window), &grid_width, &grid_height);
+
+ vte_terminal_set_size (VTE_TERMINAL (priv->active_screen),
+ grid_width, grid_height);
+ }
+
+ return TRUE;
+}
+
+void
+terminal_window_update_geometry (TerminalWindow *window)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GtkWidget *widget;
+ GdkGeometry *hints = &priv->hints;
+ GtkBorder padding;
+ GtkRequisition vbox_request, widget_request;
+ int grid_width, grid_height;
+ int char_width, char_height;
+ int chrome_width, chrome_height;
+ int csd_width = 0, csd_height = 0;
+
+ if (gtk_widget_in_destruction (GTK_WIDGET (window)))
+ return;
+
+ if (priv->active_screen == nullptr)
+ return;
+
+ widget = GTK_WIDGET (priv->active_screen);
+
+ /* We set geometry hints from the active term; best thing
+ * I can think of to do. Other option would be to try to
+ * get some kind of union of all hints from all terms in the
+ * window, but that doesn't make too much sense.
+ */
+ terminal_screen_get_cell_size (priv->active_screen, &char_width, &char_height);
+
+ terminal_screen_get_size (priv->active_screen, &grid_width, &grid_height);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "%dx%d cells of %dx%d px = %dx%d px\n",
+ grid_width, grid_height, char_width, char_height,
+ char_width * grid_width, char_height * grid_height);
+
+ gtk_style_context_get_padding(gtk_widget_get_style_context(widget),
+ gtk_widget_get_state_flags(widget),
+ &padding);
+
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "padding = %dx%d px\n",
+ padding.left + padding.right,
+ padding.top + padding.bottom);
+
+ gtk_widget_get_preferred_size (priv->main_vbox, nullptr, &vbox_request);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "content area requests %dx%d px\n",
+ vbox_request.width, vbox_request.height);
+
+
+ chrome_width = vbox_request.width - (char_width * grid_width);
+ chrome_height = vbox_request.height - (char_height * grid_height);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "chrome: %dx%d px\n",
+ chrome_width, chrome_height);
+
+ if (priv->realized)
+ {
+ /* Only when having been realize the CSD can be calculated. Do this by
+ * using the actual allocation rather then the preferred size as the
+ * the preferred size takes the natural size of e.g. the title bar into
+ * account which can be far wider then the contents size when using a
+ * very long title */
+ GtkAllocation toplevel_allocation, vbox_allocation;
+
+ gtk_widget_get_allocation (GTK_WIDGET (priv->main_vbox), &vbox_allocation);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "terminal widget allocation %dx%d px\n",
+ vbox_allocation.width, vbox_allocation.height);
+
+ gtk_widget_get_allocation (GTK_WIDGET (window), &toplevel_allocation);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "window allocation %dx%d px\n",
+ toplevel_allocation.width, toplevel_allocation.height);
+
+ csd_width = toplevel_allocation.width - vbox_allocation.width;
+ csd_height = toplevel_allocation.height - vbox_allocation.height;
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "CSDs: %dx%d px\n",
+ csd_width, csd_height);
+ }
+
+ gtk_widget_get_preferred_size (widget, nullptr, &widget_request);
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "terminal widget requests %dx%d px\n",
+ widget_request.width, widget_request.height);
+
+ if (!priv->realized)
+ {
+ /* Don't actually set the geometry hints until we have been realized,
+ * because we don't know how large the client-side decorations are going
+ * to be. We also avoid setting priv->old_csd_width or
+ * priv->old_csd_height, so that next time through this function we'll
+ * definitely recalculate the hints.
+ *
+ * Similarly, the size request doesn't seem to include the padding
+ * until we've been redrawn at least once. Don't resize the window
+ * until we've done that. */
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY, "not realized yet\n");
+ }
+ else if (window_state_is_snapped(priv->window_state))
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "Not applying geometry in snapped state\n");
+ }
+ else if (char_width != priv->old_char_width ||
+ char_height != priv->old_char_height ||
+ padding.left + padding.right != priv->old_padding_width ||
+ padding.top + padding.bottom != priv->old_padding_height ||
+ chrome_width != priv->old_chrome_width ||
+ chrome_height != priv->old_chrome_height ||
+ csd_width != priv->old_csd_width ||
+ csd_height != priv->old_csd_height ||
+ widget != (GtkWidget*) priv->old_geometry_widget)
+ {
+ hints->base_width = chrome_width + csd_width;
+ hints->base_height = chrome_height + csd_height;
+
+ hints->width_inc = char_width;
+ hints->height_inc = char_height;
+
+ /* min size is min size of the whole window, remember. */
+ hints->min_width = hints->base_width + hints->width_inc * MIN_WIDTH_CHARS;
+ hints->min_height = hints->base_height + hints->height_inc * MIN_HEIGHT_CHARS;
+
+ gtk_window_set_geometry_hints (GTK_WINDOW (window),
+ nullptr,
+ hints,
+ GdkWindowHints(GDK_HINT_RESIZE_INC |
+ GDK_HINT_MIN_SIZE |
+ GDK_HINT_BASE_SIZE));
+
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] hints: base %dx%d min %dx%d inc %d %d\n",
+ window,
+ hints->base_width,
+ hints->base_height,
+ hints->min_width,
+ hints->min_height,
+ hints->width_inc,
+ hints->height_inc);
+
+ priv->old_csd_width = csd_width;
+ priv->old_csd_height = csd_height;
+ priv->old_geometry_widget = widget;
+ }
+ else
+ {
+ _terminal_debug_print (TERMINAL_DEBUG_GEOMETRY,
+ "[window %p] hints: increment unchanged, not setting\n",
+ window);
+ }
+
+ /* We need these for the size calculation in terminal_window_update_size()
+ * (at least under GTK >= 3.19), so we set them unconditionally. */
+ priv->old_char_width = char_width;
+ priv->old_char_height = char_height;
+ priv->old_chrome_width = chrome_width;
+ priv->old_chrome_height = chrome_height;
+ priv->old_padding_width = padding.left + padding.right;
+ priv->old_padding_height = padding.top + padding.bottom;
+}
+
+static void
+confirm_close_response_cb (GtkWidget *dialog,
+ int response,
+ TerminalWindow *window)
+{
+ TerminalScreen *screen;
+
+ screen = (TerminalScreen*)g_object_get_data (G_OBJECT (dialog), "close-screen");
+
+ gtk_widget_destroy (dialog);
+
+ if (response != GTK_RESPONSE_ACCEPT)
+ return;
+
+ if (screen)
+ terminal_window_remove_screen (window, screen);
+ else
+ gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+/* Returns: TRUE if closing needs to wait until user confirmation;
+ * FALSE if the terminal or window can close immediately.
+ */
+static gboolean
+confirm_close_window_or_tab (TerminalWindow *window,
+ TerminalScreen *screen)
+{
+ TerminalWindowPrivate *priv = window->priv;
+ GtkWidget *dialog;
+ gboolean do_confirm;
+ int n_tabs;
+
+ if (priv->confirm_close_dialog)
+ {
+ /* WTF, already have one? It's modal, so how did that happen? */
+ gtk_dialog_response (GTK_DIALOG (priv->confirm_close_dialog),
+ GTK_RESPONSE_DELETE_EVENT);
+ }
+
+ do_confirm = g_settings_get_boolean (terminal_app_get_global_settings (terminal_app_get ()),
+ TERMINAL_SETTING_CONFIRM_CLOSE_KEY);
+ if (!do_confirm)
+ return FALSE;
+
+ if (screen)
+ {
+ do_confirm = terminal_screen_has_foreground_process (screen, nullptr, nullptr);
+ n_tabs = 1;
+ }
+ else
+ {
+ GList *tabs, *t;
+
+ do_confirm = FALSE;
+
+ tabs = terminal_window_list_screen_containers (window);
+ n_tabs = g_list_length (tabs);
+
+ for (t = tabs; t != nullptr; t = t->next)
+ {
+ TerminalScreen *terminal_screen;
+
+ terminal_screen = terminal_screen_container_get_screen (TERMINAL_SCREEN_CONTAINER (t->data));
+ if (terminal_screen_has_foreground_process (terminal_screen, nullptr, nullptr))
+ {
+ do_confirm = TRUE;
+ break;
+ }
+ }
+ g_list_free (tabs);
+ }
+
+ if (!do_confirm)
+ return FALSE;
+
+ dialog = priv->confirm_close_dialog =
+ gtk_message_dialog_new (GTK_WINDOW (window),
+ GtkDialogFlags(GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT),
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_CANCEL,
+ "%s", n_tabs > 1 ? _("Close this window?") : _("Close this terminal?"));
+
+ if (n_tabs > 1)
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", _("There are still processes running in some terminals in this window. "
+ "Closing the window will kill all of them."));
+ else
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", _("There is still a process running in this terminal. "
+ "Closing the terminal will kill it."));
+
+ gtk_window_set_title (GTK_WINDOW (dialog), "");
+
+ GtkWidget *remove_button = gtk_dialog_add_button (GTK_DIALOG (dialog), n_tabs > 1 ? _("C_lose Window") : _("C_lose Terminal"), GTK_RESPONSE_ACCEPT);
+ gtk_style_context_add_class (gtk_widget_get_style_context (remove_button), "destructive-action");
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+
+ g_object_set_data (G_OBJECT (dialog), "close-screen", screen);
+
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &priv->confirm_close_dialog);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (confirm_close_response_cb), window);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ return TRUE;
+}
+
+void
+terminal_window_request_close (TerminalWindow *window)
+{
+ g_return_if_fail (TERMINAL_IS_WINDOW (window));
+
+ if (confirm_close_window_or_tab (window, nullptr))
+ return;
+
+ gtk_widget_destroy (GTK_WIDGET (window));
+}
+
+const char *
+terminal_window_get_uuid (TerminalWindow *window)
+{
+ g_return_val_if_fail (TERMINAL_IS_WINDOW (window), nullptr);
+
+ return window->priv->uuid;
+}
diff --git a/src/terminal-window.hh b/src/terminal-window.hh
new file mode 100644
index 0000000..e6659aa
--- /dev/null
+++ b/src/terminal-window.hh
@@ -0,0 +1,97 @@
+/*
+ * Copyright © 2001 Havoc Pennington
+ *
+ * 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/>.
+ */
+
+#ifndef TERMINAL_WINDOW_H
+#define TERMINAL_WINDOW_H
+
+#include <gtk/gtk.h>
+
+#include "terminal-screen.hh"
+
+G_BEGIN_DECLS
+
+#define TERMINAL_TYPE_WINDOW (terminal_window_get_type ())
+#define TERMINAL_WINDOW(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TERMINAL_TYPE_WINDOW, TerminalWindow))
+#define TERMINAL_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TERMINAL_TYPE_WINDOW, TerminalWindowClass))
+#define TERMINAL_IS_WINDOW(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TERMINAL_TYPE_WINDOW))
+#define TERMINAL_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TERMINAL_TYPE_WINDOW))
+#define TERMINAL_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TERMINAL_TYPE_WINDOW, TerminalWindowClass))
+
+typedef struct _TerminalWindowClass TerminalWindowClass;
+typedef struct _TerminalWindowPrivate TerminalWindowPrivate;
+
+struct _TerminalWindow
+{
+ GtkApplicationWindow parent_instance;
+
+ TerminalWindowPrivate *priv;
+};
+
+struct _TerminalWindowClass
+{
+ GtkApplicationWindowClass parent_class;
+};
+
+GType terminal_window_get_type (void) G_GNUC_CONST;
+
+TerminalWindow* terminal_window_new (GApplication *app);
+
+GtkUIManager *terminal_window_get_ui_manager (TerminalWindow *window);
+
+int terminal_window_get_active_screen_num (TerminalWindow *window);
+
+void terminal_window_add_screen (TerminalWindow *window,
+ TerminalScreen *screen,
+ int position);
+
+void terminal_window_remove_screen (TerminalWindow *window,
+ TerminalScreen *screen);
+
+void terminal_window_move_screen (TerminalWindow *source_window,
+ TerminalWindow *dest_window,
+ TerminalScreen *screen,
+ int dest_position);
+
+/* Menubar visibility is part of session state, except that
+ * if it isn't restored from session, the window gets the setting
+ * from the profile of the first screen added to the window
+ */
+void terminal_window_set_menubar_visible (TerminalWindow *window,
+ gboolean setting);
+
+void terminal_window_update_size (TerminalWindow *window);
+
+void terminal_window_switch_screen (TerminalWindow *window,
+ TerminalScreen *screen);
+TerminalScreen* terminal_window_get_active (TerminalWindow *window);
+
+GList* terminal_window_list_screen_containers (TerminalWindow *window);
+
+gboolean terminal_window_parse_geometry (TerminalWindow *window,
+ const char *geometry);
+
+void terminal_window_update_geometry (TerminalWindow *window);
+
+GtkWidget* terminal_window_get_mdi_container (TerminalWindow *window);
+
+void terminal_window_request_close (TerminalWindow *window);
+
+const char *terminal_window_get_uuid (TerminalWindow *window);
+
+G_END_DECLS
+
+#endif /* TERMINAL_WINDOW_H */
diff --git a/src/terminal-window.ui b/src/terminal-window.ui
new file mode 100644
index 0000000..b0f63fd
--- /dev/null
+++ b/src/terminal-window.ui
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <template class="TerminalWindow" parent="GtkApplicationWindow">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="main_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/terminal.about b/src/terminal.about
new file mode 100644
index 0000000..3b097d5
--- /dev/null
+++ b/src/terminal.about
@@ -0,0 +1,5 @@
+[About]
+Authors=Behdad Esfahbod;Egmont Koblinger;Guilherme de S. Pastore;Havoc Pennington;Christian Persch;Mariano Suárez-Alvarez;
+Contributors=
+;Artists=
+Documenters=GNOME Documentation Team <gnome-doc-list%gnome.org>;
diff --git a/src/terminal.cc b/src/terminal.cc
new file mode 100644
index 0000000..520b104
--- /dev/null
+++ b/src/terminal.cc
@@ -0,0 +1,629 @@
+/*
+ * Copyright © 2001, 2002 Havoc Pennington
+ * Copyright © 2002 Red Hat, Inc.
+ * Copyright © 2002 Sun Microsystems
+ * Copyright © 2003 Mariano Suarez-Alvarez
+ * Copyright © 2008, 2010, 2011 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#include "terminal-debug.hh"
+#include "terminal-defines.hh"
+#include "terminal-i18n.hh"
+#include "terminal-options.hh"
+#include "terminal-gdbus-generated.h"
+#include "terminal-defines.hh"
+#include "terminal-client-utils.hh"
+#include "terminal-libgsystem.hh"
+
+GS_DEFINE_CLEANUP_FUNCTION0(TerminalOptions*, gs_local_options_free, terminal_options_free)
+#define gs_free_options __attribute__ ((cleanup(gs_local_options_free)))
+
+/* Wait-for-exit helper */
+
+typedef struct {
+ GMainLoop *loop;
+ int status;
+} RunData;
+
+static void
+receiver_child_exited_cb (TerminalReceiver *receiver,
+ int status,
+ RunData *data)
+{
+ data->status = status;
+
+ if (g_main_loop_is_running (data->loop))
+ g_main_loop_quit (data->loop);
+}
+
+static void
+factory_name_owner_notify_cb (TerminalFactory *factory,
+ GParamSpec *pspec,
+ RunData *data)
+{
+ /* Name owner change to nullptr can only mean that the server
+ * went away before it could send out our child-exited signal.
+ * Assume the server was killed and thus our child process
+ * too, and return with the corresponding exit code.
+ */
+ if (g_dbus_proxy_get_name_owner(G_DBUS_PROXY (factory)) != nullptr)
+ return;
+
+ data->status = W_EXITCODE(0, SIGKILL);
+
+ if (g_main_loop_is_running (data->loop))
+ g_main_loop_quit (data->loop);
+}
+
+static int
+run_receiver (TerminalFactory *factory,
+ TerminalReceiver *receiver)
+{
+ RunData data = { g_main_loop_new (nullptr, FALSE), 0 };
+ gulong receiver_exited_id = g_signal_connect (receiver, "child-exited",
+ G_CALLBACK (receiver_child_exited_cb), &data);
+ gulong factory_notify_id = g_signal_connect (factory, "notify::g-name-owner",
+ G_CALLBACK (factory_name_owner_notify_cb), &data);
+ g_main_loop_run (data.loop);
+ g_signal_handler_disconnect (receiver, receiver_exited_id);
+ g_signal_handler_disconnect (factory, factory_notify_id);
+ g_main_loop_unref (data.loop);
+
+ /* Mangle the exit status */
+ int exit_code;
+ if (WIFEXITED (data.status))
+ exit_code = WEXITSTATUS (data.status);
+ else if (WIFSIGNALED (data.status))
+ exit_code = 128 + (int) WTERMSIG (data.status);
+ else if (WCOREDUMP (data.status))
+ exit_code = 127;
+ else
+ exit_code = 127;
+
+ return exit_code;
+}
+
+/* Factory helpers */
+
+static gboolean
+get_factory_exit_status (const char *service_name,
+ const char *message,
+ int *exit_status)
+{
+ gs_free char *pattern = nullptr, *number = nullptr;
+ gs_unref_regex GRegex *regex = nullptr;
+ gs_free_match_info GMatchInfo *match_info = nullptr;
+ gint64 v;
+ char *end;
+ GError *err = nullptr;
+
+ pattern = g_strdup_printf ("org.freedesktop.DBus.Error.Spawn.ChildExited: Process %s exited with status (\\d+)$",
+ service_name);
+ regex = g_regex_new (pattern, GRegexCompileFlags(0), GRegexMatchFlags(0), &err);
+ g_assert_no_error (err);
+
+ if (!g_regex_match (regex, message, GRegexMatchFlags(0), &match_info))
+ return FALSE;
+
+ number = g_match_info_fetch (match_info, 1);
+ g_assert_nonnull (number);
+
+ errno = 0;
+ v = g_ascii_strtoll (number, &end, 10);
+ if (errno || end == number || *end != '\0' || v < 0 || v > G_MAXINT)
+ return FALSE;
+
+ *exit_status = (int)v;
+ return TRUE;
+}
+
+static gboolean
+handle_factory_error (const char *service_name,
+ GError *error)
+{
+ int exit_status;
+
+ if (!g_dbus_error_is_remote_error (error) ||
+ !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_CHILD_EXITED) ||
+ !get_factory_exit_status (service_name, error->message, &exit_status))
+ return FALSE;
+
+ g_dbus_error_strip_remote_error (error);
+ terminal_printerr ("%s\n\n", error->message);
+
+ switch (exit_status) {
+ case _EXIT_FAILURE_WRONG_ID:
+ terminal_printerr ("You tried to run gnome-terminal-server with elevated privileged. This is not supported.\n");
+ break;
+ case _EXIT_FAILURE_NO_UTF8:
+ terminal_printerr ("The environment that gnome-terminal-server was launched with specified a non-UTF-8 locale. This is not supported.\n");
+ break;
+ case _EXIT_FAILURE_UNSUPPORTED_LOCALE:
+ terminal_printerr ("The environment that gnome-terminal-server was launched with specified an unsupported locale.\n");
+ break;
+ case _EXIT_FAILURE_GTK_INIT:
+ terminal_printerr ("The environment that gnome-terminal-server was launched with most likely contained an incorrect or unset \"DISPLAY\" variable.\n");
+ break;
+ default:
+ break;
+ }
+ terminal_printerr ("See https://wiki.gnome.org/Apps/Terminal/FAQ#Exit_status_%d for more information.\n", exit_status);
+
+ return TRUE;
+}
+
+static gboolean
+handle_create_instance_error (const char *service_name,
+ GError *error)
+{
+ if (handle_factory_error (service_name, error))
+ return TRUE;
+
+ g_dbus_error_strip_remote_error (error);
+ terminal_printerr ("Error creating terminal: %s\n", error->message);
+ return FALSE; /* don't abort */
+}
+
+static gboolean
+handle_create_receiver_proxy_error (const char *service_name,
+ GError *error)
+{
+ if (handle_factory_error (service_name, error))
+ return TRUE;
+
+ g_dbus_error_strip_remote_error (error);
+ terminal_printerr ("Failed to create proxy for terminal: %s\n", error->message);
+ return FALSE; /* don't abort */
+}
+
+static gboolean
+handle_exec_error (const char *service_name,
+ GError *error)
+{
+ if (handle_factory_error (service_name, error))
+ return TRUE;
+
+ g_dbus_error_strip_remote_error (error);
+ terminal_printerr ("Error: %s\n", error->message);
+ return FALSE; /* don't abort */
+}
+
+static gboolean
+factory_proxy_new_for_service_name (const char *service_name,
+ gboolean ping_server,
+ gboolean connect_signals,
+ TerminalFactory **factory_ptr,
+ char **service_name_ptr,
+ GError **error)
+{
+ if (service_name == nullptr)
+ service_name = TERMINAL_APPLICATION_ID;
+
+ gs_free_error GError *err = nullptr;
+ gs_unref_object TerminalFactory *factory =
+ terminal_factory_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ connect_signals ? 0 : G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
+ service_name,
+ TERMINAL_FACTORY_OBJECT_PATH,
+ nullptr /* cancellable */,
+ &err);
+ if (factory == nullptr) {
+ if (!handle_factory_error (service_name, err))
+ terminal_printerr ("Error constructing proxy for %s:%s: %s\n",
+ service_name, TERMINAL_FACTORY_OBJECT_PATH, err->message);
+ g_propagate_error (error, err);
+ err = nullptr;
+ return FALSE;
+ }
+
+ if (ping_server) {
+ /* If we try to use the environment specified server, we need to make
+ * sure it actually exists so we can later fall back to the default name.
+ * There doesn't appear to a way to fail proxy creation above if the
+ * unique name doesn't exist; so we do it this way.
+ */
+ gs_unref_variant GVariant *v = g_dbus_proxy_call_sync (G_DBUS_PROXY (factory),
+ "org.freedesktop.DBus.Peer.Ping",
+ g_variant_new ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ 1000 /* 1s */,
+ nullptr /* cancelleable */,
+ &err);
+ if (v == nullptr) {
+ g_propagate_error (error, err);
+ err = nullptr;
+ return FALSE;
+ }
+ }
+
+ gs_transfer_out_value (factory_ptr, &factory);
+ *service_name_ptr = g_strdup (service_name);
+ return TRUE;
+}
+
+static gboolean
+factory_proxy_new (TerminalOptions *options,
+ TerminalFactory **factory_ptr,
+ char **service_name_ptr,
+ char **parent_screen_object_path_ptr,
+ GError **error)
+{
+ const char *service_name = options->server_app_id;
+
+ /* If --app-id was specified, or the environment does not specify
+ * the server to use, create the factory proxy from the given (or default)
+ * name, with no fallback.
+ *
+ * If the server specified by the environment doesn't exist, fall back to the
+ * default server, and ignore the environment-specified parent screen.
+ */
+ if (options->server_app_id == nullptr &&
+ options->server_unique_name != nullptr) {
+ gs_free_error GError *err = nullptr;
+ if (factory_proxy_new_for_service_name (options->server_unique_name,
+ TRUE,
+ options->wait,
+ factory_ptr,
+ service_name_ptr,
+ &err)) {
+ *parent_screen_object_path_ptr = g_strdup (options->parent_screen_object_path);
+ return TRUE;
+ }
+
+ terminal_printerr ("Failed to use specified server: %s\n",
+ err->message);
+ terminal_printerr ("Falling back to default server.\n");
+
+ /* Fall back to the default */
+ service_name = nullptr;
+ }
+
+ *parent_screen_object_path_ptr = nullptr;
+
+ return factory_proxy_new_for_service_name (service_name,
+ FALSE,
+ options->wait,
+ factory_ptr,
+ service_name_ptr,
+ error);
+}
+
+static bool
+handle_show_preferences_remote (TerminalOptions *options,
+ const char *service_name)
+{
+ gs_free_error GError *error = nullptr;
+ gs_unref_object GDBusConnection *bus = nullptr;
+ gs_free char *object_path = nullptr;
+ GVariantBuilder builder;
+
+ /* For reasons (!?), the org.gtk.Actions interface's object path
+ * is derived from the service name, i.e. for service name
+ * "foo.bar.baz" the object path is "/foo/bar/baz".
+ * This means that without the name (like when given only the unique name),
+ * we cannot activate the action.
+ */
+ if (!service_name ||
+ g_dbus_is_unique_name(service_name)) {
+ return false;
+ }
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, &error);
+ if (bus == nullptr) {
+ terminal_printerr ("Failed to get session bus: %s\n", error->message);
+ return true;
+ }
+
+ object_path = g_strdelimit (g_strdup_printf (".%s", service_name), ".", '/');
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sava{sv})"));
+ g_variant_builder_add (&builder, "s", "preferences");
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
+ g_variant_builder_close (&builder);
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+ if (options->startup_id)
+ g_variant_builder_add (&builder, "{sv}",
+ "desktop-startup-id", g_variant_new_string (options->startup_id));
+ if (options->activation_token)
+ g_variant_builder_add (&builder, "{sv}",
+ "activation-token", g_variant_new_string (options->activation_token));
+ g_variant_builder_close (&builder);
+
+ if (!g_dbus_connection_call_sync (bus,
+ service_name,
+ object_path,
+ "org.gtk.Actions",
+ "Activate",
+ g_variant_builder_end (&builder),
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ 30 * 1000 /* ms timeout */,
+ nullptr /* cancelleable */,
+ &error)) {
+ terminal_printerr ("Activate call failed: %s\n", error->message);
+ return true;
+ }
+
+ return true;
+}
+
+static void
+handle_show_preferences(TerminalOptions *options,
+ const char *service_name)
+{
+ // First try remoting to the specified server
+ if (handle_show_preferences_remote(options, service_name))
+ return;
+
+ // If that isn't possible, launch the prefs binary directly
+ auto launcher = g_subprocess_launcher_new(GSubprocessFlags(0));
+ gs_free auto exe = terminal_client_get_file_uninstalled(TERM_BINDIR,
+ TERM_LIBEXECDIR,
+ TERMINAL_PREFERENCES_BINARY_NAME,
+ G_FILE_TEST_IS_EXECUTABLE);
+ char *argv[2] = {exe, nullptr};
+
+ gs_free_error GError* error = nullptr;
+ if (!g_subprocess_launcher_spawnv(launcher, argv, &error)) {
+ terminal_printerr ("Failed to launch preferences: %s\n", error->message);
+ }
+}
+
+/**
+ * handle_options:
+ * @app:
+ * @options: a #TerminalOptions
+ * @allow_resume: whether to merge the terminal configuration from the
+ * saved session on resume
+ * @wait_for_receiver: location to store the #TerminalReceiver to wait for
+ *
+ * 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
+ */
+static gboolean
+handle_options (TerminalOptions *options,
+ TerminalFactory *factory,
+ const char *service_name,
+ const char *parent_screen_object_path,
+ TerminalReceiver **wait_for_receiver)
+{
+ /* We need to forward the locale encoding to the server, see bug #732128 */
+ const char *encoding;
+ g_get_charset (&encoding);
+
+ if (options->show_preferences) {
+ handle_show_preferences (options, service_name);
+ } else {
+ /* Make sure we open at least one window */
+ terminal_options_ensure_window (options);
+ }
+
+ const char *factory_unique_name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (factory));
+
+ for (GList *lw = options->initial_windows; lw != nullptr; lw = lw->next)
+ {
+ InitialWindow *iw = (InitialWindow*)lw->data;
+
+ g_assert_nonnull (iw);
+
+ guint window_id = 0;
+
+ gs_free char *previous_screen_object_path = nullptr;
+ if (iw->implicit_first_window)
+ previous_screen_object_path = g_strdup (parent_screen_object_path);
+
+ /* Now add the tabs */
+ for (GList *lt = iw->tabs; lt != nullptr; lt = lt->next)
+ {
+ InitialTab *it = (InitialTab*)lt->data;
+ g_assert_nonnull (it);
+
+ GVariantBuilder builder;
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ terminal_client_append_create_instance_options (&builder,
+ options->display_name,
+ options->startup_id,
+ options->activation_token,
+ iw->geometry,
+ iw->role,
+ it->profile ? it->profile : options->default_profile,
+ encoding,
+ it->title ? it->title : options->default_title,
+ it->active,
+ iw->start_maximized,
+ iw->start_fullscreen);
+
+ /* This will be used to apply missing defaults */
+ if (parent_screen_object_path != nullptr)
+ g_variant_builder_add (&builder, "{sv}",
+ "parent-screen", g_variant_new_object_path (parent_screen_object_path));
+
+ /* This will be used to get the parent window */
+ if (previous_screen_object_path)
+ g_variant_builder_add (&builder, "{sv}",
+ "window-from-screen", g_variant_new_object_path (previous_screen_object_path));
+ if (window_id)
+ g_variant_builder_add (&builder, "{sv}",
+ "window-id", g_variant_new_uint32 (window_id));
+ /* Restored windows shouldn't demand attention; see bug #586308. */
+ if (iw->source_tag == SOURCE_SESSION)
+ g_variant_builder_add (&builder, "{sv}",
+ "present-window", g_variant_new_boolean (FALSE));
+ if (options->zoom_set || it->zoom_set)
+ g_variant_builder_add (&builder, "{sv}",
+ "zoom", g_variant_new_double (it->zoom_set ? it->zoom : options->zoom));
+ if (iw->force_menubar_state)
+ g_variant_builder_add (&builder, "{sv}",
+ "show-menubar", g_variant_new_boolean (iw->menubar_state));
+
+ gs_free_error GError *err = nullptr;
+ gs_free char *object_path = nullptr;
+ if (!terminal_factory_call_create_instance_sync
+ (factory,
+ g_variant_builder_end (&builder),
+ &object_path,
+ nullptr /* cancellable */,
+ &err)) {
+ if (handle_create_instance_error (service_name, err))
+ return FALSE;
+ else
+ continue; /* Continue processing the remaining options! */
+ }
+
+ /* Deprecated and not working on new server anymore */
+ char *p = strstr (object_path, "/window/");
+ if (p) {
+ char *end = nullptr;
+ guint64 value;
+
+ errno = 0;
+ p += strlen ("/window/");
+ value = g_ascii_strtoull (p, &end, 10);
+ if (errno == 0 && end != p && *end == '/')
+ window_id = (guint) value;
+ }
+
+ g_free (previous_screen_object_path);
+ previous_screen_object_path = g_strdup (object_path);
+
+ gs_unref_object TerminalReceiver *receiver =
+ terminal_receiver_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ (it->wait ? 0 : G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS)),
+ factory_unique_name,
+ object_path,
+ nullptr /* cancellable */,
+ &err);
+ if (receiver == nullptr) {
+ if (handle_create_receiver_proxy_error (service_name, err))
+ return FALSE;
+ else
+ continue; /* Continue processing the remaining options! */
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ char **argv = it->exec_argv ? it->exec_argv : options->exec_argv;
+ int argc = argv ? g_strv_length (argv) : 0;
+
+ PassFdElement *fd_array = it->fd_array ? (PassFdElement*)it->fd_array->data : nullptr;
+ gsize fd_array_len = it->fd_array ? it->fd_array->len : 0;
+
+ terminal_client_append_exec_options (&builder,
+ !options->no_environment,
+ it->working_dir ? it->working_dir
+ : options->default_working_dir,
+ fd_array, fd_array_len,
+ argc == 0);
+
+ if (!terminal_receiver_call_exec_sync (receiver,
+ g_variant_builder_end (&builder),
+ g_variant_new_bytestring_array ((const char * const *) argv, argc),
+ it->fd_list, nullptr /* outfdlist */,
+ nullptr /* cancellable */,
+ &err)) {
+ if (handle_exec_error (service_name, err))
+ return FALSE;
+ else
+ continue; /* Continue processing the remaining options! */
+ }
+
+ if (it->wait)
+ gs_transfer_out_value (wait_for_receiver, &receiver);
+
+ if (options->print_environment)
+ g_print ("%s=%s\n", TERMINAL_ENV_SCREEN, object_path);
+ }
+ }
+
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ int exit_code = EXIT_FAILURE;
+
+ g_log_set_writer_func (terminal_log_writer, nullptr, nullptr);
+
+ g_set_prgname ("gnome-terminal");
+
+ setlocale (LC_ALL, "");
+
+ terminal_i18n_init (TRUE);
+
+ _terminal_debug_init ();
+
+ gs_free_error GError *error = nullptr;
+ gs_free_options TerminalOptions *options = terminal_options_parse (&argc, &argv, &error);
+ if (options == nullptr) {
+ terminal_printerr (_("Failed to parse arguments: %s\n"), error->message);
+ return exit_code;
+ }
+
+ g_set_application_name (_("Terminal"));
+
+ gs_unref_object TerminalFactory *factory = nullptr;
+ gs_free char *service_name = nullptr;
+ gs_free char *parent_screen_object_path = nullptr;
+ if (!factory_proxy_new (options,
+ &factory,
+ &service_name,
+ &parent_screen_object_path,
+ &error))
+ return exit_code;
+
+ if (options->print_environment) {
+ const char *name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (factory));
+ if (name_owner != nullptr)
+ g_print ("%s=%s\n", TERMINAL_ENV_SERVICE_NAME, name_owner);
+ else
+ return exit_code;
+ }
+
+ TerminalReceiver *receiver = nullptr;
+ if (!handle_options (options, factory, service_name, parent_screen_object_path, &receiver))
+ return exit_code;
+
+ if (receiver != nullptr) {
+ exit_code = run_receiver (factory, receiver);
+ g_object_unref (receiver);
+ } else
+ exit_code = EXIT_SUCCESS;
+
+ return exit_code;
+}
diff --git a/src/terminal.common.css b/src/terminal.common.css
new file mode 100644
index 0000000..4c57726
--- /dev/null
+++ b/src/terminal.common.css
@@ -0,0 +1,22 @@
+/* Generic theme-independent CSS file */
+
+terminal-window scrolledwindow undershoot.top,
+terminal-window scrolledwindow undershoot.bottom,
+terminal-window scrolledwindow overshoot.top,
+terminal-window scrolledwindow overshoot.bottom
+{
+ background: none;
+}
+
+popover button.model
+{
+ padding-left: 16px;
+ padding-right: 16px;
+}
+
+.disclosure-button
+{
+ padding-left: 4px;
+ padding-right: 4px;
+ min-width: 0;
+}
diff --git a/src/terminal.gresource.xml b/src/terminal.gresource.xml
new file mode 100644
index 0000000..dfa8f5c
--- /dev/null
+++ b/src/terminal.gresource.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2012 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, or (at your option)
+ any later version.
+
+ This program is distributed in the hope conf 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/>.
+-->
+<gresources>
+ <gresource prefix="/org/gnome/terminal">
+ <file alias="css/terminal.css" compressed="true">terminal.common.css</file>
+ <file alias="ui/headerbar.ui" compressed="true" preprocess="xml-stripblanks">terminal-headerbar.ui</file>
+ <file alias="ui/headerbar-menu.ui" compressed="true" preprocess="xml-stripblanks">terminal-headermenu.ui</file>
+ <file alias="ui/menubar-with-mnemonics.ui" compressed="true" preprocess="xml-stripblanks">terminal-menubar-with-mnemonics.ui</file>
+ <file alias="ui/menubar-without-mnemonics.ui" compressed="true" preprocess="xml-stripblanks">terminal-menubar-without-mnemonics.ui</file>
+ <file alias="ui/notebook-menu.ui" compressed="true" preprocess="xml-stripblanks">terminal-notebook-menu.ui</file>
+ <file alias="ui/search-popover.ui" compressed="true" preprocess="xml-stripblanks">search-popover.ui</file>
+ <file alias="ui/terminal.about" compressed="true">terminal.about</file>
+ <file alias="ui/window.ui" compressed="true" preprocess="xml-stripblanks">terminal-window.ui</file>
+ </gresource>
+</gresources>