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/tmpl/vte.sgml b/doc/reference/tmpl/vte.sgml
index a7b98a0..6c3fa4c 100644
--- a/doc/reference/tmpl/vte.sgml
+++ b/doc/reference/tmpl/vte.sgml
@@ -440,53 +440,6 @@ for the terminal.
 @menushell: 
 
 
-<!-- ##### FUNCTION vte_terminal_fork_command ##### -->
-<para>
-</para>
-
-@terminal: 
-@command: 
-@argv: 
-@envv: 
-@directory: 
-@lastlog: 
-@utmp: 
-@wtmp: 
-@Returns: 
-
-
-<!-- ##### FUNCTION vte_terminal_forkpty ##### -->
-<para>
-
-</para>
-
-@terminal: 
-@envv: 
-@directory: 
-@lastlog: 
-@utmp: 
-@wtmp: 
-@Returns: 
-
-
-<!-- ##### FUNCTION vte_terminal_set_pty ##### -->
-<para>
-
-</para>
-
-@terminal: 
-@pty_master: 
-
-
-<!-- ##### FUNCTION vte_terminal_get_pty ##### -->
-<para>
-
-</para>
-
-@terminal: 
-@Returns: 
-
-
 <!-- ##### FUNCTION vte_terminal_feed ##### -->
 <para>
 
@@ -1183,6 +1136,121 @@ for the terminal.
 @ypad: 
 
 
+<!-- ##### ENUM VteSpawnFlags ##### -->
+<para>
+
+</para>
+
+@VTE_SPAWN_NO_LASTLOG: 
+@VTE_SPAWN_NO_UTMP: 
+@VTE_SPAWN_NO_WTMP: 
+@VTE_SPAWN_NO_HELPER: 
+@VTE_SPAWN_NO_FALLBACK: 
+@VTE_SPAWN_DEFAULT: 
+
+<!-- ##### ENUM VteSpawnError ##### -->
+<para>
+
+</para>
+
+@VTE_SPAWN_ERROR_PTY_HELPER_FAILED: 
+@VTE_SPAWN_ERROR_PTY98_FAILED: 
+@VTE_SPAWN_ERROR_UNKNOWN: 
+
+<!-- ##### FUNCTION vte_spawn_error_quark ##### -->
+<para>
+
+</para>
+
+@Returns: 
+
+
+<!-- ##### FUNCTION vte_terminal_spawn ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@flags: 
+@callback: 
+@callback_data: 
+@error: 
+@Returns: 
+
+
+<!-- ##### FUNCTION vte_terminal_spawn_child_setup ##### -->
+<para>
+
+</para>
+
+@data: 
+
+
+<!-- ##### FUNCTION vte_terminal_fork_command ##### -->
+<para>
+</para>
+
+@terminal: 
+@command: 
+@argv: 
+@envv: 
+@working_directory: 
+@lastlog: 
+@utmp: 
+@wtmp: 
+@Returns: 
+
+
+<!-- ##### FUNCTION vte_terminal_fork_command_full ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@vte_spawn_flags: 
+@working_directory: 
+@argv: 
+@envv: 
+@g_spawn_flags: 
+@child_setup: 
+@child_setup_data: 
+@child_pid: 
+@error: 
+@Returns: 
+
+
+<!-- ##### FUNCTION vte_terminal_forkpty ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@envv: 
+@working_directory: 
+@lastlog: 
+@utmp: 
+@wtmp: 
+@Returns: 
+
+
+<!-- ##### FUNCTION vte_terminal_set_pty ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@pty_master: 
+
+
+<!-- ##### FUNCTION vte_terminal_get_pty ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@Returns: 
+
+
 <!-- ##### FUNCTION vte_terminal_get_adjustment ##### -->
 <para>
 
diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt
index edfa51b..558a699 100644
--- a/doc/reference/vte-sections.txt
+++ b/doc/reference/vte-sections.txt
@@ -7,10 +7,6 @@ VteTerminalCursorShape
 VteTerminalCursorBlinkMode
 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
@@ -85,6 +81,19 @@ vte_terminal_set_encoding
 vte_terminal_get_encoding
 vte_terminal_get_status_line
 vte_terminal_get_padding
+
+<SUBSECTION>
+VteSpawnFlags
+VteSpawnError
+vte_spawn_error_quark
+vte_terminal_spawn
+vte_terminal_spawn_child_setup
+vte_terminal_fork_command
+vte_terminal_fork_command_full
+vte_terminal_forkpty
+vte_terminal_set_pty
+vte_terminal_get_pty
+
 <SUBSECTION Standard>
 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 453f8d9..bcdce37 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -163,7 +163,7 @@ EXTRA_DIST += $(pkglib_SCRIPTS)
 TEST_SH = check-doc-syntax.sh
 EXTRA_DIST += $(TEST_SH)
 
-check_PROGRAMS = dumpkeys iso2022 pty reaper reflect-text-view reflect-vte mev ssfe table trie xticker vteconv vtetc
+check_PROGRAMS = dumpkeys iso2022 reaper reflect-text-view reflect-vte mev ssfe table trie xticker vteconv vtetc
 TESTS = table trie $(TEST_SH)
 
 AM_CFLAGS = $(GLIB_CFLAGS) $(GOBJECT_CFLAGS)
