[vte/new-pty] Add VtePty and adapt the VteTerminal APIs to it



commit 406b44ae0abc0fd6e145859ec418616c20772641
Author: Christian Persch <chpe gnome org>
Date:   Thu Jan 14 18:08:33 2010 +0100

    Add VtePty and adapt the VteTerminal APIs to it
    
    Add VtePty as a GObject holding the info about the PTY. Add new API to
    VteTerminal to set a VtePty, and deprecate the old API that takes a FD
    to the PTY. Also deprecate the whole of the undocumented _vte_pty_*()
    APIs.
    
    Add vte_terminal_fork_command_full() variant that allow providing a
    custom child setup function and that returns a GError on failure.
    
    Bug #585841, bug #320128, bug #514447, bug #588871.

 Makefile.am                    |    2 +-
 doc/reference/vte-docs.xml     |    3 +
 doc/reference/vte-sections.txt |   50 ++-
 doc/reference/vte.types        |    5 +-
 gnome-pty-helper/configure.in  |    5 +-
 src/Makefile.am                |   14 +-
 src/pty.c                      | 1652 ++++++++++++++++++++++++++++++----------
 src/pty.h                      |    6 +-
 src/vte-private.h              |    3 +-
 src/vte.c                      |  775 +++++++++++++------
 src/vte.h                      |   40 +-
 src/vteapp.c                   |   32 +-
 src/vtepty-private.h           |   43 +
 src/vtepty.h                   |  121 +++
 14 files changed, 2058 insertions(+), 693 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 2163351..dd622c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -19,7 +19,7 @@ endif
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = vte.pc
 
-DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc
+DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --disable-silent-rules
 
 MAINTAINERCLEANFILES = \
 		ChangeLog \
diff --git a/doc/reference/vte-docs.xml b/doc/reference/vte-docs.xml
index 254ff14..4453760 100644
--- a/doc/reference/vte-docs.xml
+++ b/doc/reference/vte-docs.xml
@@ -56,6 +56,9 @@
       <xi:include href="xml/vte-terminal.xml"/>
     </chapter>
     <chapter>
+      <xi:include href="xml/vte-pty.xml"/>
+    </chapter>
+    <chapter>
       <xi:include href="xml/vte-version.xml"/>
     </chapter>
 
diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt
index 87d13ae..0b9dfa0 100644
--- a/doc/reference/vte-sections.txt
+++ b/doc/reference/vte-sections.txt
@@ -10,10 +10,6 @@ VteTerminalWriteFlags
 VteSelectionFunc
 vte_terminal_new
 vte_terminal_im_append_menuitems
-vte_terminal_fork_command
-vte_terminal_forkpty
-vte_terminal_set_pty
-vte_terminal_get_pty
 vte_terminal_feed
 vte_terminal_feed_child
 vte_terminal_feed_child_binary
@@ -88,6 +84,18 @@ vte_terminal_get_encoding
 vte_terminal_get_status_line
 vte_terminal_get_padding
 vte_terminal_write_contents
+
+<SUBSECTION>
+vte_terminal_fork_command
+vte_terminal_fork_command_full
+vte_terminal_forkpty
+vte_terminal_get_pty
+vte_terminal_get_pty_object
+vte_terminal_pty_new
+vte_terminal_set_pty
+vte_terminal_set_pty_object
+vte_terminal_watch_child
+
 <SUBSECTION Standard>
 VTE_TYPE_TERMINAL_CURSOR_BLINK_MODE
 vte_terminal_cursor_blink_mode_get_type
@@ -126,6 +134,40 @@ VteTerminalPrivate
 </SECTION>
 
 <SECTION>
+<FILE>vte-pty</FILE>
+<TITLE>Vte PTY</TITLE>
+
+VtePtyFlags
+VtePtyError
+VtePty
+vte_pty_new
+vte_pty_new_foreign
+vte_pty_close
+vte_pty_child_setup
+vte_pty_get_fd
+vte_pty_set_size
+vte_pty_get_size
+vte_pty_set_term
+vte_pty_set_utf8
+
+<SUBSECTION Standard>
+vte_pty_flags_get_type
+VTE_TYPE_PTY_FLAGS
+vte_pty_error_get_type
+VTE_TYPE_PTY_ERROR
+vte_pty_error_quark
+VTE_PTY_ERROR
+vte_pty_get_type
+VTE_TYPE_PTY
+VTE_PTY
+VTE_PTY_CLASS
+VTE_IS_PTY
+VTE_IS_PTY_CLASS
+VTE_PTY_GET_CLASS
+VtePtyClass
+</SECTION>
+
+<SECTION>
 <FILE>vte-version</FILE>
 <TITLE>Version Information</TITLE>
 
diff --git a/doc/reference/vte.types b/doc/reference/vte.types
index 94d16d9..83b1aec 100644
--- a/doc/reference/vte.types
+++ b/doc/reference/vte.types
@@ -5,15 +5,18 @@
 #include "vte.h"
 #include "vteversion.h"
 #include "vteaccess.h"
+#include "vtepty.h"
 #include "vtetypebuiltins.h"
 #include "reaper.h"
 
+vte_pty_get_type
 vte_reaper_get_type
-
 vte_terminal_get_type
 vte_terminal_accessible_get_type
 vte_terminal_accessible_factory_get_type
 
+vte_pty_error_get_type
+vte_pty_flags_get_type
 vte_terminal_anti_alias_get_type
 vte_terminal_cursor_blink_mode_get_type
 vte_terminal_cursor_shape_get_type
diff --git a/gnome-pty-helper/configure.in b/gnome-pty-helper/configure.in
index dd14b71..73a400e 100644
--- a/gnome-pty-helper/configure.in
+++ b/gnome-pty-helper/configure.in
@@ -10,7 +10,10 @@ AC_PROG_CC
 AC_STDC_HEADERS
 AM_PROG_CC_STDC
 
-AM_MAINTAINER_MODE
+if test -z "$enable_maintainer_mode"; then
+  enable_maintainer_mode=yes
+fi
+AM_MAINTAINER_MODE([enable])
 
 AC_CHECK_HEADERS(sys/syslimits.h sys/time.h sys/types.h sys/un.h alloca.h lastlog.h libutil.h paths.h pty.h stropts.h termios.h ttyent.h util.h utmp.h utmpx.h pty.h util.h libutil.h ttyent.h)
 have_openpty=0
diff --git a/src/Makefile.am b/src/Makefile.am
index b980e7b..cd45e75 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,7 +4,7 @@ EXTRA_DIST = iso2022.txt
 
 # The library
 
-pkginclude_HEADERS = pty.h reaper.h vte.h vteaccess.h vtetypebuiltins.h vteversion.h
+pkginclude_HEADERS = pty.h reaper.h vte.h vteaccess.h vtepty.h vtetypebuiltins.h vteversion.h
 
 lib_LTLIBRARIES = libvte.la
 
@@ -65,6 +65,8 @@ libvte_la_SOURCES = \
 	vtedraw.c \
 	vtedraw.h \
 	vteint.h \
+	vtepty.h \
+	vtepty-private.h \
 	vteregex.c \
 	vteregex.h \
 	vterowdata.c \
@@ -96,7 +98,7 @@ libvte_la_CFLAGS = $(VTE_CFLAGS)
 libvte_la_LIBADD = $(VTE_LIBS)
 libvte_la_LDFLAGS = \
 	-version-info $(LIBVTE_LTVERSION) \
-	-export-symbols-regex "^vte_terminal_.*|^_vte_pty_.*|^vte_reaper_.*|_vte_debug_.*" \
+	-export-symbols-regex "^vte_terminal_.*|^vte_pty_.*|^_vte_pty_.*|^vte_reaper_.*|_vte_debug_.*" \
 	@LIBTOOL_EXPORT_OPTIONS@ @LIBTOOL_FLAGS@
 
 
@@ -116,14 +118,14 @@ marshal.h: marshal.list
 
 vtetypebuiltins.h: stamp-vtetypebuiltins.h
 	@true
-stamp-vtetypebuiltins.h: vtetypebuiltins.h.template vte.h
-	$(AM_V_GEN) $(GLIB_MKENUMS) --template $< $(filter-out $<,$^) > xgen-vtbh \
+stamp-vtetypebuiltins.h: vtetypebuiltins.h.template vte.h vtepty.h
+	$(AM_V_GEN) $(GLIB_MKENUMS) --template $< $(filter %.h,$^) > xgen-vtbh \
 	&& (cmp -s xgen-vtbh vtetypebuiltins.h || cp xgen-vtbh vtetypebuiltins.h ) \
 	&& rm -f xgen-vtbh \
 	&& echo timestamp > $(@F)
 
-vtetypebuiltins.c: vtetypebuiltins.c.template vte.h
-	$(AM_V_GEN) $(GLIB_MKENUMS) --template $< $(filter-out $<,$^) > xgen-vtbc \
+vtetypebuiltins.c: vtetypebuiltins.c.template vte.h vtepty.h
+	$(AM_V_GEN) $(GLIB_MKENUMS) --template $< $(filter %.h,$^) > xgen-vtbc \
 	&& (cmp -s xgen-vtbc vtetypebuiltins.c || cp xgen-vtbc vtetypebuiltins.c ) \
 	&& rm -f xgen-vtbc
 
diff --git a/src/pty.c b/src/pty.c
index 4968e6a..d50d12b 100644
--- a/src/pty.c
+++ b/src/pty.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2001,2002 Red Hat, Inc.
+ * Copyright © 2009, 2010 Christian Persch
  *
  * This is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Library General Public License as published by
@@ -16,7 +17,23 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+/**
+ * SECTION: vte-pty
+ * @short_description: Functions for starting a new process on a new pseudo-terminal and for
+ * manipulating pseudo-terminals
+ *
+ * The terminal widget uses these functions to start commands with new controlling
+ * pseudo-terminals and to resize pseudo-terminals.
+ *
+ * Since: 0.24
+ */
+
 #include <config.h>
+
+#include "vtepty.h"
+#include "vtepty-private.h"
+#include "vte.h"
+
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -41,6 +58,7 @@
 #include <stropts.h>
 #endif
 #include <glib.h>
+#include <gio/gio.h>
 #include "debug.h"
 #include "pty.h"
 
@@ -61,7 +79,6 @@
 static gboolean _vte_pty_helper_started = FALSE;
 static pid_t _vte_pty_helper_pid = -1;
 static int _vte_pty_helper_tunnel = -1;
-static GTree *_vte_pty_helper_map = NULL;
 #endif
 
 /* Reset the handlers for all known signals to their defaults.  The parent
@@ -144,7 +161,9 @@ _vte_pty_reset_signal_handlers(void)
 #endif
 }
 
-struct vte_pty_child_setup_data {
+typedef struct _VtePtyPrivate VtePtyPrivate;
+
+typedef struct {
 	enum {
 		TTY_OPEN_BY_NAME,
 		TTY_OPEN_BY_FD
@@ -153,31 +172,90 @@ struct vte_pty_child_setup_data {
 		const char *name;
 		int fd;
 	} tty;
+} VtePtyChildSetupData;
+
+/**
+ * VtePty:
+ *
+ * Since: 0.24
+ */
+struct _VtePty {
+        GObject parent_instance;
+
+        /* <private> */
+        VtePtyPrivate *priv;
 };
-static void
-vte_pty_child_setup (gpointer arg)
+
+struct _VtePtyPrivate {
+        VtePtyFlags flags;
+        int pty_fd;
+
+        const char *term;
+        VtePtyChildSetupData child_setup_data;
+
+        gpointer helper_tag; /* only use when using_helper is TRUE */
+
+        guint utf8 : 1;
+        guint foreign : 1;
+        guint using_helper : 1;
+};
+
+struct _VtePtyClass {
+        GObjectClass parent_class;
+};
+
+/**
+ * vte_pty_child_setup:
+ * @pty: a #VtePty
+ *
+ * FIXMEchpe
+ *
+ * Since: 0.24
+ */
+void
+vte_pty_child_setup (VtePty *pty)
 {
-	struct vte_pty_child_setup_data *data = arg;
+        VtePtyPrivate *priv = pty->priv;
+	VtePtyChildSetupData *data = &priv->child_setup_data;
 	int fd = -1;
 	const char *tty = NULL;
 
+        if (priv->foreign) {
+                fd = priv->pty_fd;
+        } else {
+                /* Save the name of the pty -- we'll need it later to acquire
+                * it as our controlling terminal.
+                */
+                switch (data->mode) {
+                        case TTY_OPEN_BY_NAME:
+                                tty = data->tty.name;
+                                break;
+                        case TTY_OPEN_BY_FD:
+                                fd = data->tty.fd;
+                                tty = ttyname(fd);
+                                break;
+                }
+
+                _vte_debug_print (VTE_DEBUG_PTY,
+                                "Setting up child pty: name = %s, fd = %d\n",
+                                        tty ? tty : "(none)", fd);
+
+
+                /* Try to reopen the pty to acquire it as our controlling terminal. */
+                /* FIXMEchpe: why not just use the passed fd in TTY_OPEN_BY_FD mode? */
+                if (tty != NULL) {
+                        int i = open(tty, O_RDWR);
+                        if (i != -1) {
+                                if (fd != -1){
+                                        close(fd);
+                                }
+                                fd = i;
+                        }
+                }
+        }
 
-	/* Save the name of the pty -- we'll need it later to acquire
-	 * it as our controlling terminal. */
-	switch (data->mode) {
-		case TTY_OPEN_BY_NAME:
-			tty = data->tty.name;
-			break;
-		case TTY_OPEN_BY_FD:
-			fd = data->tty.fd;
-			tty = ttyname(fd);
-			break;
-	}
-
-	_vte_debug_print (VTE_DEBUG_PTY,
-			"Setting up child pty: name = %s, fd = %d\n",
-				tty ? tty : "(none)", fd);
-
+	if (fd == -1)
+		_exit (127);
 
 	/* Start a new session and become process-group leader. */
 #if defined(HAVE_SETSID) && defined(HAVE_SETPGID)
@@ -186,20 +264,6 @@ vte_pty_child_setup (gpointer arg)
 	setpgid(0, 0);
 #endif
 
-	/* Try to reopen the pty to acquire it as our controlling terminal. */
-	if (tty != NULL) {
-		int i = open(tty, O_RDWR);
-		if (i != -1) {
-			if (fd != -1){
-				close(fd);
-			}
-			fd = i;
-		}
-	}
-
-	if (fd == -1)
-		_exit (127);
-
 #ifdef TIOCSCTTY
 	/* TIOCSCTTY is defined?  Let's try that, too. */
 	ioctl(fd, TIOCSCTTY, fd);
@@ -248,29 +312,73 @@ vte_pty_child_setup (gpointer arg)
 		close(fd);
 	}
 
-
 	/* Reset our signals -- our parent may have done any number of
 	 * weird things to them. */
 	_vte_pty_reset_signal_handlers();
+
+        /* Now set the TERM environment variable */
+        if (priv->term != NULL) {
+                g_setenv("TERM", priv->term, TRUE);
+        }
 }
 
 /* TODO: clean up the spawning
  * - replace current env rather than adding!
- * - allow user control over flags (eg DO_NOT_CLOSE)
- * - additional user callback for child setup
  */
 
