[gnome-terminal] all: Rework spawning



commit bc8a7f9422b123c078cd99fd147014547297b968
Author: Christian Persch <chpe src gnome org>
Date:   Wed Nov 20 23:12:52 2019 +0100

    all: Rework spawning
    
    There were two distinct code paths for spawning, one async when creating a new
    terminal from within gnome-terminal, and one sync when creating a new terminal
    from arguments received via dbus. Unify them, always using an async aproach.
    This ensures that the TerminalScreen is realised (and has had its size set) by
    the time the VtePty is created, so that the child process has the correct size
    right from the start, instead of receiving a SIGWINCH shortly afterwards.
    
    https://gitlab.gnome.org/GNOME/vte/issues/188

 src/terminal-app.c    |  47 ----
 src/terminal-app.h    |  13 -
 src/terminal-gdbus.c  |  73 +++---
 src/terminal-screen.c | 680 +++++++++++++++++++++++++++++++++-----------------
 src/terminal-screen.h |  43 ++--
 src/terminal-window.c |  28 ++-
 6 files changed, 536 insertions(+), 348 deletions(-)
---
diff --git a/src/terminal-app.c b/src/terminal-app.c
index b509048f..6a4fe03f 100644
--- a/src/terminal-app.c
+++ b/src/terminal-app.c
@@ -982,53 +982,6 @@ terminal_app_new (const char *app_id)
                        NULL);
 }
 
-/**
- * terminal_app_new_window:
- * @app:
- * @monitor:
- *
- * Creates a new #TerminalWindow on the default display.
- */
-TerminalWindow *
-terminal_app_new_window (TerminalApp *app,
-                         int monitor)
-{
-  TerminalWindow *window;
-
-  window = terminal_window_new (G_APPLICATION (app));
-
-  return window;
-}
-
-TerminalScreen *
-terminal_app_new_terminal (TerminalApp     *app,
-                           TerminalWindow  *window,
-                           GSettings       *profile,
-                           const char      *charset,
-                           char           **override_command,
-                           const char      *title,
-                           const char      *working_dir,
-                           char           **child_env,
-                           double           zoom)
-{
-  TerminalScreen *screen;
-
-  g_return_val_if_fail (TERMINAL_IS_APP (app), NULL);
-  g_return_val_if_fail (TERMINAL_IS_WINDOW (window), NULL);
-
-  screen = terminal_screen_new (profile, charset, override_command, title,
-                                working_dir, child_env, zoom);
-
-  terminal_window_add_screen (window, screen, -1);
-  terminal_window_switch_screen (window, screen);
-  gtk_widget_grab_focus (GTK_WIDGET (screen));
-
-  /* Launch the child on idle */
-  _terminal_screen_launch_child_on_idle (screen);
-
-  return screen;
-}
-
 TerminalScreen *
 terminal_app_get_screen_by_uuid (TerminalApp *app,
                                  const char  *uuid)