@@ -276,9 +276,6 @@ trie_LDADD = $(VTE_LIBS)
 vtetc_SOURCES = vtetc.c vtetc.h debug.c debug.h
 vtetc_CPPFLAGS = -DTERMCAP_MAIN
 
-pty_SOURCES = pty.c pty.h debug.c debug.h
-pty_CPPFLAGS = -DPTY_MAIN -DLIBEXECDIR='"$(libexecdir)"'
-
 reaper_SOURCES = reaper.c reaper.h debug.c debug.h marshal.c marshal.h
 reaper_CPPFLAGS =  -DREAPER_MAIN -DLOCALEDIR='"$(localedir)"'
 
diff --git a/src/pty.c b/src/pty.c
index 4968e6a..a558375 100644
--- a/src/pty.c
+++ b/src/pty.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2001,2002 Red Hat, Inc.
+ * Copyright © 2009 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
@@ -17,6 +18,7 @@
  */
 
 #include <config.h>
+
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -153,9 +155,11 @@ struct vte_pty_child_setup_data {
 		const char *name;
 		int fd;
 	} tty;
+        const char *term;
 };
-static void
-vte_pty_child_setup (gpointer arg)
+
+void
+_vte_pty_child_setup (gpointer arg)
 {
 	struct vte_pty_child_setup_data *data = arg;
 	int fd = -1;
@@ -248,178 +252,42 @@ 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 */
+        g_setenv("TERM", data->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)
-{
-	g_ptr_array_add (array,
-			g_strconcat (name, "=", value, NULL));
-}
-
-static gchar **
-merge_environ (char **envp)
-{
-	GHashTable *table;
-	gchar **parent_environ;
-	GPtrArray *array;
-	gint i;
-
-	table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
-	parent_environ = g_listenv ();
-	for (i = 0; parent_environ[i] != NULL; i++) {
-		g_hash_table_replace (table,
-			              g_strdup (parent_environ[i]),
-				      g_strdup (g_getenv (parent_environ[i])));
-	}
-	g_strfreev (parent_environ);
-
-	if (envp != NULL) {
-		for (i = 0; envp[i] != NULL; i++) {
-			gchar *name = g_strdup (envp[i]);
-			gchar *value = strchr (name, '=');
-			if (value) {
-				*value = '\0';
-				value = g_strdup (value + 1);
-			}
-			g_hash_table_replace (table, name, value);
-		}
-	}
-
-	array = g_ptr_array_sized_new (g_hash_table_size (table) + 1);
-	g_hash_table_foreach (table, (GHFunc) collect_variables, array);
-	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)
-{
-	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)");
-		}
-
-		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
-
-	return ret;
-}
-
 /* 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_fork_on_pty_name (VteTerminal *terminal,
+                           VteSpawnCallback spawn_callback,
+                           gpointer spawn_callback_data,
+                           const char *path,
+			   int parent_fd,
+			   int columns,
+                           int rows,
+                           GPid *child,
+                           GError **error)
 {
 	struct vte_pty_child_setup_data data;
 
 	data.mode = TTY_OPEN_BY_NAME;
 	data.tty.name = path;
+        data.term = vte_terminal_get_emulation(terminal);
 
-	if (!_vte_pty_run_on_pty(&data,
-			command, argv, envp, directory,
-			child, NULL)) {
-		/* XXX propagate the error */
+        if (!spawn_callback(terminal,
+                            &data,
+                            spawn_callback_data,
+                            child,
+                            error)) {
 		return FALSE;
 	}
 
@@ -430,20 +298,26 @@ _vte_pty_fork_on_pty_name (const char *path, int parent_fd, char **envp,
 /* 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_on_pty_fd (VteTerminal *terminal,
+                         VteSpawnCallback spawn_callback,
+                         gpointer spawn_callback_data,
+                         int fd,
+                         int columns,
+                         int rows,
+                         GPid *child,
+                         GError **error)
 {
 	struct vte_pty_child_setup_data data;
 
 	data.mode = TTY_OPEN_BY_FD;
 	data.tty.fd = fd;
+        data.term = vte_terminal_get_emulation(terminal);
 
-	if (!_vte_pty_run_on_pty(&data,
-				command, argv, envp, directory,
-				child, NULL)) {
-		/* XXX propagate the error */
+        if (!spawn_callback(terminal,
+                            &data,
+                            spawn_callback_data,
+                            child,
+                            error)) {
 		return FALSE;
 	}
 
@@ -572,6 +446,9 @@ _vte_pty_getpt(void)
 		fd = open("/dev/ptc", O_RDWR | O_NOCTTY); /* AIX */
 	}
 #endif
+        if (fd == -1)
+                return fd;
+
 	/* Set it to blocking. */
 	flags = fcntl(fd, F_GETFL);
 	flags &= ~(O_NONBLOCK);
