mirror of
https://github.com/lihop/godot-xterm.git
synced 2024-11-22 09:40:25 +01:00
feat(pty): further pty development
This commit is contained in:
parent
52a259b019
commit
c36500615d
4 changed files with 332 additions and 110 deletions
|
@ -1,8 +1,10 @@
|
||||||
// SPDX-FileCopyrightText: 2021, 2024 Leroy Hopson <godot-xterm@leroy.nix.nz>
|
// SPDX-FileCopyrightText: 2021, 2023-2024 Leroy Hopson <godot-xterm@leroy.nix.nz>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "pty.h"
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
|
|
||||||
|
#include <uv.h>
|
||||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
@ -194,3 +196,131 @@ const Terminal::KeyMap Terminal::KEY_MAP = {
|
||||||
{{KEY_F34, '\0'}, XKB_KEY_F34},
|
{{KEY_F34, '\0'}, XKB_KEY_F34},
|
||||||
{{KEY_F35, '\0'}, XKB_KEY_F35},
|
{{KEY_F35, '\0'}, XKB_KEY_F35},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Error PTY::uv_err_to_godot_err(const int uv_err) {
|
||||||
|
if (uv_err >= 0)
|
||||||
|
return OK;
|
||||||
|
|
||||||
|
// Rough translation of libuv error to godot error.
|
||||||
|
// Not necessarily accurate.
|
||||||
|
|
||||||
|
switch (uv_err) {
|
||||||
|
case UV_EEXIST: // file already exists
|
||||||
|
return ERR_ALREADY_EXISTS;
|
||||||
|
|
||||||
|
case UV_EADDRINUSE: // address already in use
|
||||||
|
return ERR_ALREADY_IN_USE;
|
||||||
|
|
||||||
|
case UV_EBUSY: // resource busy or locked
|
||||||
|
case UV_ETXTBSY: // text file is busy
|
||||||
|
return ERR_BUSY;
|
||||||
|
|
||||||
|
case UV_ECONNREFUSED: // connection refused
|
||||||
|
return ERR_CANT_CONNECT;
|
||||||
|
|
||||||
|
case UV_ECONNABORTED: // software caused connection abort
|
||||||
|
case UV_ECONNRESET: // connection reset by peer
|
||||||
|
case UV_EISCONN: // socket is already connected
|
||||||
|
case UV_ENOTCONN: // socket is not connected
|
||||||
|
return ERR_CONNECTION_ERROR;
|
||||||
|
|
||||||
|
case UV_ENODEV: // no such device
|
||||||
|
case UV_ENXIO: // no such device or address
|
||||||
|
case UV_ESRCH: // no such process
|
||||||
|
return ERR_DOES_NOT_EXIST;
|
||||||
|
|
||||||
|
case UV_EROFS: // read-only file system
|
||||||
|
return ERR_FILE_CANT_WRITE;
|
||||||
|
|
||||||
|
case UV_EOF: // end of file
|
||||||
|
return ERR_FILE_EOF;
|
||||||
|
|
||||||
|
case UV_ENOENT: // no such file or directory
|
||||||
|
return ERR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
case UV_EAI_BADFLAGS: // bad ai_flags value
|
||||||
|
case UV_EAI_BADHINTS: // invalid value for hints
|
||||||
|
case UV_EFAULT: // bad address in system call argument
|
||||||
|
case UV_EFTYPE: // inappropriate file type or format
|
||||||
|
case UV_EINVAL: // invalid argument
|
||||||
|
case UV_ENOTTY: // inappropriate ioctl for device
|
||||||
|
case UV_EPROTOTYPE: // protocol wrong type for socket
|
||||||
|
return ERR_INVALID_PARAMETER; // Parameter passed is invalid
|
||||||
|
|
||||||
|
case UV_ENOSYS: // function not implemented
|
||||||
|
return ERR_METHOD_NOT_FOUND;
|
||||||
|
|
||||||
|
case UV_EAI_MEMORY: // out of memory
|
||||||
|
return ERR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
case UV_E2BIG: // argument list too long
|
||||||
|
case UV_EFBIG: // file too large
|
||||||
|
case UV_EMSGSIZE: // message too long
|
||||||
|
case UV_ENAMETOOLONG: // name too long
|
||||||
|
case UV_EOVERFLOW: // value too large for defined data type
|
||||||
|
case UV_ERANGE: // result too large
|
||||||
|
return ERR_PARAMETER_RANGE_ERROR; // Parameter given out of range
|
||||||
|
|
||||||
|
case UV_ETIMEDOUT:
|
||||||
|
return ERR_TIMEOUT; // connection timed out
|
||||||
|
|
||||||
|
case UV_EACCES: // permission denied
|
||||||
|
case UV_EPERM: // operation not permitted
|
||||||
|
case UV_EXDEV: // cross-device link not permitted
|
||||||
|
return ERR_UNAUTHORIZED;
|
||||||
|
|
||||||
|
case UV_EADDRNOTAVAIL: // address not available
|
||||||
|
case UV_EAFNOSUPPORT: // address family not supported
|
||||||
|
case UV_EAGAIN: // resource temporarily unavailable
|
||||||
|
case UV_EAI_ADDRFAMILY: // address family not supported
|
||||||
|
case UV_EAI_FAMILY: // ai_family not supported
|
||||||
|
case UV_EAI_SERVICE: // service not available for socket type
|
||||||
|
case UV_EAI_SOCKTYPE: // socket type not supported
|
||||||
|
case UV_ENOPROTOOPT: // protocol not available
|
||||||
|
case UV_ENOTSUP: // operation not supported on socket
|
||||||
|
case UV_EPROTONOSUPPORT: // protocol not supported
|
||||||
|
case UV_ESOCKTNOSUPPORT: // socket type not supported
|
||||||
|
return ERR_UNAVAILABLE; // What is requested is
|
||||||
|
// unsupported/unavailable
|
||||||
|
|
||||||
|
case UV_EAI_NODATA: // no address
|
||||||
|
case UV_EDESTADDRREQ: // destination address required
|
||||||
|
return ERR_UNCONFIGURED;
|
||||||
|
|
||||||
|
case UV_EAI_AGAIN: // temporary failure
|
||||||
|
case UV_EAI_CANCELED: // request canceled
|
||||||
|
case UV_EAI_FAIL: // permanent failure
|
||||||
|
case UV_EAI_NONAME: // unknown node or service
|
||||||
|
case UV_EAI_OVERFLOW: // argument buffer overflow
|
||||||
|
case UV_EAI_PROTOCOL: // resolved protocol is unknown
|
||||||
|
case UV_EALREADY: // connection already in progress
|
||||||
|
case UV_EBADF: // bad file descriptor
|
||||||
|
case UV_ECANCELED: // operation canceled
|
||||||
|
case UV_ECHARSET: // invalid Unicode character
|
||||||
|
case UV_EHOSTUNREACH: // host is unreachable
|
||||||
|
case UV_EIO: // i/o error
|
||||||
|
case UV_EILSEQ: // illegal byte sequence
|
||||||
|
case UV_EISDIR: // illegal operation on a directory
|
||||||
|
case UV_ELOOP: // too many symbolic links encountered
|
||||||
|
case UV_EMFILE: // too many open files
|
||||||
|
case UV_ENETDOWN: // network is down
|
||||||
|
case UV_ENETUNREACH: // network is unreachable
|
||||||
|
case UV_ENFILE: // file table overflow
|
||||||
|
case UV_ENOBUFS: // no buffer space available
|
||||||
|
case UV_ENOMEM: // not enough memory
|
||||||
|
case UV_ESHUTDOWN: // cannot send after transport endpoint shutdown
|
||||||
|
case UV_EINTR: // interrupted system call
|
||||||
|
case UV_EMLINK: // too many links
|
||||||
|
case UV_ENONET: // machine is not on the network
|
||||||
|
case UV_ENOSPC: // no space left on device
|
||||||
|
case UV_ENOTDIR: // not a directory
|
||||||
|
case UV_ENOTEMPTY: // directory not empty
|
||||||
|
case UV_ENOTSOCK: // socket operation on non-socket
|
||||||
|
case UV_EPIPE: // broken pipe
|
||||||
|
case UV_EPROTO: // protocol error
|
||||||
|
case UV_ESPIPE: // invalid seek
|
||||||
|
case UV_UNKNOWN: // unknown error
|
||||||
|
default:
|
||||||
|
return FAILED; // Generic fail error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,20 +8,71 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define UV_ERR_MSG(uv_err) \
|
||||||
|
String(uv_err_name(uv_err)) + String(": ") + String(uv_strerror(uv_err))
|
||||||
|
|
||||||
|
#define ERR_FAIL_UV_ERR(uv_err) \
|
||||||
|
ERR_FAIL_COND_V_MSG(uv_err < 0, PTY::uv_err_to_godot_err(uv_err), \
|
||||||
|
UV_ERR_MSG(uv_err))
|
||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
|
void _alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf);
|
||||||
|
void _read_cb(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf);
|
||||||
|
void _close_cb(uv_handle_t *handle);
|
||||||
|
|
||||||
|
void PTY::_bind_methods() {
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGHUP);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGINT);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGQUIT);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGILL);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGTRAP);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGABRT);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGBUS);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGFPE);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGKILL);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGUSR1);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGSEGV);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGUSR2);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGPIPE);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGALRM);
|
||||||
|
BIND_ENUM_CONSTANT(SIGNAL_SIGTERM);
|
||||||
|
|
||||||
|
ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
|
||||||
|
ADD_SIGNAL(MethodInfo("exited", PropertyInfo(Variant::INT, "exit_code"), PropertyInfo(Variant::INT, "signal_code")));
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("get_env"), &PTY::get_env);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_env", "env"), &PTY::set_env);
|
||||||
|
ClassDB::add_property("PTY", PropertyInfo(Variant::DICTIONARY, "env"), "set_env", "get_env");
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("get_use_os_env"), &PTY::get_use_os_env);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_use_os_env", "use_os_env"), &PTY::set_use_os_env);
|
||||||
|
ClassDB::add_property("PTY", PropertyInfo(Variant::BOOL, "use_os_env"), "set_use_os_env", "get_use_os_env");
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("get_pts"), &PTY::get_pts);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("fork", "file", "args", "cwd", "cols", "rows"), &PTY::fork, DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL("."), DEFVAL(80), DEFVAL(24));
|
||||||
|
ClassDB::bind_method(D_METHOD("open", "cols", "rows"), &PTY::open, DEFVAL(80), DEFVAL(24));
|
||||||
|
ClassDB::bind_method(D_METHOD("write", "data"), &PTY::write);
|
||||||
|
ClassDB::bind_method(D_METHOD("kill", "signal"), &PTY::kill);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("_on_exit", "exit_code", "signal_code"), &PTY::_on_exit);
|
||||||
|
}
|
||||||
|
|
||||||
PTY::PTY() {
|
PTY::PTY() {
|
||||||
os = OS::get_singleton();
|
status = STATUS_NONE;
|
||||||
|
|
||||||
env["TERM"] = "xterm-256color";
|
env["TERM"] = "xterm-256color";
|
||||||
env["COLORTERM"] = "truecolor";
|
env["COLORTERM"] = "truecolor";
|
||||||
|
|
||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
uv_pipe_init(uv_default_loop(), &pipe, false);
|
||||||
|
pipe.data = this;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
PTY::~PTY() {
|
PTY::~PTY() {
|
||||||
#if (defined(__linux__) || defined(__APPLE__)) && !defined(_PTY_DISABLED)
|
_close();
|
||||||
if (pid > 0) kill(SIGNAL_SIGHUP);
|
|
||||||
if (fd > 0) close(fd);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int PTY::get_cols() const {
|
int PTY::get_cols() const {
|
||||||
|
@ -48,6 +99,10 @@ void PTY::set_use_os_env(const bool value) {
|
||||||
use_os_env = value;
|
use_os_env = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String PTY::get_pts() const {
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
Error PTY::fork(const String &file, const PackedStringArray &args, const String &cwd, const int cols, const int rows) {
|
Error PTY::fork(const String &file, const PackedStringArray &args, const String &cwd, const int cols, const int rows) {
|
||||||
String fork_file = _get_fork_file(file);
|
String fork_file = _get_fork_file(file);
|
||||||
Dictionary fork_env = _get_fork_env();
|
Dictionary fork_env = _get_fork_env();
|
||||||
|
@ -56,11 +111,24 @@ Error PTY::fork(const String &file, const PackedStringArray &args, const String
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
String helper_path = ProjectSettings::get_singleton()->globalize_path("res://addons/godot_xterm/native/bin/spawn-helper");
|
String helper_path = ProjectSettings::get_singleton()->globalize_path("res://addons/godot_xterm/native/bin/spawn-helper");
|
||||||
result = PTYUnix::fork(fork_file, args, PackedStringArray(), cwd, cols, rows, -1, -1, true, helper_path, Callable(this, "_on_exit"));
|
result = PTYUnix::fork(fork_file, args, PackedStringArray(), cwd, cols, rows, -1, -1, true, helper_path, Callable(this, "_on_exit"));
|
||||||
fd = result["fd"];
|
|
||||||
pid = result["pid"];
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return static_cast<Error>((int)result["error"]);
|
Error err = static_cast<Error>((int)result["error"]);
|
||||||
|
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to fork.");
|
||||||
|
|
||||||
|
fd = result["fd"];
|
||||||
|
pid = result["pid"];
|
||||||
|
pts = result["pty"];
|
||||||
|
|
||||||
|
# if defined(__linux__) || defined(__APPLE__)
|
||||||
|
err = _pipe_open(fd);
|
||||||
|
if (err != OK) {
|
||||||
|
status = STATUS_ERROR;
|
||||||
|
ERR_FAIL_V_MSG(err, "Failed to open pipe.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::kill(const int signal) {
|
void PTY::kill(const int signal) {
|
||||||
|
@ -71,14 +139,19 @@ void PTY::kill(const int signal) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Error PTY::open(const int cols, const int rows) const {
|
Error PTY::open(const int cols, const int rows) {
|
||||||
Dictionary result;
|
Dictionary result;
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
result = PTYUnix::open(cols, rows);
|
result = PTYUnix::open(cols, rows);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return static_cast<Error>((int)result["error"]);
|
Error err = static_cast<Error>((int)result["error"]);
|
||||||
|
ERR_FAIL_COND_V(err != OK, err);
|
||||||
|
|
||||||
|
pts = result["pty"];
|
||||||
|
|
||||||
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::resize(const int cols, const int rows) const {
|
void PTY::resize(const int cols, const int rows) const {
|
||||||
|
@ -87,45 +160,39 @@ void PTY::resize(const int cols, const int rows) const {
|
||||||
void PTY::write(const Variant &data) const {
|
void PTY::write(const Variant &data) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::_bind_methods() {
|
void PTY::_process(double delta) {
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGHUP);
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGINT);
|
if (status == STATUS_CONNECTED) {
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGQUIT);
|
if (!uv_is_active((uv_handle_t *)&pipe)) {
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGILL);
|
uv_read_start((uv_stream_t *)&pipe, _alloc_buffer, _read_cb);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGTRAP);
|
}
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGABRT);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGBUS);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGFPE);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGKILL);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGUSR1);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGSEGV);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGUSR2);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGPIPE);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGALRM);
|
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGTERM);
|
|
||||||
|
|
||||||
ADD_SIGNAL(MethodInfo("exited", PropertyInfo(Variant::INT, "exit_code"), PropertyInfo(Variant::INT, "signal_code")));
|
uv_run(uv_default_loop(), UV_RUN_NOWAIT);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_env"), &PTY::get_env);
|
void PTY::_close() {
|
||||||
ClassDB::bind_method(D_METHOD("set_env", "env"), &PTY::set_env);
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
ClassDB::add_property("PTY", PropertyInfo(Variant::DICTIONARY, "env"), "set_env", "get_env");
|
if (!uv_is_closing((uv_handle_t *)&pipe)) {
|
||||||
|
uv_close((uv_handle_t *)&pipe, _close_cb);
|
||||||
|
}
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_use_os_env"), &PTY::get_use_os_env);
|
uv_run(uv_default_loop(), UV_RUN_NOWAIT);
|
||||||
ClassDB::bind_method(D_METHOD("set_use_os_env", "use_os_env"), &PTY::set_use_os_env);
|
|
||||||
ClassDB::add_property("PTY", PropertyInfo(Variant::BOOL, "use_os_env"), "set_use_os_env", "get_use_os_env");
|
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("fork", "file", "args", "cwd", "cols", "rows"), &PTY::fork, DEFVAL(""), DEFVAL(PackedStringArray()), DEFVAL("."), DEFVAL(80), DEFVAL(24));
|
if (fd > 0) close(fd);
|
||||||
ClassDB::bind_method(D_METHOD("open", "cols", "rows"), &PTY::open, DEFVAL(80), DEFVAL(24));
|
if (pid > 0) kill(SIGNAL_SIGHUP);
|
||||||
ClassDB::bind_method(D_METHOD("write", "data"), &PTY::write);
|
|
||||||
ClassDB::bind_method(D_METHOD("kill", "signal"), &PTY::kill);
|
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("_on_exit", "exit_code", "signal_code"), &PTY::_on_exit);
|
fd = -1;
|
||||||
|
pid = -1;
|
||||||
|
status = STATUS_NONE;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
String PTY::_get_fork_file(const String &file) const {
|
String PTY::_get_fork_file(const String &file) const {
|
||||||
if (!file.is_empty()) return file;
|
if (!file.is_empty()) return file;
|
||||||
|
|
||||||
String shell_env = os->get_environment("SHELL");
|
String shell_env = OS::get_singleton()->get_environment("SHELL");
|
||||||
if (!shell_env.is_empty()) {
|
if (!shell_env.is_empty()) {
|
||||||
return shell_env;
|
return shell_env;
|
||||||
}
|
}
|
||||||
|
@ -186,6 +253,62 @@ Dictionary PTY::_get_fork_env() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::_on_exit(int exit_code, int exit_signal) {
|
void PTY::_on_exit(int exit_code, int exit_signal) {
|
||||||
pid = -1;
|
|
||||||
emit_signal(StringName("exited"), exit_code, exit_signal);
|
emit_signal(StringName("exited"), exit_code, exit_signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
|
||||||
|
void _alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
||||||
|
buf->base = (char *)malloc(suggested_size);
|
||||||
|
buf->len = suggested_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _read_cb(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf) {
|
||||||
|
PTY *pty = static_cast<PTY *>(pipe->data);
|
||||||
|
|
||||||
|
if (nread < 0) {
|
||||||
|
switch (nread) {
|
||||||
|
case UV_EOF:
|
||||||
|
// Normal after shell exits.
|
||||||
|
case UV_EIO:
|
||||||
|
// Can happen when the process exits.
|
||||||
|
// As long as PTY has caught it, we should be fine.
|
||||||
|
uv_read_stop(pipe);
|
||||||
|
pty->status = PTY::Status::STATUS_NONE;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
pty->status = PTY::Status::STATUS_ERROR;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedByteArray data;
|
||||||
|
data.resize(nread);
|
||||||
|
memcpy(data.ptrw(), buf->base, nread);
|
||||||
|
std::free((char *)buf->base);
|
||||||
|
pty->call_deferred("emit_signal", "data_received", data);
|
||||||
|
|
||||||
|
// Stop reading until the next poll, otherwise _read_cb could be called
|
||||||
|
// repeatedly, blocking Godot, and eventually resulting in a memory pool
|
||||||
|
// allocation error. This can be triggered with the command `cat /dev/urandom`
|
||||||
|
// if reading is not stopped.
|
||||||
|
uv_read_stop(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _close_cb(uv_handle_t *pipe) {
|
||||||
|
PTY *pty = static_cast<PTY *>(pipe->data);
|
||||||
|
pty->status = PTY::Status::STATUS_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error PTY::_pipe_open(const int fd) {
|
||||||
|
ERR_FAIL_COND_V_MSG(fd < 0, FAILED, "File descriptor must be a non-negative integer value.");
|
||||||
|
|
||||||
|
ERR_FAIL_UV_ERR(uv_pipe_open(&pipe, fd));
|
||||||
|
ERR_FAIL_UV_ERR(uv_stream_set_blocking((uv_stream_t *)&pipe, false));
|
||||||
|
ERR_FAIL_UV_ERR(uv_read_start((uv_stream_t *)&pipe, _alloc_buffer, _read_cb));
|
||||||
|
|
||||||
|
status = STATUS_CONNECTED;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <godot_cpp/classes/node.hpp>
|
#include <godot_cpp/classes/node.hpp>
|
||||||
#include <godot_cpp/classes/os.hpp>
|
#include <godot_cpp/classes/os.hpp>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
namespace godot
|
namespace godot
|
||||||
{
|
{
|
||||||
|
@ -31,9 +32,19 @@ namespace godot
|
||||||
SIGNAL_SIGTERM = 15,
|
SIGNAL_SIGTERM = 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
STATUS_NONE,
|
||||||
|
STATUS_CONNECTING,
|
||||||
|
STATUS_CONNECTED,
|
||||||
|
STATUS_PAUSED,
|
||||||
|
STATUS_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
PTY();
|
PTY();
|
||||||
~PTY();
|
~PTY();
|
||||||
|
|
||||||
|
Status status = STATUS_NONE;
|
||||||
|
|
||||||
int get_cols() const;
|
int get_cols() const;
|
||||||
int get_rows() const;
|
int get_rows() const;
|
||||||
|
|
||||||
|
@ -43,19 +54,21 @@ namespace godot
|
||||||
bool get_use_os_env() const;
|
bool get_use_os_env() const;
|
||||||
void set_use_os_env(const bool value);
|
void set_use_os_env(const bool value);
|
||||||
|
|
||||||
|
String get_pts() const;
|
||||||
|
|
||||||
Error fork(const String &file = "", const PackedStringArray &args = PackedStringArray(), const String &cwd = ".", const int cols = 80, const int rows = 24);
|
Error fork(const String &file = "", const PackedStringArray &args = PackedStringArray(), const String &cwd = ".", const int cols = 80, const int rows = 24);
|
||||||
void kill(const int signum = Signal::SIGNAL_SIGHUP);
|
void kill(const int signum = Signal::SIGNAL_SIGHUP);
|
||||||
Error open(const int cols = 80, const int rows = 24) const;
|
Error open(const int cols = 80, const int rows = 24);
|
||||||
void resize(const int cols, const int rows) const;
|
void resize(const int cols, const int rows) const;
|
||||||
void resizev(const Vector2i &size) const { resize(size.x, size.y); };
|
void resizev(const Vector2i &size) const { resize(size.x, size.y); };
|
||||||
void write(const Variant &data) const;
|
void write(const Variant &data) const;
|
||||||
|
|
||||||
|
void _process(double delta) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OS *os;
|
|
||||||
|
|
||||||
int pid = -1;
|
int pid = -1;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
|
||||||
|
@ -65,9 +78,19 @@ namespace godot
|
||||||
Dictionary env = Dictionary();
|
Dictionary env = Dictionary();
|
||||||
bool use_os_env = true;
|
bool use_os_env = true;
|
||||||
|
|
||||||
|
String pts = "";
|
||||||
|
|
||||||
String _get_fork_file(const String &file) const;
|
String _get_fork_file(const String &file) const;
|
||||||
Dictionary _get_fork_env() const;
|
Dictionary _get_fork_env() const;
|
||||||
void _on_exit(int exit_code, int exit_signal);
|
void _on_exit(int exit_code, int exit_signal);
|
||||||
|
void _close();
|
||||||
|
|
||||||
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
|
uv_pipe_t pipe;
|
||||||
|
Error _pipe_open(const int fd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static Error uv_err_to_godot_err(const int uv_err);
|
||||||
};
|
};
|
||||||
} // namespace godot
|
} // namespace godot
|
||||||
|
|
||||||
|
|
|
@ -22,60 +22,10 @@ func test_fork_succeeds():
|
||||||
assert_eq(err, OK)
|
assert_eq(err, OK)
|
||||||
|
|
||||||
|
|
||||||
func xtest_fork_has_output():
|
func test_fork_emits_data_received():
|
||||||
pty.call_deferred("fork", "exit")
|
pty.call_deferred("fork", "sh", ["-c", "echo'"])
|
||||||
await wait_for_signal(pty.data_received, 1)
|
await wait_for_signal(pty.data_received, 1)
|
||||||
var expected := PackedByteArray(
|
assert_signal_emitted(pty, "data_received")
|
||||||
[
|
|
||||||
101,
|
|
||||||
120,
|
|
||||||
101,
|
|
||||||
99,
|
|
||||||
118,
|
|
||||||
112,
|
|
||||||
40,
|
|
||||||
51,
|
|
||||||
41,
|
|
||||||
32,
|
|
||||||
102,
|
|
||||||
97,
|
|
||||||
105,
|
|
||||||
108,
|
|
||||||
101,
|
|
||||||
100,
|
|
||||||
46,
|
|
||||||
58,
|
|
||||||
32,
|
|
||||||
78,
|
|
||||||
111,
|
|
||||||
32,
|
|
||||||
115,
|
|
||||||
117,
|
|
||||||
99,
|
|
||||||
104,
|
|
||||||
32,
|
|
||||||
102,
|
|
||||||
105,
|
|
||||||
108,
|
|
||||||
101,
|
|
||||||
32,
|
|
||||||
111,
|
|
||||||
114,
|
|
||||||
32,
|
|
||||||
100,
|
|
||||||
105,
|
|
||||||
114,
|
|
||||||
101,
|
|
||||||
99,
|
|
||||||
116,
|
|
||||||
111,
|
|
||||||
114,
|
|
||||||
121,
|
|
||||||
13,
|
|
||||||
10
|
|
||||||
]
|
|
||||||
)
|
|
||||||
assert_signal_emitted_with_parameters(pty, "data_received", [expected])
|
|
||||||
|
|
||||||
|
|
||||||
func test_open_succeeds():
|
func test_open_succeeds():
|
||||||
|
@ -90,13 +40,13 @@ func test_open_creates_a_new_pty():
|
||||||
assert_eq(new_num_pts, num_pts + 1)
|
assert_eq(new_num_pts, num_pts + 1)
|
||||||
|
|
||||||
|
|
||||||
func xtest_open_pty_has_correct_name():
|
func test_open_pty_has_correct_name():
|
||||||
var original_pts = helper.get_pts()
|
var original_pts = helper.get_pts()
|
||||||
var result = pty.open()
|
pty.open()
|
||||||
var new_pts = helper.get_pts()
|
var new_pts = helper.get_pts()
|
||||||
for pt in original_pts:
|
for pt in original_pts:
|
||||||
new_pts.erase(pt)
|
new_pts.erase(pt)
|
||||||
#assert_eq(result[1].pty, new_pts[0])
|
assert_eq(pty.get_pts(), new_pts[0])
|
||||||
|
|
||||||
|
|
||||||
func xtest_open_pty_has_correct_win_size():
|
func xtest_open_pty_has_correct_win_size():
|
||||||
|
@ -147,8 +97,6 @@ func test_emits_exit_code_on_failure():
|
||||||
|
|
||||||
|
|
||||||
func test_emits_exited_on_kill():
|
func test_emits_exited_on_kill():
|
||||||
if OS.get_name() == "macOS":
|
|
||||||
return
|
|
||||||
pty.call("fork", "yes")
|
pty.call("fork", "yes")
|
||||||
await wait_frames(1)
|
await wait_frames(1)
|
||||||
pty.call_deferred("kill", PTY.SIGNAL_SIGKILL)
|
pty.call_deferred("kill", PTY.SIGNAL_SIGKILL)
|
||||||
|
@ -157,8 +105,6 @@ func test_emits_exited_on_kill():
|
||||||
|
|
||||||
|
|
||||||
func test_emits_exited_with_signal():
|
func test_emits_exited_with_signal():
|
||||||
if OS.get_name() == "macOS":
|
|
||||||
return
|
|
||||||
pty.call("fork", "yes")
|
pty.call("fork", "yes")
|
||||||
await wait_frames(1)
|
await wait_frames(1)
|
||||||
pty.call_deferred("kill", PTY.SIGNAL_SIGSEGV)
|
pty.call_deferred("kill", PTY.SIGNAL_SIGSEGV)
|
||||||
|
|
Loading…
Reference in a new issue