[vte] pty: Separate PTY code from its GObject wrapper



commit 8c72345d1df56287c8ffaa2186f5913ac587d837
Author: Christian Persch <chpe src gnome org>
Date:   Sat Oct 12 20:10:55 2019 +0200

    pty: Separate PTY code from its GObject wrapper

 src/fwd.hh            |  28 ++
 src/meson.build       |  11 +-
 src/pty.cc            | 770 +++++++++-----------------------------------------
 src/pty.hh            |  83 ++++++
 src/vte.cc            | 217 +++++---------
 src/vtegtk.cc         |  48 +++-
 src/vteinternal.hh    |  51 ++--
 src/vtepty-private.h  |  33 ---
 src/vtepty.cc         | 722 ++++++++++++++++++++++++++++++++++++++++++++++
 src/vteptyinternal.hh |  35 +++
 src/vtetypes.hh       |  19 +-
 src/widget.cc         |  27 ++
 src/widget.hh         |  13 +-
 13 files changed, 1182 insertions(+), 875 deletions(-)
---
diff --git a/src/fwd.hh b/src/fwd.hh
new file mode 100644
index 00000000..11525a98
--- /dev/null
+++ b/src/fwd.hh
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2019 Christian Persch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+namespace vte {
+
+namespace base {
+
+class Pty;
+
+} // namespace base
+
+} // namespace vte
diff --git a/src/meson.build b/src/meson.build
index 2921d838..efaf81f4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -49,6 +49,13 @@ parser_sources = files(
   'parser.hh',
 )
 