@@ -602,10 +479,35 @@ _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:
+ * @terminal: the #VteTerminal
+ * @spawn_callback: the #VteSpawnCallback
+ * @spawn_callback_data: the user data to pass to @callback
+ * @columns:
+ * @rows:
+ * @child: location to store the child's PID
+ * @pty_fd: location to store the PTY FD
+ * @error: location to store a #GError
+ *
+ * Opens a PTY, and calls @callback.
+ *
+ * If opening the PTY succeeded, and @callback returned %TRUE,
+ * @child will contain the child's PID and @pty_fd the
+ * PTY FD.
+ *
+ * Returns: %TRUE on success, or %FALSE on error with @error filled in
+ */
+static gboolean
+_vte_pty_open_unix98(VteTerminal *terminal,
+                     VteSpawnFlags vte_spawn_flags,
+                     VteSpawnCallback spawn_callback,
+                     gpointer spawn_callback_data,
+                     int columns,
+                     int rows,
+                     GPid *child,
+                     int *pty_fd,
+                     GError **error)
 {
 	int fd;
 	char *buf;
@@ -613,29 +515,44 @@ _vte_pty_open_unix98(GPid *child, char **env_add,
 	/* 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) {
+	if (fd == -1) {
+                g_set_error (error, VTE_SPAWN_ERROR,
+                             VTE_SPAWN_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)) {
+                        g_set_error_literal (error, VTE_SPAWN_ERROR,
+                                             VTE_SPAWN_ERROR_PTY98_FAILED,
+                                             "PTY setup failed");
+
 			_vte_debug_print(VTE_DEBUG_PTY,
 					"PTY setup failed, bailing.\n");
 			close(fd);
-			fd = -1;
+			return FALSE;
 		} 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)) {
+			if (!_vte_pty_fork_on_pty_name(terminal,
+                                                       spawn_callback,
+                                                       spawn_callback_data,
+                                                       buf, fd,
+                                                       columns, rows,
+                                                       child,
+                                                       error)) {
 				close(fd);
-				fd = -1;
+                                g_free(buf);
+
+				return FALSE;
 			}
 			g_free(buf);
 		}
-	}
-	return fd;
+
+        *pty_fd = fd;
+        return TRUE;
 }
 
 #ifdef VTE_USE_GNOME_PTY_HELPER
@@ -822,7 +739,7 @@ _vte_direct_compare(gconstpointer a, gconstpointer b)
 }
 
 static gboolean
-_vte_pty_start_helper(void)
+_vte_pty_start_helper(GError **error)
 {
 	int i, tmp[2], tunnel;
 	/* Sanity check. */
@@ -831,27 +748,31 @@ _vte_pty_start_helper(void)
 		 * 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_SPAWN_ERROR,
+                            VTE_SPAWN_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;
+                // FIXME doesn't this leak FDs tmp[0] and tmp[1] ?
+		goto failure;
 	}
 	close(tmp[0]);
 	close(tmp[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,
@@ -874,13 +795,73 @@ _vte_pty_start_helper(void)
 	_vte_pty_helper_map = g_tree_new(_vte_direct_compare);
 	atexit(_vte_pty_stop_helper);
 	return TRUE;
+
+failure:
+        g_set_error(error, VTE_SPAWN_ERROR,
+                    VTE_SPAWN_ERROR_PTY_HELPER_FAILED,
+                    "Failed to start gnome-pty-helper: %s",
+                    g_strerror (errno));
+        return FALSE;
 }
 
 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 (VteSpawnFlags vte_spawn_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 ((vte_spawn_flags & VTE_SPAWN_NO_LASTLOG) == 0) {
+		op += 1;
+	}
+	if ((vte_spawn_flags & VTE_SPAWN_NO_UTMP) == 0) {
+		op += 2;
+	}
+	if ((vte_spawn_flags & VTE_SPAWN_NO_WTMP) == 0) {
+		op += 4;
+	}
+	g_assert(op >= 0 && op < (int) G_N_ELEMENTS(opmap));
+
+        return opmap[op];
+}
+
+/*
+ * _vte_pty_open_with_helper:
+ * @vte_spawn_flags: some #VteSpawnFlags
+ * @terminal: the #VteTerminal
+ * @spawn_callback: the #VteSpawnCallback
+ * @spawn_callback_data: the user data to pass to @callback
+ * @columns:
+ * @rows:
+ * @child: location to store the child's PID
+ * @pty_fd: location to store the PTY FD
+ * @error: location to store a #GError
+ *
+ * Opens a PTY using gnome-pty-helper, and spawns the command.
+ *
+ * If opening the PTY succeeded, and @callback returned %TRUE,
+ * @child will contain the child's PID and @pty_fd the
+ * PTY FD.
+ *
+ * Returns: %TRUE on success, or %FALSE on error with @error filled in
+ */
+static gboolean
+_vte_pty_open_with_helper(VteTerminal *terminal,
+                          VteSpawnFlags vte_spawn_flags,
+                          VteSpawnCallback spawn_callback,
+                          gpointer spawn_callback_data,
+                          int columns,
+                          int rows,
+                          GPid *child,
+                          int *pty_fd,
+                          GError **error)
 {
 	GnomePtyOps ops;
 	int ret;
@@ -888,32 +869,47 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 	gpointer tag;
 	/* We have to use the pty helper here. */
 	if (!_vte_pty_helper_started) {
-		_vte_pty_helper_started = _vte_pty_start_helper();
+		_vte_pty_helper_started = _vte_pty_start_helper(error);
 	}
 	/* Try to open a new descriptor. */
 	if (_vte_pty_helper_started) {
-		ops = op;
+		ops = _vte_pty_helper_ops_from_flags(vte_spawn_flags);
 		/* Send our request. */
 		if (n_write(_vte_pty_helper_tunnel,
 			    &ops, sizeof(ops)) != sizeof(ops)) {
-			return -1;
+                        g_set_error (error, VTE_SPAWN_ERROR,
+                                     VTE_SPAWN_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_SPAWN_ERROR,
+                                     VTE_SPAWN_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_SPAWN_ERROR,
+                                             VTE_SPAWN_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_SPAWN_ERROR,
+                                     VTE_SPAWN_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,9 +917,16 @@ _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_SPAWN_ERROR,
+                                     VTE_SPAWN_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read master or slave pty from gnome-pty-helper: %s",
+                                     g_strerror (errsv));
+			return FALSE;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY,
 				"Got master pty %d and slave pty %d.\n",
@@ -934,87 +937,112 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 			      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)) {
+		if (!_vte_pty_fork_on_pty_fd(terminal,
+                                             spawn_callback,
+                                             spawn_callback_data,
+                                             childfd,
+                                             columns, rows,
+                                             child,
+                                             error)) {
+			g_tree_remove(_vte_pty_helper_map,
+				      GINT_TO_POINTER(parentfd));
+
 			close(parentfd);
 			close(childfd);
-			return -1;
+
+			return FALSE;
 		}
 		close(childfd);
-		return parentfd;
+
+                *pty_fd = parentfd;
+                return TRUE;
 	}
-	return -1;
+	return FALSE;
 }
-#endif
+
+#endif /* VTE_USE_GNOME_PTY_HELPER */
 
 /**
  * _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
+ * @terminal: the #VteTerminal
+ * @vte_spawn_flags: some #VteSpawnFlags
+ * @spawn_callback: the #VteSpawnCallback
+ * @spawn_callback_data: the user data to pass to @callback
+ * @columns:
+ * @rows:
+ * @child_pid: location to store the child's PID
+ * @pty_fd: location to store the PTY FD
+ * @error: location to store a #GError
+ *
+ * Opens a PTY with window size set to @rows x @columns,
+ * and calls @callback.
  *
- * 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.
+ * If opening the PTY succeeded, and @callback returned %TRUE,
+ * @child will contain the child's PID and @pty_fd the
+ * PTY FD.
  *
  * Returns: an open file descriptor for the pty master, -1 on failure
+ *   with @error filled in
  */
-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)
+gboolean
+_vte_pty_open(VteTerminal *terminal,
+              VteSpawnFlags vte_spawn_flags,
+              VteSpawnCallback spawn_callback,
+              gpointer spawn_callback_data,
+              int columns,
+              int rows,
+              pid_t *child_pid,
+              int *pty_fd,
+              GError **error)
+
 {
-	GPid child;
-	int ret = -1;
+        GError *err = NULL;
+        gboolean ret = FALSE;
+
+        g_assert(child_pid != NULL && pty_fd != NULL);
+
 #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;
-	}
+	if ((vte_spawn_flags & VTE_SPAWN_NO_HELPER) == 0) {
+		ret = _vte_pty_open_with_helper(terminal,
+                                                vte_spawn_flags,
+                                                spawn_callback,
+                                                spawn_callback_data,
+                                                columns, rows,
+                                                child_pid,
+                                                pty_fd,
+                                                &err);
+                g_assert(ret || err != NULL);
+
+                if (ret)
+                        goto out;
+
+                /* Only do fallback if gnome-pty-helper failed! */
+                if ((vte_spawn_flags & VTE_SPAWN_NO_FALLBACK) ||
+                    !g_error_matches(err,
+                                     VTE_SPAWN_ERROR,
+                                     VTE_SPAWN_ERROR_PTY_HELPER_FAILED)) {
+                        g_propagate_error (error, err);
+                        goto out;
+                }
+
+                g_clear_error(&err);
+        }
+#endif /* VTE_USE_GNOME_PTY_HELPER */
+
+        ret = _vte_pty_open_unix98(terminal,
+                                   vte_spawn_flags,
+                                   spawn_callback,
+                                   spawn_callback_data,
+                                   columns, rows,
+                                   child_pid,
+                                   pty_fd,
+                                   error);
+
+out:
 	_vte_debug_print(VTE_DEBUG_PTY,
-			"Returning ptyfd = %d, child = %ld.\n",
-			ret, (long) child);
+			"Returning %s with ptyfd = %d, child = %ld.\n",
+			ret ? "TRUE" : "FALSE", *pty_fd, (long) *child_pid);
+
 	return ret;
 }
 