diff --git a/src/terminal-app.h b/src/terminal-app.h
index 6246c305..a36361e0 100644
--- a/src/terminal-app.h
+++ b/src/terminal-app.h
@@ -67,19 +67,6 @@ char *terminal_app_new_profile (TerminalApp *app,
 void terminal_app_remove_profile (TerminalApp *app,
                                   GSettings *profile);
 
-TerminalWindow * terminal_app_new_window   (TerminalApp *app,
-                                            int monitor);
-
-TerminalScreen *terminal_app_new_terminal (TerminalApp     *app,
-                                           TerminalWindow  *window,
-                                           GSettings       *profile,
-                                           const char      *charset,
-                                           char           **override_command,
-                                           const char      *title,
-                                           const char      *working_dir,
-                                           char           **child_env,
-                                           double           zoom);
-
 char *terminal_app_dup_screen_object_path (TerminalApp *app,
                                            TerminalScreen *screen);
 
diff --git a/src/terminal-gdbus.c b/src/terminal-gdbus.c
index 10ba48e6..f58e2fa8 100644
--- a/src/terminal-gdbus.c
+++ b/src/terminal-gdbus.c
@@ -84,6 +84,33 @@ terminal_receiver_impl_set_screen (TerminalReceiverImpl *impl,
 
 /* Class implementation */
 
+typedef struct {
+  TerminalReceiver *receiver;
+  GDBusMethodInvocation *invocation;
+} ExecData;
+
+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 %NULL */
+         GError *error, /* set on error, %NULL 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, NULL /* outfdlist */);
+  }
+}
+
 static gboolean
 terminal_receiver_impl_exec (TerminalReceiver *receiver,
                              GDBusMethodInvocation *invocation,
@@ -95,10 +122,10 @@ terminal_receiver_impl_exec (TerminalReceiver *receiver,
   TerminalReceiverImplPrivate *priv = impl->priv;
   const char *working_directory;
   gboolean shell;
-  char **exec_argv, **envv;
   gsize exec_argc;
-  GVariant *fd_array;
-  GError *error;
+  gs_free char **exec_argv = NULL; /* container needs to be freed, strings not owned */
+  gs_free char **envv = NULL; /* container needs to be freed, strings not owned */
+  gs_unref_variant GVariant *fd_array = NULL;
 
   if (priv->screen == NULL) {
     g_dbus_method_invocation_return_error_literal (invocation,
@@ -163,24 +190,25 @@ terminal_receiver_impl_exec (TerminalReceiver *receiver,
 
   exec_argv = (char **) g_variant_get_bytestring_array (arguments, &exec_argc);
 
-  error = NULL;
+  ExecData *exec_data = g_new (ExecData, 1);
+  exec_data->receiver = g_object_ref (receiver);
+  exec_data->invocation = g_object_ref (invocation);
+
+  GError *err = NULL;
   if (!terminal_screen_exec (priv->screen,
                              exec_argc > 0 ? exec_argv : NULL,
                              envv,
                              shell,
                              working_directory,
                              fd_list, fd_array,
-                             &error)) {
-    g_dbus_method_invocation_take_error (invocation, error);
-  } else {
-    terminal_receiver_complete_exec (receiver, invocation, NULL /* outfdlist */);
+                             (TerminalScreenExecCallback) exec_cb,
+                             exec_data /* adopted */,
+                             (GDestroyNotify) exec_data_free,
+                             NULL /* cancellable */,
+                             &err)) {
+    g_dbus_method_invocation_take_error (invocation, err);
   }
 
-  g_free (exec_argv);
-  g_free (envv);
-  if (fd_array)
-    g_variant_unref (fd_array);
-
 out:
 
   return TRUE; /* handled */
@@ -385,22 +413,7 @@ terminal_factory_impl_create_instance (TerminalFactory *factory,
     const char *startup_id, *role;
     gboolean start_maximized, start_fullscreen;
 
-    /* We don't do multi-display anymore */
-#if 0
-    const char *display_name;
-    if (g_variant_lookup (options, "display", "^&ay", &display_name)) {
-      GdkDisplay *display = gdk_display_get_default ();
-      const char *default_display_name = display ? gdk_display_get_name (display) : NULL;
-
-      if (g_strcmp0 (default_display_name, display_name) != 0)
-        g_printerr ("Display \"%s\" requested but default display is \"%s\"\n",
-                    display_name, default_display_name);
-      /* Open window on our display anyway */
-    }
-#endif
-
-    int monitor = 0;
-    window = terminal_app_new_window (app, monitor);
+    window = terminal_window_new (G_APPLICATION (app));
     have_new_window = TRUE;
 
     if (g_variant_lookup (options, "desktop-startup-id", "^&ay", &startup_id))
@@ -471,7 +484,7 @@ terminal_factory_impl_create_instance (TerminalFactory *factory,
   g_assert_nonnull (profile);
 
   /* Now we can create the new screen */
-  TerminalScreen *screen = terminal_screen_new (profile, encoding, NULL, title, NULL, NULL, zoom);
+  TerminalScreen *screen = terminal_screen_new (profile, encoding, title, zoom);
   terminal_window_add_screen (window, screen, -1);
 
   /* Apply window properties */
diff --git a/src/terminal-screen.c b/src/terminal-screen.c
index 42837d3c..4f3297ec 100644
--- a/src/terminal-screen.c
+++ b/src/terminal-screen.c
@@ -66,11 +66,30 @@
 #define SPAWN_TIMEOUT (30 * 1000 /* 30s */)
 
 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 */
   int *fd_list;
   int fd_list_len;
   const int *fd_array;
   gsize fd_array_len;
-} FDSetupData;
+
+  /* async exec callback */
+  TerminalScreenExecCallback callback;
+  gpointer callback_data;
+  GDestroyNotify callback_data_destroy_notify;
+
+  /* Cancellable */
+  GCancellable *cancellable;
+} ExecData;
 
 typedef struct
 {
@@ -86,13 +105,11 @@ struct _TerminalScreenPrivate
   GSettings *profile; /* never NULL */
   guint profile_changed_id;
   guint profile_forgotten_id;
-  char *initial_working_directory;
-  char **initial_env;
-  char **override_command;
-  gboolean shell;
   int child_pid;
   GSList *match_tags;
-  guint launch_child_source_id;
+  gboolean exec_on_realize;
+  guint idle_exec_source;
+  ExecData *exec_data;
 };
 
 enum
@@ -108,7 +125,6 @@ enum {
   PROP_0,
   PROP_PROFILE,
   PROP_TITLE,
-  PROP_INITIAL_ENVIRONMENT
 };
 
 enum
@@ -138,9 +154,6 @@ static void terminal_screen_system_font_changed_cb (GSettings *,
 static gboolean terminal_screen_popup_menu (GtkWidget *widget);
 static gboolean terminal_screen_button_press (GtkWidget *widget,
                                               GdkEventButton *event);
-static gboolean terminal_screen_do_exec (TerminalScreen *screen,
-                                         FDSetupData    *data,
-                                         GError **error);
 static void terminal_screen_child_exited  (VteTerminal *terminal,
                                            int status);
 
@@ -158,9 +171,27 @@ static char* terminal_screen_check_match       (TerminalScreen            *scree
                                                 GdkEvent                  *event,
                                                 int                  *flavor);
 
-static void terminal_screen_set_override_command (TerminalScreen  *screen,
-                                                  char           **argv,
-                                                  gboolean         shell);
+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];
 
@@ -205,6 +236,120 @@ fake_dup3 (int fd, int fd2, int 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 = NULL;
+  gs_free char *str2 = NULL;
+  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)
+{
+  if (data == NULL)
+    return NULL;
+
+  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 (data->fd_list != NULL) {
+    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 == NULL)
+    return;
+
+  if (--data->refcount > 0)
+    return;
+
+  g_strfreev (data->argv);
+  g_strfreev (data->exec_argv);
+  g_strfreev (data->envv);
+  g_free (data->cwd);
+  g_free (data->fd_list);
+
+  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 == NULL)
+    return;
+
+  if (cancelled) {
+    gs_free_error GError *err = NULL;
+    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 = NULL;
+}
+
 G_DEFINE_TYPE (TerminalScreen, terminal_screen, VTE_TYPE_TERMINAL)
 
 static void
@@ -288,6 +433,13 @@ terminal_screen_realize (GtkWidget *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
@@ -419,9 +571,6 @@ terminal_screen_get_property (GObject *object,
       case PROP_PROFILE:
         g_value_set_object (value, terminal_screen_get_profile (screen));
         break;
-      case PROP_INITIAL_ENVIRONMENT:
-        g_value_set_boxed (value, terminal_screen_get_initial_environment (screen));
-        break;
       case PROP_TITLE:
         g_value_set_string (value, terminal_screen_get_title (screen));
         break;
@@ -444,9 +593,6 @@ terminal_screen_set_property (GObject *object,
       case PROP_PROFILE:
         terminal_screen_set_profile (screen, g_value_get_object (value));
         break;
-      case PROP_INITIAL_ENVIRONMENT:
-        terminal_screen_set_initial_environment (screen, g_value_get_boxed (value));
-        break;
       case PROP_TITLE:
         /* not writable */
       default:
@@ -532,13 +678,6 @@ terminal_screen_class_init (TerminalScreenClass *klass)
                           NULL,
                           G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | 
G_PARAM_STATIC_BLURB));
 
-  g_object_class_install_property
-    (object_class,
-     PROP_INITIAL_ENVIRONMENT,
-     g_param_spec_boxed ("initial-environment", NULL, NULL,
-                         G_TYPE_STRV,
-                         G_PARAM_READWRITE | 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);
@@ -582,12 +721,14 @@ terminal_screen_dispose (GObject *object)
                                         0, 0, NULL, NULL,
                                         screen);
 
-  if (priv->launch_child_source_id != 0)
+  if (priv->idle_exec_source != 0)
     {
-      g_source_remove (priv->launch_child_source_id);
-      priv->launch_child_source_id = 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,
@@ -613,10 +754,6 @@ terminal_screen_finalize (GObject *object)
 
   terminal_screen_set_profile (screen, NULL);
 
-  g_free (priv->initial_working_directory);
-  g_strfreev (priv->override_command);
-  g_strfreev (priv->initial_env);
-
   g_slist_free_full (priv->match_tags, (GDestroyNotify) free_tag_data);
 
   g_free (priv->uuid);
@@ -627,19 +764,12 @@ terminal_screen_finalize (GObject *object)
 TerminalScreen *
 terminal_screen_new (GSettings       *profile,
                      const char      *charset,
-                     char           **override_command,
                      const char      *title,
-                     const char      *working_dir,
-                     char           **child_env,
                      double           zoom)
 {
-  TerminalScreen *screen;
-  TerminalScreenPrivate *priv;
-
   g_return_val_if_fail (G_IS_SETTINGS (profile), NULL);
 
-  screen = g_object_new (TERMINAL_TYPE_SCREEN, NULL);
-  priv = screen->priv;
+  TerminalScreen *screen = g_object_new (TERMINAL_TYPE_SCREEN, NULL);
 
   terminal_screen_set_profile (screen, profile);
 
@@ -649,12 +779,10 @@ terminal_screen_new (GSettings       *profile,
    * profile's encoding if it's changed during the lifetime
    * of this terminal.
    */
-  if (charset != NULL &&
-      override_command != NULL) {
+  if (charset != NULL)
     vte_terminal_set_encoding (VTE_TERMINAL (screen),
                                charset,
                                NULL);
-  }
 
   vte_terminal_set_size (VTE_TERMINAL (screen),
                          g_settings_get_int (profile, TERMINAL_PROFILE_DEFAULT_SIZE_COLUMNS_KEY),
@@ -683,57 +811,199 @@ terminal_screen_new (GSettings       *profile,
     g_string_free (seq, TRUE);
   }
 
-  priv->initial_working_directory = g_strdup (working_dir);
-
-  if (override_command)
-    terminal_screen_set_override_command (screen, override_command, FALSE);
-  else
-    terminal_screen_set_override_command (screen, NULL, TRUE);
-
-  if (child_env)
-    terminal_screen_set_initial_environment (screen, child_env);
-
   vte_terminal_set_font_scale (VTE_TERMINAL (screen), zoom);
   terminal_screen_set_font (screen);
 
   return screen;
 }
 
-gboolean 
-terminal_screen_exec (TerminalScreen *screen,
-                      char          **argv,
-                      char          **envv,
-                      gboolean        shell,
-                      const char     *cwd,
-                      GUnixFDList    *fd_list,
-                      GVariant       *fd_array,
-                      GError        **error)
+static gboolean
+terminal_screen_reexec_from_exec_data (TerminalScreen *screen,
+                                       ExecData *data,
+                                       char **envv,
+                                       const char *cwd,
+                                       GCancellable *cancellable,
+                                       GError **error)
 {
-  TerminalScreenPrivate *priv;
-  FDSetupData *data;
+  _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 : NULL,
+                               envv ? envv : data ? data->envv : NULL,
+                               data ? data->as_shell : TRUE,
+                               cwd ? cwd : data ? data->cwd : NULL,
+                               NULL /* fd list */, NULL /* fd array */,
+                               NULL, NULL, NULL, /* 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 == NULL)
+    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);
+  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,
+                                                NULL /* 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 == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  g_return_val_if_fail (gtk_widget_get_parent (GTK_WIDGET (screen)) != NULL, FALSE);
+
+  _TERMINAL_DEBUG_IF (TERMINAL_DEBUG_PROCESSES) {
+    gs_free char *argv_str = NULL;
+    _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;
 
-  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 = NULL;
+  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_set_initial_environment (screen, envv);
-  terminal_screen_set_override_command (screen, argv, shell);
+    terminal_screen_show_info_bar (screen, err, FALSE);
+    g_propagate_error (error, err);
+    exec_data_unref (data); /* frees the callback data */
+    return FALSE;
+  }
 
-  g_free (priv->initial_working_directory);
-  priv->initial_working_directory = g_strdup (cwd);
+  gs_free char *path = NULL;
+  gs_free char *shell = NULL;
+  gs_strfreev char **envv = terminal_screen_get_child_environment (screen,
+                                                                  initial_envv,
+                                                                  &path,
+                                                                  &shell);
+
+  gboolean preserve_cwd = FALSE;
+  GSpawnFlags spawn_flags = G_SPAWN_SEARCH_PATH_FROM_ENVP;
+  if (initial_envv)
+    spawn_flags |= VTE_SPAWN_NO_PARENT_ENVV;
+  gs_strfreev char **exec_argv = NULL;
+  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) {
+    data->cwd = g_strdup (cwd);
+  } else {
+    cwd = g_get_home_dir ();
+    envv = g_environ_unsetenv (envv, "PWD");
+  }
 
   if (fd_list) {
     const int *fds;
 
-    data = g_new (FDSetupData, 1);
     fds = g_unix_fd_list_peek_fds (fd_list, &data->fd_list_len);
     data->fd_list = g_memdup (fds, (data->fd_list_len + 1) * sizeof (int));
     data->fd_array = g_variant_get_fixed_array (fd_array, &data->fd_array_len, 2 * sizeof (int));
-  } else
-    data = NULL;
+  }
 
-  return terminal_screen_do_exec (screen, data, error);
+  data->argv = g_strdupv (argv);
+  data->exec_argv = g_strdupv (exec_argv);
+  data->envv = g_strdupv (envv);
+  data->as_shell = as_shell;
+  data->pty_flags = VTE_PTY_DEFAULT;
+  data->spawn_flags = spawn_flags;
+  data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+
+  terminal_screen_clear_exec_data (screen, TRUE);
+  priv->exec_data = data;
+
+  terminal_screen_queue_idle_exec (screen);
+
+  return TRUE;
 }
 
 const char*
@@ -1043,45 +1313,6 @@ terminal_screen_ref_profile (TerminalScreen *screen)
   return NULL;
 }
 
-static void
-terminal_screen_set_override_command (TerminalScreen *screen,
-                                      char          **argv,
-                                      gboolean        shell)
-{
-  TerminalScreenPrivate *priv;
-
-  g_return_if_fail (TERMINAL_IS_SCREEN (screen));
-
-  priv = screen->priv;
-  g_strfreev (priv->override_command);
-  if (argv)
-    priv->override_command = g_strdupv (argv);
-  else
-    priv->override_command = NULL;
-  priv->shell = shell;
-}
-
-void
-terminal_screen_set_initial_environment (TerminalScreen *screen,
-                                         char          **argv)
-{
-  TerminalScreenPrivate *priv;
-
-  g_return_if_fail (TERMINAL_IS_SCREEN (screen));
-
-  priv = screen->priv;
-  g_assert (priv->initial_env == NULL);
-  priv->initial_env = g_strdupv (argv);
-}
-
-char**
-terminal_screen_get_initial_environment (TerminalScreen *screen)
-{
-  g_return_val_if_fail (TERMINAL_IS_SCREEN (screen), NULL);
-
-  return screen->priv->initial_env;
-}
-
 static gboolean
 should_preserve_cwd (TerminalPreserveWorkingDirectory preserve_cwd,
                      const char *path,
@@ -1104,13 +1335,15 @@ should_preserve_cwd (TerminalPreserveWorkingDirectory preserve_cwd,
 }
 
 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)
+terminal_screen_get_child_command (TerminalScreen *screen,
+                                   char          **exec_argv,
+                                   const char     *path_env,
+                                   const char     *shell_env,
+                                   gboolean        as_shell,
+                                   gboolean       *preserve_cwd_p,
+                                   GSpawnFlags    *spawn_flags_p,
+                                   char         ***argv_p,
+                                   GError        **err)
 {
   TerminalScreenPrivate *priv = screen->priv;
   GSettings *profile = priv->profile;
@@ -1123,9 +1356,9 @@ get_child_command (TerminalScreen *screen,
 
   preserve_cwd = g_settings_get_enum (profile, TERMINAL_PROFILE_PRESERVE_WORKING_DIRECTORY_KEY);
 
-  if (priv->override_command)
+  if (exec_argv)
     {
-      argv = g_strdupv (priv->override_command);
+      argv = g_strdupv (exec_argv);
 
       *preserve_cwd_p = should_preserve_cwd (preserve_cwd, path_env, argv[0]);
       *spawn_flags_p |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
@@ -1141,7 +1374,7 @@ get_child_command (TerminalScreen *screen,
       *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)
+  else if (as_shell)
     {
       const char *only_name;
       char *shell;
@@ -1185,12 +1418,12 @@ get_child_command (TerminalScreen *screen,
 }
 
 static char**
-get_child_environment (TerminalScreen *screen,
-                       char **path,
-                       char **shell)
+terminal_screen_get_child_environment (TerminalScreen *screen,
+                                       char **initial_envv,
+                                       char **path,
+                                       char **shell)
 {
   TerminalApp *app = terminal_app_get ();
-  TerminalScreenPrivate *priv = screen->priv;
   char **env;
   char *e, *v;
   GHashTable *env_table;
@@ -1200,7 +1433,7 @@ get_child_environment (TerminalScreen *screen,
 
   env_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
-  env = priv->initial_env;
+  env = initial_envv;
   if (env)
     {
       for (i = 0; env[i]; ++i)
@@ -1271,7 +1504,7 @@ info_bar_response_cb (GtkWidget *info_bar,
       break;
     case RESPONSE_RELAUNCH:
       gtk_widget_destroy (info_bar);
-      _terminal_screen_launch_child_on_idle (screen);
+      terminal_screen_reexec (screen, NULL, NULL, NULL, NULL);
       break;
     case RESPONSE_EDIT_PREFERENCES:
       terminal_app_edit_preferences (terminal_app_get (),
@@ -1285,17 +1518,7 @@ info_bar_response_cb (GtkWidget *info_bar,
 }
 
 static void
-free_fd_setup_data (FDSetupData *data)
-{
-  if (data == NULL)
-    return;
-
-  g_free (data->fd_list);
-  g_free (data);
-}
-
-static void
-terminal_screen_child_setup (FDSetupData *data)
+terminal_screen_child_setup (ExecData *data)
 {
   int *fds = data->fd_list;
   int n_fds = data->fd_list_len;
@@ -1367,131 +1590,124 @@ terminal_screen_child_setup (FDSetupData *data)
   }
 }
 
+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 ? NULL : _("_Relaunch"), RESPONSE_RELAUNCH,
+                                    NULL);
+  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;
-  TerminalScreenPrivate *priv;
+  TerminalScreen *screen = TERMINAL_SCREEN (terminal);
+  ExecData *exec_data = user_data;
 
   /* Terminal was destroyed while the spawn operation was in progress; nothing to do. */
   if (terminal == NULL)
-    return;
+    goto out;
 
-  screen = TERMINAL_SCREEN (terminal);
-  priv = screen->priv;
+  TerminalScreenPrivate *priv = screen->priv;
 
   priv->child_pid = pid;
 
   if (error) {
-    GtkWidget *info_bar;
-
+     // FIXMEchpe should be unnecessary, vte already does this internally
     vte_terminal_set_pty (terminal, NULL);
-    info_bar = terminal_info_bar_new (GTK_MESSAGE_ERROR,
-                                      _("_Preferences"), RESPONSE_EDIT_PREFERENCES,
-                                      _("_Relaunch"), RESPONSE_RELAUNCH,
-                                      NULL);
-    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);
 
-    return;
+    gboolean can_reexec = TRUE; /* FIXME */
+    terminal_screen_show_info_bar (screen, error, can_reexec);
   }
-}
 
-static gboolean
-terminal_screen_do_exec (TerminalScreen *screen,
-                         FDSetupData    *data /* adopting */,
-                         GError        **error)
-{
-  TerminalScreenPrivate *priv = screen->priv;
-  VteTerminal *terminal = VTE_TERMINAL (screen);
-  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;
+  /* Retain info for reexec, if possible */
+  ExecData *new_exec_data = exec_data_clone (exec_data);
+  terminal_screen_clear_exec_data (screen, FALSE);
+  priv->exec_data = new_exec_data;
 
-  if (priv->child_pid != -1) {
-    g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
-                         "Cannot launch a new child process while the terminal is still running another 
child process");
-    return FALSE;
-  }
+out:
 
-  priv->launch_child_source_id = 0;
+  /* Must do this even if the terminal was destroyed */
+  exec_data_callback (exec_data, error, screen);
 
-  _terminal_debug_print (TERMINAL_DEBUG_PROCESSES,
-                         "[screen %p] now launching the child process\n",
-                         screen);
+  exec_data_unref (exec_data);
+}
 
-  env = get_child_environment (screen, &path, &shell);
+static gboolean
+idle_exec_cb (TerminalScreen *screen)
+{
+  TerminalScreenPrivate *priv = screen->priv;
 
-  if (!get_child_command (screen,
-                          shell, path,
-                          &preserve_cwd, &spawn_flags, &argv,
-                          error))
-    return FALSE;
+  priv->idle_exec_source = 0;
 
-  if (preserve_cwd) {
-    cwd = priv->initial_working_directory;
-  } else {
-    cwd = g_get_home_dir ();
-    env = g_environ_unsetenv (env, "PWD");
+  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);
   }
 
+  VteTerminal *terminal = VTE_TERMINAL (screen);
   vte_terminal_spawn_async (terminal,
-                            pty_flags,
-                            cwd,
-                            argv,
-                            env,
-                            spawn_flags,
-                            (GSpawnChildSetupFunc) (data ? terminal_screen_child_setup : NULL),
-                            data,
-                            (GDestroyNotify) (data ? free_fd_setup_data : NULL),
+                            data->pty_flags,
+                            data->cwd,
+                            data->exec_argv,
+                            data->envv,
+                            data->spawn_flags,
+                            (GSpawnChildSetupFunc) terminal_screen_child_setup,
+                            exec_data_ref (data),
+                            (GDestroyNotify) exec_data_unref,
                             SPAWN_TIMEOUT,
-                            cancellable,
-                            spawn_result_cb, NULL);
-
-  return TRUE; /* can't report any more errors since they only occur async */
-}
+                            data->cancellable,
+                            spawn_result_cb,
+                            exec_data_ref (data));
 
-static gboolean
-terminal_screen_launch_child_cb (TerminalScreen *screen)
-{
-  terminal_screen_do_exec (screen, NULL, NULL /* don't care */);
   return FALSE; /* don't run again */
 }
 
-void
-_terminal_screen_launch_child_on_idle (TerminalScreen *screen)
+static void
+terminal_screen_queue_idle_exec (TerminalScreen *screen)
 {
   TerminalScreenPrivate *priv = screen->priv;
 
-  if (priv->launch_child_source_id != 0)
+  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->launch_child_source_id = g_idle_add ((GSourceFunc) terminal_screen_launch_child_cb, screen);
+  priv->idle_exec_source = g_idle_add ((GSourceFunc) idle_exec_cb, screen);
 }
 
 static TerminalScreenPopupInfo *
@@ -1654,9 +1870,8 @@ terminal_screen_button_press (GtkWidget      *widget,
  * @screen:
  *
  * Tries to determine the current working directory of the foreground process
- * in @screen's PTY, falling back to the current working directory of the
- * primary child.
- * 
+ * in @screen's PTY.
+ *
  * Returns: a newly allocated string containing the current working directory,
  *   or %NULL on failure
  */
@@ -1669,8 +1884,9 @@ terminal_screen_get_current_dir (TerminalScreen *screen)
   if (uri != NULL)
     return g_filename_from_uri (uri, NULL, NULL);
 
-  if (screen->priv->initial_working_directory)
-    return g_strdup (screen->priv->initial_working_directory);
+  ExecData *data = screen->priv->exec_data;
+  if (data && data->cwd)
+    return g_strdup (data->cwd);
 
   return NULL;
 }
@@ -1701,16 +1917,16 @@ terminal_screen_child_exited (VteTerminal *terminal,
                          screen);
 
   priv->child_pid = -1;
-  
+
   action = 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_launch_child_on_idle (screen);
+      terminal_screen_reexec (screen, NULL, NULL, NULL, NULL);
       break;
     case TERMINAL_EXIT_HOLD: {
       GtkWidget *info_bar;
diff --git a/src/terminal-screen.h b/src/terminal-screen.h
index ff77fcf7..82384bb4 100644
--- a/src/terminal-screen.h
+++ b/src/terminal-screen.h
@@ -77,32 +77,43 @@ const char *terminal_screen_get_uuid (TerminalScreen *screen);
 
 TerminalScreen *terminal_screen_new (GSettings       *profile,
                                      const char      *charset,
-                                     char           **override_command,
                                      const char      *title,
-                                     const char      *working_dir,
-                                     char           **child_env,
                                      double           zoom);
 
-gboolean terminal_screen_exec (TerminalScreen *screen,
-                               char          **argv,
-                               char          **envv,
-                               gboolean        shell,
-                               const char     *cwd,
-                               GUnixFDList    *fd_list,
-                               GVariant       *fd_array,
-                               GError        **error);
+typedef void (* TerminalScreenExecCallback) (TerminalScreen *screen,
+                                             GError         *error,
+                                             gpointer        user_data);
 
-void _terminal_screen_launch_child_on_idle (TerminalScreen *screen);
+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);
 
-void         terminal_screen_set_initial_environment (TerminalScreen  *screen,
-                                                      char           **argv);
-char **      terminal_screen_get_initial_environment (TerminalScreen  *screen);
-
 const char* terminal_screen_get_title          (TerminalScreen *screen);
 
 char *terminal_screen_get_current_dir (TerminalScreen *screen);
diff --git a/src/terminal-window.c b/src/terminal-window.c
index 16ea682b..7c263e03 100644
--- a/src/terminal-window.c
+++ b/src/terminal-window.c
@@ -380,9 +380,11 @@ action_new_terminal_cb (GSimpleAction *action,
     }
   }
 
+  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 (priv->active_screen);
+    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
@@ -392,14 +394,20 @@ action_new_terminal_cb (GSimpleAction *action,
     return;
 
   if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW)
-    window = terminal_app_new_window (app, 0);
+    window = terminal_window_new (G_APPLICATION (app));
+
+  TerminalScreen *screen = terminal_screen_new (profile,
+                                                NULL /* charset */,
+                                                NULL /* 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));
 
-  new_working_directory = terminal_screen_get_current_dir (priv->active_screen);
-  terminal_app_new_terminal (app, window, profile, NULL /* use profile encoding */,
-                             NULL, NULL,
-                             new_working_directory,
-                             terminal_screen_get_initial_environment (priv->active_screen),
-                             1.0);
+  /* Start child process, if possible by using the same args as the parent screen */
+  terminal_screen_reexec_from_screen (screen, parent_screen, NULL, NULL);
 
   if (mode == TERMINAL_NEW_TERMINAL_MODE_WINDOW)
     gtk_window_present (GTK_WINDOW (window));
@@ -919,7 +927,7 @@ action_tab_detach_cb (GSimpleAction *action,
   terminal_screen_get_size (screen, &width, &height);
   g_snprintf (geometry, sizeof (geometry), "%dx%d", width, height);
 
-  new_window = terminal_app_new_window (app, 0);
+  new_window = terminal_window_new (G_APPLICATION (app));
 
   terminal_window_move_screen (window, new_window, screen, -1);
 
@@ -1614,7 +1622,7 @@ handle_tab_droped_on_desktop (GtkNotebook *source_notebook,
   source_window = TERMINAL_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source_notebook)));
   g_return_val_if_fail (TERMINAL_IS_WINDOW (source_window), NULL);
 
-  new_window = terminal_app_new_window (terminal_app_get (), 0);
+  new_window = terminal_window_new (G_APPLICATION (terminal_app_get ()));
   new_priv = new_window->priv;
   new_priv->present_on_insert = TRUE;
 



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