/* * 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, see . */ /** * 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 #include #include #include #include #include "debug.h" #include #include "cxx-utils.hh" #include "libc-glue.hh" #include "pty.hh" #include "refptr.hh" #include "spawn.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; /* */ 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_SPAWN_NO_PARENT_ENVV: * * Use this as a spawn flag (together with flags from #GSpawnFlags) in * vte_pty_spawn_async(). * * Normally, the spawned process inherits the environment from the parent * process; when this flag is used, only the environment variables passed * to vte_pty_spawn_async() etc. are passed to the child process. */ /** * VTE_SPAWN_NO_SYSTEMD_SCOPE: * * Use this as a spawn flag (together with flags from #GSpawnFlags) in * vte_pty_spawn_async(). * * Prevents vte_pty_spawn_async() etc. from moving the newly created child * process to a systemd user scope. * * Since: 0.60 */ /** * VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE * * Use this as a spawn flag (together with flags from #GSpawnFlags) in * vte_pty_spawn_async(). * * Requires vte_pty_spawn_async() etc. to move the newly created child * process to a systemd user scope; if that fails, the whole spawn fails. * * This is supported on Linux only. * * Since: 0.60 */ /** * vte_pty_child_setup: * @pty: a #VtePty */ void vte_pty_child_setup (VtePty *pty) noexcept try { g_return_if_fail(pty != nullptr); auto impl = IMPL(pty); g_return_if_fail(impl != nullptr); impl->child_setup(); } catch (...) { vte::log_exception(); } /** * 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) noexcept { /* No way to determine the pixel size; set it to (0, 0), meaning * "undefined". */ return _vte_pty_set_size(pty, rows, columns, 0, 0, error); } bool _vte_pty_set_size(VtePty *pty, int rows, int columns, int cell_height_px, int cell_width_px, GError **error) noexcept try { 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, cell_height_px, cell_width_px)) return true; auto errsv = vte::libc::ErrnoSaver{}; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to set window size: %s", g_strerror(errsv)); return false; } catch (...) { return vte::glib::set_error_from_exception(error); } /** * 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) noexcept try { 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; auto errsv = vte::libc::ErrnoSaver{}; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to get window size: %s", g_strerror(errsv)); return false; } catch (...) { return vte::glib::set_error_from_exception(error); } /** * 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) noexcept try { 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; auto errsv = vte::libc::ErrnoSaver{}; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "%s failed: %s", "tc[sg]etattr", g_strerror(errsv)); return false; } catch (...) { return vte::glib::set_error_from_exception(error); } /** * vte_pty_close: * @pty: a #VtePty * * Since 0.42 this is a no-op. * * Deprecated: 0.42 */ void vte_pty_close (VtePty *pty) noexcept { /* 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) noexcept try { VtePty *pty = VTE_PTY (initable); VtePtyPrivate *priv = pty->priv; 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) { auto errsv = vte::libc::ErrnoSaver{}; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to open PTY: %s", g_strerror(errsv)); return FALSE; } return !g_cancellable_set_error_if_cancelled(cancellable, error); } catch (...) { return vte::glib::set_error_from_exception(error); } 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) noexcept try { VtePty *pty = VTE_PTY (object); VtePtyPrivate *priv = pty->priv; auto implptr = vte::base::RefPtr{priv->pty}; // moved G_OBJECT_CLASS (vte_pty_parent_class)->finalize (object); } catch (...) { vte::log_exception(); } 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) noexcept { 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. * * Note also that %G_SPAWN_STDOUT_TO_DEV_NULL, %G_SPAWN_STDERR_TO_DEV_NULL, * and %G_SPAWN_CHILD_INHERITS_STDIN are not supported, since stdin, stdout * and stderr of the child process will always be connected to the PTY. * * Note that you should set the PTY's size using vte_pty_set_size() before * spawning the child process, so that the child process has the correct * size from the start instead of starting with a default size and then * shortly afterwards receiving a SIGWINCH signal. You * should prefer using vte_terminal_pty_new_sync() which does this * automatically. * * 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) noexcept { 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) noexcept { 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) noexcept try { g_return_val_if_fail(VTE_IS_PTY(pty), FALSE); return IMPL(pty)->fd(); } catch (...) { vte::log_exception(); return -1; } static constexpr inline auto all_spawn_flags() noexcept { return (G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_FILE_AND_ARGV_ZERO | G_SPAWN_SEARCH_PATH_FROM_ENVP | G_SPAWN_CLOEXEC_PIPES | VTE_SPAWN_NO_PARENT_ENVV | VTE_SPAWN_NO_SYSTEMD_SCOPE | VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE); } static constexpr inline auto forbidden_spawn_flags() noexcept { return GSpawnFlags(G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_CHILD_INHERITS_STDIN); } static constexpr inline auto ignored_spawn_flags() noexcept { return GSpawnFlags(G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD); } static vte::base::SpawnContext spawn_context_from_args(VtePty* pty, char const* working_directory, char const* const* argv, char const* const* envv, int const* fds, int n_fds, int const* fd_map_to, int n_fd_map_to, GSpawnFlags spawn_flags, GSpawnChildSetupFunc child_setup, void* child_setup_data, GDestroyNotify child_setup_data_destroy) { auto context = vte::base::SpawnContext{}; context.set_pty(vte::glib::make_ref(pty)); context.set_cwd(working_directory); context.set_fallback_cwd(g_get_home_dir()); context.set_child_setup(child_setup, child_setup_data, child_setup_data_destroy); if ((spawn_flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) || (spawn_flags & G_SPAWN_SEARCH_PATH)) context.set_search_path(); if (spawn_flags & G_SPAWN_FILE_AND_ARGV_ZERO) context.set_argv(argv[0], argv + 1); else context.set_argv(argv[0], argv); context.set_environ(envv); if (spawn_flags & VTE_SPAWN_NO_PARENT_ENVV) context.set_no_inherit_environ(); if (spawn_flags & VTE_SPAWN_NO_SYSTEMD_SCOPE) context.set_no_systemd_scope(); if (spawn_flags & VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE) context.set_require_systemd_scope(); context.add_fds(fds, n_fds); context.add_map_fds(fds, n_fds, fd_map_to, n_fd_map_to); return context; } bool _vte_pty_spawn_sync(VtePty* pty, char const* working_directory, char const* const* argv, char const* const* envv, GSpawnFlags spawn_flags, GSpawnChildSetupFunc child_setup, gpointer child_setup_data, GDestroyNotify child_setup_data_destroy, GPid* child_pid /* out */, int timeout, GCancellable* cancellable, GError** error) noexcept try { /* These are ignored or need not be passed since the behaviour is the default */ g_warn_if_fail((spawn_flags & ignored_spawn_flags()) == 0); /* This may be upgraded to a g_return_if_fail in the future */ g_warn_if_fail((spawn_flags & forbidden_spawn_flags()) == 0); spawn_flags = GSpawnFlags(spawn_flags & ~forbidden_spawn_flags()); auto op = vte::base::SpawnOperation{spawn_context_from_args(pty, working_directory, argv, envv, nullptr, 0, nullptr, 0, spawn_flags, child_setup, child_setup_data, child_setup_data_destroy), timeout, cancellable}; auto err = vte::glib::Error{}; auto rv = vte::base::SpawnOperation::run_sync(op, child_pid, err); if (!rv) err.propagate(error); return rv; } catch (...) { return vte::glib::set_error_from_exception(error); } /* * _vte_pty_check_envv: * @strv: * * Validates that each element is of the form 'KEY=VALUE'. */ bool _vte_pty_check_envv(char const* const* strv) noexcept { if (!strv) return true; for (int i = 0; strv[i]; ++i) { const char *str = strv[i]; const char *equal = strchr(str, '='); if (equal == nullptr || equal == str) return false; } return true; } /** * vte_pty_spawn_with_fds_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 * @fds: (nullable) (array length=n_fds) (transfer none) (scope call): an array of file descriptors, or %NULL * @n_fds: the number of file descriptors in @fds, or 0 if @fds is %NULL * @map_fds: (nullable) (array length=n_map_fds) (transfer none) (scope call): an array of integers, or %NULL * @n_map_fds: the number of elements in @map_fds, or 0 if @map_fds is %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: (nullable) (closure child_setup): user data for @child_setup, or %NULL * @child_setup_data_destroy: (nullable) (destroy child_setup_data): a #GDestroyNotify for @child_setup_data, or %NULL * @timeout: a timeout value in ms, -1 for the default timeout, or G_MAXINT to wait indefinitely * @cancellable: (allow-none): a #GCancellable, or %NULL * @callback: (nullable) (scope async): a #GAsyncReadyCallback, or %NULL * @user_data: (nullable) (closure callback): user data for @callback * * 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 also that %G_SPAWN_STDOUT_TO_DEV_NULL, %G_SPAWN_STDERR_TO_DEV_NULL, * and %G_SPAWN_CHILD_INHERITS_STDIN are not supported in @spawn_flags, since * stdin, stdout and stderr of the child process will always be connected to * the PTY. Also %G_SPAWN_LEAVE_DESCRIPTORS_OPEN is not supported; and * %G_SPAWN_DO_NOT_REAP_CHILD will always be added to @spawn_flags. * * If @fds is not %NULL, the child process will map the file descriptors from * @fds according to @map_fds; @n_map_fds must be less or equal to @n_fds. * This function will take ownership of the file descriptors in @fds; * you must not use or close them after this call. All file descriptors in @fds * must have the FD_CLOEXEC flag set on them; it will be unset in the child process * before calling man:execve(2). Note also that no file descriptor may be mapped * to stdin, stdout, or stderr (file descriptors 0, 1, or 2), since these will be * assigned to the PTY. All open file descriptors apart from those mapped as above * will be closed when execve() is called. * * Beginning with 0.60, and on linux only, and unless %VTE_SPAWN_NO_SYSTEMD_SCOPE is * passed in @spawn_flags, the newly created child process will be moved to its own * systemd user scope; and if %VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE is passed, and creation * of the systemd user scope fails, the whole spawn will fail. * You can override the options used for the systemd user scope by * providing a systemd override file for 'vte-spawn-.scope' unit. See man:systemd.unit(5) * for further information. * * See vte_pty_new(), and vte_terminal_watch_child() for more information. * * Since: 0.62 */ void vte_pty_spawn_with_fds_async(VtePty *pty, char const* working_directory, char const* const* argv, char const* const* envv, int const* fds, int n_fds, int const* fd_map_to, int n_fd_map_to, GSpawnFlags spawn_flags, GSpawnChildSetupFunc child_setup, gpointer child_setup_data, GDestroyNotify child_setup_data_destroy, int timeout, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) noexcept try { g_return_if_fail(argv != nullptr); g_return_if_fail(argv[0] != nullptr); g_return_if_fail(envv == nullptr || _vte_pty_check_envv(envv)); g_return_if_fail(n_fds == 0 || fds != nullptr); for (auto i = 0; i < n_fds; ++i) g_return_if_fail(vte::libc::fd_get_cloexec(fds[i])); g_return_if_fail(n_fd_map_to == 0 || fd_map_to != nullptr); for (auto i = 0; i < n_fd_map_to; ++i) /* Invalid and stdin/out/err not allowed */ g_return_if_fail(fd_map_to[i] < -1 || fd_map_to[i] > 2); g_return_if_fail((spawn_flags & ~all_spawn_flags()) == 0); g_return_if_fail(!child_setup_data || child_setup); g_return_if_fail(!child_setup_data_destroy || child_setup_data); g_return_if_fail(timeout >= -1); g_return_if_fail(cancellable == nullptr || G_IS_CANCELLABLE (cancellable)); /* These are ignored or need not be passed since the behaviour is the default */ g_warn_if_fail((spawn_flags & ignored_spawn_flags()) == 0); /* This may be upgraded to a g_return_if_fail in the future */ g_warn_if_fail((spawn_flags & forbidden_spawn_flags()) == 0); spawn_flags = GSpawnFlags(spawn_flags & ~forbidden_spawn_flags()); auto op = std::make_unique (spawn_context_from_args(pty, working_directory, argv, envv, fds, n_fds, fd_map_to, n_fd_map_to, spawn_flags, child_setup, child_setup_data, child_setup_data_destroy), timeout, cancellable); vte::base::SpawnOperation::run_async(std::move(op), (void*)vte_pty_spawn_async, /* tag */ callback, user_data); } catch (...) { // FIXME: make the function above exception safe. It needs to guarantee // that the callback will be invoked regardless of when the throw occurred. vte::log_exception(); } /** * 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: (nullable) (closure child_setup): user data for @child_setup, or %NULL * @child_setup_data_destroy: (nullable) (destroy child_setup_data): a #GDestroyNotify for @child_setup_data, or %NULL * @timeout: a timeout value in ms, -1 for the default timeout, or G_MAXINT to wait indefinitely * @cancellable: (allow-none): a #GCancellable, or %NULL * @callback: (nullable) (scope async): a #GAsyncReadyCallback, or %NULL * @user_data: (nullable) (closure callback): user data for @callback * * Like vte_pty_spawn_with_fds_async(), except that this function does not * allow passing file descriptors to the child process. See vte_pty_spawn_with_fds_async() * 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) noexcept { vte_pty_spawn_with_fds_async(pty, working_directory, argv, envv, nullptr, 0, nullptr, 0, spawn_flags, child_setup, child_setup_data, child_setup_data_destroy, timeout, cancellable, callback, user_data); } /** * 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) noexcept { 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 (g_task_get_source_tag(G_TASK (result)) == vte_pty_spawn_async, false); g_return_val_if_fail(error == nullptr || *error == nullptr, false); auto pid = g_task_propagate_int(G_TASK(result), error); if (child_pid) *child_pid = pid; return pid != -1; }