@@ -1092,66 +1120,3 @@ _vte_pty_close(int pty)
 	}
 #endif
 }
-
-#ifdef PTY_MAIN
-int fd;
-
-static void
-sigchld_handler(int signum)
-{
-	/* This is very unsafe.  Never do it in production code. */
-	_vte_pty_close(fd);
-}
-
-int
-main(int argc, char **argv)
-{
-	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;
-}
-#endif
diff --git a/src/pty.h b/src/pty.h
index 063f92d..4edf01f 100644
--- a/src/pty.h
+++ b/src/pty.h
@@ -21,16 +21,26 @@
 
 #include <sys/types.h>
 
+#include <glib.h>
+#include "vte.h"
+
 G_BEGIN_DECLS
 
+void _vte_pty_child_setup (gpointer arg);
+
 /* Start up the given binary (exact path, not interpreted at all) in a
  * pseudo-terminal of its own, returning the descriptor for the master
  * side of the PTY pair, logging the session to the specified files, and
  * storing the child's PID in the given argument. */
-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);
+gboolean _vte_pty_open(VteTerminal *terminal,
+                       VteSpawnFlags vte_spawn_flags,
+                       VteSpawnCallback spawn_callback,
+                       gpointer spawn_callback_data,
+                       int columns,
+                       int rows,
+                       pid_t *child_pid,
+                       int *pty_fd,
+                       GError **error);
 
 /* Set or read the size of a terminal.  Returns 0 on success, -1 on failure,
  * with errno set to defined return codes from ioctl(). */