+pty_sources = files(
+  'pty.cc',
+  'pty.hh',
+  'vtepty.cc',
+  'vteptyinternal.hh',
+)
+
 regex_sources = files(
   'regex.cc',
   'regex.hh'
@@ -59,7 +66,7 @@ utf8_sources = files(
   'utf8.hh',
 )
 
-libvte_common_sources = debug_sources + modes_sources + parser_sources + regex_sources + utf8_sources + 
files(
+libvte_common_sources = debug_sources + modes_sources + parser_sources + pty_sources + regex_sources + 
utf8_sources + files(
   'attr.hh',
   'bidi.cc',
   'bidi.hh',
@@ -71,7 +78,6 @@ libvte_common_sources = debug_sources + modes_sources + parser_sources + regex_s
   'color-triple.hh',
   'keymap.cc',
   'keymap.h',
-  'pty.cc',
   'reaper.cc',
   'reaper.hh',
   'refptr.hh',
@@ -91,7 +97,6 @@ libvte_common_sources = debug_sources + modes_sources + parser_sources + regex_s
   'vtegtk.hh',
   'vteinternal.hh',
   'vtepcre2.h',
-  'vtepty-private.h',
   'vteregex.cc',
   'vteregexinternal.hh',
   'vterowdata.cc',
diff --git a/src/pty.cc b/src/pty.cc
index 87cc0dd1..df60c9f5 100644
--- a/src/pty.cc
+++ b/src/pty.cc
@@ -1,11 +1,11 @@
 /*
  * Copyright (C) 2001,2002 Red Hat, Inc.
- * Copyright © 2009, 2010 Christian Persch
+ * Copyright © 2009, 2010, 2019 Christian Persch
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
+ * version 3 of the License, or (at your option) any later version.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -28,8 +28,10 @@
 
 #include <config.h>
 
+#include "pty.hh"
+
 #include <vte/vte.h>
-#include "vtepty-private.h"
+#include "vteptyinternal.hh"
 #include "vtetypes.hh"
 #include "vtespawn.hh"
 
@@ -79,51 +81,25 @@
 
 #define VTE_TERMINFO_NAME "xterm-256color"
 
-#define I_(string) (g_intern_static_string(string))
-
-typedef struct _VtePtyPrivate VtePtyPrivate;
-
-typedef struct {
-       GSpawnChildSetupFunc extra_child_setup;
-       gpointer extra_child_setup_data;
-} VtePtyChildSetupData;
-
-/**
- * VtePty:
- */
-struct _VtePty {
-        GObject parent_instance;
-
-        /* <private> */
-        VtePtyPrivate *priv;
-};
+namespace vte::base {
 
-struct _VtePtyPrivate {
-        VtePtyFlags flags;
-        int pty_fd;
-
-        VtePtyChildSetupData child_setup_data;
-
-        guint utf8 : 1;
-        guint foreign : 1;
-};
-
-struct _VtePtyClass {
-        GObjectClass parent_class;
-};
+Pty*
+Pty::ref() noexcept
+{
+        g_atomic_int_inc(&m_refcount);
+        return this;
+}
 
-/**
- * vte_pty_child_setup:
- * @pty: a #VtePty
- *
- * FIXMEchpe
- */
 void
-vte_pty_child_setup (VtePty *pty)
+Pty::unref() noexcept
 {
-        VtePtyPrivate *priv = pty->priv;
-       VtePtyChildSetupData *data = &priv->child_setup_data;
+        if (g_atomic_int_dec_and_test(&m_refcount))
+                delete this;
+}
 
+void
+Pty::child_setup() const noexcept
+{
         /* Unblock all signals */
         sigset_t set;
         sigemptyset(&set);
@@ -141,7 +117,7 @@ vte_pty_child_setup (VtePty *pty)
                 signal(n, SIG_DFL);
         }
 
-        auto masterfd = priv->pty_fd;
+        auto masterfd = fd();
         if (masterfd == -1)
                 _exit(127);
 
@@ -156,7 +132,7 @@ vte_pty_child_setup (VtePty *pty)
         }
 
         /* Note: *not* O_CLOEXEC! */
-        auto const fd_flags = int{O_RDWR | ((priv->flags & VTE_PTY_NO_CTTY) ? O_NOCTTY : 0)};
+        auto const fd_flags = int{O_RDWR | ((m_flags & VTE_PTY_NO_CTTY) ? O_NOCTTY : 0)};
         auto fd = int{-1};
 
 #ifdef __linux__
@@ -187,7 +163,7 @@ vte_pty_child_setup (VtePty *pty)
                                   "Setting up child pty: master FD = %d name = %s\n",
                                   masterfd, name);
 
-                fd = open(name, fd_flags);
+                fd = ::open(name, fd_flags);
                 if (fd == -1) {
                         _vte_debug_print (VTE_DEBUG_PTY, "Failed to open PTY: %m\n");
                         _exit(127);
@@ -197,7 +173,7 @@ vte_pty_child_setup (VtePty *pty)
         assert(fd != -1);
 
 #if defined(HAVE_SETSID) && defined(HAVE_SETPGID)
-        if (!(priv->flags & VTE_PTY_NO_SESSION)) {
+        if (!(m_flags & VTE_PTY_NO_SESSION)) {
                 /* Start a new session and become process-group leader. */
                 _vte_debug_print (VTE_DEBUG_PTY, "Starting new session\n");
                 setsid();
@@ -206,7 +182,7 @@ vte_pty_child_setup (VtePty *pty)
 #endif
 
 #ifdef TIOCSCTTY
-        if (!(priv->flags & VTE_PTY_NO_CTTY)) {
+        if (!(m_flags & VTE_PTY_NO_CTTY)) {
                 ioctl(fd, TIOCSCTTY, fd);
         }
 #endif
@@ -263,8 +239,8 @@ vte_pty_child_setup (VtePty *pty)
         g_setenv ("VTE_VERSION", version, TRUE);
 
        /* Finally call an extra child setup */
-       if (data->extra_child_setup) {
-               data->extra_child_setup (data->extra_child_setup_data);
+       if (m_extra_child_setup.func) {
+               m_extra_child_setup.func(m_extra_child_setup.data);
        }
 }
 
@@ -344,6 +320,13 @@ __vte_pty_merge_environ (char **envp,
        return (gchar **) g_ptr_array_free (array, FALSE);
 }
 
+static void
+pty_child_setup_cb(void* data)
+{
+        vte::base::Pty* pty = reinterpret_cast<vte::base::Pty*>(data);
+        pty->child_setup();
+}
+
 /*
  * __vte_pty_spawn:
  * @pty: a #VtePty
@@ -375,27 +358,24 @@ __vte_pty_merge_environ (char **envp,
  *
  * 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 */,
-                 int timeout,
-                 GCancellable *cancellable,
-                 GError **error)
+bool
+Pty::spawn(char const* directory,
+           char** argv,
+           char** envv,
+           GSpawnFlags spawn_flags_,
+           GSpawnChildSetupFunc child_setup_func,
+           gpointer child_setup_data,
+           GPid* child_pid /* out */,
+           int timeout,
+           GCancellable* cancellable,
+           GError** error) noexcept
 {
-       VtePtyPrivate *priv = pty->priv;
-        VtePtyChildSetupData *data = &priv->child_setup_data;
         guint spawn_flags = (guint) spawn_flags_;
-       gboolean ret = TRUE;
-        gboolean inherit_envv;
-        char **envp2;
-        gint i;
-        GError *err = NULL;
+        bool ret{true};
+        bool inherit_envv;
+        char** envp2;
+        int i;
+        GError* err{nullptr};
         GPollFD pollfd;
 
         if (cancellable && !g_cancellable_make_pollfd(cancellable, &pollfd)) {
@@ -405,13 +385,14 @@ __vte_pty_spawn (VtePty *pty,
                             g_io_error_from_errno(errsv),
                             "Failed to make cancellable pollfd: %s",
                             g_strerror(errsv));
-                return FALSE;
+                return false;
         }
 
         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.
+        /* We do NOT support this flag. If you want to have some FD open in the child
+         * process, simply use a child setup function that unsets the CLOEXEC flag
+         * on that FD.
          */
         spawn_flags &= ~G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
 
@@ -433,156 +414,123 @@ __vte_pty_spawn (VtePty *pty,
                             directory ? directory : "(none)");
         }
 
-       data->extra_child_setup = child_setup;
-       data->extra_child_setup_data = child_setup_data;
+       m_extra_child_setup.func = child_setup_func;
+       m_extra_child_setup.data = child_setup_data;
 
         ret = vte_spawn_async_with_pipes_cancellable(directory,
                                                      argv, envp2,
                                                      (GSpawnFlags)spawn_flags,
-                                                     (GSpawnChildSetupFunc)vte_pty_child_setup,
-                                                     pty,
+                                                     (GSpawnChildSetupFunc)pty_child_setup_cb,
+                                                     this,
                                                      child_pid,
-                                                     NULL, NULL, NULL,
+                                                     nullptr, nullptr, nullptr,
                                                      timeout,
-                                                     cancellable ? &pollfd : NULL,
+                                                     cancellable ? &pollfd : nullptr,
                                                      &err);
         if (!ret &&
-            directory != NULL &&
+            directory != nullptr &&
             g_error_matches(err, G_SPAWN_ERROR, G_SPAWN_ERROR_CHDIR)) {
                 /* try spawning in our working directory */
                 g_clear_error(&err);
-                ret = vte_spawn_async_with_pipes_cancellable(NULL,
+                ret = vte_spawn_async_with_pipes_cancellable(nullptr,
                                                              argv, envp2,
                                                              (GSpawnFlags)spawn_flags,
-                                                             (GSpawnChildSetupFunc)vte_pty_child_setup,
-                                                             pty,
+                                                             (GSpawnChildSetupFunc)pty_child_setup_cb,
+                                                             this,
                                                              child_pid,
-                                                             NULL, NULL, NULL,
+                                                             nullptr, nullptr, nullptr,
                                                              timeout,
-                                                             cancellable ? &pollfd : NULL,
+                                                             cancellable ? &pollfd : nullptr,
                                                              &err);
         }
 
         g_strfreev (envp2);
 
-       data->extra_child_setup = NULL;
-       data->extra_child_setup_data = NULL;
+       m_extra_child_setup.func = nullptr;
+       m_extra_child_setup.data = nullptr;
 
         if (cancellable)
                 g_cancellable_release_fd(cancellable);
 
         if (ret)
-                return TRUE;
+                return true;
 
-        g_propagate_error (error, err);
-        return FALSE;
+        g_propagate_error(error, err);
+        return false;
 }
 
-/**
- * vte_pty_set_size:
- * @pty: a #VtePty
+/*
+ * Pty::set_size:
  * @rows: the desired number of rows
  * @columns: the desired number of columns
- * @error: (allow-none): 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.
  *
- * If setting the window size failed, @error will be set to a #GIOError.
- *
- * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ * Returns: %true on success, or %false on error with errno set
  */
-gboolean
-vte_pty_set_size(VtePty *pty,
-                 int rows,
-                 int columns,
-                 GError **error)
+bool
+Pty::set_size(int rows,
+              int columns) const noexcept
 {
-       struct winsize size;
-        int master;
-       int ret;
-
-        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
-
-        master = vte_pty_get_fd(pty);
+        auto master = fd();
 
+       struct winsize size;
        memset(&size, 0, sizeof(size));
        size.ws_row = rows > 0 ? rows : 24;
        size.ws_col = columns > 0 ? columns : 80;
        _vte_debug_print(VTE_DEBUG_PTY,
                        "Setting size on fd %d to (%d,%d).\n",
                        master, columns, rows);
-       ret = ioctl(master, TIOCSWINSZ, &size);
-       if (ret != 0) {
-                vte::util::restore_errno errsv;
-
-                g_set_error(error, G_IO_ERROR,
-                            g_io_error_from_errno(errsv),
-                            "Failed to set window size: %s",
-                            g_strerror(errsv));
+        auto ret = ioctl(master, TIOCSWINSZ, &size);
 
-               _vte_debug_print(VTE_DEBUG_PTY,
-                               "Failed to set size on %d: %s.\n",
-                               master, g_strerror(errsv));
-                return FALSE;
-       }
+        if (ret != 0) {
+                vte::util::restore_errno errsv;
+                _vte_debug_print(VTE_DEBUG_PTY,
+                                 "Failed to set size on %d: %m\n", master);
+        }
 
-        return TRUE;
+        return ret == 0;
 }
 
-/**
- * vte_pty_get_size:
- * @pty: a #VtePty
+/*
+ * Pty::get_size:
  * @rows: (out) (allow-none): a location to store the number of rows, or %NULL
  * @columns: (out) (allow-none): a location to store the number of columns, or %NULL
- * @error: return location to store a #GError, or %NULL
  *
  * Reads the pseudo terminal's window size.
  *
  * If getting the window size failed, @error will be set to a #GIOError.
  *
- * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ * Returns: %true on success, or %false on error with errno set
  */
-gboolean
-vte_pty_get_size(VtePty *pty,
-                 int *rows,
-                 int *columns,
-                 GError **error)
+bool
+Pty::get_size(int* rows,
+              int* columns) const noexcept
 {
-       struct winsize size;
-        int master;
-       int ret;
-
-        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
-
-        master = vte_pty_get_fd(pty);
+        auto master = fd();
 
+       struct winsize size;
        memset(&size, 0, sizeof(size));
-       ret = ioctl(master, TIOCGWINSZ, &size);
-       if (ret == 0) {
-               if (columns != NULL) {
+        auto ret = ioctl(master, TIOCGWINSZ, &size);
+        if (ret == 0) {
+               if (columns != nullptr) {
                        *columns = size.ws_col;
                }
-               if (rows != NULL) {
+               if (rows != nullptr) {
                        *rows = size.ws_row;
                }
                _vte_debug_print(VTE_DEBUG_PTY,
                                "Size on fd %d is (%d,%d).\n",
                                master, size.ws_col, size.ws_row);
-                return TRUE;
-       } else {
-                vte::util::restore_errno errsv;
+                return true;
+       }
 
-                g_set_error(error, G_IO_ERROR,
-                            g_io_error_from_errno(errsv),
-                            "Failed to get window size: %s",
-                            g_strerror(errsv));
+        vte::util::restore_errno errsv;
+        _vte_debug_print(VTE_DEBUG_PTY,
+                         "Failed to read size from fd %d: %m\n", master);
 
-               _vte_debug_print(VTE_DEBUG_PTY,
-                               "Failed to read size from fd %d: %s\n",
-                               master, g_strerror(errsv));
-                return FALSE;
-       }
+        return false;
 }
 
 static int
@@ -722,521 +670,65 @@ _vte_pty_open_foreign(int masterfd /* consumed */)
         return fd.steal();
 }
 
-/**
- * vte_pty_set_utf8:
- * @pty: a #VtePty
+/*
+ * Pty::set_utf8:
  * @utf8: whether or not the pty is in UTF-8 mode
- * @error: (allow-none): return location to store a #GError, or %NULL
  *
  * 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.
  *
- * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ * Returns: %true on success, or %false on error with errno set
  */
-gboolean
-vte_pty_set_utf8(VtePty *pty,
-                 gboolean utf8,
-                 GError **error)
+bool
+Pty::set_utf8(bool utf8) const noexcept
 {
 #if defined(HAVE_TCSETATTR) && defined(IUTF8)
-        VtePtyPrivate *priv;
        struct termios tio;
-       tcflag_t saved_cflag;
-
-        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
-
-        priv = pty->priv;
-        g_return_val_if_fail (priv->pty_fd != -1, FALSE);
-
-        if (tcgetattr(priv->pty_fd, &tio) == -1) {
+        if (tcgetattr(fd(), &tio) == -1) {
                 vte::util::restore_errno errsv;
-                g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
-                            "%s failed: %s", "tcgetattr", g_strerror(errsv));
-                return FALSE;
+                _vte_debug_print(VTE_DEBUG_PTY, "%s failed: %m", "tcgetattr");
+                return false;
         }
 
-        saved_cflag = tio.c_iflag;
+        auto saved_cflag = tio.c_iflag;
         if (utf8) {
                 tio.c_iflag |= IUTF8;
         } else {
-              tio.c_iflag &= ~IUTF8;
+                tio.c_iflag &= ~IUTF8;
         }
 
         /* Only set the flag if it changes */
         if (saved_cflag != tio.c_iflag &&
-            tcsetattr(priv->pty_fd, TCSANOW, &tio) == -1) {
+            tcsetattr(fd(), TCSANOW, &tio) == -1) {
                 vte::util::restore_errno errsv;
-                g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
-                            "%s failed: %s", "tcgetattr", g_strerror(errsv));
-                return FALSE;
+                _vte_debug_print(VTE_DEBUG_PTY, "%s failed: %m", "tcsetattr");
+                return false;
        }
 #endif
 
-        return TRUE;
-}
-
-/**
- * vte_pty_close:
- * @pty: a #VtePty
- *
- * Since 0.42 this is a no-op.
- *
- * Deprecated: 0.42
- */
-void
-vte_pty_close (VtePty *pty)
-{
-}
-
-/* VTE PTY class */
-
-enum {
-        PROP_0,
-        PROP_FLAGS,
-        PROP_FD,
-};
-
-/* GInitable impl */
-
-static gboolean
-vte_pty_initable_init (GInitable *initable,
-                       GCancellable *cancellable,
-                       GError **error)
-{
-        VtePty *pty = VTE_PTY (initable);
-        VtePtyPrivate *priv = pty->priv;
-
-        if (cancellable != NULL) {
-                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
-                                    "Cancellable initialisation not supported");
-                return FALSE;
-        }
-
-        if (priv->foreign) {
-                priv->pty_fd = _vte_pty_open_foreign(priv->pty_fd);
-        } else {
-                priv->pty_fd = _vte_pty_open_posix();
-        }
-
-        if (priv->pty_fd == -1) {
-                vte::util::restore_errno errsv;
-                g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
-                            "Failed to open PTY: %s", g_strerror(errsv));
-                return FALSE;
-        }
-
-        return TRUE;
-}
-
-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_ADD_PRIVATE (VtePty)
-                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, vte_pty_initable_iface_init))
-
-static void
-vte_pty_init (VtePty *pty)
-{
-        VtePtyPrivate *priv;
-
-        priv = pty->priv = (VtePtyPrivate *)vte_pty_get_instance_private (pty);
-
-        priv->flags = VTE_PTY_DEFAULT;
-        priv->pty_fd = -1;
-        priv->foreign = FALSE;
-}
-
-static void
-vte_pty_finalize (GObject *object)
-{
-        VtePty *pty = VTE_PTY (object);
-        VtePtyPrivate *priv = pty->priv;
-
-        /* 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;
-
-        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 = (VtePtyFlags) g_value_get_flags(value);
-                break;
-
-        case PROP_FD:
-                priv->pty_fd = g_value_get_int(value);
-                priv->foreign = (priv->pty_fd != -1);
-                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);
-
-        object_class->set_property = vte_pty_set_property;
-        object_class->get_property = vte_pty_get_property;
-        object_class->finalize     = vte_pty_finalize;
-
-        /**
-         * VtePty:flags:
-         *
-         * Flags.
-         */
-        g_object_class_install_property
-                (object_class,
-                 PROP_FLAGS,
-                 g_param_spec_flags ("flags", NULL, NULL,
-                                     VTE_TYPE_PTY_FLAGS,
-                                     VTE_PTY_DEFAULT,
-                                     (GParamFlags) (G_PARAM_READWRITE |
-                                                    G_PARAM_CONSTRUCT_ONLY |
-                                                    G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)));
-
-        /**
-         * VtePty:fd:
-         *
-         * The file descriptor of the PTY master.
-         */
-        g_object_class_install_property
-                (object_class,
-                 PROP_FD,
-                 g_param_spec_int ("fd", NULL, NULL,
-                                   -1, G_MAXINT, -1,
-                                   (GParamFlags) (G_PARAM_READWRITE |
-                                                  G_PARAM_CONSTRUCT_ONLY |
-                                                  G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)));
-}
-
-/* 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
- */
-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_sync: (constructor)
- * @flags: flags from #VtePtyFlags
- * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @error: (allow-none): return location for a #GError, or %NULL
- *
- * Allocates a new pseudo-terminal.
- *
- * You can later use fork() or the g_spawn_async() family 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.
- *
- * When using vte_terminal_spawn_sync() with a custom child setup
- * function, vte_pty_child_setup() will be called before the supplied
- * function; you must not call it again.
- *
- * Also, you MUST pass the %G_SPAWN_DO_NOT_REAP_CHILD flag.
- *
- * Returns: (transfer full): a new #VtePty, or %NULL on error with @error filled in
- */
-VtePty *
-vte_pty_new_sync (VtePtyFlags flags,
-                  GCancellable *cancellable,
-                  GError **error)
-{
-        return (VtePty *) g_initable_new (VTE_TYPE_PTY,
-                                          cancellable,
-                                          error,
-                                          "flags", flags,
-                                          NULL);
-}
-
-/**
- * vte_pty_new_foreign_sync: (constructor)
- * @fd: a file descriptor to the PTY
- * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @error: (allow-none): return location for a #GError, or %NULL
- *
- * 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: (transfer full): a new #VtePty for @fd, or %NULL on error with @error filled in
- */
-VtePty *
-vte_pty_new_foreign_sync (int fd,
-                          GCancellable *cancellable,
-                          GError **error)
-{
-        g_return_val_if_fail(fd >= 0, NULL);
-
-        return (VtePty *) g_initable_new (VTE_TYPE_PTY,
-                                          cancellable,
-                                          error,
-                                          "fd", fd,
-                                          NULL);
+        return true;
 }
 
-/**
- * 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 of have
- *   its flags changed
- */
-int
-vte_pty_get_fd (VtePty *pty)
+Pty*
+Pty::create(VtePtyFlags flags)
 {
-        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;
-}
+        auto fd = _vte_pty_open_posix();
+        if (fd == -1)
+                return nullptr;
 
-typedef struct {
-        VtePty* m_pty;
-        char* m_working_directory;
-        char** m_argv;
-        char** m_envv;
-        GSpawnFlags m_spawn_flags;
-        GSpawnChildSetupFunc m_child_setup;
-        gpointer m_child_setup_data;
-        GDestroyNotify m_child_setup_data_destroy;
-        int m_timeout;
-} AsyncSpawnData;
-
-static AsyncSpawnData*
-async_spawn_data_new (VtePty* pty,
-                      char const* working_directory,
-                      char** argv,
-                      char** envv,
-                      GSpawnFlags spawn_flags,
-                      GSpawnChildSetupFunc child_setup,
-                      gpointer child_setup_data,
-                      GDestroyNotify child_setup_data_destroy,
-                      int timeout)
-{
-        auto data = g_new(AsyncSpawnData, 1);
-
-        data->m_pty = (VtePty*)g_object_ref(pty);
-        data->m_working_directory = g_strdup(working_directory);
-        data->m_argv = g_strdupv(argv);
-        data->m_envv = envv ? g_strdupv(envv) : nullptr;
-        data->m_spawn_flags = spawn_flags;
-        data->m_child_setup = child_setup;
-        data->m_child_setup_data = child_setup_data;
-        data->m_child_setup_data_destroy = child_setup_data_destroy;
-        data->m_timeout = timeout;
-
-        return data;
+        return new Pty{fd, flags};
 }
 
-static void
-async_spawn_data_free(gpointer data_)
+Pty*
+Pty::create_foreign(int fd,
+                    VtePtyFlags flags)
 {
-        AsyncSpawnData *data = reinterpret_cast<AsyncSpawnData*>(data_);
-
-        g_free(data->m_working_directory);
-        g_strfreev(data->m_argv);
-        g_strfreev(data->m_envv);
-        if (data->m_child_setup_data && data->m_child_setup_data_destroy)
-                data->m_child_setup_data_destroy(data->m_child_setup_data);
-        g_object_unref(data->m_pty);
+        fd = _vte_pty_open_foreign(fd);
+        if (fd == -1)
+                return nullptr;
 
-        g_free(data);
+        return new Pty{fd, flags};
 }
 
-static void
-async_spawn_run_in_thread(GTask *task,
-                          gpointer object,
-                          gpointer data_,
-                          GCancellable *cancellable)
-{
-        AsyncSpawnData *data = reinterpret_cast<AsyncSpawnData*>(data_);
-
-        GPid pid;
-        GError *error = NULL;
-        if (__vte_pty_spawn(data->m_pty,
-                            data->m_working_directory,
-                            data->m_argv,
-                            data->m_envv,
-                            (GSpawnFlags)data->m_spawn_flags,
-                            data->m_child_setup, data->m_child_setup_data,
-                            &pid,
-                            data->m_timeout,
-                            cancellable,
-                            &error))
-                g_task_return_pointer(task, g_memdup(&pid, sizeof(pid)), g_free);
-        else
-                g_task_return_error(task, error);
-}
-
-/**
- * vte_pty_spawn_async:
- * @pty: a #VtePty
- * @working_directory: (allow-none): the name of a directory the command should start
- *   in, or %NULL to use the current working directory
- * @argv: (array zero-terminated=1) (element-type filename): child's argument vector
- * @envv: (allow-none) (array zero-terminated=1) (element-type filename): a list of environment
- *   variables to be added to the environment before starting the process, or %NULL
- * @spawn_flags: flags from #GSpawnFlags
- * @child_setup: (allow-none) (scope async): an extra child setup function to run in the child just before 
exec(), or %NULL
- * @child_setup_data: (closure child_setup): user data for @child_setup, or %NULL
- * @child_setup_data_destroy: (destroy child_setup_data): a #GDestroyNotify for @child_setup_data, or %NULL
- * @timeout: a timeout value in ms, or -1 to wait indefinitely
- * @cancellable: (allow-none): a #GCancellable, or %NULL
- *
- * Starts the specified command under the pseudo-terminal @pty.
- * The @argv and @envv lists should be %NULL-terminated.
- * The "TERM" environment variable is automatically set to a default value,
- * but can be overridden from @envv.
- * @pty_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 open file descriptors will be closed in the child. If you want
- * to keep some file descriptor open for use in the child process, you need to
- * use a child setup function that unsets the FD_CLOEXEC flag on that file
- * descriptor.
- *
- * See vte_pty_new(), g_spawn_async() and vte_terminal_watch_child() for more information.
- *
- * Since: 0.48
- */
-void
-vte_pty_spawn_async(VtePty *pty,
-                    const char *working_directory,
-                    char **argv,
-                    char **envv,
-                    GSpawnFlags spawn_flags,
-                    GSpawnChildSetupFunc child_setup,
-                    gpointer child_setup_data,
-                    GDestroyNotify child_setup_data_destroy,
-                    int timeout,
-                    GCancellable *cancellable,
-                    GAsyncReadyCallback callback,
-                    gpointer user_data)
-{
-        g_return_if_fail(argv != nullptr);
-        g_return_if_fail(!child_setup_data || child_setup);
-        g_return_if_fail(!child_setup_data_destroy || child_setup_data);
-        g_return_if_fail(cancellable == nullptr || G_IS_CANCELLABLE (cancellable));
-        g_return_if_fail(callback);
-
-        auto data = async_spawn_data_new(pty,
-                                         working_directory, argv, envv,
-                                         spawn_flags,
-                                         child_setup, child_setup_data, child_setup_data_destroy,
-                                         timeout);
-
-        auto task = g_task_new(pty, cancellable, callback, user_data);
-        g_task_set_source_tag(task, (void*)vte_pty_spawn_async);
-        g_task_set_task_data(task, data, async_spawn_data_free);
-        g_task_run_in_thread(task, async_spawn_run_in_thread);
-        g_object_unref(task);
-}
-
-/**
- * vte_pty_spawn_finish:
- * @pty: a #VtePty
- * @result: a #GAsyncResult
- * @child_pid: (out) (allow-none) (transfer full): a location to store the child PID, or %NULL
- * @error: (allow-none): return location for a #GError, or %NULL
- *
- * Returns: %TRUE on success, or %FALSE on error with @error filled in
- *
- * Since: 0.48
- */
-gboolean
-vte_pty_spawn_finish(VtePty *pty,
-                     GAsyncResult *result,
-                     GPid *child_pid /* out */,
-                     GError **error)
-{
-        g_return_val_if_fail (VTE_IS_PTY (pty), FALSE);
-        g_return_val_if_fail (G_IS_TASK (result), FALSE);
-        g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
-
-        gpointer pidptr = g_task_propagate_pointer(G_TASK(result), error);
-        if (pidptr == nullptr) {
-                if (child_pid)
-                        *child_pid = -1;
-                return FALSE;
-        }
-
-        if (child_pid)
-                *child_pid = *(GPid*)pidptr;
-        if (error)
-                *error = nullptr;
-
-        g_free(pidptr);
-        return TRUE;
-}
+} // namespace vte::base
diff --git a/src/pty.hh b/src/pty.hh
new file mode 100644
index 00000000..f03d2791
--- /dev/null
+++ b/src/pty.hh
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2018, 2019 Christian Persch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "vte/vteenums.h"
+#include "vtetypes.hh"
+
+namespace vte::base {
+
+class Pty {
+private:
+        mutable volatile int m_refcount{1};
+
+        mutable struct {
+                GSpawnChildSetupFunc func{nullptr};
+                void* data{nullptr};
+        } m_extra_child_setup;
+
+        vte::util::smart_fd m_pty_fd{};
+
+        VtePtyFlags m_flags{VTE_PTY_DEFAULT};
+
+public:
+        constexpr Pty(int fd = -1,
+                      VtePtyFlags flags = VTE_PTY_DEFAULT) noexcept
+                : m_pty_fd{fd},
+                  m_flags{flags}
+        {
+        }
+
+        Pty(Pty const&) = delete;
+        Pty(Pty&&) = delete;
+        Pty operator=(Pty const&) = delete;
+        Pty operator=(Pty&&) = delete;
+
+        Pty* ref() noexcept;
+        void unref() noexcept;
+
+        inline constexpr int fd() const noexcept { return m_pty_fd; }
+        inline constexpr auto flags() const noexcept { return m_flags; }
+
+        void child_setup() const noexcept;
+
+        bool spawn(char const* directory,
+                   char** argv,
+                   char** envv,
+                   GSpawnFlags spawn_flags_,
+                   GSpawnChildSetupFunc child_setup,
+                   void* child_setup_data,
+                   GPid* child_pid /* out */,
+                   int timeout,
+                   GCancellable* cancellable,
+                   GError** error) noexcept;
+
+        bool set_size(int rows,
+                      int columns) const noexcept;
+        bool get_size(int* rows,
+                      int* columns) const noexcept;
+        bool set_utf8(bool utf8) const noexcept;
+
+        static Pty* create(VtePtyFlags flags);
+        static Pty* create_foreign(int fd,
+                                   VtePtyFlags flags);
+};
+
+} // namespace vte::base
diff --git a/src/vte.cc b/src/vte.cc
index 45d480c8..abbead5a 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -63,7 +63,6 @@
 #include "marshal.h"
 #include "vteaccess.h"
 #include "vtepty.h"