-static void
-collect_variables (char *name, char *value, GPtrArray *array)
+/*
+ * __vte_pty_get_argv:
+ * @command: the command to run
+ * @argv: the argument vector
+ * @flags: (inout) flags from #GSpawnFlags
+ *
+ * Creates an argument vector to pass to g_spawn_async() from @command and
+ * @argv, modifying * flags if necessary.
+ *
+ * Returns: a newly allocated array of strings. Free with g_strfreev()
+ */
+char **
+__vte_pty_get_argv (const char *command,
+                    char **argv,
+                    GSpawnFlags *flags /* inout */)
 {
-	g_ptr_array_add (array,
-			g_strconcat (name, "=", value, NULL));
+	char **argv2;
+	int i, argc;
+
+        g_return_val_if_fail(command != NULL, NULL);
+
+	/* push the command into argv[0] */
+	argc = argv ? g_strv_length (argv) : 0;
+	argv2 = g_new (char *, argc + 2);
+
+	argv2[0] = g_strdup (command);
+
+	for (i = 0; i < argc; i++) {
+		argv2[i+1] = g_strdup (argv[i]);
+	}
+	argv2[i+1] = NULL;
+
+        if (argv) {
+                *flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+        }
+
+	return argv2;
 }
 
+/*
+ * __vte_pty_merge_environ:
+ * @envp: environment vector
+ *
+ * Merges @envp to the parent environment, and returns a new environment vector.
+ *
+ * Returns: a newly allocated string array. Free using g_strfreev()
+ */
 static gchar **
-merge_environ (char **envp)
+__vte_pty_merge_environ (char **envp)
 {
 	GHashTable *table;
+        GHashTableIter iter;
+        char *name, *value;
 	gchar **parent_environ;
 	GPtrArray *array;
 	gint i;
@@ -287,8 +395,8 @@ merge_environ (char **envp)
 
 	if (envp != NULL) {
 		for (i = 0; envp[i] != NULL; i++) {
-			gchar *name = g_strdup (envp[i]);
-			gchar *value = strchr (name, '=');
+			name = g_strdup (envp[i]);
+			value = strchr (name, '=');
 			if (value) {
 				*value = '\0';
 				value = g_strdup (value + 1);
@@ -298,175 +406,221 @@ merge_environ (char **envp)
 	}
 
 	array = g_ptr_array_sized_new (g_hash_table_size (table) + 1);
-	g_hash_table_foreach (table, (GHFunc) collect_variables, array);
+        g_hash_table_iter_init(&iter, table);
+        while (g_hash_table_iter_next(&iter, (gpointer) &name, (gpointer) &value)) {
+                g_ptr_array_add (array, g_strconcat (name, "=", value, NULL));
+        }
+        g_assert(g_hash_table_size(table) == array->len);
 	g_hash_table_destroy (table);
 	g_ptr_array_add (array, NULL);
+
 	return (gchar **) g_ptr_array_free (array, FALSE);
 }
 
-/* Run the given command (if specified) */
-static gboolean
-_vte_pty_run_on_pty (struct vte_pty_child_setup_data *data,
-		     const char *command, char **argv, char **envp,
-		     const char *directory,
-		     GPid *pid, GError **error)
+/*
+ * __vte_pty_get_pty_flags:
+ * @lastlog: %TRUE if the session should be logged to the lastlog
+ * @utmp: %TRUE if the session should be logged to the utmp/utmpx log
+ * @wtmp: %TRUE if the session should be logged to the wtmp/wtmpx log
+ *
+ * Combines the @lastlog, @utmp, @wtmp arguments into the coresponding
+ * #VtePtyFlags flags.
+ *
+ * Returns: flags from #VtePtyFlags
+ */
+VtePtyFlags
+__vte_pty_get_pty_flags(gboolean lastlog,
+                        gboolean utmp,
+                        gboolean wtmp)
 {
-	gboolean ret = TRUE;
-	GError *local_error = NULL;
-
-	if (command != NULL) {
-		gchar **arg2, **envp2;
-		gint i, argc;
-
-		/* push the command into argv[0] */
-		argc = argv ? g_strv_length (argv) : 0;
-		arg2 = g_new (char *, argc + 2);
-		arg2[0] = g_strdup (command);
-		for (i = 0; i < argc; i++) {
-			arg2[i+1] = g_strdup (argv[i]);
-		}
-		arg2[i+1] = NULL;
-
-		/* add the given environment to the childs */
-		envp2 = merge_environ (envp);
-
-		_VTE_DEBUG_IF (VTE_DEBUG_MISC) {
-			g_printerr ("Spawing command '%s'\n", command);
-			for (i = 0; arg2[i] != NULL; i++) {
-				g_printerr ("    argv[%d] = %s\n", i, arg2[i]);
-			}
-			for (i = 0; envp2[i] != NULL; i++) {
-				g_printerr ("    env[%d] = %s\n", i, envp2[i]);
-			}
-			g_printerr ("    directory: %s\n",
-					directory ? directory : "(none)");
-		}
+        VtePtyFlags flags = VTE_PTY_DEFAULT;
 
-		ret = g_spawn_async_with_pipes (directory,
-				arg2, envp2,
-				G_SPAWN_CHILD_INHERITS_STDIN |
-				G_SPAWN_SEARCH_PATH |
-				G_SPAWN_DO_NOT_REAP_CHILD |
-				(argv ? G_SPAWN_FILE_AND_ARGV_ZERO : 0),
-				vte_pty_child_setup, data,
-				pid,
-				NULL, NULL, NULL,
-				&local_error);
-		if (ret == FALSE) {
-			if (g_error_matches (local_error,
-						G_SPAWN_ERROR,
-						G_SPAWN_ERROR_CHDIR)) {
-				/* try spawning in our working directory */
-				g_clear_error (&local_error);
-				ret = g_spawn_async_with_pipes (NULL,
-						arg2, envp2,
-						G_SPAWN_CHILD_INHERITS_STDIN |
-						G_SPAWN_SEARCH_PATH |
-						G_SPAWN_DO_NOT_REAP_CHILD |
-						(argv ? G_SPAWN_FILE_AND_ARGV_ZERO : 0),
-						vte_pty_child_setup, data,
-						pid,
-						NULL, NULL, NULL,
-						&local_error);
-			}
-		}
-		g_strfreev (arg2);
-		g_strfreev (envp2);
-
-		_vte_debug_print (VTE_DEBUG_MISC,
-				"Spawn result: %s%s\n",
-				ret?"Success":"Failure - ",
-				ret?"":local_error->message);
-		if (local_error)
-			g_propagate_error (error, local_error);
-	}
-#ifdef HAVE_FORK
-	else {
-		*pid = fork();
-		switch (*pid) {
-			case -1:
-				g_set_error (error,
-						G_SPAWN_ERROR,
-						G_SPAWN_ERROR_FAILED,
-						"Unable to fork: %s",
-						g_strerror (errno));
-				ret = FALSE;
-			case 0: /* child */
-				vte_pty_child_setup (data);
-				break;
-			default: /* parent */
-				break;
-		}
-	}
-#endif
+        if (!lastlog)
+                flags |= VTE_PTY_NO_LASTLOG;
+        if (!utmp)
+                flags |= VTE_PTY_NO_UTMP;
+        if (!wtmp)
+                flags |= VTE_PTY_NO_WTMP;
 
-	return ret;
+        return flags;
 }
 
-/* Open the named PTY slave, fork off a child (storing its PID in child),
- * and exec the named command in its own session as a process group leader */
-static gboolean
-_vte_pty_fork_on_pty_name (const char *path, int parent_fd, char **envp,
-			   const char *command, char **argv,
-			   const char *directory,
-			   int columns, int rows, GPid *child)
+/*
+ * __vte_pty_spawn:
+ * @pty: a #VtePty
+ * @directory: the name of a directory the command should start in, or %NULL
+ *   to use the cwd
+ * @argv: child's argument vector
+ * @envv: a list of environment variables to be added to the environment before
+ *   starting the process, or %NULL
+ * @spawn_flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @child_setup_data: user data for @child_setup
+ * @child_pid: a location to store the child PID, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Uses g_spawn_async() to spawn the command in @argv. The child's environment will
+ * be the parent environment with the variables in @envv set afterwards.
+ *
+ * Enforces the vte_terminal_watch_child() requirements by adding
+ * %G_SPAWN_DO_NOT_REAP_CHILD to @spawn_flags.
+ *
+ * Note that the %G_SPAWN_LEAVE_DESCRIPTORS_OPEN flag is not supported;
+ * it will be cleared!
+ *
+ * If spawning the command in @working_directory fails because the child
+ * is unable to chdir() to it, falls back trying to spawn the command
+ * in the parent's working directory.
+ *
+ * Returns: %TRUE on success, or %FALSE on failure with @error filled in
+ */
+gboolean
+__vte_pty_spawn (VtePty *pty,
+                 const char *directory,
+                 char **argv,
+                 char **envv,
+                 GSpawnFlags spawn_flags,
+                 GSpawnChildSetupFunc child_setup,
+                 gpointer child_setup_data,
+                 GPid *child_pid /* out */,
+                 GError **error)
 {
-	struct vte_pty_child_setup_data data;
-
-	data.mode = TTY_OPEN_BY_NAME;
-	data.tty.name = path;
-
-	if (!_vte_pty_run_on_pty(&data,
-			command, argv, envp, directory,
-			child, NULL)) {
-		/* XXX propagate the error */
-		return FALSE;
-	}
-
-	_vte_pty_set_size(parent_fd, columns, rows);
-	return TRUE;
+	gboolean ret = TRUE;
+        char **envp2;
+        gint i;
+        GError *err = NULL;
+
+        spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
+
+        /* FIXMEchpe: Enforce this until I've checked our code to make sure
+         * it doesn't leak out internal FDs into the child this way.
+         */
+        spawn_flags &= ~G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
+
+        /* add the given environment to the childs */
+        envp2 = __vte_pty_merge_environ (envv);
+
+        _VTE_DEBUG_IF (VTE_DEBUG_MISC) {
+                g_printerr ("Spawing command:\n");
+                for (i = 0; argv[i] != NULL; i++) {
+                        g_printerr ("    argv[%d] = %s\n", i, argv[i]);
+                }
+                for (i = 0; envp2[i] != NULL; i++) {
+                        g_printerr ("    env[%d] = %s\n", i, envp2[i]);
+                }
+                g_printerr ("    directory: %s\n",
+                            directory ? directory : "(none)");
+        }
+
+        ret = g_spawn_async_with_pipes(directory,
+                                       argv, envp2,
+                                       spawn_flags,
+                                       child_setup ? child_setup
+                                                   : (GSpawnChildSetupFunc) vte_pty_child_setup,
+                                       child_setup ? child_setup_data : pty,
+                                       child_pid,
+                                       NULL, NULL, NULL,
+                                       &err);
+        if (!ret &&
+            directory != NULL &&
+            g_error_matches(err, G_SPAWN_ERROR, G_SPAWN_ERROR_CHDIR)) {
+                /* try spawning in our working directory */
+                g_clear_error(&err);
+                ret = g_spawn_async_with_pipes(NULL,
+                                               argv, envp2,
+                                               spawn_flags,
+                                               child_setup ? child_setup
+                                                           : (GSpawnChildSetupFunc) vte_pty_child_setup,
+                                               child_setup ? child_setup_data : pty,
+                                               child_pid,
+                                               NULL, NULL, NULL,
+                                               &err);
+        }
+
+        g_strfreev (envp2);
+
+        if (ret)
+                return TRUE;
+
+        g_propagate_error (error, err);
+        return FALSE;
 }
 
-/* Fork off a child (storing its PID in child), and exec the named command
- * in its own session as a process group leader using the given terminal. */
-static gboolean
-_vte_pty_fork_on_pty_fd (int fd, char **envp,
-			 const char *command, char **argv,
-			 const char *directory,
-			 int columns, int rows, GPid *child)
+/*
+ * __vte_pty_fork:
+ * @pty: a #VtePty
+ * @pid: (out) a location to store a #GPid, or %NULL
+ * @error: a location to store a #GError, or %NULL
+ *
+ * Forks and calls vte_pty_child_setup() in the child.
+ *
+ * Returns: %TRUE on success, or %FALSE on failure with @error filled in
+ */
+gboolean
+__vte_pty_fork(VtePty *pty,
+               GPid *pid,
+               GError **error)
 {
-	struct vte_pty_child_setup_data data;
-
-	data.mode = TTY_OPEN_BY_FD;
-	data.tty.fd = fd;
-
-	if (!_vte_pty_run_on_pty(&data,
-				command, argv, envp, directory,
-				child, NULL)) {
-		/* XXX propagate the error */
-		return FALSE;
-	}
+#ifdef HAVE_FORK
+        gboolean ret = TRUE;
+
+        *pid = fork();
+        switch (*pid) {
+                case -1:
+                        g_set_error(error,
+                                    G_SPAWN_ERROR,
+                                    G_SPAWN_ERROR_FAILED,
+                                    "Unable to fork: %s",
+                                    g_strerror(errno));
+                        ret = FALSE;
+                case 0: /* child */
+                        vte_pty_child_setup(pty);
+                        break;
+                default: /* parent */
+                        break;
+        }
 
-	_vte_pty_set_size(fd, columns, rows);
-	return TRUE;
+	return ret;
+#else /* !HAVE_FORK */
+        g_set_error_literal(error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+                            "No fork implementation");
+        return FALSE;
+#endif /* HAVE_FORK */
 }
 
 /**
  * vte_pty_set_size:
- * @master: the file descriptor of the pty master
- * @columns: the desired number of columns
+ * @pty: a #VtePty
  * @rows: the desired number of rows
+ * @columns: the desired number of columns
+ * @error: return location to store a #GError, or %NULL
  *
  * Attempts to resize the pseudo terminal's window size.  If successful, the
  * OS kernel will send #SIGWINCH to the child process group.
  *
- * Returns: 0 on success, -1 on failure.
+ * If setting the window size failed, @error will be set to a #GIOError.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ *
+ * Since: 0.24
  */
-int
-_vte_pty_set_size(int master, int columns, int rows)
+gboolean
+vte_pty_set_size(VtePty *pty,
+                 int rows,
+                 int columns,
+                 GError **error)
 {
+        VtePtyPrivate *priv;
 	struct winsize size;
+        int master;
 	int ret;
+
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+
+        priv = pty->priv;
+
+        master = vte_pty_get_fd(pty);
+
 	memset(&size, 0, sizeof(size));
 	size.ws_row = rows ? rows : 24;
 	size.ws_col = columns ? columns : 80;
@@ -475,28 +629,57 @@ _vte_pty_set_size(int master, int columns, int rows)
 			master, columns, rows);
 	ret = ioctl(master, TIOCSWINSZ, &size);
 	if (ret != 0) {
+                int errsv = errno;
+
+                g_set_error(error, G_IO_ERROR,
+                            g_io_error_from_errno(errsv),
+                            "Failed to set window size: %s",
+                            g_strerror(errno));
+
 		_vte_debug_print(VTE_DEBUG_PTY,
 				"Failed to set size on %d: %s.\n",
-				master, strerror(errno));
+				master, g_strerror(errsv));
+
+                errno = errsv;
+
+                return FALSE;
 	}
-	return ret;
+
+        return TRUE;
 }
 
 /**
  * vte_pty_get_size:
- * @master: the file descriptor of the pty master
- * @columns: a place to store the number of columns
+ * @pty: a #VtePty
  * @rows: a place to store the number of rows
+ * @columns: a place to store the number of columns
+ * @error: return location to store a #GError, or %NULL
  *
- * Attempts to read the pseudo terminal's window size.
+ * Reads the pseudo terminal's window size.
  *
- * Returns: 0 on success, -1 on failure.
+ * If getting the window size failed, @error will be set to a #GIOError.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ *
+ * Since: 0.24
  */