diff --git a/src/vte-private.h b/src/vte-private.h
index 6a8b3c7..9847c36 100644
--- a/src/vte-private.h
+++ b/src/vte-private.h
@@ -169,7 +169,6 @@ 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 */
 	GIOChannel *pty_channel;	/* master channel */
 	guint pty_input_source;
diff --git a/src/vte.c b/src/vte.c
index 2824554..5041bd9 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2001-2004 Red Hat, Inc.
+ * Copyright © 2008, 2009 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
@@ -3331,29 +3332,106 @@ _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_spawn_error_quark:
+ *
+ * The VTE spawn error domain.
+ *
+ * Returns: the error domain for VTE spawn errors
+ *
+ * Since: 0.24
+ */
+GQuark
+vte_spawn_error_quark(void)
 {
-        GObject *object = G_OBJECT(terminal);
-	char **env_add;
-	int i, fd;
-	pid_t pid;
+  static GQuark quark = 0;
+
+  if (G_UNLIKELY (quark == 0))
+    quark = g_quark_from_static_string("vte-spawn-error");
+
+  return quark;
+}
+
+/**
+ * vte_terminal_spawn_child_setup:
+ * @data: the data supplied by vte_terminal_spawn()'s callback
+ *
+ * Function to be called in the child of a fork() or as child setup
+ * function in a g_spawn_*() call in the callback of vte_terminal_spawn().
+ *
+ * Since: 0.24
+ */
+void
+vte_terminal_spawn_child_setup (gpointer data)
+{
+        g_assert (data != NULL);
+
+        _vte_pty_child_setup (data);
+}
+
+/**
+ * vte_terminal_spawn:
+ * @terminal:
+ * @flags: flags from #VteSpawnFlags
+ * @callback: a #VteSpawnCallback
+ * @user_data: user data for @callback
+ * @error: location for a #GError
+ *
+ * Allocates a new pseudo-terminal. If this succeeds, calls
+ * @callback with the specified @user_data. The callback should use
+ * fork() or the g_spawn_* API to start a process on the PTY. 
+ *
+ * If using fork(), it MUST call vte_terminal_spawn_child_setup() in the
+ * child with the @child_setup_data supplied to @callback.
+ *
+ * If using g_spawn_*(), it MUST either use vte_terminal_spawn_child_setup ()
+ * and the supplied #VteChildSetupData directly as the child setup and child
+ * setup data for g_spawn_*(), or call vte_terminal_spawn_child_setup () with
+ * the @child_setup_data supplied to @callback from its own child setup
+ * function.
+ * Also, you MUST pass the %G_SPAWN_DO_NOT_REAP_CHILD flag.
+ *
+ * The new child's PID must be returned in @callback's @child_pid
+ * out param.
+ *
+ * @callback must return %TRUE on success, and %FALSE on failure; in
+ * the latter case @callback's @error parameter must be filled in.
+ *
+ * When using the GNOME PTY Helper,
+ * unless some the %VTE_SPAWN_NO_LASTLOG, %VTE_SPAWN_NO_UTMP or
+ * %VTE_SPAWN_NO_WTMP flags are passed in @vte_spawn_flags, the
+ * session is logged in the corresponding lastlog, utmp or wtmp
+ * system files.
+ *
+ * When passing %VTE_SPAWN_NO_HELPER in @vte_spawn_flags, the
+ * GNOME PTY Helper bypassed entirely.
+ *
+ * When passing %VTE_SPAWN_NO_FALLBACK in @vte_spawn_flags,
+ * and opening a PTY using the PTY helper fails, there will
+ * be no fallback allocate a PTY using Unix98 PTY functions.
+ *
+ * Since: 0.24
+ */
+gboolean
+vte_terminal_spawn (VteTerminal *terminal,
+		    VteSpawnFlags flags,
+		    VteSpawnCallback callback,
+		    gpointer callback_data,
+		    GError **error)
+{
+        GObject *object;
 	VteReaper *reaper;
+        int fd = -1;
+	pid_t pid = -1;
 
-        g_object_freeze_notify(object);
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
+        g_return_val_if_fail(callback, FALSE);
+        g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
 
-	/* 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;
+        object = G_OBJECT(terminal);
+
+        g_object_freeze_notify(object);
 
 	/* Close any existing ptys. */
 	if (terminal->pvt->pty_channel != NULL) {
@@ -3373,17 +3451,21 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
 
 	/* 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);
-
+        fd = -1;
+	if (!_vte_pty_open(terminal,
+                           flags,
+                           callback, callback_data,
+                           terminal->column_count, terminal->row_count,
+                           &pid,
+                           &fd,
+                           error)) {
                 g_object_thaw_notify(object);
 
-		return -1;
+		return FALSE;
 	}
 
+        g_assert (pid > 0 && fd >= 0);
+
 	/* If we successfully started the process, set up to listen for its
 	 * output. */
 	if (pid != 0) {
@@ -3410,23 +3492,227 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
 			g_object_unref(reaper);
 	}
 
-	/* Clean up. */
-	g_strfreev(env_add);
-
         g_object_thaw_notify(object);
 
-	/* Return the pid to the caller. */
-	return pid;
+	return TRUE;
+}
+
+static VteSpawnFlags
+_vte_terminal_get_vte_spawn_flags(gboolean lastlog,
+                                  gboolean utmp,
+                                  gboolean wtmp)
+{
+        VteSpawnFlags flags = 0;
+
+        if (!lastlog)
+                flags |= VTE_SPAWN_NO_LASTLOG;
+        if (!utmp)
+                flags |= VTE_SPAWN_NO_UTMP;
+        if (!wtmp)
+                flags |= VTE_SPAWN_NO_WTMP;
+
+        return flags;
+}
+
+static void
+collect_variables (char *name, char *value, GPtrArray *array)
+{
+	g_ptr_array_add (array,
+			g_strconcat (name, "=", value, NULL));
+}
+
+static gchar **
+merge_environ (char **envp)
+{
+	GHashTable *table;
+	gchar **parent_environ;
+	GPtrArray *array;
+	gint i;
+
+	table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+	parent_environ = g_listenv ();
+	for (i = 0; parent_environ[i] != NULL; i++) {
+		g_hash_table_replace (table,
+			              g_strdup (parent_environ[i]),
+				      g_strdup (g_getenv (parent_environ[i])));
+	}
+	g_strfreev (parent_environ);
+
+	if (envp != NULL) {
+		for (i = 0; envp[i] != NULL; i++) {
+			gchar *name = g_strdup (envp[i]);
+			gchar *value = strchr (name, '=');
+			if (value) {
+				*value = '\0';
+				value = g_strdup (value + 1);
+			}
+			g_hash_table_replace (table, name, value);
+		}
+	}
+
+	array = g_ptr_array_sized_new (g_hash_table_size (table) + 1);
+	g_hash_table_foreach (table, (GHFunc) collect_variables, array);
+	g_hash_table_destroy (table);
+	g_ptr_array_add (array, NULL);
+	return (gchar **) g_ptr_array_free (array, FALSE);
+}
+
+typedef struct {
+        const char *working_directory;
+        char **argv;
+        char **envv;
+        GSpawnFlags spawn_flags;
+        gpointer vte_spawn_child_setup_data;
+        GSpawnChildSetupFunc user_child_setup;
+        gpointer user_child_setup_data;
+} ForkCallbackData;
+
+static void
+_vte_terminal_fork_command_child_setup (gpointer child_setup_data)
+{
+        ForkCallbackData *data = (ForkCallbackData *) child_setup_data;
+
+        vte_terminal_spawn_child_setup(data->vte_spawn_child_setup_data);
+
+        if (data->user_child_setup) {
+                data->user_child_setup(data->user_child_setup_data);
+        }
+}
+
+static gboolean
+_vte_terminal_fork_command (VteTerminal *terminal,
+                            gpointer child_setup_data,
+                            gpointer spawn_callback_data,
+                            GPid *pid,
+                            GError **error)
+{
+        ForkCallbackData *data = (ForkCallbackData *) spawn_callback_data;
+        const char *directory = data->working_directory;
+        char **envp = data->envv;
+        char **argv = data->argv;
+        GSpawnFlags spawn_flags = data->spawn_flags;
+	gboolean ret = TRUE;
+        char **envp2;
+        gint i;
+        GError *err = NULL;
+
+        /* Pass the child setup data down to our own child setup function */
+        data->vte_spawn_child_setup_data = child_setup_data;
+
+        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 = merge_environ (envp);
+
+        _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,
+                                       _vte_terminal_fork_command_child_setup,
+                                       data,
+                                       pid,
+                                       NULL, NULL, NULL,
+                                       &err);
+        if (!ret && 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,
+                                               _vte_terminal_fork_command_child_setup,
+                                               data,
+                                               pid,
+                                               NULL, NULL, NULL,
+                                               &err);
+        }
+
+        g_strfreev (envp2);
+
+        if (ret)
+                return TRUE;
+                
+        g_propagate_error (error, err);
+        return FALSE;
+}
+
+static char *
+_vte_terminal_get_user_shell (void)
+{
+	struct passwd *pwd;
+	char *command;
+
+	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;
+}
+
+static char **
+_vte_terminal_get_argv (const char *command,
+                        char **argv)
+{
+	char **argv2;
+	int i, argc;
+
+	/* push the command into argv[0] */
+	argc = argv ? g_strv_length (argv) : 0;
+	argv2 = g_new (char *, argc + 2);
+
+	argv2[0] = command != NULL ? g_strdup (command) : _vte_terminal_get_user_shell ();
+
+	for (i = 0; i < argc; i++) {
+		argv2[i+1] = g_strdup (argv[i]);
+	}
+	argv2[i+1] = NULL;
+
+	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
@@ -3440,57 +3726,176 @@ _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
+ * 
+ * @Deprecated: 0.24: Use vte_terminal_spawn() with a callback that calls one of the g_spawn_* functions
  */
 pid_t
 vte_terminal_fork_command(VteTerminal *terminal,
-			  const char *command, char **argv, char **envv,
-			  const char *directory,
-			  gboolean lastlog, gboolean utmp, gboolean wtmp)
+			  const char *command,
+                          char **argv,
+                          char **envv,
+			  const char *working_directory,
+			  gboolean lastlog,
+                          gboolean utmp,
+                          gboolean wtmp)
+{
+        ForkCallbackData data;
+        gboolean ret;
+#ifdef VTE_DEBUG
+        GError *error = NULL;
+        GError **err = &error;
+#else
+        GError **err = NULL;
+#endif
+
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+
+        data.working_directory = working_directory;
+        data.envv = envv;
+        data.argv = _vte_terminal_get_argv (command, argv);
+        data.user_child_setup = NULL;
+        data.user_child_setup_data = NULL;
+
+        data.spawn_flags = G_SPAWN_CHILD_INHERITS_STDIN |
+                           G_SPAWN_SEARCH_PATH;
+        if (argv) {
+                data.spawn_flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+        }
+
+        ret = vte_terminal_spawn(terminal,
+                                 _vte_terminal_get_vte_spawn_flags (lastlog, utmp, wtmp),
+                                 _vte_terminal_fork_command,
+                                 &data,
+                                 err);
+        g_strfreev (data.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 terminal->pvt->pty_pid;
+}
+
+/**
+ * vte_terminal_fork_command_full:
+ * @terminal: a #VteTerminal
+ * @vte_spawn_flags: flags from #VteSpawnFlags
+ * @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
+ * @g_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
+ *
+ * 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 @g_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_spawn() 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,
+                               VteSpawnFlags vte_spawn_flags,
+                               const char *working_directory,
+                               char **argv,
+                               char **envv,
+                               GSpawnFlags g_spawn_flags,
+                               GSpawnChildSetupFunc child_setup,
+                               gpointer child_setup_data,
+                               GPid *child_pid /* out */,
+                               GError **error)
 {
-	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+        ForkCallbackData data;
 
-	/* 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), 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);
+
+        data.working_directory = working_directory;
+        data.envv = envv;
+        data.argv = argv;
+
+        data.spawn_flags = g_spawn_flags;
+        data.user_child_setup = child_setup;
+        data.user_child_setup_data = child_setup_data;
+
+         /* FIXMEchpe: why this flag? */
+        data.spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
+
+        if (!vte_terminal_spawn(terminal,
+                                vte_spawn_flags,
+                                _vte_terminal_fork_command,
+                                &data,
+                                error))
+                return FALSE;
 
-	/* Start up the command and get the PTY of the master. */
-	return _vte_terminal_fork_basic(terminal, command, argv, envv,
-				        directory, lastlog, utmp, wtmp);
+        *child_pid = terminal->pvt->pty_pid;
+
+        return TRUE;
 }
 