-#include "vtepty-private.h"
 #include "vtegtk.hh"
 
 #include <new> /* placement new */
@@ -130,6 +129,14 @@ vte_g_array_fill(GArray *array, gconstpointer item, guint final_size)
        } while (--final_size);
 }
 
+void
+Terminal::unset_widget() noexcept
+{
+        m_real_widget = nullptr;
+        m_terminal = nullptr;
+        m_widget = nullptr;
+}
+
 // FIXMEchpe replace this with a method on VteRing
 VteRowData*
 Terminal::ring_insert(vte::grid::row_t position,
@@ -3111,7 +3118,7 @@ Terminal::child_watch_done(pid_t pid,
         m_pty_pid = -1;
 
         /* Close out the PTY. */
-        set_pty(nullptr);
+        unset_pty();
 
         /* Tell observers what's happened. */
         if (m_real_widget)
@@ -3173,12 +3180,12 @@ io_write_cb(GIOChannel *channel,
 void
 Terminal::connect_pty_write()
 {
-        g_assert(m_pty != nullptr);
+        assert(pty());
         g_warn_if_fail(m_input_enabled);
 
        if (m_pty_channel == nullptr) {
                m_pty_channel =
-                       g_io_channel_unix_new(vte_pty_get_fd(m_pty));
+                       g_io_channel_unix_new(pty()->fd());
        }
 
        if (m_pty_output_source == 0) {
@@ -3255,7 +3262,8 @@ Terminal::watch_child (pid_t child_pid)
 {
         // FIXMEchpe: support passing child_pid = -1 to remove the wathch
         g_assert(child_pid != -1);
-        g_assert(m_pty != nullptr);
+        if (!pty())
+                return;
 
         GObject *object = G_OBJECT(m_terminal);
         g_object_freeze_notify(object);
@@ -3286,86 +3294,6 @@ Terminal::watch_child (pid_t child_pid)
         g_object_thaw_notify(object);
 }
 
-/*
- * Terminal::spawn_sync:
- * @pty_flags: flags from #VtePtyFlags
- * @working_directory: (allow-none): the name of a directory the command should start
- *   in, or %NULL to use the current working directory
- * @argv: (array zero-terminated=1) (element-type filename): child's argument vector
- * @envv: (allow-none) (array zero-terminated=1) (element-type filename): a list of environment
- *   variables to be added to the environment before starting the process, or %NULL
- * @spawn_flags: flags from #GSpawnFlags
- * @child_setup: (allow-none) (scope call): an extra child setup function to run in the child just before 
exec(), or %NULL
- * @child_setup_data: user data for @child_setup
- * @child_pid: (out) (allow-none) (transfer full): a location to store the child PID, or %NULL
- * @cancellable: (allow-none): a #GCancellable, or %NULL
- * @error: (allow-none): 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 a default value,
- * but can be overridden from @envv.
- * @pty_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.
- *
- * See vte_pty_new(), g_spawn_async() and vte_terminal_watch_child() for more information.
- *
- * Returns: %TRUE on success, or %FALSE on error with @error filled in
- */
-bool
-Terminal::spawn_sync(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 */,
-                               GCancellable *cancellable,
-                               GError **error)
-{
-        guint spawn_flags = (guint)spawn_flags_;
-        VtePty *new_pty;
-        GPid pid;
-
-        g_assert(argv != nullptr);
-        g_assert(child_setup_data == nullptr || child_setup != nullptr);
-        g_assert(error == nullptr || *error == nullptr);
-
-        new_pty = vte_terminal_pty_new_sync(m_terminal, pty_flags, cancellable, error);
-        if (new_pty == nullptr)
-                return false;
-
-        /* We do NOT support this flag. If you want to have some FD open in the child
-         * process, simply use a child setup function that unsets the CLOEXEC flag
-         * on that FD.
-         */
-        spawn_flags &= ~G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
-
-        if (!__vte_pty_spawn(new_pty,
-                             working_directory,
-                             argv,
-                             envv,
-                             (GSpawnFlags)spawn_flags,
-                             child_setup, child_setup_data,
-                             &pid,
-                             -1 /* no timeout */, cancellable,
-                             error)) {
-                g_object_unref(new_pty);
-                return false;
-        }
-
-        set_pty(new_pty);
-        g_object_unref (new_pty);
-        watch_child(pid);
-
-        if (child_pid)
-                *child_pid = pid;
-
-        return true;
-}
-
 /* Handle an EOF from the client. */
 void
 Terminal::pty_channel_eof()
@@ -3374,7 +3302,7 @@ Terminal::pty_channel_eof()
 
         g_object_freeze_notify(object);
 
-        set_pty(nullptr);
+        unset_pty();
 
        /* Emit a signal that we read an EOF. */
        queue_eof();
@@ -4126,8 +4054,8 @@ Terminal::send_child(char const* data,
 
         /* If there's a place for it to go, add the data to the
          * outgoing buffer. */
-        // FIXMEchpe: shouldn't require m_pty for this
-        if ((cooked_length > 0) && (m_pty != NULL)) {
+        // FIXMEchpe: shouldn't require pty for this
+        if ((cooked_length > 0) && pty()) {
                 _vte_byte_array_append(m_outgoing, cooked, cooked_length);
                 _VTE_DEBUG_IF(VTE_DEBUG_KEYBOARD) {
                         for (i = 0; i < cooked_length; i++) {
@@ -4198,7 +4126,7 @@ Terminal::feed_child_binary(guint8 const* data,
 
                /* If there's a place for it to go, add the data to the
                 * outgoing buffer. */
-               if (m_pty != NULL) {
+               if (pty()) {
                        _vte_byte_array_append(m_outgoing,
                                           data, length);
                        /* If we need to start waiting for the child pty to
@@ -4706,8 +4634,8 @@ Terminal::widget_key_press(GdkEventKey *event)
                                suppress_meta_esc = TRUE;
                                break;
                        case VTE_ERASE_TTY:
-                               if (m_pty != nullptr &&
-                                   tcgetattr(vte_pty_get_fd(m_pty), &tio) != -1)
+                               if (pty() &&
+                                   tcgetattr(pty()->fd(), &tio) != -1)
                                {
                                        normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
                                        normal_length = 1;
@@ -4719,8 +4647,8 @@ Terminal::widget_key_press(GdkEventKey *event)
 #ifndef _POSIX_VDISABLE
 #define _POSIX_VDISABLE '\0'
 #endif
-                               if (m_pty != nullptr &&
-                                   tcgetattr(vte_pty_get_fd(m_pty), &tio) != -1 &&
+                               if (pty() &&
+                                   tcgetattr(pty()->fd(), &tio) != -1 &&
                                    tio.c_cc[VERASE] != _POSIX_VDISABLE)
                                {
                                        normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
@@ -4756,8 +4684,8 @@ Terminal::widget_key_press(GdkEventKey *event)
                                normal_length = 1;
                                break;
                        case VTE_ERASE_TTY:
-                               if (m_pty != nullptr &&
-                                   tcgetattr(vte_pty_get_fd(m_pty), &tio) != -1)
+                               if (pty() &&
+                                   tcgetattr(pty()->fd(), &tio) != -1)
                                {
                                        normal = g_strdup_printf("%c", tio.c_cc[VERASE]);
                                        normal_length = 1;
@@ -7529,11 +7457,11 @@ Terminal::set_cell_height_scale(double scale)
 void
 Terminal::refresh_size()
 {
-        if (!m_pty)
+        if (!pty())
                 return;
 
        int rows, columns;
-        if (!vte_pty_get_size(m_pty, &rows, &columns, nullptr)) {
+        if (!pty()->get_size(&rows, &columns)) {
                 /* Error reading PTY size, use defaults */
                 rows = VTE_ROWS;
                 columns = VTE_COLUMNS;
@@ -7701,16 +7629,12 @@ Terminal::set_size(long columns,
        old_rows = m_row_count;
        old_columns = m_column_count;
 
-       if (m_pty != NULL) {
-                GError *error = NULL;
-
+       if (pty()) {
                /* Try to set the terminal size, and read it back,
                 * in case something went awry.
                  */
-               if (!vte_pty_set_size(m_pty, rows, columns, &error)) {
-                       g_warning("%s\n", error->message);
-                        g_error_free(error);
-               }
+               if (!pty()->set_size(rows, columns))
+                       g_warning("Failed to set PTY size: %m\n");
                refresh_size();
        } else {
                m_row_count = rows;
@@ -7914,7 +7838,6 @@ Terminal::Terminal(vte::platform::Widget* w,
 
        /* Setting the terminal type and size requires the PTY master to
         * be set up properly first. */
-        m_pty = nullptr;
         set_size(VTE_COLUMNS, VTE_ROWS);
        m_pty_input_source = 0;
        m_pty_output_source = 0;
@@ -8204,7 +8127,7 @@ Terminal::~Terminal()
        int sel;
 
         terminate_child();
-        set_pty(nullptr, false /* don't process remaining data */);
+        unset_pty(false /* don't notify widget */, false /* don't process remaining data */);
         remove_update_timeout(this);
 
         /* Stop processing input. */
@@ -10197,50 +10120,61 @@ Terminal::reset(bool clear_tabstops,
         g_object_thaw_notify(object);
 }
 
-bool
-Terminal::set_pty(VtePty *new_pty,
-                  bool process_remaining)
+void
+Terminal::unset_pty(bool notify_widget,
+                    bool process_remaining)
 {
-        if (new_pty == m_pty)
-                return false;
+        /* This may be called from inside or from widget,
+         * and must notify the widget if not called from it.
+         */
 
-        if (m_pty != nullptr) {
-                disconnect_pty_read();
-                disconnect_pty_write();
+        disconnect_pty_read();
+        disconnect_pty_write();
 
-                if (m_pty_channel != nullptr) {
-                        g_io_channel_unref (m_pty_channel);
-                        m_pty_channel = nullptr;
-                }
+        if (m_pty_channel != nullptr) {
+                g_io_channel_unref (m_pty_channel);
+                m_pty_channel = nullptr;
+        }
 
-               /* 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 (!m_incoming_queue.empty() && process_remaining) {
-                       process_incoming();
-                        while (!m_incoming_queue.empty())
-                                m_incoming_queue.pop();
+        /* 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 (!m_incoming_queue.empty() && process_remaining) {
+                process_incoming();
+                while (!m_incoming_queue.empty())
+                        m_incoming_queue.pop();
 
-                       m_input_bytes = 0;
-               }
-               stop_processing(this);
+                m_input_bytes = 0;
+        }
+        stop_processing(this);
+
+        m_utf8_decoder.reset(); // FIXMEchpe necessary here?
+
+        /* Clear the outgoing buffer as well. */
+        _vte_byte_array_clear(m_outgoing);
 
-                m_utf8_decoder.reset(); // FIXMEchpe necessary here?
+        m_pty.reset();
 
-               /* Clear the outgoing buffer as well. */
-               _vte_byte_array_clear(m_outgoing);
+        if (notify_widget && widget())
+                widget()->unset_pty();
+}
 
-                g_object_unref(m_pty);
-                m_pty = nullptr;
+bool
+Terminal::set_pty(vte::base::Pty *new_pty,
+                  bool process_remaining)
+{
+        if (pty().get() == new_pty)
+                return false;
+
+        if (pty()) {
+                unset_pty(false /* don't notify widget */, process_remaining);
         }
 
-        if (new_pty == nullptr) {
-                m_pty = nullptr;
+        m_pty = vte::base::make_ref(new_pty);
+        if (!new_pty)
                 return true;
-        }
 
-        m_pty = (VtePty *)g_object_ref(new_pty);
-        int pty_master = vte_pty_get_fd(m_pty);
+        int pty_master = pty()->fd();
 
         /* Ensure the FD is non-blocking */
         int flags = fcntl(pty_master, F_GETFL);
@@ -10251,11 +10185,8 @@ Terminal::set_pty(VtePty *new_pty,
 
         set_size(m_column_count, m_row_count);
 
-        GError *error = nullptr;
-        if (!vte_pty_set_utf8(m_pty, m_using_utf8, &error)) {
-                g_warning ("Failed to set UTF8 mode: %s\n", error->message);
-                g_error_free (error);
-        }
+        if (!pty()->set_utf8(m_using_utf8))
+                g_warning ("Failed to set UTF8 mode: %m\n");
 
         /* Open channels to listen for input on. */
         connect_pty_read();
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index fe5d1c41..e3bff16b 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -56,6 +56,7 @@
 #include "widget.hh"
 
 #include "vtegtk.hh"
+#include "vteptyinternal.hh"
 #include "vteregexinternal.hh"
 
 #define I_(string) (g_intern_static_string(string))
@@ -2600,10 +2601,9 @@ vte_terminal_watch_child (VteTerminal *terminal,
         g_return_if_fail(VTE_IS_TERMINAL(terminal));
         g_return_if_fail(child_pid != -1);
 
-        auto impl = IMPL(terminal);
-        g_return_if_fail(impl->m_pty != NULL);
+        g_return_if_fail(WIDGET(terminal)->pty() != nullptr);
 
-        impl->watch_child(child_pid);
+        IMPL(terminal)->watch_child(child_pid);
 }
 
 /**
@@ -2663,16 +2663,34 @@ vte_terminal_spawn_sync(VteTerminal *terminal,
         g_return_val_if_fail(child_setup_data == NULL || child_setup, FALSE);
         g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
 
-        return IMPL(terminal)->spawn_sync(pty_flags,
-                                         working_directory,
-                                         argv,
-                                         envv,
-                                         spawn_flags_,
-                                         child_setup,
-                                         child_setup_data,
-                                         child_pid,
-                                         cancellable,
-                                         error);
+        auto new_pty = vte_terminal_pty_new_sync(terminal, pty_flags, cancellable, error);
+        if (new_pty == nullptr)
+                return false;
+
+        GPid pid;
+        if (!_vte_pty_spawn(new_pty,
+                            working_directory,
+                            argv,
+                            envv,
+                            spawn_flags_,
+                            child_setup, child_setup_data,
+                            &pid,
+                            -1 /* no timeout */,
+                            cancellable,
+                            error)) {
+                g_object_unref(new_pty);
+                return false;
+        }
+
+        vte_terminal_set_pty(terminal, new_pty);
+        g_object_unref (new_pty);
+
+        vte_terminal_watch_child(terminal, pid);
+
+        if (child_pid)
+                *child_pid = pid;
+
+        return true;
 }
 
 typedef struct {
@@ -4232,7 +4250,7 @@ vte_terminal_set_pty(VteTerminal *terminal,
         GObject *object = G_OBJECT(terminal);
         g_object_freeze_notify(object);
 
-        if (IMPL(terminal)->set_pty(pty))
+        if (WIDGET(terminal)->set_pty(pty))
                 g_object_notify_by_pspec(object, pspecs[PROP_PTY]);
 
         g_object_thaw_notify(object);
@@ -4251,7 +4269,7 @@ vte_terminal_get_pty(VteTerminal *terminal)
 {
         g_return_val_if_fail (VTE_IS_TERMINAL (terminal), NULL);
 
-        return IMPL(terminal)->m_pty;
+        return WIDGET(terminal)->pty();
 }
 
 /**
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 17d1c4db..216e268f 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -37,6 +37,7 @@
 #include "vteregexinternal.hh"
 
 #include "chunk.hh"
+#include "pty.hh"
 #include "utf8.hh"
 
 #include <list>
@@ -311,9 +312,16 @@ public:
         ~Terminal();
 
 public:
-        vte::platform::Widget* m_real_widget;
-        VteTerminal *m_terminal;
-        GtkWidget *m_widget;
+        vte::platform::Widget* m_real_widget{nullptr};
+        inline constexpr auto widget() const noexcept { return m_real_widget; }
+
+        VteTerminal *m_terminal{nullptr};
+        inline constexpr auto vte_terminal() const noexcept { return m_terminal; }
+
+        GtkWidget *m_widget{nullptr};
+        inline constexpr auto gtk_widget() const noexcept { return m_widget; }
+
+        void unset_widget() noexcept;
 
         /* Metric and sizing data: dimensions of the window */
         vte::grid::row_t m_row_count{VTE_ROWS};
@@ -327,7 +335,14 @@ public:
         vte::terminal::modes::Private m_modes_private{};
 
        /* PTY handling data. */
-        VtePty *m_pty;
+        vte::base::RefPtr<vte::base::Pty> m_pty{};
+        inline constexpr auto& pty() const noexcept { return m_pty; }
+
+        void unset_pty(bool notify_widget = true,
+                       bool process_remaining = true);
+        bool set_pty(vte::base::Pty* pty,
+                     bool process_remaining = true);
+
         GIOChannel *m_pty_channel;      /* master channel */
         guint m_pty_input_source;
         guint m_pty_output_source;
@@ -928,32 +943,6 @@ public:
         void im_reset();
         void im_update_cursor();
 
-        bool spawn_sync(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 */,
-                        GCancellable *cancellable,
-                        GError **error);
-#if 0
-        void spawn_async(VtePtyFlags pty_flags,
-                         const char *working_directory,
-                         char **argv,
-                         char **envv,
-                         GSpawnFlags spawn_flags_,
-                         GSpawnChildSetupFunc child_setup,
-                         gpointer child_setup_data,
-                         GDestroyNotify child_setup_data_destroy,
-                         GCancellable *cancellable,
-                         GAsyncReadyCallback callback,
-                         gpointer user_data);
-        bool spawn_finish(GAsyncResult *result,
-                          GPid *child_pid /* out */);
-#endif
-
         void reset(bool clear_tabstops,
                    bool clear_history,
                    bool from_api = false);
@@ -1251,8 +1240,6 @@ public:
         bool set_font_scale(double scale);
         bool set_input_enabled(bool enabled);
         bool set_mouse_autohide(bool autohide);
-        bool set_pty(VtePty *pty,
-                     bool proces_remaining = true);
         bool set_rewrap_on_resize(bool rewrap);
         bool set_scrollback_lines(long lines);
         bool set_scroll_on_keystroke(bool scroll);
diff --git a/src/vtepty.cc b/src/vtepty.cc
new file mode 100644
index 00000000..78f89c44
--- /dev/null
+++ b/src/vtepty.cc
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2001,2002 Red Hat, Inc.
+ * Copyright © 2009, 2010, 2019 Christian Persch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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.
+ */
+
+#include <config.h>
+
+#include <vte/vte.h>
+#include "vtetypes.hh"
+#include "vtespawn.hh"
+
+#include <errno.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include "debug.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "pty.hh"
+#include "vteptyinternal.hh"
+
+#if !GLIB_CHECK_VERSION(2, 42, 0)
+#define G_PARAM_EXPLICIT_NOTIFY 0
+#endif
+
+#define I_(string) (g_intern_static_string(string))
+
+typedef struct _VtePtyPrivate VtePtyPrivate;
+
+typedef struct {
+       GSpawnChildSetupFunc extra_child_setup;
+       gpointer extra_child_setup_data;
+} VtePtyChildSetupData;
+
+/**
+ * VtePty:
+ */
+struct _VtePty {
+        GObject parent_instance;
+
+        /* <private> */
+        VtePtyPrivate *priv;
+};
+
+struct _VtePtyPrivate {
+        vte::base::Pty* pty; /* owned */
+        int foreign_fd; /* foreign FD if  != -1 */
+        VtePtyFlags flags;
+};
+
+struct _VtePtyClass {
+        GObjectClass parent_class;
+};
+
+vte::base::Pty*
+_vte_pty_get_impl(VtePty* pty)
+{
+        return pty->priv->pty;
+}
+
+#define IMPL(wrapper) (_vte_pty_get_impl(wrapper))
+
+/**
+ * vte_pty_child_setup:
+ * @pty: a #VtePty
+ *
+ * FIXMEchpe
+ */
+void
+vte_pty_child_setup (VtePty *pty)
+{
+        g_return_if_fail(pty != nullptr);
+        auto impl = IMPL(pty);
+        g_return_if_fail(impl != nullptr);
+
+        impl->child_setup();
+}
+
+/*
+ * __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
+ * @timeout: a timeout value in ms, or %NULL
+ * @cancellable: a #GCancellable, 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 */,
+               int timeout,
+               GCancellable *cancellable,
+               GError **error)
+{
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+
+        auto impl = IMPL(pty);
+        g_return_val_if_fail(impl != nullptr, FALSE);
+
+        return impl->spawn(directory,
+                           argv,
+                           envv,
+                           spawn_flags_,
+                           child_setup,
+                           child_setup_data,
+                           child_pid,
+                           timeout,
+                           cancellable,
+                           error);
+}
+
+/**
+ * vte_pty_set_size:
+ * @pty: a #VtePty
+ * @rows: the desired number of rows
+ * @columns: the desired number of columns
+ * @error: (allow-none): 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.
+ *
+ * If setting the window size failed, @error will be set to a #GIOError.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ */
+gboolean
+vte_pty_set_size(VtePty *pty,
+                 int rows,
+                 int columns,
+                 GError **error)
+{
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+        auto impl = IMPL(pty);
+        g_return_val_if_fail(impl != nullptr, FALSE);
+
+        if (impl->set_size(rows, columns))
+                return true;
+
+        vte::util::restore_errno errsv;
+        g_set_error(error, G_IO_ERROR,
+                    g_io_error_from_errno(errsv),
+                    "Failed to set window size: %s",
+                    g_strerror(errsv));
+
+        return false;
+}
+
+/**
+ * vte_pty_get_size:
+ * @pty: a #VtePty
+ * @rows: (out) (allow-none): a location to store the number of rows, or %NULL
+ * @columns: (out) (allow-none): a location to store the number of columns, or %NULL
+ * @error: return location to store a #GError, or %NULL
+ *
+ * Reads the pseudo terminal's window size.
+ *
+ * If getting the window size failed, @error will be set to a #GIOError.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ */
+gboolean
+vte_pty_get_size(VtePty *pty,
+                 int *rows,
+                 int *columns,
+                 GError **error)
+{
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+        auto impl = IMPL(pty);
+        g_return_val_if_fail(impl != nullptr, FALSE);
+
+        if (impl->get_size(rows, columns))
+                return true;
+
+        vte::util::restore_errno errsv;
+        g_set_error(error, G_IO_ERROR,
+                    g_io_error_from_errno(errsv),
+                    "Failed to get window size: %s",
+                    g_strerror(errsv));
+        return false;
+}
+
+/**
+ * vte_pty_set_utf8:
+ * @pty: a #VtePty
+ * @utf8: whether or not the pty is in UTF-8 mode
+ * @error: (allow-none): return location to store a #GError, or %NULL
+ *
+ * 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.
+ *
+ * Returns: %TRUE on success, %FALSE on failure with @error filled in
+ */
+gboolean
+vte_pty_set_utf8(VtePty *pty,
+                 gboolean utf8,
+                 GError **error)
+{
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+        auto impl = IMPL(pty);
+        g_return_val_if_fail(impl != nullptr, FALSE);
+
+        if (impl->set_utf8(utf8))
+                return true;
+
+        vte::util::restore_errno errsv;
+        g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
+                    "%s failed: %s", "tc[sg]etattr", g_strerror(errsv));
+        return false;
+}
+
+/**
+ * vte_pty_close:
+ * @pty: a #VtePty
+ *
+ * Since 0.42 this is a no-op.
+ *
+ * Deprecated: 0.42
+ */
+void
+vte_pty_close (VtePty *pty)
+{
+        /* impl->close(); */
+}
+
+/* VTE PTY class */
+
+enum {
+        PROP_0,
+        PROP_FLAGS,
+        PROP_FD,
+};
+
+/* GInitable impl */
+
+static gboolean
+vte_pty_initable_init (GInitable *initable,
+                       GCancellable *cancellable,
+                       GError **error)
+{
+        VtePty *pty = VTE_PTY (initable);
+        VtePtyPrivate *priv = pty->priv;
+
+        if (cancellable != NULL) {
+                g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                                    "Cancellable initialisation not supported");
+                return FALSE;
+        }
+
+        if (priv->foreign_fd != -1) {
+                priv->pty = vte::base::Pty::create_foreign(priv->foreign_fd, priv->flags);
+                priv->foreign_fd = -1;
+        } else {
+                priv->pty = vte::base::Pty::create(priv->flags);
+        }
+
+        if (priv->pty == nullptr) {
+                vte::util::restore_errno errsv;
+                g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv),
+                            "Failed to open PTY: %s", g_strerror(errsv));
+                return FALSE;
+        }
+
+        return TRUE;
+}
+
+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_ADD_PRIVATE (VtePty)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, vte_pty_initable_iface_init))
+
+static void
+vte_pty_init (VtePty *pty)
+{
+        VtePtyPrivate *priv;
+
+        priv = pty->priv = (VtePtyPrivate *)vte_pty_get_instance_private (pty);
+
+        priv->pty = nullptr;
+        priv->foreign_fd = -1;
+        priv->flags = VTE_PTY_DEFAULT;
+}
+
+static void
+vte_pty_finalize (GObject *object)
+{
+        VtePty *pty = VTE_PTY (object);
+        VtePtyPrivate *priv = pty->priv;
+
+        if (priv->pty != nullptr)
+                priv->pty->unref();
+
+        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;
+
+        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 = (VtePtyFlags) g_value_get_flags(value);
+                break;
+
+        case PROP_FD:
+                priv->foreign_fd = g_value_get_int(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);
+
+        object_class->set_property = vte_pty_set_property;
+        object_class->get_property = vte_pty_get_property;
+        object_class->finalize     = vte_pty_finalize;
+
+        /**
+         * VtePty:flags:
+         *
+         * Flags.
+         */
+        g_object_class_install_property
+                (object_class,
+                 PROP_FLAGS,
+                 g_param_spec_flags ("flags", NULL, NULL,
+                                     VTE_TYPE_PTY_FLAGS,
+                                     VTE_PTY_DEFAULT,
+                                     (GParamFlags) (G_PARAM_READWRITE |
+                                                    G_PARAM_CONSTRUCT_ONLY |
+                                                    G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)));
+
+        /**
+         * VtePty:fd:
+         *
+         * The file descriptor of the PTY master.
+         */
+        g_object_class_install_property
+                (object_class,
+                 PROP_FD,
+                 g_param_spec_int ("fd", NULL, NULL,
+                                   -1, G_MAXINT, -1,
+                                   (GParamFlags) (G_PARAM_READWRITE |
+                                                  G_PARAM_CONSTRUCT_ONLY |
+                                                  G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)));
+}
+
+/* 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
+ */
+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_sync: (constructor)
+ * @flags: flags from #VtePtyFlags
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @error: (allow-none): return location for a #GError, or %NULL
+ *
+ * Allocates a new pseudo-terminal.
+ *
+ * You can later use fork() or the g_spawn_async() family 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.
+ *
+ * When using vte_terminal_spawn_sync() with a custom child setup
+ * function, vte_pty_child_setup() will be called before the supplied
+ * function; you must not call it again.
+ *
+ * Also, you MUST pass the %G_SPAWN_DO_NOT_REAP_CHILD flag.
+ *
+ * Returns: (transfer full): a new #VtePty, or %NULL on error with @error filled in
+ */
+VtePty *
+vte_pty_new_sync (VtePtyFlags flags,
+                  GCancellable *cancellable,
+                  GError **error)
+{
+        return (VtePty *) g_initable_new (VTE_TYPE_PTY,
+                                          cancellable,
+                                          error,
+                                          "flags", flags,
+                                          NULL);
+}
+
+/**
+ * vte_pty_new_foreign_sync: (constructor)
+ * @fd: a file descriptor to the PTY
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @error: (allow-none): return location for a #GError, or %NULL
+ *
+ * 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: (transfer full): a new #VtePty for @fd, or %NULL on error with @error filled in
+ */
+VtePty *
+vte_pty_new_foreign_sync (int fd,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+        g_return_val_if_fail(fd != -1, nullptr);
+
+        return (VtePty *) g_initable_new (VTE_TYPE_PTY,
+                                          cancellable,
+                                          error,
+                                          "fd", fd,
+                                          NULL);
+}
+
+/**
+ * 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 or have
+ *   its flags changed
+ */
+int
+vte_pty_get_fd (VtePty *pty)
+{
+        g_return_val_if_fail(VTE_IS_PTY(pty), FALSE);
+        auto impl = IMPL(pty);
+        g_return_val_if_fail(impl != nullptr, FALSE);
+
+        return impl->fd();
+}
+
+typedef struct {
+        VtePty* m_pty;
+        char* m_working_directory;
+        char** m_argv;
+        char** m_envv;
+        GSpawnFlags m_spawn_flags;
+        GSpawnChildSetupFunc m_child_setup;
+        gpointer m_child_setup_data;
+        GDestroyNotify m_child_setup_data_destroy;
+        int m_timeout;
+} AsyncSpawnData;
+
+static AsyncSpawnData*
+async_spawn_data_new (VtePty* pty,
+                      char const* working_directory,
+                      char** argv,
+                      char** envv,
+                      GSpawnFlags spawn_flags,
+                      GSpawnChildSetupFunc child_setup,
+                      gpointer child_setup_data,
+                      GDestroyNotify child_setup_data_destroy,
+                      int timeout)
+{
+        auto data = g_new(AsyncSpawnData, 1);
+
+        data->m_pty = (VtePty*)g_object_ref(pty);
+        data->m_working_directory = g_strdup(working_directory);
+        data->m_argv = g_strdupv(argv);
+        data->m_envv = envv ? g_strdupv(envv) : nullptr;
+        data->m_spawn_flags = spawn_flags;
+        data->m_child_setup = child_setup;
+        data->m_child_setup_data = child_setup_data;
+        data->m_child_setup_data_destroy = child_setup_data_destroy;
+        data->m_timeout = timeout;
+
+        return data;
+}
+
+static void
+async_spawn_data_free(gpointer data_)
+{
+        AsyncSpawnData *data = reinterpret_cast<AsyncSpawnData*>(data_);
+
+        g_free(data->m_working_directory);
+        g_strfreev(data->m_argv);
+        g_strfreev(data->m_envv);
+        if (data->m_child_setup_data && data->m_child_setup_data_destroy)
+                data->m_child_setup_data_destroy(data->m_child_setup_data);
+        g_object_unref(data->m_pty);
+
+        g_free(data);
+}
+
+static void
+async_spawn_run_in_thread(GTask *task,
+                          gpointer object,
+                          gpointer data_,
+                          GCancellable *cancellable)
+{
+        AsyncSpawnData *data = reinterpret_cast<AsyncSpawnData*>(data_);
+
+        GPid pid;
+        GError *error = NULL;
+        if (_vte_pty_spawn(data->m_pty,
+                            data->m_working_directory,
+                            data->m_argv,
+                            data->m_envv,
+                            (GSpawnFlags)data->m_spawn_flags,
+                            data->m_child_setup, data->m_child_setup_data,
+                            &pid,
+                            data->m_timeout,
+                            cancellable,
+                            &error))
+                g_task_return_pointer(task, g_memdup(&pid, sizeof(pid)), g_free);
+        else
+                g_task_return_error(task, error);
+}
+
+/**
+ * vte_pty_spawn_async:
+ * @pty: a #VtePty
+ * @working_directory: (allow-none): the name of a directory the command should start
+ *   in, or %NULL to use the current working directory
+ * @argv: (array zero-terminated=1) (element-type filename): child's argument vector
+ * @envv: (allow-none) (array zero-terminated=1) (element-type filename): a list of environment
+ *   variables to be added to the environment before starting the process, or %NULL
+ * @spawn_flags: flags from #GSpawnFlags
+ * @child_setup: (allow-none) (scope async): an extra child setup function to run in the child just before 
exec(), or %NULL
+ * @child_setup_data: (closure child_setup): user data for @child_setup, or %NULL
+ * @child_setup_data_destroy: (destroy child_setup_data): a #GDestroyNotify for @child_setup_data, or %NULL
+ * @timeout: a timeout value in ms, or -1 to wait indefinitely
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ *
+ * Starts the specified command under the pseudo-terminal @pty.
+ * The @argv and @envv lists should be %NULL-terminated.
+ * The "TERM" environment variable is automatically set to a default value,
+ * but can be overridden from @envv.
+ * @pty_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 open file descriptors will be closed in the child. If you want
+ * to keep some file descriptor open for use in the child process, you need to
+ * use a child setup function that unsets the FD_CLOEXEC flag on that file
+ * descriptor.
+ *
+ * See vte_pty_new(), g_spawn_async() and vte_terminal_watch_child() for more information.
+ *
+ * Since: 0.48
+ */
+void
+vte_pty_spawn_async(VtePty *pty,
+                    const char *working_directory,
+                    char **argv,
+                    char **envv,
+                    GSpawnFlags spawn_flags,
+                    GSpawnChildSetupFunc child_setup,
+                    gpointer child_setup_data,
+                    GDestroyNotify child_setup_data_destroy,
+                    int timeout,
+                    GCancellable *cancellable,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data)
+{
+        g_return_if_fail(argv != nullptr);
+        g_return_if_fail(!child_setup_data || child_setup);
+        g_return_if_fail(!child_setup_data_destroy || child_setup_data);
+        g_return_if_fail(cancellable == nullptr || G_IS_CANCELLABLE (cancellable));
+        g_return_if_fail(callback);
+
+        auto data = async_spawn_data_new(pty,
+                                         working_directory, argv, envv,
+                                         spawn_flags,
+                                         child_setup, child_setup_data, child_setup_data_destroy,
+                                         timeout);
+
+        auto task = g_task_new(pty, cancellable, callback, user_data);
+        g_task_set_source_tag(task, (void*)vte_pty_spawn_async);
+        g_task_set_task_data(task, data, async_spawn_data_free);
+        g_task_run_in_thread(task, async_spawn_run_in_thread);
+        g_object_unref(task);
+}
+
+/**
+ * vte_pty_spawn_finish:
+ * @pty: a #VtePty
+ * @result: a #GAsyncResult
+ * @child_pid: (out) (allow-none) (transfer full): a location to store the child PID, or %NULL
+ * @error: (allow-none): return location for a #GError, or %NULL
+ *
+ * Returns: %TRUE on success, or %FALSE on error with @error filled in
+ *
+ * Since: 0.48
+ */
+gboolean
+vte_pty_spawn_finish(VtePty *pty,
+                     GAsyncResult *result,
+                     GPid *child_pid /* out */,
+                     GError **error)
+{
+        g_return_val_if_fail (VTE_IS_PTY (pty), FALSE);
+        g_return_val_if_fail (G_IS_TASK (result), FALSE);
+        g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+        gpointer pidptr = g_task_propagate_pointer(G_TASK(result), error);
+        if (pidptr == nullptr) {
+                if (child_pid)
+                        *child_pid = -1;
+                return FALSE;
+        }
+
+        if (child_pid)
+                *child_pid = *(GPid*)pidptr;
+        if (error)
+                *error = nullptr;
+
+        g_free(pidptr);
+        return TRUE;
+}
diff --git a/src/vteptyinternal.hh b/src/vteptyinternal.hh
new file mode 100644
index 00000000..12dcf38c
--- /dev/null
+++ b/src/vteptyinternal.hh
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Christian Persch
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "vte/vtepty.h"
+#include "pty.hh"
+
+vte::base::Pty* _vte_pty_get_impl(VtePty* pty);
+
+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 */,
+                        int timeout,
+                        GCancellable *cancellable,
+                        GError **error);
diff --git a/src/vtetypes.hh b/src/vtetypes.hh
index 4092e43f..51dc424a 100644
--- a/src/vtetypes.hh
+++ b/src/vtetypes.hh
@@ -21,6 +21,7 @@
 #include <gdk/gdk.h>
 #include <errno.h>
 
+#include <cassert>
 #include <cstdint>
 #include <memory>
 
@@ -211,16 +212,16 @@ namespace util {
 
         class smart_fd {
         public:
-                smart_fd() : m_fd(-1) { }
-                explicit smart_fd(int fd) : m_fd(fd) { }
-                ~smart_fd() { if (m_fd != -1) { restore_errno errsv; close(m_fd); } }
+                constexpr smart_fd() noexcept = default;
+                explicit constexpr smart_fd(int fd) noexcept : m_fd{fd} { }
+                ~smart_fd() noexcept { if (m_fd != -1) { restore_errno errsv; close(m_fd); } }
 
-                inline smart_fd& operator = (int rhs) { if (m_fd != -1) { restore_errno errsv; close(m_fd); 
} m_fd = rhs; return *this; }
-                inline smart_fd& operator = (smart_fd& rhs) { if (&rhs != this) { if (m_fd != -1) { 
restore_errno errsv; close(m_fd); } m_fd = rhs.m_fd; rhs.m_fd = -1; } return *this; }
-                inline operator int () const { return m_fd; }
-                inline operator int* () { g_assert(m_fd == -1); return &m_fd; }
+                inline smart_fd& operator = (int rhs) noexcept { if (m_fd != -1) { restore_errno errsv; 
close(m_fd); } m_fd = rhs; return *this; }
+                inline smart_fd& operator = (smart_fd& rhs) noexcept { if (&rhs != this) { if (m_fd != -1) { 
restore_errno errsv; close(m_fd); } m_fd = rhs.m_fd; rhs.m_fd = -1; } return *this; }
+                inline constexpr operator int () const noexcept { return m_fd; }
+                inline constexpr operator int* () noexcept { assert(m_fd == -1); return &m_fd; }
 
-                int steal() { auto d = m_fd; m_fd = -1; return d; }
+                int steal() noexcept { auto d = m_fd; m_fd = -1; return d; }
 
                 /* Prevent accidents */
                 smart_fd(smart_fd const&) = delete;
@@ -229,7 +230,7 @@ namespace util {
                 smart_fd& operator = (smart_fd&&) = delete;
 
         private:
-                int m_fd;
+                int m_fd{-1};
         };
 
 } /* namespace util */
diff --git a/src/widget.cc b/src/widget.cc
index bbc0dadc..eef88311 100644
--- a/src/widget.cc
+++ b/src/widget.cc
@@ -26,6 +26,7 @@
 #include <string>
 
 #include "vtegtk.hh"
+#include "vteptyinternal.hh"
 #include "debug.h"
 
 using namespace std::literals;
@@ -355,6 +356,32 @@ Widget::set_cursor(CursorType type) noexcept
         }
 }
 
+bool
+Widget::set_pty(VtePty* pty_obj) noexcept
+{
+        if (pty() == pty_obj)
+                return false;
+
+        m_pty = vte::glib::make_ref(pty_obj);
+        terminal()->set_pty(_vte_pty_get_impl(pty()));
+
+        return true;
+}
+
+void
+Widget::unset_pty() noexcept
+{
+        if (!pty())
+                return;
+
+        /* This is only called from Terminal, so we need
+         * to explicitly notify the VteTerminal:pty property,
+         * but we do NOT need to call Terminal::set_pty(nullptr).
+         */
+        m_pty.reset();
+        g_object_notify_by_pspec(object(), pspecs[PROP_PTY]);
+}
+
 void
 Widget::size_allocate(GtkAllocation* allocation) noexcept
 {
diff --git a/src/widget.hh b/src/widget.hh
index 6338cb70..e4b71869 100644
--- a/src/widget.hh
+++ b/src/widget.hh
@@ -22,8 +22,11 @@
 #include <variant>
 
 #include "vteterminal.h"
+#include "vtepty.h"
+
 #include "vteinternal.hh"
 
+#include "fwd.hh"
 #include "refptr.hh"
 
 namespace vte {
@@ -52,7 +55,7 @@ public:
         GtkWidget* gtk() const noexcept { return m_widget; }
         VteTerminal* vte() const noexcept { return reinterpret_cast<VteTerminal*>(m_widget); }
 
-        vte::terminal::Terminal* terminal() const noexcept { return m_terminal; }
+        inline constexpr vte::terminal::Terminal* terminal() const noexcept { return m_terminal; }
 
         void constructed() noexcept { m_terminal->widget_constructed(); }
         void dispose() noexcept;
@@ -108,6 +111,9 @@ public:
 
         void emit_child_exited(int status) noexcept;
 
+        bool set_pty(VtePty* pty) noexcept;
+        inline auto pty() const noexcept { return m_pty.get(); }
+
 protected:
 
         enum class CursorType {
@@ -143,6 +149,8 @@ protected:
 
         void im_set_cursor_location(cairo_rectangle_int_t const* rect) noexcept;
 
+        void unset_pty() noexcept;
+
 public: // FIXMEchpe
         void im_preedit_changed() noexcept;
 
@@ -162,6 +170,9 @@ private:
 
         /* Input method */
         vte::glib::RefPtr<GtkIMContext> m_im_context;
+
+        /* PTY */
+        vte::glib::RefPtr<VtePty> m_pty;
 };
 
 } // namespace platform


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