[gnome-terminal] profile: editor: Add setting whether to preserve the working directory



commit 79b2291b12cfa106149437d63394deddddbc7dca
Author: Christian Persch <chpe src gnome org>
Date:   Mon Sep 30 22:03:55 2019 +0200

    profile: editor: Add setting whether to preserve the working directory
    
    Add a setting to control whether newly opened terminals open in the
    working directory of the parent terminal. By default, the working
    directory is only preserved when the command is a shell (as per /etc/shells).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=706927
    https://gitlab.gnome.org/GNOME/gnome-terminal/issues/126

 src/org.gnome.Terminal.gschema.xml |  15 ++++-
 src/preferences.ui                 |  60 ++++++++++++++++++-
 src/profile-editor.c               |  10 ++++
 src/terminal-enums.h               |   6 ++
 src/terminal-schemas.h             |   1 +
 src/terminal-screen.c              |  81 +++++++++++++++++--------
 src/terminal-util.c                | 119 ++++++++++++++++++++++++++++++++++++-
 src/terminal-util.h                |   3 +
 8 files changed, 265 insertions(+), 30 deletions(-)
---
diff --git a/src/org.gnome.Terminal.gschema.xml b/src/org.gnome.Terminal.gschema.xml
index b9e7d741..72ae48bb 100644
--- a/src/org.gnome.Terminal.gschema.xml
+++ b/src/org.gnome.Terminal.gschema.xml
@@ -53,11 +53,17 @@
     <value nick='hold' value='2'/>
   </enum>
 
-   <enum id='org.gnome.Terminal.CJKWidth'>
+  <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" /> -->
@@ -257,6 +263,13 @@
       <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>
diff --git a/src/preferences.ui b/src/preferences.ui
index 00b6f697..627f0430 100644
--- a/src/preferences.ui
+++ b/src/preferences.ui
@@ -235,6 +235,28 @@
       </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>
@@ -1901,6 +1923,40 @@
                                 <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>
@@ -1911,7 +1967,7 @@
                                 <property name="xalign">0</property>
                               </object>
                               <packing>
-                                <property name="top_attach">3</property>
+                                <property name="top_attach">4</property>
                                 <property name="left_attach">0</property>
                               </packing>
                             </child>
@@ -1930,7 +1986,7 @@
                                 </child>
                               </object>
                               <packing>
-                                <property name="top_attach">3</property>
+                                <property name="top_attach">4</property>
                                 <property name="left_attach">1</property>
                               </packing>
                             </child>
diff --git a/src/profile-editor.c b/src/profile-editor.c
index 8916c2ed..d7e81b08 100644
--- a/src/profile-editor.c
+++ b/src/profile-editor.c
@@ -1117,10 +1117,20 @@ profile_prefs_load (const char *uuid, GSettings *profile)
                                "active",
                                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",