+#ifdef HAVE_FORK
+static gboolean
+_vte_terminal_forkpty (VteTerminal *terminal,
+                       gpointer child_setup_data,
+                       gpointer spawn_callback_data,
+                       GPid *pid,
+                       GError **error)
+{
+        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_terminal_spawn_child_setup(child_setup_data);
+                        break;
+                default: /* parent */
+                        break;
+        }
+
+	return ret;
+}
+#endif /* HAVE_FORK */
+
 /**
  * vte_terminal_forkpty:
  * @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
@@ -3501,20 +3906,58 @@ 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 is 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 vte_terminal_spawn() with a callback that calls fork()
  */
 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
+        gboolean ret;
+#ifdef VTE_DEBUG
+        GError *error = NULL;
+        GError **err = &error;
+#else
+        GError **err = NULL;
+#endif
+
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+
+        ret = vte_terminal_spawn(terminal,
+                                 _vte_terminal_get_vte_spawn_flags (lastlog, utmp, wtmp),
+                                 _vte_terminal_forkpty,
+                                 NULL,
+                                 err);
+
+#ifdef VTE_DEBUG
+        if (error) {
+                _vte_debug_print(VTE_DEBUG_MISC,
+                                 "vte_terminal_forkpty failed: %s\n", error->message);
+                g_error_free(error);
+        }
+#endif
+
+        if (!ret)
+                return -1;
 