-int
-_vte_pty_get_size(int master, int *columns, int *rows)
+gboolean
+vte_pty_get_size(VtePty *pty,
+                 int *rows,
+                 int *columns,
+                 GError **error)
 {
+        VtePtyPrivate *priv;
 	struct winsize size;
+        int master;
 	int ret;
+
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+
+        priv = pty->priv;
+
+        master = vte_pty_get_fd(pty);
+
 	memset(&size, 0, sizeof(size));
 	ret = ioctl(master, TIOCGWINSZ, &size);
 	if (ret == 0) {
@@ -509,14 +692,32 @@ _vte_pty_get_size(int master, int *columns, int *rows)
 		_vte_debug_print(VTE_DEBUG_PTY,
 				"Size on fd %d is (%d,%d).\n",
 				master, size.ws_col, size.ws_row);
+                return TRUE;
 	} else {
+                int errsv = errno;
+
+                g_set_error(error, G_IO_ERROR,
+                            g_io_error_from_errno(errsv),
+                            "Failed to get window size: %s",
+                            g_strerror(errno));
+
 		_vte_debug_print(VTE_DEBUG_PTY,
-				"Failed to read size from fd %d.\n",
-				master);
+				"Failed to read size from fd %d: %s\n",
+				master, g_strerror(errsv));
+
+                errno = errsv;
+
+                return FALSE;
 	}
-	return ret;
 }
 
+/*
+ * _vte_pty_ptsname:
+ * @master: file descriptor to the PTY master
+ *
+ * Returns: a newly allocated string containing the file name of the
+ *   PTY slave device, or %NULL on failure
+ */
 static char *
 _vte_pty_ptsname(int master)
 {
@@ -558,6 +759,14 @@ _vte_pty_ptsname(int master)
 	return NULL;
 }
 
+/*
+ * _vte_pty_getpt:
+ *
+ * Opens a file descriptor for the next available PTY master.
+ * Sets the descriptor to blocking mode!
+ *
+ * Returns: a new file descriptor, or %-1 on failure
+ */
 static int
 _vte_pty_getpt(void)
 {
@@ -572,7 +781,11 @@ _vte_pty_getpt(void)
 		fd = open("/dev/ptc", O_RDWR | O_NOCTTY); /* AIX */
 	}
 #endif
+        if (fd == -1)
+                return fd;
+
 	/* Set it to blocking. */
+        /* FIXMEchpe: why?? vte_terminal_set_pty does the inverse... */
 	flags = fcntl(fd, F_GETFL);
 	flags &= ~(O_NONBLOCK);
 	fcntl(fd, F_SETFL, flags);
@@ -602,40 +815,58 @@ _vte_pty_unlockpt(int fd)
 #endif
 }
 
-static int
-_vte_pty_open_unix98(GPid *child, char **env_add,
-		     const char *command, char **argv,
-		     const char *directory, int columns, int rows)
+/*
+ * _vte_pty_open_unix98:
+ * @pty: a #VtePty
+ * @error: a location to store a #GError, or %NULL
+ *
+ * Opens a new file descriptor to a new PTY master.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ */
+static gboolean
+_vte_pty_open_unix98(VtePty *pty,
+                     GError **error)
 {
+        VtePtyPrivate *priv = pty->priv;
 	int fd;
 	char *buf;
 
 	/* Attempt to open the master. */
 	fd = _vte_pty_getpt();
 	_vte_debug_print(VTE_DEBUG_PTY, "Allocated pty on fd %d.\n", fd);
-	if (fd != -1) {
-		/* Read the slave number and unlock it. */
-		if (((buf = _vte_pty_ptsname(fd)) == NULL) ||
-		    (_vte_pty_grantpt(fd) != 0) ||
-		    (_vte_pty_unlockpt(fd) != 0)) {
-			_vte_debug_print(VTE_DEBUG_PTY,
-					"PTY setup failed, bailing.\n");
-			close(fd);
-			fd = -1;
-		} else {
-			/* Start up a child process with the given command. */
-			if (!_vte_pty_fork_on_pty_name(buf, fd,
-						      env_add, command,
-						      argv, directory,
-						      columns, rows,
-						      child)) {
-				close(fd);
-				fd = -1;
-			}
-			g_free(buf);
-		}
-	}
-	return fd;
+	if (fd == -1) {
+                g_set_error (error, VTE_PTY_ERROR,
+                             VTE_PTY_ERROR_PTY98_FAILED,
+                             "getpt failed: %s", g_strerror(errno));
+                return FALSE;
+        }
+
+        /* Read the slave number and unlock it. */
+        if (((buf = _vte_pty_ptsname(fd)) == NULL) ||
+            (_vte_pty_grantpt(fd) != 0) ||
+            (_vte_pty_unlockpt(fd) != 0)) {
+                int errsv = errno;
+
+                g_set_error(error, VTE_PTY_ERROR,
+                            VTE_PTY_ERROR_PTY98_FAILED,
+                            "PTY setup failed: %s",
+                            g_strerror(errsv));
+
+                _vte_debug_print(VTE_DEBUG_PTY,
+                                "PTY setup failed, bailing.\n");
+                close(fd);
+                errno = errsv;
+
+                return FALSE;
+        }
+
+        priv->pty_fd = fd;
+        priv->child_setup_data.mode = TTY_OPEN_BY_NAME;
+        priv->child_setup_data.tty.name = buf;
+        priv->using_helper = FALSE;
+
+        return TRUE;
 }
 
 #ifdef VTE_USE_GNOME_PTY_HELPER
@@ -799,14 +1030,15 @@ n_write(int fd, const void *buffer, size_t count)
 	return n;
 }
 
-
-
+/*
+ * _vte_pty_stop_helper:
+ *
+ * Terminates the running GNOME PTY helper.
+ */
 static void
 _vte_pty_stop_helper(void)
 {
 	if (_vte_pty_helper_started) {
-		g_tree_destroy(_vte_pty_helper_map);
-		_vte_pty_helper_map = NULL;
 		close(_vte_pty_helper_tunnel);
 		_vte_pty_helper_tunnel = -1;
 		kill(_vte_pty_helper_pid, SIGTERM);
@@ -815,43 +1047,55 @@ _vte_pty_stop_helper(void)
 	}
 }
 
-static gint
-_vte_direct_compare(gconstpointer a, gconstpointer b)
-{
-	return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
-}
-
+/*
+ * _vte_pty_start_helper:
+ * @error: a location to store a #GError, or %NULL
+ *
+ * Starts the GNOME PTY helper process, if it is not already running.
+ *
+ * Returns: %TRUE if the helper was already started, or starting it succeeded,
+ *   %FALSE on failure with @error filled in
+ */
 static gboolean