+                                            G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET,
+                                            (GSettingsBindGetMapping) string_to_enum,
+                                            (GSettingsBindSetMapping) enum_to_string,
+                                            terminal_preserve_working_directory_get_type, NULL);
+
   profile_prefs_settings_bind (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY,
                                gtk_builder_get_object (builder,
                                                        "use-custom-command-checkbutton"),
                                "active", 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"),
diff --git a/src/terminal-enums.h b/src/terminal-enums.h
index bdd354e3..3bae788b 100644
--- a/src/terminal-enums.h
+++ b/src/terminal-enums.h
@@ -53,6 +53,12 @@ typedef enum {
   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-schemas.h b/src/terminal-schemas.h
index bd2fa8c5..8f3b214b 100644
--- a/src/terminal-schemas.h
+++ b/src/terminal-schemas.h
@@ -61,6 +61,7 @@ G_BEGIN_DECLS
 #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"
diff --git a/src/terminal-screen.c b/src/terminal-screen.c
index 8a68caa4..af68fc34 100644
--- a/src/terminal-screen.c
+++ b/src/terminal-screen.c
@@ -1084,26 +1084,53 @@ terminal_screen_get_initial_environment (TerminalScreen *screen)
   return screen->priv->initial_env;
 }
 
+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 != NULL &&
+      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
 get_child_command (TerminalScreen *screen,
+                   const char     *path_env,
                    const char     *shell_env,
+                   gboolean       *preserve_cwd_p,
                    GSpawnFlags    *spawn_flags_p,
                    char         ***argv_p,
                    GError        **err)
 {
   TerminalScreenPrivate *priv = screen->priv;
   GSettings *profile = priv->profile;
+  TerminalPreserveWorkingDirectory preserve_cwd;
   char **argv;
 
-  g_assert (spawn_flags_p != NULL && argv_p != NULL);
+  g_assert (spawn_flags_p != NULL && argv_p != NULL && preserve_cwd_p != NULL);
 
   *argv_p = argv = NULL;
 
+  preserve_cwd = g_settings_get_enum (profile, TERMINAL_PROFILE_PRESERVE_WORKING_DIRECTORY_KEY);
+
   if (priv->override_command)
     {
       argv = g_strdupv (priv->override_command);
 
-      *spawn_flags_p |= G_SPAWN_SEARCH_PATH;
+      *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, argv[0]);
+      *spawn_flags_p |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
     }
   else if (g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY))
     {
@@ -1113,7 +1140,8 @@ get_child_command (TerminalScreen *screen,
       if (!g_shell_parse_argv (argv_str, NULL, &argv, err))
         return FALSE;
 
-      *spawn_flags_p |= G_SPAWN_SEARCH_PATH;
+      *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, argv[0]);
+      *spawn_flags_p |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
     }
   else if (priv->shell)
     {
@@ -1126,8 +1154,10 @@ get_child_command (TerminalScreen *screen,
       only_name = strrchr (shell, '/');
       if (only_name != NULL)
         only_name++;
-      else
+      else {
         only_name = shell;
+        *spawn_flags_p |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
+      }
 
       argv = g_new (char*, 3);
 
@@ -1140,6 +1170,7 @@ get_child_command (TerminalScreen *screen,
 
       argv[argc++] = NULL;
 
+      *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, shell);
       *spawn_flags_p |= G_SPAWN_FILE_AND_ARGV_ZERO;
     }
 
@@ -1157,7 +1188,7 @@ get_child_command (TerminalScreen *screen,
 
 static char**
 get_child_environment (TerminalScreen *screen,
-                       const char *cwd,
+                       char **path,
                        char **shell)
 {
   TerminalApp *app = terminal_app_get ();
@@ -1216,6 +1247,7 @@ get_child_environment (TerminalScreen *screen,
     g_ptr_array_add (retval, g_strdup_printf ("%s=%s", e, v ? v : ""));
   g_ptr_array_add (retval, NULL);
 
+  *path = g_strdup (g_hash_table_lookup (env_table, "PATH"));
   *shell = g_strdup (g_hash_table_lookup (env_table, "SHELL"));
 
   g_hash_table_destroy (env_table);
@@ -1388,14 +1420,16 @@ terminal_screen_do_exec (TerminalScreen *screen,
 {
   TerminalScreenPrivate *priv = screen->priv;
   VteTerminal *terminal = VTE_TERMINAL (screen);
-  GSettings *profile;
-  char **env, **argv;
-  char *shell = NULL;
-  const char *working_dir;
   VtePtyFlags pty_flags = VTE_PTY_DEFAULT;
   GSpawnFlags spawn_flags = G_SPAWN_SEARCH_PATH_FROM_ENVP |
                             VTE_SPAWN_NO_PARENT_ENVV;
   GCancellable *cancellable = NULL;
+  gs_strfreev char **argv = NULL;
+  gs_strfreev char **env = NULL;
+  gs_free char *path = NULL;
+  gs_free char *shell = NULL;
+  gboolean preserve_cwd = FALSE;
+  const char *cwd;
 
   if (priv->child_pid != -1) {
     g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
@@ -1409,23 +1443,24 @@ terminal_screen_do_exec (TerminalScreen *screen,
                          "[screen %p] now launching the child process\n",
                          screen);
 
-  profile = priv->profile;
-
-  if (priv->initial_working_directory &&
-      !g_settings_get_boolean (profile, TERMINAL_PROFILE_USE_CUSTOM_COMMAND_KEY))
-    working_dir = priv->initial_working_directory;
-  else
-    working_dir = g_get_home_dir ();
+  env = get_child_environment (screen, &path, &shell);
 
-  env = get_child_environment (screen, working_dir, &shell);
-
-  argv = NULL;
-  if (!get_child_command (screen, shell, &spawn_flags, &argv, error))
+  if (!get_child_command (screen,
+                          shell, path,
+                          &preserve_cwd, &spawn_flags, &argv,
+                          error))
     return FALSE;
 
+  if (preserve_cwd) {
+    cwd = priv->initial_working_directory;
+  } else {
+    cwd = g_get_home_dir ();
+    env = g_environ_unsetenv (env, "PWD");
+  }
+
   vte_terminal_spawn_async (terminal,
                             pty_flags,
-                            working_dir,
+                            cwd,
                             argv,
                             env,
                             spawn_flags,
@@ -1436,10 +1471,6 @@ terminal_screen_do_exec (TerminalScreen *screen,
                             cancellable,
                             spawn_result_cb, NULL);
 
-  g_free (shell);
-  g_strfreev (argv);
-  g_strfreev (env);
-
   return TRUE; /* can't report any more errors since they only occur async */
 }
 
diff --git a/src/terminal-util.c b/src/terminal-util.c
index 39e90bf1..ab749d83 100644
--- a/src/terminal-util.c
+++ b/src/terminal-util.c
@@ -713,8 +713,15 @@ terminal_util_get_etc_shells (void)
   char *str, *nl, *end;
   GPtrArray *arr;
 
-  if (!g_file_get_contents ("/etc/shells", &contents, &len, &err) || len == 0)
-    return NULL;
+  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",
+      NULL
+    };
+    return g_strdupv (default_shells);
+  }
 
   arr = g_ptr_array_new ();
   str = contents;
@@ -1430,3 +1437,111 @@ terminal_util_save_print_settings (GtkPrintSettings *settings,
 
   save_cache_keyfile (keyfile, TERMINAL_PRINT_SETTINGS_FILENAME);
 }
+
+/* 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 %NULL 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 == NULL)
+    return NULL;
+
+  /* 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) != NULL
+      )
+    {
+      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 NULL;
+    }
+
+  if (path == NULL)
+    {
+      /* 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 = 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 = 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 NULL;
+}
+
+/* END code copied from glib */
diff --git a/src/terminal-util.h b/src/terminal-util.h
index abd34fd7..37e04dba 100644
--- a/src/terminal-util.h
+++ b/src/terminal-util.h
@@ -108,6 +108,9 @@ void terminal_util_load_print_settings (GtkPrintSettings **settings,
 void terminal_util_save_print_settings (GtkPrintSettings *settings,
                                         GtkPageSetup *page_setup);
 
+char *terminal_util_find_program_in_path (const char *path,
+                                          const char *program);
+
 G_END_DECLS
 
 #endif /* TERMINAL_UTIL_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]