-	return _vte_terminal_fork_basic(terminal, NULL, NULL, envv,
-				       directory, lastlog, utmp, wtmp);
+        return terminal->pvt->pty_pid;
+
+#else /* !HAVE_FORK */
+        g_set_error_literal(error, VTE_SPAWN_ERROR, VTE_SPAWN_ERROR_UNKNOWN,
+                            "No fork implementation");
+        return -1;
+#endif /* HAVE_FORK */
 }
 
 /* Handle an EOF from the client. */
@@ -13673,10 +14116,16 @@ vte_terminal_set_pty(VteTerminal *terminal, int pty_master)
                close (terminal->pvt->pty_master);
        }
        terminal->pvt->pty_master = pty_master;
+
+       if (pty_master == -1) {
+                g_object_notify(object, "pty");
+                g_object_thaw_notify(object);
+                return;
+       }
+
        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) {
diff --git a/src/vte.h b/src/vte.h
index 5fe448f..08b6876 100644
--- a/src/vte.h
+++ b/src/vte.h
@@ -212,21 +212,86 @@ 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. */
+/**
+ * VteSpawnFlags:
+ * @VTE_SPAWN_NO_LASTLOG: don't record the session in lastlog
+ * @VTE_SPAWN_NO_UTMP: don't record the session in utmp
+ * @VTE_SPAWN_NO_WTMP: don't record the session in wtmp
+ * @VTE_SPAWN_NO_HELPER: don't use the GNOME PTY helper to allocate the PTY
+ * @VTE_SPAWN_NO_FALLBACK: when allocating the PTY with the PTY helper fails,
+ *   don't fall back to try using PTY98
+ * @VTE_SPAWN_DEFAULT: the default flags
+ * 
+ * Since: 0.24
+ */
+typedef enum {
+  VTE_SPAWN_NO_LASTLOG  = 1 << 0,
+  VTE_SPAWN_NO_UTMP     = 1 << 1,
+  VTE_SPAWN_NO_WTMP     = 1 << 2,
+  VTE_SPAWN_NO_HELPER   = 1 << 3,
+  VTE_SPAWN_NO_FALLBACK = 1 << 4,
+  VTE_SPAWN_DEFAULT     = 0
+} VteSpawnFlags;
+
+/**
+ * VteSpawnError:
+ * @VTE_SPAWN_ERROR_PTY_HELPER_FAILED: failure when using the GNOME PTY helper to
+ *   allocate the PTY
+ * @VTE_SPAWN_ERROR_PTY98_FAILED: failure when using PTY98 to allocate the PTY
+ * @VTE_SPAWN_ERROR_UNKNOWN: unknown failure opening the PTY
+ *
+ * Since: 0.24
+ */
+typedef enum {
+  VTE_SPAWN_ERROR_PTY_HELPER_FAILED = 0,
+  VTE_SPAWN_ERROR_PTY98_FAILED,
+  VTE_SPAWN_ERROR_UNKNOWN
+} VteSpawnError;
+
+GQuark vte_spawn_error_quark (void);
+
+#define VTE_SPAWN_ERROR (vte_spawn_error_quark ())
+
 GtkWidget *vte_terminal_new(void);
+
+typedef gboolean (* VteSpawnCallback) (VteTerminal *terminal,
+                                       gpointer child_setup_data,
+                                       gpointer callback_user_data,
+                                       GPid *child_pid /* out */,
+                                       GError **error);
+
+void vte_terminal_spawn_child_setup (gpointer data);
+
+gboolean vte_terminal_spawn (VteTerminal *terminal,
+                             VteSpawnFlags flags,
+                             VteSpawnCallback callback,
+                             gpointer callback_data,
+                             GError **error);
+
+#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. */
 pid_t vte_terminal_forkpty(VteTerminal *terminal,
-			   char **envv, const char *directory,
+			   char **envv, const char *working_directory,
 			   gboolean lastlog,
 			   gboolean utmp,
 			   gboolean wtmp);
+#endif /* VTE_DISABLE_DEPRECATED */
+
+gboolean vte_terminal_fork_command_full(VteTerminal *terminal,
+                                        VteSpawnFlags vte_spawn_flags,
+                                        const char *working_directory,
+                                        char **argv,
+                                        char **envv,
+                                        GSpawnFlags g_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. */