-_vte_pty_start_helper(void)
+_vte_pty_start_helper(GError **error)
 {
-	int i, tmp[2], tunnel;
+	int i, tunnel;
+        int tmp[2] = { -1, -1 };
+
+        if (_vte_pty_helper_started)
+                return TRUE;
+
 	/* Sanity check. */
 	if (access(LIBEXECDIR "/gnome-pty-helper", X_OK) != 0) {
 		/* Give the user some clue as to why session logging is not
 		 * going to work (assuming we can open a pty using some other
 		 * method). */
 		g_warning(_("can not run %s"), LIBEXECDIR "/gnome-pty-helper");
+		g_set_error(error, VTE_PTY_ERROR,
+                            VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                            _("can not run %s"), LIBEXECDIR "/gnome-pty-helper");
 		return FALSE;
 	}
 	/* Create a communication link for use with the helper. */
 	tmp[0] = open("/dev/null", O_RDONLY);
 	if (tmp[0] == -1) {
-		return FALSE;
+		goto failure;
 	}
 	tmp[1] = open("/dev/null", O_RDONLY);
 	if (tmp[1] == -1) {
-		close(tmp[0]);
-		return FALSE;
+		goto failure;
 	}
 	if (_vte_pty_pipe_open(&_vte_pty_helper_tunnel, &tunnel) != 0) {
-		return FALSE;
+		goto failure;
 	}
 	close(tmp[0]);
 	close(tmp[1]);
+        tmp[0] = tmp[1] = -1;
+
 	/* Now fork and start the helper. */
 	_vte_pty_helper_pid = fork();
 	if (_vte_pty_helper_pid == -1) {
-		return FALSE;
+		goto failure;
 	}
 	if (_vte_pty_helper_pid == 0) {
 		/* Child.  Close descriptors.  No need to close all,
@@ -871,49 +1115,125 @@ _vte_pty_start_helper(void)
 		_exit(1);
 	}
 	close(tunnel);
-	_vte_pty_helper_map = g_tree_new(_vte_direct_compare);
 	atexit(_vte_pty_stop_helper);
+
+        _vte_pty_helper_started = TRUE;
 	return TRUE;
+
+failure:
+        g_set_error(error, VTE_PTY_ERROR,
+                    VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                    "Failed to start gnome-pty-helper: %s",
+                    g_strerror (errno));
+
+        if (tmp[0] != -1)
+                close(tmp[0]);
+        if (tmp[1] != -1)
+                close(tmp[1]);
+
+        return FALSE;
 }
 
+/*
+ * _vte_pty_helper_ops_from_flags:
+ * @flags: flags from #VtePtyFlags
+ *
+ * Translates @flags into the corresponding op code for the
+ * GNOME PTY helper.
+ *
+ * Returns: the #GnomePtyOps corresponding to @flags
+ */
 static int
-_vte_pty_open_with_helper(GPid *child, char **env_add,
-			  const char *command, char **argv,
-			  const char *directory,
-			  int columns, int rows, int op)
+_vte_pty_helper_ops_from_flags (VtePtyFlags flags)
 {
+	int op = 0;
+	static const int opmap[8] = {
+		GNOME_PTY_OPEN_NO_DB_UPDATE,		/* 0 0 0 */
+		GNOME_PTY_OPEN_PTY_LASTLOG,		/* 0 0 1 */
+		GNOME_PTY_OPEN_PTY_UTMP,		/* 0 1 0 */
+		GNOME_PTY_OPEN_PTY_LASTLOGUTMP,		/* 0 1 1 */
+		GNOME_PTY_OPEN_PTY_WTMP,		/* 1 0 0 */
+		GNOME_PTY_OPEN_PTY_LASTLOGWTMP,		/* 1 0 1 */
+		GNOME_PTY_OPEN_PTY_UWTMP,		/* 1 1 0 */
+		GNOME_PTY_OPEN_PTY_LASTLOGUWTMP,	/* 1 1 1 */
+	};
+	if ((flags & VTE_PTY_NO_LASTLOG) == 0) {
+		op += 1;
+	}
+	if ((flags & VTE_PTY_NO_UTMP) == 0) {
+		op += 2;
+	}
+	if ((flags & VTE_PTY_NO_WTMP) == 0) {
+		op += 4;
+	}
+	g_assert(op >= 0 && op < (int) G_N_ELEMENTS(opmap));
+
+        return opmap[op];
+}
+
+/*
+ * _vte_pty_open_with_helper:
+ * @pty: a #VtePty
+ * @error: a location to store a #GError, or %NULL
+ *
+ * Opens a new file descriptor to a new PTY master using the
+ * GNOME PTY helper.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ */
+static gboolean
+_vte_pty_open_with_helper(VtePty *pty,
+                          GError **error)
+{
+        VtePtyPrivate *priv = pty->priv;
 	GnomePtyOps ops;
 	int ret;
 	int parentfd = -1, childfd = -1;
 	gpointer tag;
+
 	/* We have to use the pty helper here. */
-	if (!_vte_pty_helper_started) {
-		_vte_pty_helper_started = _vte_pty_start_helper();
-	}
+	if (!_vte_pty_start_helper(error))
+                return FALSE;
+
 	/* Try to open a new descriptor. */
-	if (_vte_pty_helper_started) {
-		ops = op;
+	{
+		ops = _vte_pty_helper_ops_from_flags(priv->flags);
 		/* Send our request. */
 		if (n_write(_vte_pty_helper_tunnel,
 			    &ops, sizeof(ops)) != sizeof(ops)) {
-			return -1;
+                        g_set_error (error, VTE_PTY_ERROR,
+                                     VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to send request to gnome-pty-helper: %s",
+                                     g_strerror(errno));
+			return FALSE;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY, "Sent request to helper.\n");
 		/* Read back the response. */
 		if (n_read(_vte_pty_helper_tunnel,
 			   &ret, sizeof(ret)) != sizeof(ret)) {
-			return -1;
+                        g_set_error (error, VTE_PTY_ERROR,
+                                     VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read response from gnome-pty-helper: %s",
+                                     g_strerror(errno));
+			return FALSE;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY,
 				"Received response from helper.\n");
 		if (ret == 0) {
-			return -1;
+                        g_set_error_literal (error, VTE_PTY_ERROR,
+                                             VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                                             "gnome-pty-helper failed to open pty");
+			return FALSE;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY, "Helper returns success.\n");
 		/* Read back a tag. */
 		if (n_read(_vte_pty_helper_tunnel,
 			   &tag, sizeof(tag)) != sizeof(tag)) {
-			return -1;
+                        g_set_error (error, VTE_PTY_ERROR,
+                                     VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read tag from gnome-pty-helper: %s",
+                                     g_strerror(errno));
+			return FALSE;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY, "Tag = %p.\n", tag);
 		/* Receive the master and slave ptys. */
@@ -921,152 +1241,101 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 				      &parentfd, &childfd);
 
 		if ((parentfd == -1) || (childfd == -1)) {
+                        int errsv = errno;
+
 			close(parentfd);
 			close(childfd);
-			return -1;
+
+                        g_set_error (error, VTE_PTY_ERROR,
+                                     VTE_PTY_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read master or slave pty from gnome-pty-helper: %s",
+                                     g_strerror(errsv));
+                        errno = errsv;
+			return FALSE;
 		}
+
 		_vte_debug_print(VTE_DEBUG_PTY,
 				"Got master pty %d and slave pty %d.\n",
 				parentfd, childfd);
 
-		/* Add the parent and the tag to our map. */
-		g_tree_insert(_vte_pty_helper_map,
-			      GINT_TO_POINTER(parentfd),
-			      tag);
-		/* Start up a child process with the given command. */
-		if (!_vte_pty_fork_on_pty_fd(childfd, env_add, command,
-					    argv, directory,
-					    columns, rows, child)) {
-			close(parentfd);
-			close(childfd);
-			return -1;
-		}
-		close(childfd);
-		return parentfd;
-	}
-	return -1;
-}
-#endif
+                priv->using_helper = TRUE;
+                priv->helper_tag = tag;
+                priv->pty_fd = parentfd;
 
-/**
- * _vte_pty_open:
- * @child: location to store the new process's ID
- * @env_add: a list of environment variables to add to the child's environment
- * @command: name of the binary to run
- * @argv: arguments to pass to @command
- * @directory: directory to start the new command in, or %NULL
- * @columns: desired window columns
- * @rows: desired window rows
- * @lastlog: %TRUE if the lastlog should be updated
- * @utmp: %TRUE if the utmp or utmpx log should be updated
- * @wtmp: %TRUE if the wtmp or wtmpx log should be updated
- *
- * Starts a new copy of @command running under a psuedo-terminal, optionally in
- * the supplied @directory, with window size set to @rows x @columns
- * and variables in @env_add added to its environment.  If any combination of
- * @lastlog, @utmp, and @wtmp is set, then the session is logged in the
- * corresponding system files.
- *
- * Returns: an open file descriptor for the pty master, -1 on failure
- */
-int
-_vte_pty_open(pid_t *child_pid, char **env_add,
-	      const char *command, char **argv, const char *directory,
-	      int columns, int rows,
-	      gboolean lastlog, gboolean utmp, gboolean wtmp)
-{
-	GPid child;
-	int ret = -1;
-#ifdef VTE_USE_GNOME_PTY_HELPER
-	int op = 0;
-	int opmap[8] = {
-		GNOME_PTY_OPEN_NO_DB_UPDATE,		/* 0 0 0 */
-		GNOME_PTY_OPEN_PTY_LASTLOG,		/* 0 0 1 */
-		GNOME_PTY_OPEN_PTY_UTMP,		/* 0 1 0 */
-		GNOME_PTY_OPEN_PTY_LASTLOGUTMP,		/* 0 1 1 */
-		GNOME_PTY_OPEN_PTY_WTMP,		/* 1 0 0 */
-		GNOME_PTY_OPEN_PTY_LASTLOGWTMP,		/* 1 0 1 */
-		GNOME_PTY_OPEN_PTY_UWTMP,		/* 1 1 0 */
-		GNOME_PTY_OPEN_PTY_LASTLOGUWTMP,	/* 1 1 1 */
-	};
-	if (lastlog) {
-		op += 1;
-	}
-	if (utmp) {
-		op += 2;
-	}
-	if (wtmp) {
-		op += 4;
-	}
-	g_assert(op >= 0 && op < (int) G_N_ELEMENTS(opmap));
-	if (ret == -1 && op != 0) {
-		ret = _vte_pty_open_with_helper(&child, env_add, command, argv,
-						directory,
-						columns, rows, opmap[op]);
-	}
-#endif
-	if (ret == -1) {
-		ret = _vte_pty_open_unix98(&child, env_add, command, argv,
-					   directory, columns, rows);
-	}
-	if (ret != -1) {
-		*child_pid = (pid_t) child;
+                priv->child_setup_data.mode = TTY_OPEN_BY_FD;
+                priv->child_setup_data.tty.fd = childfd;
+
+                // FIXMEchpe do this later
+//                 _vte_pty_set_size(fd, columns, rows);
+                return TRUE;
 	}
-	_vte_debug_print(VTE_DEBUG_PTY,
-			"Returning ptyfd = %d, child = %ld.\n",
-			ret, (long) child);
-	return ret;
 }
 
+#endif /* VTE_USE_GNOME_PTY_HELPER */
+
 /**
- * _vte_pty_set_utf8:
- * @pty: The pty master descriptor.
- * @utf8: Whether or not the pty is in UTF-8 mode.
+ * vte_pty_set_utf8:
+ * @pty: a #VtePty
+ * @utf8: Whether or not the pty is in UTF-8 mode
  *
  * Tells the kernel whether the terminal is UTF-8 or not, in case it can make
  * use of the info.  Linux 2.6.5 or so defines IUTF8 to make the line
  * discipline do multibyte backspace correctly.
+ *
+ * Since: 0.24
  */
 void
-_vte_pty_set_utf8(int pty, gboolean utf8)
+vte_pty_set_utf8(VtePty *pty,
+                 gboolean utf8)
 {
 #if defined(HAVE_TCSETATTR) && defined(IUTF8)
+        VtePtyPrivate *priv;
 	struct termios tio;
 	tcflag_t saved_cflag;
-	if (pty != -1) {
-		if (tcgetattr(pty, &tio) != -1) {
+
+        g_return_if_fail(VTE_IS_PTY(pty));
+        priv = pty->priv;
+
+        priv->utf8 = utf8 != FALSE;
+
+	if (priv->pty_fd != -1) {
+		if (tcgetattr(priv->pty_fd, &tio) != -1) {
 			saved_cflag = tio.c_iflag;
 			tio.c_iflag &= ~IUTF8;
 			if (utf8) {
 				tio.c_iflag |= IUTF8;
 			}
 			if (saved_cflag != tio.c_iflag) {
-				tcsetattr(pty, TCSANOW, &tio);
+				tcsetattr(priv->pty_fd, TCSANOW, &tio);
 			}
 		}
 	}
 #endif
+
+        g_object_notify(G_OBJECT(pty), "utf-8");
 }
 
 /**
- * _vte_pty_close:
- * @pty: the pty master descriptor.
+ * vte_pty_close:
+ * @pty: a #VtePty
  *
- * Cleans up the PTY associated with the descriptor, specifically any logging
- * performed for the session.  The descriptor itself remains open.
+ * Cleans up the PTY, specifically any logging performed for the session.
+ * The file descriptor to the PTY master remains open.
+ *
+ * Since: 0.24
  */
 void
-_vte_pty_close(int pty)
+vte_pty_close (VtePty *pty)
 {
 #ifdef VTE_USE_GNOME_PTY_HELPER
+        VtePtyPrivate *priv = pty->priv;
 	gpointer tag;
 	GnomePtyOps ops;
-	if (_vte_pty_helper_map != NULL) {
-		if (g_tree_lookup(_vte_pty_helper_map, GINT_TO_POINTER(pty))) {
+
+        if (priv->using_helper) {
 			/* Signal the helper that it needs to close its
 			 * connection. */
-			tag = g_tree_lookup(_vte_pty_helper_map,
-					    GINT_TO_POINTER(pty));
+			tag = priv->helper_tag;
 
 			ops = GNOME_PTY_CLOSE_PTY;
 			if (n_write(_vte_pty_helper_tunnel,
@@ -1085,73 +1354,626 @@ _vte_pty_close(int pty)
 			}
 			n_read(_vte_pty_helper_tunnel, &ops, 1);
 
-			/* Remove the item from the map. */
-			g_tree_remove(_vte_pty_helper_map,
-				      GINT_TO_POINTER(pty));
-		}
+                        priv->helper_tag = NULL;
+                        priv->using_helper = FALSE;
 	}
 #endif
 }
 
-#ifdef PTY_MAIN
-int fd;
+/* VTE PTY class */
+
+enum {
+        PROP_0,
+        PROP_FLAGS,
+        PROP_FD,
+        PROP_UTF8,
+        PROP_TERM
+};
+
+/* GInitable impl */
+
+static gboolean
+vte_pty_initable_init (GInitable *initable,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+        VtePty *pty = VTE_PTY (initable);
+        VtePtyPrivate *priv = pty->priv;
+        gboolean ret = FALSE;
+        GError *err = NULL;
+
+        if (cancellable != NULL) {
+                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                    "Cancellable initialisation not supported");
+                return FALSE;
+        }
+
+        /* If we already have a (foreign) FD, we're done. */
+        if (priv->foreign) {
+                g_assert(priv->pty_fd != -1);
+                // FIXMEchpe fill in the child_setup_data struct
+                return TRUE;
+        }
+
+#ifdef VTE_USE_GNOME_PTY_HELPER
+	if ((priv->flags & VTE_PTY_NO_HELPER) == 0) {
+		ret = _vte_pty_open_with_helper(pty, &err);
+                g_assert(ret || err != NULL);
+
+                if (ret)
+                        goto out;
+
+                /* Only do fallback if gnome-pty-helper failed! */
+                if ((priv->flags & VTE_PTY_NO_FALLBACK) ||
+                    !g_error_matches(err,
+                                     VTE_PTY_ERROR,
+                                     VTE_PTY_ERROR_PTY_HELPER_FAILED)) {
+                        g_propagate_error (error, err);
+                        goto out;
+                }
+
+                g_error_free(err);
+                /* Fall back to unix98 PTY */
+        }
+#endif /* VTE_USE_GNOME_PTY_HELPER */
+
+        ret = _vte_pty_open_unix98(pty, error);
+
+  out:
+	_vte_debug_print(VTE_DEBUG_PTY,
+			"vte_pty_initable_init returning %s with ptyfd = %d\n",
+			ret ? "TRUE" : "FALSE", priv->pty_fd);
+
+	return ret;
+}
+
+static void
+vte_pty_initable_iface_init (GInitableIface  *iface)
+{
+        iface->init = vte_pty_initable_init;
+}
+
+/* GObjectClass impl */
+
+G_DEFINE_TYPE_WITH_CODE (VtePty, vte_pty, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, vte_pty_initable_iface_init))
 
 static void
-sigchld_handler(int signum)
+vte_pty_init (VtePty *pty)
 {
-	/* This is very unsafe.  Never do it in production code. */
-	_vte_pty_close(fd);
+        VtePtyPrivate *priv;
+
+        priv = pty->priv = G_TYPE_INSTANCE_GET_PRIVATE (pty, VTE_TYPE_PTY, VtePtyPrivate);
+
+        priv->flags = VTE_PTY_DEFAULT;
+        priv->pty_fd = -1;
+        priv->foreign = FALSE;
+        priv->using_helper = FALSE;
+        priv->helper_tag = NULL;
+        priv->term = vte_terminal_get_default_emulation(NULL /* that's ok, this function is just retarded */); /* already interned */
 }
 
+static void
+vte_pty_finalize (GObject *object)
+{
+        VtePty *pty = VTE_PTY (object);
+        VtePtyPrivate *priv = pty->priv;
+
+        if (priv->child_setup_data.mode == TTY_OPEN_BY_FD &&
+            priv->child_setup_data.tty.fd != -1) {
+                /* Close the child FD */
+                close(priv->child_setup_data.tty.fd);
+        }
+
+        vte_pty_close(pty);
+
+        /* Close the master FD */
+        if (priv->pty_fd != -1) {
+                close(priv->pty_fd);
+        }
+
+        G_OBJECT_CLASS (vte_pty_parent_class)->finalize (object);
+}
+
+static void
+vte_pty_get_property (GObject    *object,
+                       guint       property_id,
+                       GValue     *value,
+                       GParamSpec *pspec)
+{
+        VtePty *pty = VTE_PTY (object);
+        VtePtyPrivate *priv = pty->priv;
+
+        switch (property_id) {
+        case PROP_FLAGS:
+                g_value_set_flags(value, priv->flags);
+                break;
+
+        case PROP_FD:
+                g_value_set_int(value, vte_pty_get_fd(pty));
+                break;
+
+        case PROP_UTF8:
+                g_value_set_boolean(value, priv->utf8);
+                break;
+
+        case PROP_TERM:
+                g_value_set_string(value, priv->term);
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+        }
+}
+
+static void
+vte_pty_set_property (GObject      *object,
+                       guint         property_id,
+                       const GValue *value,
+                       GParamSpec   *pspec)
+{
+        VtePty *pty = VTE_PTY (object);
+        VtePtyPrivate *priv = pty->priv;
+
+        switch (property_id) {
+        case PROP_FLAGS:
+                priv->flags = g_value_get_flags(value);
+                break;
+
+        case PROP_FD:
+                priv->pty_fd = g_value_get_int(value);
+                priv->foreign = (priv->pty_fd != -1);
+                break;
+
+        case PROP_UTF8:
+                vte_pty_set_utf8 (pty, g_value_get_boolean (value));
+                break;
+
+        case PROP_TERM:
+                vte_pty_set_term(pty, g_value_get_string(value));
+                break;
+
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        }
+}
+
+static void
+vte_pty_class_init (VtePtyClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        g_type_class_add_private(object_class, sizeof(VtePtyPrivate));
+
+        object_class->set_property = vte_pty_set_property;
+        object_class->get_property = vte_pty_get_property;
+        object_class->finalize     = vte_pty_finalize;
+
+        /**
+         * VtePty:flags:
+         *
+         * Controls how the session is recorded in lastlog, utmp, and wtmp,
+         * and whether to use the GNOME PTY helper.
+         *
+         * Since: 0.24
+         */
+        g_object_class_install_property
+                (object_class,
+                 PROP_FLAGS,
+                 g_param_spec_flags ("flags", NULL, NULL,
+                                     VTE_TYPE_PTY_FLAGS,
+                                     VTE_PTY_DEFAULT,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_CONSTRUCT_ONLY |
+                                     G_PARAM_STATIC_STRINGS));
+
+        /**
+         * VtePty:fd:
+         *
+         * The file descriptor of the PTY master.
+         *
+         * Since: 0.24
+         */
+        g_object_class_install_property
+                (object_class,
+                 PROP_FD,
+                 g_param_spec_int ("fd", NULL, NULL,
+                                   -1, G_MAXINT, -1,
+                                   G_PARAM_READWRITE |
+                                   G_PARAM_CONSTRUCT_ONLY |
+                                   G_PARAM_STATIC_STRINGS));
+
+        /**
+         * VtePty:utf-8:
+         *
+         * Whether the PTY is in UTF-8 mode.
+         *
+         * Since: 0.24
+         */
+        g_object_class_install_property
+                (object_class,
+                 PROP_UTF8,
+                 g_param_spec_boolean ("utf-8", NULL, NULL,
+                                       TRUE,
+                                       G_PARAM_READWRITE |
+                                       G_PARAM_STATIC_STRINGS));
+
+        /**
+         * VtePty:term:
+         *
+         * The value to set for the TERM environment variable
+         * in vte_pty_child_setup().
+         *
+         * Since: 0.24
+         */
+        g_object_class_install_property
+                (object_class,
+                 PROP_UTF8,
+                 g_param_spec_string ("term", NULL, NULL,
+                                      "xterm",
+                                      G_PARAM_READWRITE |
+                                      G_PARAM_STATIC_STRINGS));
+}
+
+/* public API */
+
+/**
+ * vte_pty_error_quark:
+ *
+ * Error domain for VTE PTY errors. Errors in this domain will be from the #VtePtyError
+ * enumeration. See #GError for more information on error domains.
+ *
+ * Returns: the error domain for VTE PTY errors
+ *
+ * Since: 0.24
+ */
+GQuark
+vte_pty_error_quark(void)
+{
+  static GQuark quark = 0;
+
+  if (G_UNLIKELY (quark == 0))
+    quark = g_quark_from_static_string("vte-pty-error");
+
+  return quark;
+}
+
+/**
+ * vte_pty_new:
+ * @flags: flags from #VtePtyFlags
+ * @error: return location for a #GError, or %NULL
+ *
+ * Allocates a new pseudo-terminal.
+ *
+ * You can later use fork() or the g_spawn_async() familty of functions
+ * to start a process on the PTY.
+ *
+ * If using fork(), you MUST call vte_pty_child_setup() in the child.
+ *
+ * If using g_spawn_async() and friends, you MUST either use
+ * vte_pty_child_setup () directly as the child setup function, or call
+ * vte_pty_child_setup() from your own child setup function supplied.
+ *
+ * Also, you MUST pass the %G_SPAWN_DO_NOT_REAP_CHILD flag.
+ *
+ * When using the GNOME PTY Helper,
+ * unless some of the %VTE_PTY_NO_LASTLOG, %VTE_PTY_NO_UTMP or
+ * %VTE_PTY_NO_WTMP flags are passed in @flags, the
+ * session is logged in the corresponding lastlog, utmp or wtmp
+ * system files.
+ *
+ * When passing %VTE_PTY_NO_HELPER in @flags, the
+ * GNOME PTY Helper bypassed entirely.
+ *
+ * When passing %VTE_PTY_NO_FALLBACK in @flags,
+ * and opening a PTY using the PTY helper fails, there will
+ * be no fallback to allocate a PTY using Unix98 PTY functions.
+ *
+ * Returns: a new #VtePty, or %NULL on error with @error filled in
+ *
+ * Since: 0.24
+ */
+VtePty *
+vte_pty_new (VtePtyFlags flags,
+             GError **error)
+{
+        return g_initable_new (VTE_TYPE_PTY,
+                               NULL /* cancellable */,
+                               error,
+                               "flags", flags,
+                               NULL);
+}
+
+/**
+ * vte_pty_new_foreign:
+ * @fd: a file descriptor to the PTY
+ *
+ * Creates a new #VtePty for the PTY master @fd.
+ *
+ * No entry will be made in the lastlog, utmp or wtmp system files.
+ *
+ * Note that the newly created #VtePty will take ownership of @fd
+ * and close it on finalize.
+ *
+ * Returns: a new #VtePty for @fd
+ *
+ * Since: 0.24
+ */
+VtePty *
+vte_pty_new_foreign (int fd)
+{
+        VtePty *pty;
+        GError *error = NULL;
+
+        g_return_val_if_fail(fd >= 0, NULL);
+
+        pty = g_initable_new (VTE_TYPE_PTY,
+                              NULL /* cancellable */,
+                              &error,
+                              "fd", fd,
+                              NULL);
+        g_assert(error == NULL);
+
+        return pty;
+}
+
+/**
+ * vte_pty_get_fd:
+ * @pty: a #VtePty
+ *
+ * Returns: the file descriptor of the PTY master in @pty. The
+ *   file descriptor belongs to @pty and must not be closed
+ */
 int
-main(int argc, char **argv)
+vte_pty_get_fd (VtePty *pty)
 {
-	GPid child = 0;
-	char c;
-	int ret;
-	signal(SIGCHLD, sigchld_handler);
-	_vte_debug_init();
-	fd = _vte_pty_open(&child, NULL,
-			   (argc > 1) ? argv[1] : NULL,
-			   (argc > 1) ? argv + 1 : NULL,
-			   NULL,
-			   0, 0,
-			   TRUE, TRUE, TRUE);
-	if (child == 0) {
-		int i;
-		for (i = 0; ; i++) {
-			switch (i % 3) {
-			case 0:
-			case 1:
-				g_print("%d\n", i);
-				break;
-			case 2:
-				g_printerr("%d\n", i);
-				break;
-			default:
-				g_assert_not_reached();
-				break;
-			}
-			sleep(1);
-		}
-	}
-	g_print("Child pid is %d.\n", (int)child);
-	do {
-		ret = n_read(fd, &c, 1);
-		if (ret == 0) {
-			break;
-		}
-		if ((ret == -1) && (errno != EAGAIN) && (errno != EINTR)) {
-			break;
-		}
-		if (argc < 2) {
-			n_write(STDOUT_FILENO, "[", 1);
-		}
-		n_write(STDOUT_FILENO, &c, 1);
-		if (argc < 2) {
-			n_write(STDOUT_FILENO, "]", 1);
-		}
-	} while (TRUE);
-	return 0;
+        VtePtyPrivate *priv;
+
+        g_return_val_if_fail(VTE_IS_PTY(pty), -1);
+
+        priv = pty->priv;
+        g_return_val_if_fail(priv->pty_fd != -1, -1);
+
+        return priv->pty_fd;
 }
-#endif
+
+/**
+ * vte_pty_set_term:
+ * @pty: a #VtePty
+ * @emulation: the name of a terminal description
+ *
+ * Sets what value of the TERM environment variable to set
+ * when using vte_pty_child_setup().
+ *
+ * Since: 0.24
+ */
+void
+vte_pty_set_term (VtePty *pty,
+                  const char *emulation)
+{
+        VtePtyPrivate *priv;
+
+        g_return_if_fail(VTE_IS_PTY(pty));
+        g_return_if_fail(emulation != NULL);
+
+        priv = pty->priv;
+        emulation = g_intern_string(emulation);
+        if (emulation == priv->term)
+                return;
+
+        priv->term = emulation;
+        g_object_notify(G_OBJECT(pty), "term");
+}
+
+/* Reimplementation of the ugly deprecated APIs _vte_pty_*() */
+
+#ifndef VTE_DISABLE_DEPRECATED_SOURCE
+
+static GHashTable *fd_to_pty_hash = NULL;
+
+static VtePty *
+get_vte_pty_for_fd (int fd)
+{
+        VtePty *pty;
+
+        if (fd_to_pty_hash != NULL &&
+            (pty = g_hash_table_lookup(fd_to_pty_hash, &fd)) != NULL)
+                return pty;
+
+        g_warning("No VtePty found for fd %d!\n", fd);
+        return NULL;
+}
+
+/**
+ * _vte_pty_open:
+ * @child: location to store the new process's ID
+ * @env_add: a list of environment variables to add to the child's environment
+ * @command: name of the binary to run
+ * @argv: arguments to pass to @command
+ * @directory: directory to start the new command in, or %NULL
+ * @columns: desired window columns
+ * @rows: desired window rows
+ * @lastlog: %TRUE if the lastlog should be updated
+ * @utmp: %TRUE if the utmp or utmpx log should be updated
+ * @wtmp: %TRUE if the wtmp or wtmpx log should be updated
+ *
+ * Starts a new copy of @command running under a psuedo-terminal, optionally in
+ * the supplied @directory, with window size set to @rows x @columns
+ * and variables in @env_add added to its environment.  If any combination of
+ * @lastlog, @utmp, and @wtmp is set, then the session is logged in the
+ * corresponding system files.
+ *
+ * Returns: an open file descriptor for the pty master, -1 on failure
+ *
+ * Deprecated: 0.24: Use #VtePty together with fork() or the g_spawn_async() family of functions instead
+ */
+int
+_vte_pty_open(pid_t *child,
+              char **env_add,
+              const char *command,
+              char **argv,
+              const char *directory,
+              int columns,
+              int rows,
+              gboolean lastlog,
+              gboolean utmp,
+              gboolean wtmp)
+{
+        VtePty *pty;
+        GPid pid;
+        gboolean ret;
+
+        pty = vte_pty_new(__vte_pty_get_pty_flags (lastlog, utmp, wtmp), NULL);
+        if (pty == NULL)
+                return -1;
+
+        if (command != NULL) {
+                char **real_argv;
+                GSpawnFlags spawn_flags;
+
+                spawn_flags = G_SPAWN_CHILD_INHERITS_STDIN |
+                              G_SPAWN_SEARCH_PATH;
+                real_argv = __vte_pty_get_argv(command, argv, &spawn_flags);
+                ret = __vte_pty_spawn(pty,
+                                      directory,
+                                      real_argv,
+                                      env_add,
+                                      spawn_flags,
+                                      NULL, NULL,
+                                      &pid,
+                                      NULL);
+                g_strfreev(real_argv);
+        } else {
+                ret = __vte_pty_fork(pty, &pid, NULL);
+        }
+
+        if (!ret) {
+                g_object_unref(pty);
+                return -1;
+        }
+
+        vte_pty_set_size(pty, rows, columns, NULL);
+
+        /* Stash the pty in the hash so we can later retrieve it by FD */
+        if (fd_to_pty_hash == NULL) {
+                fd_to_pty_hash = g_hash_table_new_full(g_int_hash,
+                                                       g_int_equal,
+                                                       NULL,
+                                                       (GDestroyNotify) g_object_unref);
+        }
+
+        g_hash_table_insert(fd_to_pty_hash, &pty->priv->pty_fd, pty /* adopt refcount */);
+
+        if (child)
+                *child = (pid_t) pid;
+
+        return vte_pty_get_fd(pty);
+}
+
+/**
+ * _vte_pty_get_size:
+ * @master: the file descriptor of the PTY master
+ * @columns: a place to store the number of columns
+ * @rows: a place to store the number of rows
+ *
+ * Attempts to read the pseudo terminal's window size.
+ *
+ * Returns: 0 on success, -1 on failure.
+ *
+ * Deprecated: 0.24: Use #VtePty and vte_pty_get_size() instead
+ */
+int
+_vte_pty_get_size(int master,
+                  int *columns,
+                  int *rows)
+{
+        VtePty *pty;
+
+        if ((pty = get_vte_pty_for_fd(master)) == NULL)
+                return -1;
+
+        if (vte_pty_get_size(pty, rows, columns, NULL))
+                return 0;
+
+        return -1;
+}
+
+/**
+ * _vte_pty_set_size:
+ * @master: the file descriptor of the PTY master
+ * @columns: the desired number of columns
+ * @rows: the desired number of rows
+ *
+ * Attempts to resize the pseudo terminal's window size.  If successful, the
+ * OS kernel will send #SIGWINCH to the child process group.
+ *
+ * Returns: 0 on success, -1 on failure.
+ *
+ * Deprecated: 0.24: Use #VtePty and vte_pty_set_size() instead
+ */
+int
+_vte_pty_set_size(int master,
+                  int columns,
+                  int rows)
+{
+        VtePty *pty;
+
+        if ((pty = get_vte_pty_for_fd(master)) == NULL)
+                return -1;
+
+        if (vte_pty_set_size(pty, rows, columns, NULL))
+                return 0;
+
+        return -1;
+}
+
+/**
+ * _vte_pty_set_utf8:
+ * @pty: The pty master descriptor.
+ * @utf8: Whether or not the pty is in UTF-8 mode.
+ *
+ * Tells the kernel whether the terminal is UTF-8 or not, in case it can make
+ * use of the info.  Linux 2.6.5 or so defines IUTF8 to make the line
+ * discipline do multibyte backspace correctly.
+ *
+ * Deprecated: 0.24: Use #VtePty and vte_pty_set_utf8() instead
+ */
+void _vte_pty_set_utf8(int master,
+                       gboolean utf8)
+{
+        VtePty *pty;
+
+        if ((pty = get_vte_pty_for_fd(master)) == NULL)
+                return;
+
+        vte_pty_set_utf8(pty, utf8);
+}
+
+/**
+ * _vte_pty_close:
+ * @pty: the pty master descriptor.
+ *
+ * Cleans up the PTY associated with the descriptor, specifically any logging
+ * performed for the session.  The descriptor itself remains open.
+ *
+ * Deprecated: 0.24: Use #VtePty and vte_pty_close() instead
+ */
+void _vte_pty_close(int master)
+{
+        VtePty *pty;
+
+        if ((pty = get_vte_pty_for_fd(master)) == NULL)
+                return;
+
+        /* Prevent closing the FD */
+        pty->priv->pty_fd = -1;
+
+        g_hash_table_remove(fd_to_pty_hash, &master);
+
+        if (g_hash_table_size(fd_to_pty_hash) == 0) {
+                g_hash_table_destroy(fd_to_pty_hash);
+                fd_to_pty_hash = NULL;
+        }
+}
+
+#endif /* !VTE_DISABLE_DEPRECATED_SOURCE */
diff --git a/src/pty.h b/src/pty.h
index 063f92d..726d639 100644
--- a/src/pty.h
+++ b/src/pty.h
@@ -16,6 +16,8 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+#ifndef VTE_DISABLE_DEPRECATED
+
 #ifndef vte_pty_h_included
 #define vte_pty_h_included
 
@@ -45,4 +47,6 @@ void _vte_pty_close(int pty);
 
 G_END_DECLS
 
-#endif
+#endif /* vte_pty_h_included */
+
+#endif /* !VTE_DISABLE_DEPRECATED */
diff --git a/src/vte-private.h b/src/vte-private.h
index 5681e12..ba0e432 100644
--- a/src/vte-private.h
+++ b/src/vte-private.h
@@ -169,8 +169,7 @@ struct _VteTerminalPrivate {
 	int default_column_count, default_row_count;	/* default sizes */
 
 	/* PTY handling data. */
-	const char *shell;		/* shell we started */
-	int pty_master;			/* pty master descriptor */
+	VtePty *pty;
 	GIOChannel *pty_channel;	/* master channel */
 	guint pty_input_source;
 	guint pty_output_source;
diff --git a/src/vte.c b/src/vte.c
index 1f2003d..7030478 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2001-2004,2009,2010 Red Hat, Inc.
- * Copyright © 2009, 2010 Christian Persch
+ * Copyright © 2008, 2009, 2010 Christian Persch
  *
  * This is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Library General Public License as published by
@@ -54,6 +54,8 @@
 #include "pty.h"
 #include "vteaccess.h"
 #include "vteint.h"
+#include "vtepty.h"
+#include "vtepty-private.h"
 #include "vteregex.h"
 #include "vtetc.h"
 
@@ -158,6 +160,7 @@ enum {
         PROP_ICON_TITLE,
         PROP_MOUSE_POINTER_AUTOHIDE,
         PROP_PTY,
+        PROP_PTY_OBJECT,
         PROP_SCROLL_BACKGROUND,
         PROP_SCROLLBACK_LINES,
         PROP_SCROLL_ON_KEYSTROKE,
@@ -1255,7 +1258,7 @@ vte_terminal_cursor_new(VteTerminal *terminal, GdkCursorType cursor_type)
  *
  * Returns: an integer associated with this expression
  *
- * @Deprecated: 0.17.1
+ * Deprecated: 0.17.1
  */
 int
 vte_terminal_match_add(VteTerminal *terminal, const char *match)
@@ -2180,8 +2183,10 @@ vte_terminal_maybe_scroll_to_bottom(VteTerminal *terminal)
 static void
 _vte_terminal_setup_utf8 (VteTerminal *terminal)
 {
-  _vte_pty_set_utf8(terminal->pvt->pty_master,
-		    (strcmp(terminal->pvt->encoding, "UTF-8") == 0));
+        VteTerminalPrivate *pvt = terminal->pvt;
+
+        vte_pty_set_utf8(pvt->pty,
+		         strcmp(terminal->pvt->encoding, "UTF-8") == 0);
 }
 
 /**
@@ -3225,34 +3230,7 @@ vte_terminal_catch_child_exited(VteReaper *reaper, int pid, int status,
 		terminal->pvt->pty_pid = -1;
 
 		/* Close out the PTY. */
-		_vte_terminal_disconnect_pty_read(terminal);
-		_vte_terminal_disconnect_pty_write(terminal);
-		if (terminal->pvt->pty_channel != NULL) {
-			g_io_channel_unref (terminal->pvt->pty_channel);
-			terminal->pvt->pty_channel = NULL;
-		}
-		if (terminal->pvt->pty_master != -1) {
-			_vte_pty_close(terminal->pvt->pty_master);
-			close(terminal->pvt->pty_master);
-			terminal->pvt->pty_master = -1;
-                
-                        g_object_notify(object, "pty");
-		}
-
-		/* Take one last shot at processing whatever data is pending,
-		 * then flush the buffers in case we're about to run a new
-		 * command, disconnecting the timeout. */
-		if (terminal->pvt->incoming != NULL) {
-			vte_terminal_process_incoming(terminal);
-			_vte_incoming_chunks_release (terminal->pvt->incoming);
-			terminal->pvt->incoming = NULL;
-			terminal->pvt->input_bytes = 0;
-		}
-		g_array_set_size(terminal->pvt->pending, 0);
-		vte_terminal_stop_processing (terminal);
-
-		/* Clear the outgoing buffer as well. */
-		_vte_buffer_clear(terminal->pvt->outgoing);
+                vte_terminal_set_pty_object(terminal, NULL);
 
 		/* Tell observers what's happened. */
                 terminal->pvt->child_exit_status = status;
@@ -3297,9 +3275,12 @@ static void mark_output_source_invalid(VteTerminal *terminal)
 static void
 _vte_terminal_connect_pty_write(VteTerminal *terminal)
 {
+        VteTerminalPrivate *pvt = terminal->pvt;
+
+        g_assert(pvt->pty != NULL);
 	if (terminal->pvt->pty_channel == NULL) {
-		terminal->pvt->pty_channel =
-			g_io_channel_unix_new(terminal->pvt->pty_master);
+		pvt->pty_channel =
+			g_io_channel_unix_new(vte_pty_get_fd(pvt->pty));
 	}
 
 	if (terminal->pvt->pty_output_source == 0) {
@@ -3340,102 +3321,187 @@ _vte_terminal_disconnect_pty_write(VteTerminal *terminal)
 	}
 }
 
-/* Basic wrapper around _vte_pty_open(), which handles the pipefitting. */
-static pid_t
-_vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
-			 char **argv, char **envv,
-			 const char *directory,
-			 gboolean lastlog, gboolean utmp, gboolean wtmp)
+/**
+ * vte_terminal_pty_new:
+ * @terminal: a #VteTerminal
+ * @flags: flags from #VtePtyFlags
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a new #VtePty, and sets the emulation property
+ * from #VteTerminal:emulation.
+ *
+ * See vte_pty_new() for more information.
+ *
+ * Since: 0.24
+ */
+VtePty *
+vte_terminal_pty_new(VteTerminal *terminal,
+                     VtePtyFlags flags,
+                     GError **error)
 {
-        GObject *object = G_OBJECT(terminal);
-	char **env_add;
-	int i, fd;
-	pid_t pid;
-	VteReaper *reaper;
+        VteTerminalPrivate *pvt;
+        VtePty *pty;
 
-        g_object_freeze_notify(object);
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
 
-	/* Duplicate the environment, and add one more variable. */
-	i = envv ? g_strv_length(envv) : 0;
-	env_add = g_new(char *, i + 2);
-	env_add[0] = g_strdup_printf("TERM=%s", terminal->pvt->emulation);
-	for (i = 0; (envv != NULL) && (envv[i] != NULL); i++) {
-		env_add[i + 1] = g_strdup(envv[i]);
-	}
-	env_add[i + 1] = NULL;
+        pvt = terminal->pvt;
 
-	/* Close any existing ptys. */
-	if (terminal->pvt->pty_channel != NULL) {
-		g_io_channel_unref (terminal->pvt->pty_channel);
-		terminal->pvt->pty_channel = NULL;
-	}
-	if (terminal->pvt->pty_master != -1) {
-		_vte_pty_close(terminal->pvt->pty_master);
-		close(terminal->pvt->pty_master);
-		terminal->pvt->pty_master = -1;
+        pty = vte_pty_new(flags, error);
+        if (pty == NULL)
+                return NULL;
 
-                g_object_notify(object, "pty");
+        vte_pty_set_term(pty, vte_terminal_get_emulation(terminal));
 
-	}
+        return pty;
+}
 
-        terminal->pvt->child_exit_status = 0;
+/**
+ * vte_terminal_watch_child:
+ * @terminal: a #VteTerminal
+ * @child_pid: a #GPid
+ *
+ * Watches @child_pid. When the process exists, the #VteReaper::child-exited
+ * signal will be called. Use vte_terminal_get_child_exit_status() to
+ * retrieve the child's exit status.
+ *
+ * Prior to calling this function, a #VtePty must have been set in @terminal
+ * using vte_terminal_set_pty_object().
+ * When the child exits, the terminal's #VtePty will be set to %NULL.
+ *
+ * Note: g_child_watch_add() or g_child_watch_add_full() must not have
+ * been called for @child_pid, nor a #GSource for it been created with
+ * g_child_watch_source_new().
+ *
+ * Note: when using the g_spawn_async() family of functions,
+ * the %G_SPAWN_DO_NOT_REAP_CHILD flag MUST have been passed.
+ *
+ * Since: 0.24
+ */
+void
+vte_terminal_watch_child (VteTerminal *terminal,
+                          GPid child_pid)
+{
+        VteTerminalPrivate *pvt;
+        GObject *object;
+        VteReaper *reaper;
 
-	/* Open the new pty. */
-	pid = -1;
-	fd = _vte_pty_open(&pid, env_add, command, argv, directory,
-			  terminal->column_count, terminal->row_count,
-			  lastlog, utmp, wtmp);
-	if (pid == -1 || fd == -1) {
-		g_strfreev(env_add);
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+        g_return_if_fail(child_pid != -1);
 
-                g_object_thaw_notify(object);
+        pvt = terminal->pvt;
+        g_return_if_fail(pvt->pty != NULL);
 
-		return -1;
-	}
+        // FIXMEchpe: support passing child_pid = -1 to remove the wathch
 
-	/* If we successfully started the process, set up to listen for its
-	 * output. */
-	if (pid != 0) {
-		/* Set this as the child's pid. */
-		terminal->pvt->pty_pid = pid;
+        object = G_OBJECT(terminal);
 
-		vte_terminal_set_pty (terminal, fd);
+        g_object_freeze_notify(object);
 
-		/* Catch a child-exited signal from the child pid. */
-		reaper = vte_reaper_get();
-		vte_reaper_add_child(pid);
-		if (reaper != terminal->pvt->pty_reaper) {
-			if (terminal->pvt->pty_reaper != NULL) {
-				g_signal_handlers_disconnect_by_func(terminal->pvt->pty_reaper,
-						vte_terminal_catch_child_exited,
-						terminal);
-				g_object_unref(terminal->pvt->pty_reaper);
-			}
-			g_signal_connect(reaper, "child-exited",
-					G_CALLBACK(vte_terminal_catch_child_exited),
-					terminal);
-			terminal->pvt->pty_reaper = reaper;
-		} else
-			g_object_unref(reaper);
+        /* Set this as the child's pid. */
+        pvt->pty_pid = child_pid;
+        pvt->child_exit_status = 0;
+
+        /* Catch a child-exited signal from the child pid. */
+        reaper = vte_reaper_get();
+        vte_reaper_add_child(child_pid);
+        if (reaper != pvt->pty_reaper) {
+                if (terminal->pvt->pty_reaper != NULL) {
+                        g_signal_handlers_disconnect_by_func(pvt->pty_reaper,
+                                        vte_terminal_catch_child_exited,
+                                        terminal);
+                        g_object_unref(pvt->pty_reaper);
+                }
+                g_signal_connect(reaper, "child-exited",
+                                G_CALLBACK(vte_terminal_catch_child_exited),
+                                terminal);
+                pvt->pty_reaper = reaper;
+        } else {
+                g_object_unref(reaper);
 	}
 
-	/* Clean up. */
-	g_strfreev(env_add);
+        /* FIXMEchpe: call vte_terminal_set_size here? */
 
         g_object_thaw_notify(object);
+}
+
+/*
+ * _vte_terminal_get_user_shell:
+ *
+ * Uses getpwd() to determine the user's shell. If that fails, falls back
+ * to using the SHELL environment variable. As last-ditch fallback, returns
+ * "/bin/sh".
+ *
+ * Returns: a newly allocated string containing the command to run the
+ *   user's shell
+ */
+static char *
+_vte_terminal_get_user_shell (void)
+{
+	struct passwd *pwd;
+	char *command;
 
-	/* Return the pid to the caller. */
-	return pid;
+	pwd = getpwuid(getuid());
+	if (pwd != NULL) {
+	        command = g_strdup (pwd->pw_shell);
+	        _vte_debug_print(VTE_DEBUG_MISC,
+				"Using user's shell (%s).\n",
+				command ? command : "(null)");
+	}
+	if (command == NULL) {
+		if (g_getenv ("SHELL")) {
+			command = g_strdup (g_getenv ("SHELL"));
+			_vte_debug_print(VTE_DEBUG_MISC,
+					 "Using $SHELL shell (%s).\n",
+					 command);
+		} else {
+			command = g_strdup ("/bin/sh");
+			_vte_debug_print(VTE_DEBUG_MISC,
+					 "Using default shell (%s).\n",
+					 command);
+		}
+	}
+
+	g_assert (command != NULL);
+
+	return command;
+}
+
+/*
+ * _vte_terminal_get_argv:
+ * @command: the command to run
+ * @argv: the argument vector
+ * @flags: (inout) flags from #GSpawnFlags
+ *
+ * Creates an argument vector to pass to g_spawn_async() from @command and
+ * @argv, modifying * flags if necessary.
+ * Like __vte_pty_get_argv(), but returns the argument vector to spawn
+ * the user's shell if @command is %NULL.
+ *
+ * Returns: a newly allocated array of strings. Free with g_strfreev()
+ */
+static char **
+_vte_terminal_get_argv (const char *command,
+                        char **argv,
+                        GSpawnFlags *flags /* inout */)
+{
+	char **argv2;
+        char *shell = NULL;
+
+        argv2 = __vte_pty_get_argv(command ? command : (shell = _vte_terminal_get_user_shell()),
+                                   argv,
+                                   flags);
+        g_free(shell);
+        return argv2;
 }
 
 /**
  * vte_terminal_fork_command:
  * @terminal: a #VteTerminal
- * @command: the name of a binary to run, or %NULL to get user's shell
+ * @command: the name of a binary to run, or %NULL to spawn the user's shell
  * @argv: the argument list to be passed to @command, or %NULL
  * @envv: a list of environment variables to be added to the environment before
  * starting @command, or %NULL
- * @directory: the name of a directory the command should start in, or %NULL
+ * @working_directory: the name of a directory the command should start in, or %NULL
  * @lastlog: %TRUE if the session should be logged to the lastlog
  * @utmp: %TRUE if the session should be logged to the utmp/utmpx log
  * @wtmp: %TRUE if the session should be logged to the wtmp/wtmpx log
@@ -3449,49 +3515,142 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
  *
  * Note that all file descriptors except stdin/stdout/stderr will be closed
  * before calling exec() in the child.
- * 
- * Returns: the ID of the new process
+ *
+ * Returns: the PID of the new process
+ *
+ * Deprecated: 0.24: Use vte_terminal_fork_command_full()
  */
 pid_t
 vte_terminal_fork_command(VteTerminal *terminal,
-			  const char *command, char **argv, char **envv,
-			  const char *directory,
-			  gboolean lastlog, gboolean utmp, gboolean wtmp)
-{
-	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+			  const char *command,
+                          char **argv,
+                          char **envv,
+			  const char *working_directory,
+			  gboolean lastlog,
+                          gboolean utmp,
+                          gboolean wtmp)
+{
+        char **real_argv;
+        GSpawnFlags spawn_flags;
+        GPid child_pid;
+        gboolean ret;
+#ifdef VTE_DEBUG
+        GError *error = NULL;
+        GError **err = &error;
+#else
+        GError **err = NULL;
+#endif
 
-	/* Make the user's shell the default command. */
-	if (command == NULL) {
-		if (terminal->pvt->shell == NULL) {
-			struct passwd *pwd;
-
-			pwd = getpwuid(getuid());
-			if (pwd != NULL) {
-				terminal->pvt->shell = pwd->pw_shell;
-				_vte_debug_print(VTE_DEBUG_MISC,
-						"Using user's shell (%s).\n",
-						terminal->pvt->shell);
-			}
-		}
-		if (terminal->pvt->shell == NULL) {
-			if (getenv ("SHELL")) {
-				terminal->pvt->shell = getenv ("SHELL");
-				_vte_debug_print(VTE_DEBUG_MISC,
-					"Using $SHELL shell (%s).\n",
-						terminal->pvt->shell);
-			} else {
-				terminal->pvt->shell = "/bin/sh";
-				_vte_debug_print(VTE_DEBUG_MISC,
-						"Using default shell (%s).\n",
-						terminal->pvt->shell);
-			}
-		}
-		command = terminal->pvt->shell;
-	}
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+
+        spawn_flags = G_SPAWN_CHILD_INHERITS_STDIN |
+                      G_SPAWN_SEARCH_PATH;
+        real_argv = _vte_terminal_get_argv (command, argv, &spawn_flags);
+
+        ret = vte_terminal_fork_command_full(terminal,
+                                             __vte_pty_get_pty_flags(lastlog, utmp, wtmp),
+                                             working_directory,
+                                             real_argv,
+                                             envv,
+                                             spawn_flags,
+                                             NULL, NULL,
+                                             &child_pid,
+                                             err);
+        g_strfreev (real_argv);
+
+#ifdef VTE_DEBUG
+        if (error) {
+                _vte_debug_print(VTE_DEBUG_MISC,
+                                "vte_terminal_fork_command failed: %s\n", error->message);
+                g_error_free(error);
+        }
+#endif
+
+        if (!ret)
+                return -1;
+
+        return (pid_t) child_pid;
+}
+
+/**
+ * vte_terminal_fork_command_full:
+ * @terminal: a #VteTerminal
+ * @pty_flags: flags from #VtePtyFlags
+ * @argv: child's argument vector
+ * @envv: a list of environment variables to be added to the environment before
+ *   starting the process, or %NULL
+ * @working_directory: the name of a directory the command should start in, or %NULL
+ *   to use the cwd
+ * @spawn_flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @child_setup_data: user data for @child_setup
+ * @child_pid: a location to store the child PID, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Starts the specified command under a newly-allocated controlling
+ * pseudo-terminal.  The @argv and @envv lists should be %NULL-terminated.
+ * The "TERM" environment variable is automatically set to reflect the
+ * terminal widget's emulation setting.
+ * @vte_spawn_flags controls logging the session to the specified system log files.
+ *
+ * Note that %G_SPAWN_DO_NOT_REAP_CHILD will always be added to @spawn_flags.
+ *
+ * Note that all unless @flags contains %G_SPAWN_LEAVE_DESCRIPTORS_OPEN, all file
+ * descriptors except stdin/stdout/stderr will be closed before calling exec()
+ * in the child.
+ *
+ * See vte_terminal_FIXMEchpe() and g_spawn_async() for more information.
+ *
+ * Returns: %TRUE on success, or %FALSE on error with @error filled in
+ *
+ * Since: 0.24
+ */
+gboolean
+vte_terminal_fork_command_full(VteTerminal *terminal,
+                               VtePtyFlags pty_flags,
+                               const char *working_directory,
+                               char **argv,
+                               char **envv,
+                               GSpawnFlags spawn_flags,
+                               GSpawnChildSetupFunc child_setup,
+                               gpointer child_setup_data,
+                               GPid *child_pid /* out */,
+                               GError **error)
+{
+        VtePty *pty;
+        GPid pid;
+
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
+        g_return_val_if_fail(argv != NULL, FALSE);
+        g_return_val_if_fail(child_setup_data == NULL || child_setup, FALSE);
+        g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+        pty = vte_pty_new(pty_flags, error);
+        if (pty == NULL)
+                return FALSE;
+
+        /* FIXMEchpe: is this flag needed */
+        spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
+
+        if (!__vte_pty_spawn(pty,
+                             working_directory,
+                             argv,
+                             envv,
+                             spawn_flags,
+                             child_setup, child_setup_data,
+                             &pid,
+                             error)) {
+                g_object_unref(pty);
+                return FALSE;
+        }
+
+        vte_terminal_set_pty_object(terminal, pty);
+        vte_terminal_watch_child(terminal, pid);
+
+        if (child_pid)
+                *child_pid = pid;
 
-	/* Start up the command and get the PTY of the master. */
-	return _vte_terminal_fork_basic(terminal, command, argv, envv,
-				        directory, lastlog, utmp, wtmp);
+        return TRUE;
 }
 
 /**
@@ -3499,7 +3658,7 @@ vte_terminal_fork_command(VteTerminal *terminal,
  * @terminal: a #VteTerminal
  * @envv: a list of environment variables to be added to the environment before
  * starting returning in the child process, or %NULL
- * @directory: the name of a directory the child process should change to, or
+ * @working_directory: the name of a directory the child process should change to, or
  * %NULL
  * @lastlog: %TRUE if the session should be logged to the lastlog
  * @utmp: %TRUE if the session should be logged to the utmp/utmpx log
@@ -3510,20 +3669,48 @@ vte_terminal_fork_command(VteTerminal *terminal,
  * emulation setting.  If @lastlog, @utmp, or @wtmp are %TRUE, logs the session
  * to the specified system log files.
  *
+ * Note that all file descriptors except stdin/stdout/stderr will be closed
+ * in the child.
+ *
+ * Note that @envv and @working_directory are silently ignored.
+ *
  * Returns: the ID of the new process in the parent, 0 in the child, and -1 if
  * there was an error
  *
  * Since: 0.11.11
+ *
+ * Deprecated: 0.24: Use #VtePty and fork() instead
  */
 pid_t
 vte_terminal_forkpty(VteTerminal *terminal,
-		     char **envv, const char *directory,
+		     char **envv, const char *working_directory,
 		     gboolean lastlog, gboolean utmp, gboolean wtmp)
 {
-	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+#ifdef HAVE_FORK
+        VtePty *pty;
+        GPid pid;
+
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+
+        pty = vte_pty_new(__vte_pty_get_pty_flags(lastlog, utmp, wtmp), NULL);
+        if (pty == NULL)
+                return FALSE;
 
-	return _vte_terminal_fork_basic(terminal, NULL, NULL, envv,
-				       directory, lastlog, utmp, wtmp);
+        if (!__vte_pty_fork(pty,
+                            &pid,
+                            NULL)) {
+                g_object_unref(pty);
+                return FALSE;
+        }
+
+        vte_terminal_set_pty_object(terminal, pty);
+        // FIXMEchpe is that really right?
+        vte_terminal_watch_child(terminal, pid);
+
+        return pid;
+#else
+        return -1;
+#endif
 }
 
 /* Handle an EOF from the client. */
@@ -3534,36 +3721,7 @@ vte_terminal_eof(GIOChannel *channel, VteTerminal *terminal)
 
         g_object_freeze_notify(object);
 
-
-	/* Close the connections to the child -- note that the source channel
-	 * has already been dereferenced. */
-
-	_vte_terminal_disconnect_pty_read(terminal);
-	_vte_terminal_disconnect_pty_write(terminal);
-	if (terminal->pvt->pty_channel != NULL) {
-		g_io_channel_unref (terminal->pvt->pty_channel);
-		terminal->pvt->pty_channel = NULL;
-	}
-	if (terminal->pvt->pty_master != -1) {
-		_vte_pty_close(terminal->pvt->pty_master);
-		close(terminal->pvt->pty_master);
-		terminal->pvt->pty_master = -1;
-        
-                g_object_notify(object, "pty");
-	}
-
-	/* Take one last shot at processing whatever data is pending, then
-	 * flush the buffers in case we're about to run a new command,
-	 * disconnecting the timeout. */
-	vte_terminal_stop_processing (terminal);
-	if (terminal->pvt->incoming) {
-		vte_terminal_process_incoming(terminal);
-		terminal->pvt->input_bytes = 0;
-	}
-	g_array_set_size(terminal->pvt->pending, 0);
-
-	/* Clear the outgoing buffer as well. */
-	_vte_buffer_clear(terminal->pvt->outgoing);
+        vte_terminal_set_pty_object(terminal, NULL);
 
 	/* Emit a signal that we read an EOF. */
 	vte_terminal_queue_eof(terminal);
@@ -4379,7 +4537,7 @@ vte_terminal_send(VteTerminal *terminal, const char *encoding,
 		}
 		/* If there's a place for it to go, add the data to the
 		 * outgoing buffer. */
-		if ((cooked_length > 0) && (terminal->pvt->pty_master != -1)) {
+		if ((cooked_length > 0) && (terminal->pvt->pty != NULL)) {
 			_vte_buffer_append(terminal->pvt->outgoing,
 					   cooked, cooked_length);
 			_VTE_DEBUG_IF(VTE_DEBUG_KEYBOARD) {
@@ -4453,7 +4611,7 @@ vte_terminal_feed_child_binary(VteTerminal *terminal, const char *data, glong le
 
 		/* If there's a place for it to go, add the data to the
 		 * outgoing buffer. */
-		if (terminal->pvt->pty_master != -1) {
+		if (terminal->pvt->pty != NULL) {
 			_vte_buffer_append(terminal->pvt->outgoing,
 					   data, length);
 			/* If we need to start waiting for the child pty to
@@ -4877,8 +5035,8 @@ vte_terminal_key_press(GtkWidget *widget, GdkEventKey *event)
 				suppress_meta_esc = TRUE;
 				break;
 			case VTE_ERASE_TTY:
-				if (terminal->pvt->pty_master != -1 &&
-				    tcgetattr(terminal->pvt->pty_master, &tio) != -1)
+				if (terminal->pvt->pty != NULL &&
+				    tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &tio) != -1)
 				{
 					normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
 					normal_length = 1;
@@ -4890,8 +5048,8 @@ vte_terminal_key_press(GtkWidget *widget, GdkEventKey *event)
 #ifndef _POSIX_VDISABLE
 #define _POSIX_VDISABLE '\0'
 #endif
-				if (terminal->pvt->pty_master != -1 &&
-				    tcgetattr(terminal->pvt->pty_master, &tio) != -1 &&
+				if (terminal->pvt->pty != NULL &&
+				    tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &tio) != -1 &&
 				    tio.c_cc[VERASE] != _POSIX_VDISABLE)
 				{
 					normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
@@ -4920,8 +5078,8 @@ vte_terminal_key_press(GtkWidget *widget, GdkEventKey *event)
 				normal_length = 1;
 				break;
 			case VTE_ERASE_TTY:
-				if (terminal->pvt->pty_master != -1 &&
-				    tcgetattr(terminal->pvt->pty_master, &tio) != -1)
+				if (terminal->pvt->pty != NULL &&
+				    tcgetattr(vte_pty_get_fd(terminal->pvt->pty), &tio) != -1)
 				{
 					normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
 					normal_length = 1;
@@ -7554,18 +7712,19 @@ vte_terminal_get_font(VteTerminal *terminal)
 static void
 vte_terminal_refresh_size(VteTerminal *terminal)
 {
+        VteTerminalPrivate *pvt = terminal->pvt;
 	int rows, columns;
-	if (terminal->pvt->pty_master != -1) {
-		/* Use an ioctl to read the size of the terminal. */
-		if (_vte_pty_get_size(terminal->pvt->pty_master, &columns, &rows) != 0) {
-                        int errsv = errno;
+        GError *error = NULL;
 
-			g_warning(_("Error reading PTY size, using defaults: "
-				    "%s."), g_strerror(errsv));
-		} else {
-			terminal->row_count = rows;
-			terminal->column_count = columns;
-		}
+        if (pvt->pty == NULL)
+                return;
+
+        if (vte_pty_get_size(pvt->pty, &rows, &columns, &error)) {
+                terminal->row_count = rows;
+                terminal->column_count = columns;
+        } else {
+                g_warning(_("Error reading PTY size, using defaults: %s\n"), error->message);
+                g_error_free(error);
 	}
 }
 
@@ -7593,13 +7752,16 @@ vte_terminal_set_size(VteTerminal *terminal, glong columns, glong rows)
 	old_rows = terminal->row_count;
 	old_columns = terminal->column_count;
 
-	if (terminal->pvt->pty_master != -1) {
-		/* Try to set the terminal size. */
-		if (_vte_pty_set_size(terminal->pvt->pty_master, columns, rows) != 0) {
-			g_warning("Error setting PTY size: %s.",
-				  g_strerror(errno));
+	if (terminal->pvt->pty != NULL) {
+                GError *error = NULL;
+
+		/* Try to set the terminal size, and read it back,
+		 * in case something went awry.
+                 */
+		if (!vte_pty_set_size(terminal->pvt->pty, rows, columns, &error)) {
+			g_warning("%s\n", error->message);
+                        g_error_free(error);
 		}
-		/* Read the terminal size, in case something went awry. */
 		vte_terminal_refresh_size(terminal);
 	} else {
 		terminal->row_count = rows;
@@ -7774,6 +7936,7 @@ vte_terminal_set_emulation(VteTerminal *terminal, const char *emulation)
         g_object_thaw_notify(object);
 }
 
+/* FIXMEchpe deprecate this function, it's wrong */
 /**
  * vte_terminal_get_default_emulation:
  * @terminal: a #VteTerminal
@@ -7781,14 +7944,15 @@ vte_terminal_set_emulation(VteTerminal *terminal, const char *emulation)
  * Queries the terminal for its default emulation, which is attempted if the
  * terminal type passed to vte_terminal_set_emulation() is %NULL.
  *
- * Returns: the name of the default terminal type the widget attempts to emulate
+ * Returns: an interned string containing the name of the default terminal
+ *   type the widget attempts to emulate
  *
  * Since: 0.11.11
  */
 const char *
 vte_terminal_get_default_emulation(VteTerminal *terminal)
 {
-	return VTE_DEFAULT_EMULATION;
+	return g_intern_static_string(VTE_DEFAULT_EMULATION);
 }
 
 /**
@@ -7798,7 +7962,8 @@ vte_terminal_get_default_emulation(VteTerminal *terminal)
  * Queries the terminal for its current emulation, as last set by a call to
  * vte_terminal_set_emulation().
  *
- * Returns: the name of the terminal type the widget is attempting to emulate
+ * Returns: an interned string containing the name of the terminal type the
+ *   widget is attempting to emulate
  */
 const char *
 vte_terminal_get_emulation(VteTerminal *terminal)
@@ -7948,12 +8113,11 @@ vte_terminal_init(VteTerminal *terminal)
 
 	/* Setting the terminal type and size requires the PTY master to
 	 * be set up properly first. */
-	pvt->pty_master = -1;
+        pvt->pty = NULL;
 	vte_terminal_set_emulation(terminal, NULL);
 	vte_terminal_set_size(terminal,
 			      pvt->default_column_count,
 			      pvt->default_row_count);
-	g_assert (pvt->pty_master == -1);
 	pvt->pty_input_source = 0;
 	pvt->pty_output_source = 0;
 	pvt->pty_pid = -1;
@@ -8038,7 +8202,7 @@ vte_terminal_size_request(GtkWidget *widget, GtkRequisition *requisition)
 
 	vte_terminal_ensure_font (terminal);
 
-	if (terminal->pvt->pty_master != -1) {
+	if (terminal->pvt->pty != NULL) {
 		vte_terminal_refresh_size(terminal);
 		requisition->width = terminal->char_width *
 			terminal->column_count;
@@ -8060,10 +8224,10 @@ vte_terminal_size_request(GtkWidget *widget, GtkRequisition *requisition)
 			"[Terminal %p] Size request is %dx%d for %ldx%ld cells.\n",
                         terminal,
 			requisition->width, requisition->height,
-			(terminal->pvt->pty_master != -1) ?
+			(terminal->pvt->pty != NULL) ?
 			terminal->column_count :
 			terminal->pvt->default_column_count,
-			(terminal->pvt->pty_master != -1) ?
+			(terminal->pvt->pty != NULL) ?
 			terminal->row_count :
 			terminal->pvt->default_row_count);
 }
@@ -8449,9 +8613,9 @@ vte_terminal_finalize(GObject *object)
 	if (terminal->pvt->pty_channel != NULL) {
 		g_io_channel_unref (terminal->pvt->pty_channel);
 	}
-	if (terminal->pvt->pty_master != -1) {
-		_vte_pty_close(terminal->pvt->pty_master);
-		close(terminal->pvt->pty_master);
+	if (terminal->pvt->pty != NULL) {
+                vte_pty_close(terminal->pvt->pty);
+                g_object_unref(terminal->pvt->pty);
 	}
 
 	/* Remove hash tables. */
@@ -10932,7 +11096,10 @@ vte_terminal_get_property (GObject *object,
                         g_value_set_boolean (value, vte_terminal_get_mouse_autohide (terminal));
                         break;
                 case PROP_PTY:
-                        g_value_set_int (value, pvt->pty_master);
+                        g_value_set_int (value, pvt->pty != NULL ? vte_pty_get_fd(pvt->pty) : -1);
+                        break;
+                case PROP_PTY_OBJECT:
+                        g_value_set_object (value, vte_terminal_get_pty_object(terminal));
                         break;
                 case PROP_SCROLL_BACKGROUND:
                         g_value_set_boolean (value, pvt->scroll_background);
@@ -11024,6 +11191,9 @@ vte_terminal_set_property (GObject *object,
                 case PROP_PTY:
                         vte_terminal_set_pty (terminal, g_value_get_int (value));
                         break;
+                case PROP_PTY_OBJECT:
+                        vte_terminal_set_pty_object (terminal, g_value_get_object (value));
+                        break;
                 case PROP_SCROLL_BACKGROUND:
                         vte_terminal_set_scroll_background (terminal, g_value_get_boolean (value));
                         break;
@@ -11191,7 +11361,7 @@ vte_terminal_class_init(VteTerminalClass *klass)
          *
          * Emitted when the terminal receives an end-of-file from a child which
          * is running in the terminal.  This signal is frequently (but not
-         * always) emitted with a "child-exited" signal.
+         * always) emitted with a #VteTerminal::child-exited signal.
          */
 	klass->eof_signal =
                 g_signal_new(I_("eof"),
@@ -11780,7 +11950,7 @@ vte_terminal_class_init(VteTerminalClass *klass)
          * VteTerminal:background-saturation:
          *
          * If a background image has been set using #VteTerminal:background-image-file: or
-         * #VteTermina:background-image-pixbuf:, or #VteTerminal:background-transparent:,
+         * #VteTerminal:background-image-pixbuf:, or #VteTerminal:background-transparent:,
          * and the saturation value is less
          * than 1.0, the terminal will adjust the colors of the image before drawing
          * the image.  To do so, the terminal will create a copy of the background
@@ -11800,7 +11970,7 @@ vte_terminal_class_init(VteTerminalClass *klass)
          * VteTerminal:background-tint-color:
          *
          * If a background image has been set using #VteTerminal:background-image-file: or
-         * #VteTermina:background-image-pixbuf:, or #VteTerminal:background-transparent:, and
+         * #VteTerminal:background-image-pixbuf:, or #VteTerminal:background-transparent:, and
          * and the value set by VteTerminal:background-saturation: is less than 1.0,
          * the terminal
          * will adjust the color of the image before drawing the image.  To do so,
@@ -11986,6 +12156,8 @@ vte_terminal_class_init(VteTerminalClass *klass)
          * The file descriptor of the master end of the terminal's PTY.
          * 
          * Since: 0.20
+         *
+         * Deprecated: 0.24: Use the #VteTerminal:pty-object property instead
          */
         g_object_class_install_property
                 (gobject_class,
@@ -11996,6 +12168,21 @@ vte_terminal_class_init(VteTerminalClass *klass)
                                    G_PARAM_READWRITE | STATIC_PARAMS));
 
         /**
+         * VteTerminal:pty-object:
+         *
+         * The PTY object for the terminal.
+         *
+         * Since: 0.24
+         */
+        g_object_class_install_property
+                (gobject_class,
+                 PROP_PTY_OBJECT,
+                 g_param_spec_object ("pty-object", NULL, NULL,
+                                      VTE_TYPE_PTY,
+                                      G_PARAM_READWRITE |
+                                      G_PARAM_STATIC_STRINGS));
+
+        /**
          * VteTerminal:scroll-background:
          *
          * Controls whether or not the terminal will scroll the background image (if
@@ -13443,7 +13630,7 @@ vte_terminal_get_status_line(VteTerminal *terminal)
  * size.  The values returned in @xpad and @ypad are the total padding used in
  * each direction, and do not need to be doubled.
  *
- * @Deprecated: 0.24: Get the VteTerminal:inner-border style property instead
+ * Deprecated: 0.24: Get the #VteTerminal:inner-border style property instead
  */
 void
 vte_terminal_get_padding(VteTerminal *terminal, int *xpad, int *ypad)
@@ -13601,58 +13788,123 @@ vte_terminal_get_icon_title(VteTerminal *terminal)
 /**
  * vte_terminal_set_pty:
  * @terminal: a #VteTerminal
- * @pty_master: a file descriptor of the master end of a PTY
+ * @pty_master: a file descriptor of the master end of a PTY, or %-1
  *
  * Attach an existing PTY master side to the terminal widget.  Use
  * instead of vte_terminal_fork_command() or vte_terminal_forkpty().
  *
  * Since: 0.12.1
+ *
+ * Deprecated: 0.24: Use vte_pty_new_foreign() and vte_terminal_set_pty_object()
  */
 void
 vte_terminal_set_pty(VteTerminal *terminal, int pty_master)
 {
-        long flags;
+        VtePty *pty;
+
+        if (pty_master == -1) {
+                vte_terminal_set_pty_object(terminal, NULL);
+                return;
+        }
+
+        pty = vte_pty_new_foreign(pty_master);
+        g_assert(pty != NULL);
+
+        vte_terminal_set_pty_object(terminal, pty);
+        g_object_unref(pty);
+}
+
+/**
+ * vte_terminal_set_pty_object:
+ * @terminal: a #VteTerminal
+ * @pty: a #VtePty, or %NULL
+ *
+ * Sets @pty as the PTY to use in @terminal.
+ * Use %NULL to unset the PTY.
+ *
+ * Since: 0.24.
+ */
+void
+vte_terminal_set_pty_object(VteTerminal *terminal,
+                            VtePty *pty)
+{
         VteTerminalPrivate *pvt;
         GObject *object;
+        long flags;
+        int pty_master;
 
         g_return_if_fail(VTE_IS_TERMINAL(terminal));
+        g_return_if_fail(pty == NULL || VTE_IS_PTY(pty));
 
-        object = G_OBJECT(terminal);
         pvt = terminal->pvt;
-        if (pty_master == pvt->pty_master) {
+        if (pvt->pty == pty)
                 return;
-        }
+
+        object = G_OBJECT(terminal);
 
         g_object_freeze_notify(object);
 
-       if (terminal->pvt->pty_channel != NULL) {
-	       g_io_channel_unref (terminal->pvt->pty_channel);
-       }
-       if (terminal->pvt->pty_master != -1) {
-               _vte_pty_close (terminal->pvt->pty_master);
-               close (terminal->pvt->pty_master);
-       }
-       terminal->pvt->pty_master = pty_master;
-       terminal->pvt->pty_channel = g_io_channel_unix_new (pty_master);
-       g_io_channel_set_close_on_unref (terminal->pvt->pty_channel, FALSE);
-
-
-       /* Set the pty to be non-blocking. */
-       flags = fcntl (terminal->pvt->pty_master, F_GETFL);
-       if ((flags & O_NONBLOCK) == 0) {
-	       fcntl (terminal->pvt->pty_master, F_SETFL, flags | O_NONBLOCK);
-       }
-
-       vte_terminal_set_size (terminal,
+        if (pvt->pty != NULL) {
+                _vte_terminal_disconnect_pty_read(terminal);
+                _vte_terminal_disconnect_pty_write(terminal);
+
+                if (terminal->pvt->pty_channel != NULL) {
+                        g_io_channel_unref (terminal->pvt->pty_channel);
+                        pvt->pty_channel = NULL;
+                }
+
+		/* Take one last shot at processing whatever data is pending,
+		 * then flush the buffers in case we're about to run a new
+		 * command, disconnecting the timeout. */
+		if (terminal->pvt->incoming != NULL) {
+			vte_terminal_process_incoming(terminal);
+			_vte_incoming_chunks_release (terminal->pvt->incoming);
+			terminal->pvt->incoming = NULL;
+			terminal->pvt->input_bytes = 0;
+		}
+		g_array_set_size(terminal->pvt->pending, 0);
+		vte_terminal_stop_processing (terminal);
+
+		/* Clear the outgoing buffer as well. */
+		_vte_buffer_clear(terminal->pvt->outgoing);
+
+                vte_pty_close(pvt->pty);
+                g_object_unref(pvt->pty);
+                pvt->pty = NULL;
+        }
+
+        if (pty == NULL) {
+                pvt->pty = NULL;
+                g_object_notify(object, "pty");
+                g_object_notify(object, "pty-object");
+                g_object_thaw_notify(object);
+                return;
+        }
+
+        pvt->pty = g_object_ref(pty);
+        pty_master = vte_pty_get_fd(pvt->pty);
+
+        pvt->pty_channel = g_io_channel_unix_new (pty_master);
+        g_io_channel_set_close_on_unref (pvt->pty_channel, FALSE);
+
+        /* FIXMEchpe: vte_pty_open_unix98 does the inverse ... */
+        /* Set the pty to be non-blocking. */
+        flags = fcntl(pty_master, F_GETFL);
+        if ((flags & O_NONBLOCK) == 0) {
+                fcntl(pty_master, F_SETFL, flags | O_NONBLOCK);
+        }
+
+        vte_terminal_set_size(terminal,
                               terminal->column_count,
                               terminal->row_count);
 
-       _vte_terminal_setup_utf8 (terminal);
+        _vte_terminal_setup_utf8 (terminal);
 
-       /* Open channels to listen for input on. */
-       _vte_terminal_connect_pty_read (terminal);
+        /* Open channels to listen for input on. */
+        _vte_terminal_connect_pty_read (terminal);
 
         g_object_notify(object, "pty");
+        g_object_notify(object, "pty-object");
 
         g_object_thaw_notify(object);
 }
@@ -13666,12 +13918,39 @@ vte_terminal_set_pty(VteTerminal *terminal, int pty_master)
  * Return value: the file descriptor, or -1 if the terminal has no PTY.
  *
  * Since: 0.20
+ *
+ * Deprecated: 0.24: Use vte_terminal_get_pty_object() and vte_pty_get_fd()
+ */
+int
+vte_terminal_get_pty(VteTerminal *terminal)
+{
+        VteTerminalPrivate *pvt;
+
+        g_return_val_if_fail (VTE_IS_TERMINAL (terminal), -1);
+
+        pvt = terminal->pvt;
+        if (pvt->pty != NULL)
+                return vte_pty_get_fd(pvt->pty);
+
+        return -1;
+}
+
+/**
+ * vte_terminal_get_pty_object:
+ * @terminal: a #VteTerminal
+ *
+ * Returns the #VtePty of @terminal.
+ *
+ * Return value: a #VtePty, or %NULL
+ *
+ * Since: 0.24
  */
-int vte_terminal_get_pty(VteTerminal *terminal)
+VtePty *
+vte_terminal_get_pty_object(VteTerminal *terminal)
 {
-  g_return_val_if_fail (VTE_IS_TERMINAL (terminal), -1);
+        g_return_val_if_fail (VTE_IS_TERMINAL (terminal), NULL);
 
-  return terminal->pvt->pty_master;
+        return terminal->pvt->pty;
 }
 
 /**
@@ -13683,7 +13962,7 @@ int vte_terminal_get_pty(VteTerminal *terminal)
  * exit status.
  * 
  * Note that this function may only be called from the signal handler of
- * the "child-exited" signal.
+ * the #VteTerminal::child-exited signal.
  * 
  * Returns: the child's exit status
  * 
diff --git a/src/vte.h b/src/vte.h
index 8cd4372..53fb2d8 100644
--- a/src/vte.h
+++ b/src/vte.h
@@ -28,6 +28,7 @@
 
 #define __VTE_VTE_H_INSIDE__ 1
 
+#include "vtepty.h"
 #include "vtetypebuiltins.h"
 #include "vteversion.h"
 
@@ -264,21 +265,39 @@ GType vte_terminal_get_type(void);
 							     VTE_TYPE_TERMINAL)
 #define VTE_TERMINAL_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), VTE_TYPE_TERMINAL, VteTerminalClass))
 
-/* You can get by with just these two functions. */
 GtkWidget *vte_terminal_new(void);
+
+VtePty *vte_terminal_pty_new (VteTerminal *terminal,
+                              VtePtyFlags flags,
+                              GError **error);
+
+void vte_terminal_watch_child (VteTerminal *terminal,
+                               GPid child_pid);
+
+#ifndef VTE_DISABLE_DEPRECATED
 pid_t vte_terminal_fork_command(VteTerminal *terminal,
 				const char *command, char **argv,
-				char **envv, const char *directory,
+				char **envv, const char *working_directory,
 				gboolean lastlog,
 				gboolean utmp,
-				gboolean wtmp);
-
-/* Users of libzvt may find this useful. */
+				gboolean wtmp) G_GNUC_DEPRECATED;
 pid_t vte_terminal_forkpty(VteTerminal *terminal,
-			   char **envv, const char *directory,
+			   char **envv, const char *working_directory,
 			   gboolean lastlog,
 			   gboolean utmp,
-			   gboolean wtmp);
+			   gboolean wtmp) G_GNUC_DEPRECATED;
+#endif /* VTE_DISABLE_DEPRECATED */
+
+gboolean vte_terminal_fork_command_full(VteTerminal *terminal,
+                                        VtePtyFlags pty_flags,
+                                        const char *working_directory,
+                                        char **argv,
+                                        char **envv,
+                                        GSpawnFlags spawn_flags,
+                                        GSpawnChildSetupFunc child_setup,
+                                        gpointer child_setup_data,
+                                        GPid *child_pid /* out */,
+                                        GError **error);
 
 /* Send data to the terminal to display, or to the terminal's forked command
  * to handle in some way.  If it's 'cat', they should be the same. */
@@ -453,10 +472,13 @@ const char *vte_terminal_get_status_line(VteTerminal *terminal);
 void vte_terminal_get_padding(VteTerminal *terminal, int *xpad, int *ypad) G_GNUC_DEPRECATED;
 #endif
 
-/* Attach an existing PTY master side to the terminal widget.  Use
- * instead of vte_terminal_fork_command(). */
+#ifndef VTE_DISABLE_DEPRECATED
 void vte_terminal_set_pty(VteTerminal *terminal, int pty_master);
 int vte_terminal_get_pty(VteTerminal *terminal);
+#endif
+
+void vte_terminal_set_pty_object(VteTerminal *terminal, VtePty *pty);
+VtePty *vte_terminal_get_pty_object(VteTerminal *terminal);
 
 /* Accessors for bindings. */
 GtkAdjustment *vte_terminal_get_adjustment(VteTerminal *terminal);
diff --git a/src/vteapp.c b/src/vteapp.c
index 26955b5..5c7375c 100644
--- a/src/vteapp.c
+++ b/src/vteapp.c
@@ -881,13 +881,35 @@ main(int argc, char **argv)
 
 	if (!console) {
 		if (shell) {
-			/* Launch a shell. */
+                        GError *err = NULL;
+                        char **command_argv = NULL;
+                        int command_argc;
+                        GPid pid = -1;
+
 			_VTE_DEBUG_IF(VTE_DEBUG_MISC)
 				vte_terminal_feed(terminal, message, -1);
-			vte_terminal_fork_command(terminal,
-						  command, NULL, env_add,
-						  working_directory,
-						  TRUE, TRUE, TRUE);
+
+                        if (command == NULL)
+                                command = "/bin/sh"; // FIXMEchpe
+
+                        if (command != NULL) {
+                                if (!g_shell_parse_argv(command, &command_argc, &command_argv, &err) ||
+                                    !vte_terminal_fork_command_full(terminal,
+                                                                    VTE_PTY_DEFAULT,
+                                                                    NULL,
+                                                                    command_argv,
+                                                                    env_add,
+                                                                    0,
+                                                                    NULL, NULL,
+                                                                    &pid,
+                                                                    &err)) {
+                                        g_warning("Failed to fork: %s\n", err->message);
+                                        g_error_free(err);
+                                } else {
+                                        g_print("Fork succeeded, PID %d\n", pid);
+                                }
+                        }
+                        g_strfreev(command_argv);
 	#ifdef VTE_DEBUG
 			if (command == NULL) {
 				vte_terminal_feed_child(terminal,
diff --git a/src/vtepty-private.h b/src/vtepty-private.h
new file mode 100644
index 0000000..70347ee
--- /dev/null
+++ b/src/vtepty-private.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2009, 2010 Christian Persch
+ *
+ * This 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+G_BEGIN_DECLS
+
+VtePtyFlags __vte_pty_get_pty_flags(gboolean lastlog,
+                                    gboolean utmp,
+                                    gboolean wtmp);
+
+char **__vte_pty_get_argv (const char *command,
+                           char **argv,
+                           GSpawnFlags *flags /* inout */);
+
+gboolean __vte_pty_spawn (VtePty *pty,
+                          const char *working_directory,
+                          char **argv,
+                          char **envv,
+                          GSpawnFlags spawn_flags,
+                          GSpawnChildSetupFunc child_setup,
+                          gpointer child_setup_data,
+                          GPid *child_pid /* out */,
+                          GError **error);
+
+gboolean __vte_pty_fork(VtePty *pty,
+                        GPid *pid,
+                        GError **error);
+
+G_END_DECLS
diff --git a/src/vtepty.h b/src/vtepty.h
new file mode 100644
index 0000000..a705f94
--- /dev/null
+++ b/src/vtepty.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright © 2009, 2010 Christian Persch
+ *
+ * This 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 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#if !defined (__VTE_VTE_H_INSIDE__) && !defined (VTE_COMPILATION)
+#error "Only <vte/vte.h> can be included directly."
+#endif
+
+#ifndef VTE_PTY_H
+#define VTE_PTY_H
+
+#include <glib-object.h>
+
+#include <sys/types.h> /* for pid_t */
+
+G_BEGIN_DECLS
+
+/**
+ * VtePtyFlags:
+ * @VTE_PTY_NO_LASTLOG: don't record the session in lastlog
+ * @VTE_PTY_NO_UTMP: don't record the session in utmp
+ * @VTE_PTY_NO_WTMP: don't record the session in wtmp
+ * @VTE_PTY_NO_HELPER: don't use the GNOME PTY helper to allocate the PTY
+ * @VTE_PTY_NO_FALLBACK: when allocating the PTY with the PTY helper fails,
+ *   don't fall back to try using PTY98
+ * @VTE_PTY_DEFAULT: the default flags
+ *
+ * Since: 0.24
+ */
+typedef enum {
+  VTE_PTY_NO_LASTLOG  = 1 << 0,
+  VTE_PTY_NO_UTMP     = 1 << 1,
+  VTE_PTY_NO_WTMP     = 1 << 2,
+  VTE_PTY_NO_HELPER   = 1 << 3,
+  VTE_PTY_NO_FALLBACK = 1 << 4,
+  VTE_PTY_DEFAULT     = 0
+} VtePtyFlags;
+
+/**
+ * VtePtyError:
+ * @VTE_PTY_ERROR_PTY_HELPER_FAILED: failure when using the GNOME PTY helper to
+ *   allocate the PTY
+ * @VTE_PTY_ERROR_PTY98_FAILED: failure when using PTY98 to allocate the PTY
+ *
+ * Since: 0.24
+ */
+typedef enum {
+  VTE_PTY_ERROR_PTY_HELPER_FAILED = 0,
+  VTE_PTY_ERROR_PTY98_FAILED
+} VtePtyError;
+
+GQuark vte_pty_error_quark (void);
+
+/**
+ * VTE_PTY_ERROR:
+ *
+ * Error domain for VTE PTY errors. Errors in this domain will be from the #VtePtyError
+ * enumeration. See #GError for more information on error domains.
+ *
+ * Since: 0.24
+ */
+#define VTE_PTY_ERROR (vte_pty_error_quark ())
+
+/* VTE PTY object */
+
+#define VTE_TYPE_PTY            (vte_pty_get_type())
+#define VTE_PTY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), VTE_TYPE_PTY, VtePty))
+#define VTE_PTY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  VTE_TYPE_PTY, VtePtyClass))
+#define VTE_IS_PTY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), VTE_TYPE_PTY))
+#define VTE_IS_PTY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  VTE_TYPE_PTY))
+#define VTE_PTY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  VTE_TYPE_PTY, VtePtyClass))
+
+typedef struct _VtePty        VtePty;
+typedef struct _VtePtyClass   VtePtyClass;
+
+GType vte_pty_get_type (void);
+
+VtePty *vte_pty_new (VtePtyFlags flags,
+                     GError **error);
+
+VtePty *vte_pty_new_foreign (int fd);
+
+int vte_pty_get_fd (VtePty *pty);
+
+void vte_pty_close (VtePty *pty);
+
+void vte_pty_child_setup (VtePty *pty);
+
+gboolean vte_pty_get_size (VtePty *pty,
+                           int *rows,
+                           int *columns,
+                           GError **error);
+
+gboolean vte_pty_set_size (VtePty *pty,
+                           int rows,
+                           int columns,
+                           GError **error);
+
+void vte_pty_set_utf8 (VtePty *pty,
+                       gboolean utf8);
+
+void vte_pty_set_term (VtePty *pty,
+                       const char *emulation);
+
+G_END_DECLS
+
+#endif /* VTE_PTY_H */



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