mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-06-28 18:25:31 +02:00
added windows pty support
This commit is contained in:
parent
bd26137e78
commit
f3820365c6
23 changed files with 1478 additions and 695 deletions
|
@ -2,7 +2,7 @@
|
|||
|
||||
[ext_resource type="Script" path="res://addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd" id="1"]
|
||||
|
||||
[sub_resource type="Image" id="Image_84jui"]
|
||||
[sub_resource type="Image" id="Image_law8x"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "RGBA8",
|
||||
|
@ -12,7 +12,7 @@ data = {
|
|||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_q1uu0"]
|
||||
image = SubResource("Image_84jui")
|
||||
image = SubResource("Image_law8x")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_osmrc"]
|
||||
bg_color = Color(0.113329, 0.129458, 0.156802, 1)
|
||||
|
|
|
@ -26,9 +26,17 @@ sources.append([
|
|||
])
|
||||
|
||||
if env['platform'] == 'linux' or env['platform'] == 'macos':
|
||||
env.Append(LIBS=['util', env.File('thirdparty/libuv/build/libuv_a.a')])
|
||||
env.Append(LIBS=['util', env.File('thirdparty/libuv/build/libuv_a.a')])
|
||||
else:
|
||||
env.Append(CPPDEFINES=['_PTY_DISABLED'])
|
||||
env.Append(LIBS=['ws2_32.lib', 'Advapi32', 'User32', 'Userenv', 'iphlpapi'])
|
||||
if env["target"] == "template_release":
|
||||
env.Append(LIBS=[env.File('thirdparty/libuv/build/Release/uv_a.lib')])
|
||||
else:
|
||||
env.Append(LIBS=[env.File('thirdparty/libuv/build/Debug/uv_a.lib')])
|
||||
|
||||
# TODO(ast) this is a bandaid fix (see https://stackoverflow.com/questions/3007312/resolving-lnk4098-defaultlib-msvcrt-conflicts-with)
|
||||
# TODO(ast) a release build needs to use msvcrt instead of msvcrtd
|
||||
env.Append(LINKFLAGS=['/VERBOSE:LIB', '/NODEFAULTLIB:libcmtd.lib', '/NODEFAULTLIB:libcmt.lib', '/NODEFAULTLIB:msvcrt.lib'])
|
||||
|
||||
if env["platform"] == "macos":
|
||||
library = env.SharedLibrary(
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2020-2023 Leroy Hopson <godot-xterm@leroy.geek.nz>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Convenience function to keep the terminal open on failure in some terminals
|
||||
function fail () {
|
||||
echo "Failure!"
|
||||
read -p "Press any key to continue" x
|
||||
exit 1
|
||||
}
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Parse args.
|
||||
|
@ -27,8 +35,10 @@ done
|
|||
target=${target:-template_debug}
|
||||
if [ "$target" == "template_debug" ]; then
|
||||
debug_symbols="yes"
|
||||
uv_target="debug"
|
||||
else
|
||||
debug_symbols="no"
|
||||
uv_target="release"
|
||||
fi
|
||||
nproc=$(nproc || sysctl -n hw.ncpu)
|
||||
|
||||
|
@ -52,6 +62,7 @@ updateSubmodules() {
|
|||
fi
|
||||
}
|
||||
|
||||
# TODO libtsm causes warnings due to usage of nonstandard \e escape sequence. could be replaced with standard \033 or \x1b if this causes issues
|
||||
updateSubmodules LIBUV_DIR ${NATIVE_DIR}/thirdparty/libuv
|
||||
updateSubmodules LIBTSM_DIR ${NATIVE_DIR}/thirdparty/libtsm
|
||||
updateSubmodules GODOT_CPP_DIR ${NATIVE_DIR}/thirdparty/godot-cpp
|
||||
|
@ -60,23 +71,26 @@ updateSubmodules GODOT_CPP_DIR ${NATIVE_DIR}/thirdparty/godot-cpp
|
|||
cd ${LIBUV_DIR}
|
||||
mkdir build || true
|
||||
cd build
|
||||
args="-DCMAKE_BUILD_TYPE=$target -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \
|
||||
args="-DCMAKE_BUILD_TYPE=$uv_target -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \
|
||||
-DCMAKE_OSX_ARCHITECTURES=$(uname -m)"
|
||||
if [ "$target" == "template_release" ]; then
|
||||
args="$args -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL"
|
||||
else
|
||||
args="$args -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL"
|
||||
fi
|
||||
cmake .. $args
|
||||
cmake .. $args || fail
|
||||
cd ..
|
||||
cmake --build build --config $target -j$nproc
|
||||
cmake --build build --config $uv_target -j$nproc || fail
|
||||
|
||||
# Build libgodot-xterm.
|
||||
cd ${NATIVE_DIR}
|
||||
scons target=$target arch=$(uname -m) debug_symbols=$debug_symbols
|
||||
scons target=$target arch=$(uname -m) debug_symbols=$debug_symbols || fail
|
||||
|
||||
# Use Docker to build libgodot-xterm javascript.
|
||||
#if [ -x "$(command -v docker-compose)" ]; then
|
||||
# UID_GID="0:0" TARGET=$target docker-compose build javascript
|
||||
# UID_GID="$(id -u):$(id -g)" TARGET=$target docker-compose run --rm javascript
|
||||
#fi
|
||||
|
||||
echo "Done!"
|
||||
read -p "Press any key to exit" x
|
|
@ -117,6 +117,35 @@ const Terminal::KeyMap Terminal::KEY_MAP = {
|
|||
{{KEY_Z, 'z'}, XKB_KEY_z},
|
||||
{{KEY_Z, 'Z'}, XKB_KEY_Z},
|
||||
|
||||
// When CTRL is pressed, the unicode is always zero.
|
||||
// But to handle CTRL+<XY> in TSM, we still need to know the keysym
|
||||
{{KEY_A, '\0'}, XKB_KEY_a},
|
||||
{{KEY_B, '\0'}, XKB_KEY_b},
|
||||
{{KEY_C, '\0'}, XKB_KEY_c},
|
||||
{{KEY_D, '\0'}, XKB_KEY_d},
|
||||
{{KEY_E, '\0'}, XKB_KEY_e},
|
||||
{{KEY_F, '\0'}, XKB_KEY_f},
|
||||
{{KEY_G, '\0'}, XKB_KEY_g},
|
||||
{{KEY_H, '\0'}, XKB_KEY_h},
|
||||
{{KEY_I, '\0'}, XKB_KEY_i},
|
||||
{{KEY_J, '\0'}, XKB_KEY_j},
|
||||
{{KEY_K, '\0'}, XKB_KEY_k},
|
||||
{{KEY_L, '\0'}, XKB_KEY_l},
|
||||
{{KEY_M, '\0'}, XKB_KEY_m},
|
||||
{{KEY_N, '\0'}, XKB_KEY_n},
|
||||
{{KEY_O, '\0'}, XKB_KEY_o},
|
||||
{{KEY_P, '\0'}, XKB_KEY_p},
|
||||
{{KEY_Q, '\0'}, XKB_KEY_q},
|
||||
{{KEY_R, '\0'}, XKB_KEY_r},
|
||||
{{KEY_S, '\0'}, XKB_KEY_s},
|
||||
{{KEY_T, '\0'}, XKB_KEY_t},
|
||||
{{KEY_U, '\0'}, XKB_KEY_u},
|
||||
{{KEY_V, '\0'}, XKB_KEY_v},
|
||||
{{KEY_W, '\0'}, XKB_KEY_w},
|
||||
{{KEY_X, '\0'}, XKB_KEY_x},
|
||||
{{KEY_Y, '\0'}, XKB_KEY_y},
|
||||
{{KEY_Z, '\0'}, XKB_KEY_z},
|
||||
|
||||
{{KEY_0, '0'}, XKB_KEY_0},
|
||||
{{KEY_1, '1'}, XKB_KEY_1},
|
||||
{{KEY_2, '2'}, XKB_KEY_2},
|
||||
|
|
|
@ -12,17 +12,20 @@
|
|||
#if (defined(__linux__) || defined(__APPLE__)) && !defined(_PTY_DISABLED)
|
||||
#include "pty_unix.h"
|
||||
#include <unistd.h>
|
||||
#elif defined(_WIN32) && !defined(_PTY_DISABLED)
|
||||
#include "pty_win.h"
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
// Require buffer to be flushed after reaching this size.
|
||||
#define BUFFER_LIMIT 1048576 // 1MB
|
||||
|
||||
#define UV_ERR_MSG(uv_err) \
|
||||
#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))
|
||||
#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;
|
||||
|
||||
|
@ -30,60 +33,62 @@ void _alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf);
|
|||
void _write_cb(uv_write_t *req, int status) { std::free(req); }
|
||||
void _close_cb(uv_handle_t *handle) { /* no-op */ };
|
||||
|
||||
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_SIGFPE);
|
||||
BIND_ENUM_CONSTANT(SIGNAL_SIGKILL);
|
||||
BIND_ENUM_CONSTANT(SIGNAL_SIGSEGV);
|
||||
BIND_ENUM_CONSTANT(SIGNAL_SIGPIPE);
|
||||
BIND_ENUM_CONSTANT(SIGNAL_SIGALRM);
|
||||
BIND_ENUM_CONSTANT(SIGNAL_SIGTERM);
|
||||
void PTY::_bind_methods()
|
||||
{
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGHUP);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGINT);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGQUIT);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGILL);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGTRAP);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGABRT);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGFPE);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGKILL);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGSEGV);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGPIPE);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGALRM);
|
||||
BIND_ENUM_CONSTANT(IPCSIGNAL_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("set_cols", "num_cols"), &PTY::set_cols);
|
||||
ClassDB::bind_method(D_METHOD("get_cols"), &PTY::get_cols);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::INT, "cols"), "set_cols", "get_cols");
|
||||
ClassDB::bind_method(D_METHOD("set_cols", "num_cols"), &PTY::set_cols);
|
||||
ClassDB::bind_method(D_METHOD("get_cols"), &PTY::get_cols);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::INT, "cols"), "set_cols", "get_cols");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_rows", "num_rows"), &PTY::set_rows);
|
||||
ClassDB::bind_method(D_METHOD("get_rows"), &PTY::get_rows);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::INT, "rows"), "set_rows", "get_rows");
|
||||
ClassDB::bind_method(D_METHOD("set_rows", "num_rows"), &PTY::set_rows);
|
||||
ClassDB::bind_method(D_METHOD("get_rows"), &PTY::get_rows);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::INT, "rows"), "set_rows", "get_rows");
|
||||
|
||||
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_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_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("set_use_threads", "enabled"), &PTY::set_use_threads);
|
||||
ClassDB::bind_method(D_METHOD("is_using_threads"), &PTY::is_using_threads);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "is_using_threads");
|
||||
ClassDB::bind_method(D_METHOD("set_use_threads", "enabled"), &PTY::set_use_threads);
|
||||
ClassDB::bind_method(D_METHOD("is_using_threads"), &PTY::is_using_threads);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "is_using_threads");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_terminal_path", "path"), &PTY::set_terminal_path);
|
||||
ClassDB::bind_method(D_METHOD("get_terminal_path"), &PTY::get_terminal_path);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::NODE_PATH, "terminal_path", PropertyHint::PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Terminal"), "set_terminal_path", "get_terminal_path");
|
||||
ClassDB::bind_method(D_METHOD("set_terminal_path", "path"), &PTY::set_terminal_path);
|
||||
ClassDB::bind_method(D_METHOD("get_terminal_path"), &PTY::get_terminal_path);
|
||||
ClassDB::add_property("PTY", PropertyInfo(Variant::NODE_PATH, "terminal_path", PropertyHint::PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Terminal"), "set_terminal_path", "get_terminal_path");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_pts_name"), &PTY::get_pts_name);
|
||||
ClassDB::bind_method(D_METHOD("get_pts_name"), &PTY::get_pts_name);
|
||||
|
||||
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("resize", "cols", "rows"), &PTY::resize);
|
||||
ClassDB::bind_method(D_METHOD("resizev", "size"), &PTY::resizev);
|
||||
ClassDB::bind_method(D_METHOD("kill", "signal"), &PTY::kill);
|
||||
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("resize", "cols", "rows"), &PTY::resize);
|
||||
ClassDB::bind_method(D_METHOD("resizev", "size"), &PTY::resizev);
|
||||
ClassDB::bind_method(D_METHOD("kill", "signal"), &PTY::kill);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_on_exit", "exit_code", "signal_code"), &PTY::_on_exit);
|
||||
ClassDB::bind_method(D_METHOD("_on_exit", "exit_code", "signal_code"), &PTY::_on_exit);
|
||||
}
|
||||
|
||||
PTY::PTY() {
|
||||
PTY::PTY()
|
||||
{
|
||||
use_threads = true;
|
||||
|
||||
set_process_internal(false);
|
||||
|
@ -94,76 +99,92 @@ PTY::PTY() {
|
|||
env["TERM"] = "xterm-256color";
|
||||
env["COLORTERM"] = "truecolor";
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
uv_loop_init(&loop);
|
||||
uv_async_init(&loop, &async_handle, [](uv_async_t *handle) {});
|
||||
uv_pipe_init(&loop, &pipe, false);
|
||||
#ifdef _WIN32
|
||||
uv_pipe_init(&loop, &pipe_out, false);
|
||||
#endif
|
||||
pipe.data = this;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PTY::set_cols(const int num_cols) {
|
||||
if (cols != num_cols) {
|
||||
void PTY::set_cols(const int num_cols)
|
||||
{
|
||||
if (cols != num_cols)
|
||||
{
|
||||
cols = num_cols;
|
||||
resize(cols, rows);
|
||||
}
|
||||
}
|
||||
|
||||
int PTY::get_cols() const {
|
||||
return cols;
|
||||
int PTY::get_cols() const
|
||||
{
|
||||
return cols;
|
||||
}
|
||||
|
||||
void PTY::set_rows(const int num_rows) {
|
||||
if (rows != num_rows) {
|
||||
void PTY::set_rows(const int num_rows)
|
||||
{
|
||||
if (rows != num_rows)
|
||||
{
|
||||
rows = num_rows;
|
||||
resize(cols, rows);
|
||||
}
|
||||
}
|
||||
|
||||
int PTY::get_rows() const {
|
||||
return rows;
|
||||
int PTY::get_rows() const
|
||||
{
|
||||
return rows;
|
||||
}
|
||||
|
||||
Dictionary PTY::get_env() const {
|
||||
return env;
|
||||
Dictionary PTY::get_env() const
|
||||
{
|
||||
return env;
|
||||
}
|
||||
|
||||
void PTY::set_env(const Dictionary &value) {
|
||||
env = value;
|
||||
void PTY::set_env(const Dictionary &value)
|
||||
{
|
||||
env = value;
|
||||
}
|
||||
|
||||
bool PTY::get_use_os_env() const {
|
||||
return use_os_env;
|
||||
bool PTY::get_use_os_env() const
|
||||
{
|
||||
return use_os_env;
|
||||
}
|
||||
|
||||
void PTY::set_use_os_env(const bool value) {
|
||||
use_os_env = value;
|
||||
void PTY::set_use_os_env(const bool value)
|
||||
{
|
||||
use_os_env = value;
|
||||
}
|
||||
|
||||
void PTY::set_use_threads(bool p_use) {
|
||||
void PTY::set_use_threads(bool p_use)
|
||||
{
|
||||
ERR_FAIL_COND(status != STATUS_CLOSED);
|
||||
use_threads = p_use;
|
||||
}
|
||||
|
||||
bool PTY::is_using_threads() const {
|
||||
bool PTY::is_using_threads() const
|
||||
{
|
||||
return use_threads;
|
||||
}
|
||||
|
||||
void PTY::set_terminal_path(NodePath p_terminal_path) {
|
||||
void PTY::set_terminal_path(NodePath p_terminal_path)
|
||||
{
|
||||
terminal_path = p_terminal_path;
|
||||
|
||||
Callable write = Callable(this, "write");
|
||||
Callable resizev = Callable(this, "resizev");
|
||||
|
||||
// Disconnect the current terminal, if any.
|
||||
if (terminal != nullptr) {
|
||||
if (terminal != nullptr)
|
||||
{
|
||||
disconnect("data_received", Callable(terminal, "write"));
|
||||
terminal->disconnect("data_sent", write);
|
||||
terminal->disconnect("size_changed", resizev);
|
||||
}
|
||||
|
||||
terminal = Object::cast_to<Terminal>(get_node_or_null(terminal_path));
|
||||
if (terminal == nullptr) return;
|
||||
if (terminal == nullptr)
|
||||
return;
|
||||
|
||||
// Connect the new terminal.
|
||||
resize(terminal->get_cols(), terminal->get_rows());
|
||||
|
@ -175,23 +196,31 @@ void PTY::set_terminal_path(NodePath p_terminal_path) {
|
|||
connect("data_received", Callable(terminal, "write"), CONNECT_PERSIST);
|
||||
}
|
||||
|
||||
NodePath PTY::get_terminal_path() const {
|
||||
NodePath PTY::get_terminal_path() const
|
||||
{
|
||||
return terminal_path;
|
||||
}
|
||||
|
||||
String PTY::get_pts_name() const {
|
||||
String PTY::get_pts_name() const
|
||||
{
|
||||
return pts_name;
|
||||
}
|
||||
|
||||
Error PTY::fork(const String &file, const PackedStringArray &args, const String &cwd, const int p_cols, const int p_rows) {
|
||||
Error PTY::fork(const String &file, const PackedStringArray &args, const String &cwd, const int p_cols, const int p_rows)
|
||||
{
|
||||
String fork_file = _get_fork_file(file);
|
||||
Dictionary fork_env = _get_fork_env();
|
||||
Dictionary result;
|
||||
|
||||
#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");
|
||||
result = PTYUnix::fork(fork_file, args, _parse_env(fork_env), cwd, p_cols, p_rows, -1, -1, true, helper_path, Callable(this, "_on_exit"));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
String helper_path = ProjectSettings::get_singleton()->globalize_path("res://addons/godot_xterm/native/bin/spawn-helper");
|
||||
result = PTYWin::fork(fork_file, args, _parse_env(fork_env), cwd, p_cols, p_rows, -1, -1, true, helper_path, Callable(this, "_on_exit"));
|
||||
#endif
|
||||
|
||||
Error err = static_cast<Error>((int)result["error"]);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to fork.");
|
||||
|
@ -199,15 +228,22 @@ Error PTY::fork(const String &file, const PackedStringArray &args, const String
|
|||
fd = result["fd"];
|
||||
pid = result["pid"];
|
||||
pts_name = result["pty"];
|
||||
|
||||
#ifdef _WIN32
|
||||
fd_out = result["fd_out"];
|
||||
hpc = result["hpc"];
|
||||
#endif
|
||||
|
||||
status = STATUS_OPEN;
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
_pipe_open(fd);
|
||||
uv_read_start((uv_stream_t *)&pipe, _alloc_buffer, _read_cb);
|
||||
#endif
|
||||
_pipe_open(fd, &pipe);
|
||||
#ifdef _WIN32
|
||||
_pipe_open(fd_out, &pipe_out);
|
||||
#endif
|
||||
|
||||
if (use_threads) {
|
||||
uv_read_start((uv_stream_t *)&pipe, _alloc_buffer, _read_cb);
|
||||
|
||||
if (use_threads)
|
||||
{
|
||||
stop_thread.clear();
|
||||
thread->start(callable_mp(this, &PTY::_thread_func));
|
||||
}
|
||||
|
@ -216,80 +252,97 @@ Error PTY::fork(const String &file, const PackedStringArray &args, const String
|
|||
return OK;
|
||||
}
|
||||
|
||||
void PTY::kill(const int signal) {
|
||||
#if (defined(__linux__) || defined(__APPLE__)) && !defined(_PTY_DISABLED)
|
||||
if (pid > 0) {
|
||||
void PTY::kill(const int signal)
|
||||
{
|
||||
#if !defined(_PTY_DISABLED)
|
||||
if (pid > 0)
|
||||
{
|
||||
uv_kill(pid, signal);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
Error PTY::open(const int cols, const int rows) {
|
||||
Dictionary result;
|
||||
Error PTY::open(const int cols, const int rows)
|
||||
{
|
||||
Dictionary result;
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
result = PTYUnix::open(cols, rows);
|
||||
#endif
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
result = PTYUnix::open(cols, rows);
|
||||
#endif
|
||||
|
||||
Error err = static_cast<Error>((int)result["error"]);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
#if defined(_WIN32)
|
||||
result = PTYWin::open(cols, rows);
|
||||
#endif
|
||||
|
||||
fd = result["master"];
|
||||
pts_name = result["pty"];
|
||||
Error err = static_cast<Error>((int)result["error"]);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
|
||||
return OK;
|
||||
fd = result["master"];
|
||||
pts_name = result["pty"];
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void PTY::resize(const int p_cols, const int p_rows) {
|
||||
void PTY::resize(const int p_cols, const int p_rows)
|
||||
{
|
||||
cols = p_cols;
|
||||
rows = p_rows;
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
if (fd > -1) {
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
if (fd > -1)
|
||||
{
|
||||
PTYUnix::resize(fd, cols, rows);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (fd > -1)
|
||||
{
|
||||
PTYWin::resize(hpc, cols, rows);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PTY::write(const Variant &data) const {
|
||||
void PTY::write(const Variant &data) const
|
||||
{
|
||||
PackedByteArray bytes;
|
||||
|
||||
switch (data.get_type())
|
||||
{
|
||||
case Variant::STRING:
|
||||
bytes = ((String)data).to_utf8_buffer();
|
||||
break;
|
||||
case Variant::PACKED_BYTE_ARRAY:
|
||||
bytes = data;
|
||||
break;
|
||||
default:
|
||||
ERR_FAIL_MSG("Data must be a String or PackedByteArray.");
|
||||
}
|
||||
switch (data.get_type())
|
||||
{
|
||||
case Variant::STRING:
|
||||
bytes = ((String)data).to_utf8_buffer();
|
||||
break;
|
||||
case Variant::PACKED_BYTE_ARRAY:
|
||||
bytes = data;
|
||||
break;
|
||||
default:
|
||||
ERR_FAIL_MSG("Data must be a String or PackedByteArray.");
|
||||
}
|
||||
|
||||
if (status == STATUS_OPEN) {
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
if (status == STATUS_OPEN)
|
||||
{
|
||||
uv_buf_t buf;
|
||||
buf.base = (char *)bytes.ptr();
|
||||
buf.len = bytes.size();
|
||||
uv_write_t *req = (uv_write_t *)malloc(sizeof(uv_write_t));
|
||||
req->data = (void *)buf.base;
|
||||
uv_write(req, (uv_stream_t *)&pipe, &buf, 1, _write_cb);
|
||||
uv_run((uv_loop_t*)&loop, UV_RUN_NOWAIT);
|
||||
#endif
|
||||
uv_write(req, (uv_stream_t *)&pipe_out, &buf, 1, _write_cb);
|
||||
uv_run((uv_loop_t *)&loop, UV_RUN_NOWAIT);
|
||||
}
|
||||
}
|
||||
|
||||
void PTY::_notification(int p_what) {
|
||||
void PTY::_notification(int p_what)
|
||||
{
|
||||
switch (p_what)
|
||||
{
|
||||
case NOTIFICATION_INTERNAL_PROCESS:
|
||||
case NOTIFICATION_INTERNAL_PROCESS:
|
||||
{
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
if (!use_threads) uv_run(&loop, UV_RUN_NOWAIT);
|
||||
#endif
|
||||
if (!use_threads)
|
||||
uv_run(&loop, UV_RUN_NOWAIT);
|
||||
|
||||
buffer_write_mutex->lock();
|
||||
if (buffer.size() > 0) {
|
||||
if (buffer.size() > 0)
|
||||
{
|
||||
emit_signal("data_received", buffer);
|
||||
buffer.clear();
|
||||
buffer_cleared->post();
|
||||
|
@ -304,46 +357,59 @@ void PTY::_notification(int p_what) {
|
|||
}
|
||||
}
|
||||
|
||||
void PTY::_thread_func() {
|
||||
while (!stop_thread.is_set()) {
|
||||
if (buffer.size() < BUFFER_LIMIT) {
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
void PTY::_thread_func()
|
||||
{
|
||||
while (!stop_thread.is_set())
|
||||
{
|
||||
if (buffer.size() < BUFFER_LIMIT)
|
||||
{
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
#endif
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer_cleared->wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PTY::_close() {
|
||||
if (use_threads) {
|
||||
if (thread->is_started()) {
|
||||
void PTY::_close()
|
||||
{
|
||||
if (use_threads)
|
||||
{
|
||||
if (thread->is_started())
|
||||
{
|
||||
stop_thread.set();
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
uv_async_send(&async_handle);
|
||||
#endif
|
||||
thread->wait_to_finish();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
if (!uv_is_closing((uv_handle_t *)&pipe)) {
|
||||
if (!uv_is_closing((uv_handle_t *)&pipe))
|
||||
{
|
||||
uv_close((uv_handle_t *)&pipe, _close_cb);
|
||||
}
|
||||
|
||||
if (!uv_is_closing((uv_handle_t *)&async_handle)) {
|
||||
if (!uv_is_closing((uv_handle_t *)&async_handle))
|
||||
{
|
||||
uv_close((uv_handle_t *)&async_handle, _close_cb);
|
||||
}
|
||||
|
||||
if (status == STATUS_OPEN) {
|
||||
|
||||
if (status == STATUS_OPEN)
|
||||
{
|
||||
uv_run(&loop, UV_RUN_NOWAIT);
|
||||
uv_loop_close(&loop);
|
||||
}
|
||||
|
||||
if (fd > 0) close(fd);
|
||||
if (pid > 0) kill(SIGNAL_SIGHUP);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
PTYWin::close(hpc, fd, fd_out);
|
||||
fd_out = -1;
|
||||
hpc = -1;
|
||||
#else
|
||||
if (fd > 0)
|
||||
close(fd);
|
||||
if (pid > 0)
|
||||
kill(IPCSIGNAL_SIGHUP);
|
||||
#endif
|
||||
|
||||
fd = -1;
|
||||
pid = -1;
|
||||
|
@ -352,38 +418,46 @@ void PTY::_close() {
|
|||
status = STATUS_CLOSED;
|
||||
}
|
||||
|
||||
String PTY::_get_fork_file(const String &file) const {
|
||||
if (!file.is_empty()) return file;
|
||||
String PTY::_get_fork_file(const String &file) const
|
||||
{
|
||||
if (!file.is_empty())
|
||||
return file;
|
||||
|
||||
String shell_env = OS::get_singleton()->get_environment("SHELL");
|
||||
if (!shell_env.is_empty()) {
|
||||
if (!shell_env.is_empty())
|
||||
{
|
||||
return shell_env;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
#if defined(__linux__)
|
||||
return "sh";
|
||||
#elif defined(__APPLE__)
|
||||
#elif defined(__APPLE__)
|
||||
return "zsh";
|
||||
#elif defined(_WIN32)
|
||||
#elif defined(_WIN32)
|
||||
return "cmd.exe";
|
||||
#else
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
Dictionary PTY::_get_fork_env() const {
|
||||
if (!use_os_env) return env;
|
||||
Dictionary PTY::_get_fork_env() const
|
||||
{
|
||||
if (!use_os_env)
|
||||
return env;
|
||||
|
||||
#if defined(_PTY_DISABLED)
|
||||
#if defined(_PTY_DISABLED)
|
||||
return env;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// TODO This might need windows specific adjustment
|
||||
return env;
|
||||
Dictionary os_env;
|
||||
uv_env_item_t *uv_env;
|
||||
int count;
|
||||
|
||||
uv_os_environ(&uv_env, &count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
os_env[uv_env[i].name] = uv_env[i].value;
|
||||
}
|
||||
uv_os_free_environ(uv_env, count);
|
||||
|
@ -394,18 +468,19 @@ Dictionary PTY::_get_fork_env() const {
|
|||
|
||||
// Make sure we didn't start our server from inside screen.
|
||||
// http://web.mit.edu/gnu/doc/html/screen_20.html
|
||||
os_env.erase("STY");
|
||||
os_env.erase("WINDOW");
|
||||
os_env.erase("STY");
|
||||
os_env.erase("WINDOW");
|
||||
|
||||
// Delete some variables that might confuse our terminal.
|
||||
os_env.erase("WINDOWID");
|
||||
os_env.erase("TERMCAP");
|
||||
os_env.erase("COLUMNS");
|
||||
os_env.erase("LINES");
|
||||
os_env.erase("WINDOWID");
|
||||
os_env.erase("TERMCAP");
|
||||
os_env.erase("COLUMNS");
|
||||
os_env.erase("LINES");
|
||||
|
||||
// Merge in our custom environment.
|
||||
PackedStringArray keys = PackedStringArray(env.keys());
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
for (int i = 0; i < keys.size(); i++)
|
||||
{
|
||||
String key = keys[i];
|
||||
os_env[key] = env[key];
|
||||
}
|
||||
|
@ -413,11 +488,13 @@ Dictionary PTY::_get_fork_env() const {
|
|||
return os_env;
|
||||
}
|
||||
|
||||
PackedStringArray PTY::_parse_env(const Dictionary &env) const {
|
||||
PackedStringArray PTY::_parse_env(const Dictionary &env) const
|
||||
{
|
||||
PackedStringArray parsed_env;
|
||||
PackedStringArray keys = PackedStringArray(env.keys());
|
||||
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
for (int i = 0; i < keys.size(); i++)
|
||||
{
|
||||
String key = keys[i];
|
||||
parsed_env.push_back(key + "=" + String(env[key]));
|
||||
}
|
||||
|
@ -425,37 +502,41 @@ PackedStringArray PTY::_parse_env(const Dictionary &env) const {
|
|||
return parsed_env;
|
||||
}
|
||||
|
||||
void PTY::_on_exit(int exit_code, int exit_signal) {
|
||||
void PTY::_on_exit(int exit_code, int exit_signal)
|
||||
{
|
||||
call_deferred("emit_signal", "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 _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 PTY::_read_cb(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf) {
|
||||
void PTY::_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_CLOSED;
|
||||
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_CLOSED;
|
||||
return;
|
||||
default:
|
||||
pty->status = PTY::Status::STATUS_ERROR;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
pty->status = PTY::Status::STATUS_ERROR;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nread > 0) {
|
||||
if (nread > 0)
|
||||
{
|
||||
MutexLock lock(*pty->buffer_write_mutex.ptr());
|
||||
|
||||
int old_size = pty->buffer.size();
|
||||
|
@ -468,14 +549,11 @@ void PTY::_read_cb(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf) {
|
|||
}
|
||||
}
|
||||
|
||||
Error PTY::_pipe_open(const int fd) {
|
||||
Error PTY::_pipe_open(const int fd, uv_pipe_t *pipe)
|
||||
{
|
||||
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));
|
||||
ERR_FAIL_UV_ERR(uv_pipe_open(pipe, fd));
|
||||
ERR_FAIL_UV_ERR(uv_stream_set_blocking((uv_stream_t *)pipe, false));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
|
@ -19,22 +19,24 @@ namespace godot
|
|||
GDCLASS(PTY, Node)
|
||||
|
||||
public:
|
||||
enum Signal {
|
||||
SIGNAL_SIGHUP = 1,
|
||||
SIGNAL_SIGINT = 2,
|
||||
SIGNAL_SIGQUIT = 3,
|
||||
SIGNAL_SIGILL = 4,
|
||||
SIGNAL_SIGTRAP = 5,
|
||||
SIGNAL_SIGABRT = 6,
|
||||
SIGNAL_SIGFPE = 8,
|
||||
SIGNAL_SIGKILL = 9,
|
||||
SIGNAL_SIGSEGV = 11,
|
||||
SIGNAL_SIGPIPE = 13,
|
||||
SIGNAL_SIGALRM = 14,
|
||||
SIGNAL_SIGTERM = 15,
|
||||
enum IPCSignal
|
||||
{
|
||||
IPCSIGNAL_SIGHUP = 1,
|
||||
IPCSIGNAL_SIGINT = 2,
|
||||
IPCSIGNAL_SIGQUIT = 3,
|
||||
IPCSIGNAL_SIGILL = 4,
|
||||
IPCSIGNAL_SIGTRAP = 5,
|
||||
IPCSIGNAL_SIGABRT = 6,
|
||||
IPCSIGNAL_SIGFPE = 8,
|
||||
IPCSIGNAL_SIGKILL = 9,
|
||||
IPCSIGNAL_SIGSEGV = 11,
|
||||
IPCSIGNAL_SIGPIPE = 13,
|
||||
IPCSIGNAL_SIGALRM = 14,
|
||||
IPCSIGNAL_SIGTERM = 15,
|
||||
};
|
||||
|
||||
enum Status {
|
||||
enum Status
|
||||
{
|
||||
STATUS_CLOSED,
|
||||
STATUS_OPEN,
|
||||
STATUS_PAUSED,
|
||||
|
@ -65,7 +67,7 @@ namespace godot
|
|||
String get_pts_name() const;
|
||||
|
||||
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 = IPCSignal::IPCSIGNAL_SIGHUP);
|
||||
Error open(const int cols = 80, const int rows = 24);
|
||||
void resize(const int cols, const int rows);
|
||||
void resizev(const Vector2i &size) { resize(size.x, size.y); };
|
||||
|
@ -79,6 +81,10 @@ namespace godot
|
|||
private:
|
||||
int pid = -1;
|
||||
int fd = -1;
|
||||
#ifdef _WIN32
|
||||
int fd_out = -1;
|
||||
int64_t hpc = -1; // pseudoconsole handle
|
||||
#endif
|
||||
|
||||
unsigned int cols = 80;
|
||||
unsigned int rows = 24;
|
||||
|
@ -106,15 +112,17 @@ namespace godot
|
|||
bool use_threads;
|
||||
void _thread_func();
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
uv_loop_t loop;
|
||||
uv_pipe_t pipe;
|
||||
Error _pipe_open(const int fd);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
uv_pipe_t pipe_out;
|
||||
#endif
|
||||
|
||||
Error _pipe_open(const int fd, uv_pipe_t *pipe);
|
||||
|
||||
static void _read_cb(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf);
|
||||
static Error uv_err_to_godot_err(const int uv_err);
|
||||
};
|
||||
} // namespace godot
|
||||
|
||||
VARIANT_ENUM_CAST(PTY::Signal);
|
||||
VARIANT_ENUM_CAST(PTY::IPCSignal);
|
||||
|
|
|
@ -55,10 +55,10 @@
|
|||
|
||||
/* Some platforms name VWERASE and VDISCARD differently */
|
||||
#if !defined(VWERASE) && defined(VWERSE)
|
||||
#define VWERASE VWERSE
|
||||
#define VWERASE VWERSE
|
||||
#endif
|
||||
#if !defined(VDISCARD) && defined(VDISCRD)
|
||||
#define VDISCARD VDISCRD
|
||||
#define VDISCARD VDISCRD
|
||||
#endif
|
||||
|
||||
/* for pty_getproc */
|
||||
|
@ -82,7 +82,7 @@
|
|||
|
||||
/* macOS 10.14 back does not define this constant */
|
||||
#ifndef POSIX_SPAWN_SETSID
|
||||
#define POSIX_SPAWN_SETSID 1024
|
||||
#define POSIX_SPAWN_SETSID 1024
|
||||
#endif
|
||||
|
||||
/* environ for execvpe */
|
||||
|
@ -91,29 +91,32 @@ extern char **environ;
|
|||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
extern "C" {
|
||||
// Changes the current thread's directory to a path or directory file
|
||||
// descriptor. libpthread only exposes a syscall wrapper starting in
|
||||
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
|
||||
// the syscall is issued directly.
|
||||
int pthread_chdir_np(const char* dir) API_AVAILABLE(macosx(10.12));
|
||||
int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
|
||||
extern "C"
|
||||
{
|
||||
// Changes the current thread's directory to a path or directory file
|
||||
// descriptor. libpthread only exposes a syscall wrapper starting in
|
||||
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
|
||||
// the syscall is issued directly.
|
||||
int pthread_chdir_np(const char *dir) API_AVAILABLE(macosx(10.12));
|
||||
int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
|
||||
}
|
||||
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
int eintr_wrapper_counter = 0; \
|
||||
decltype(x) eintr_wrapper_result; \
|
||||
do { \
|
||||
eintr_wrapper_result = (x); \
|
||||
#define HANDLE_EINTR(x) ({ \
|
||||
int eintr_wrapper_counter = 0; \
|
||||
decltype(x) eintr_wrapper_result; \
|
||||
do \
|
||||
{ \
|
||||
eintr_wrapper_result = (x); \
|
||||
} while (eintr_wrapper_result == -1 && errno == EINTR && \
|
||||
eintr_wrapper_counter++ < 100); \
|
||||
eintr_wrapper_result; \
|
||||
eintr_wrapper_counter++ < 100); \
|
||||
eintr_wrapper_result; \
|
||||
})
|
||||
#endif
|
||||
|
||||
using namespace godot;
|
||||
|
||||
static void await_exit(Callable cb, pid_t pid) {
|
||||
static void await_exit(Callable cb, pid_t pid)
|
||||
{
|
||||
int ret;
|
||||
int stat_loc;
|
||||
#if defined(__APPLE__)
|
||||
|
@ -123,8 +126,10 @@ static void await_exit(Callable cb, pid_t pid) {
|
|||
struct kevent change = {0};
|
||||
EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
|
||||
ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));
|
||||
if (ret == -1) {
|
||||
if (errno == ESRCH) {
|
||||
if (ret == -1)
|
||||
{
|
||||
if (errno == ESRCH)
|
||||
{
|
||||
// At this point, one of the following has occurred:
|
||||
// 1. The process has died but has not yet been reaped.
|
||||
// 2. The process has died and has already been reaped.
|
||||
|
@ -132,19 +137,25 @@ static void await_exit(Callable cb, pid_t pid) {
|
|||
// kqueueable, but it may not be waitable yet either. Mark calls
|
||||
// this case the "zombie death race".
|
||||
ret = HANDLE_EINTR(waitpid(pid, &stat_loc, WNOHANG));
|
||||
if (ret == 0) {
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = kill(pid, SIGKILL);
|
||||
if (ret != -1) {
|
||||
if (ret != -1)
|
||||
{
|
||||
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
struct kevent event = {0};
|
||||
ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));
|
||||
if (ret == 1) {
|
||||
if (ret == 1)
|
||||
{
|
||||
if ((event.fflags & NOTE_EXIT) &&
|
||||
(event.ident == static_cast<uintptr_t>(pid))) {
|
||||
(event.ident == static_cast<uintptr_t>(pid)))
|
||||
{
|
||||
// The process is dead or dying. This won't block for long, if at
|
||||
// all.
|
||||
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
||||
|
@ -152,16 +163,22 @@ static void await_exit(Callable cb, pid_t pid) {
|
|||
}
|
||||
}
|
||||
#else
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
errno = 0;
|
||||
if ((ret = waitpid(pid, &stat_loc, 0)) != pid) {
|
||||
if (ret == -1 && errno == EINTR) {
|
||||
if ((ret = waitpid(pid, &stat_loc, 0)) != pid)
|
||||
{
|
||||
if (ret == -1 && errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ret == -1 && errno == ECHILD) {
|
||||
if (ret == -1 && errno == ECHILD)
|
||||
{
|
||||
// waitpid is already handled elsewhere.
|
||||
;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
@ -169,22 +186,26 @@ static void await_exit(Callable cb, pid_t pid) {
|
|||
}
|
||||
#endif
|
||||
int exit_code = 0, signal_code = 0;
|
||||
if (WIFEXITED(stat_loc)) {
|
||||
if (WIFEXITED(stat_loc))
|
||||
{
|
||||
exit_code = WEXITSTATUS(stat_loc); // errno?
|
||||
}
|
||||
if (WIFSIGNALED(stat_loc)) {
|
||||
if (WIFSIGNALED(stat_loc))
|
||||
{
|
||||
signal_code = WTERMSIG(stat_loc);
|
||||
}
|
||||
|
||||
cb.call_deferred(exit_code, signal_code);
|
||||
}
|
||||
|
||||
static void on_exit(int exit_code, int signal_code, Callable cb, Thread *thread) {
|
||||
static void on_exit(int exit_code, int signal_code, Callable cb, Thread *thread)
|
||||
{
|
||||
cb.call(exit_code, signal_code);
|
||||
thread->wait_to_finish();
|
||||
}
|
||||
|
||||
void setup_exit_callback(Callable cb, pid_t pid) {
|
||||
void setup_exit_callback(Callable cb, pid_t pid)
|
||||
{
|
||||
Thread *thread = memnew(Thread);
|
||||
|
||||
Callable exit_func = create_custom_callable_static_function_pointer(&on_exit).bind(cb, thread);
|
||||
|
@ -210,18 +231,20 @@ pty_getproc(int, char *);
|
|||
|
||||
#if defined(__APPLE__) || defined(__OpenBSD__)
|
||||
static void
|
||||
pty_posix_spawn(char** argv, char** env,
|
||||
pty_posix_spawn(char **argv, char **env,
|
||||
const struct termios *termp,
|
||||
const struct winsize *winp,
|
||||
int* master,
|
||||
pid_t* pid,
|
||||
int* err);
|
||||
int *master,
|
||||
pid_t *pid,
|
||||
int *err);
|
||||
#endif
|
||||
|
||||
struct DelBuf {
|
||||
struct DelBuf
|
||||
{
|
||||
int len;
|
||||
DelBuf(int len) : len(len) {}
|
||||
void operator()(char **p) {
|
||||
void operator()(char **p)
|
||||
{
|
||||
if (p == nullptr)
|
||||
return;
|
||||
for (int i = 0; i < len; i++)
|
||||
|
@ -231,18 +254,18 @@ struct DelBuf {
|
|||
};
|
||||
|
||||
Dictionary PTYUnix::fork(
|
||||
const String &p_file,
|
||||
const PackedStringArray &p_args,
|
||||
const PackedStringArray &p_env,
|
||||
const String &p_cwd,
|
||||
const int &p_cols,
|
||||
const int &p_rows,
|
||||
const int &p_uid,
|
||||
const int &p_gid,
|
||||
const bool &p_utf8,
|
||||
const String &p_helper_path,
|
||||
const Callable &p_on_exit
|
||||
) {
|
||||
const String &p_file,
|
||||
const PackedStringArray &p_args,
|
||||
const PackedStringArray &p_env,
|
||||
const String &p_cwd,
|
||||
const int &p_cols,
|
||||
const int &p_rows,
|
||||
const int &p_uid,
|
||||
const int &p_gid,
|
||||
const bool &p_utf8,
|
||||
const String &p_helper_path,
|
||||
const Callable &p_on_exit)
|
||||
{
|
||||
Dictionary result;
|
||||
result["error"] = FAILED;
|
||||
|
||||
|
@ -258,7 +281,8 @@ Dictionary PTYUnix::fork(
|
|||
std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));
|
||||
char **env = env_unique_ptr.get();
|
||||
env[envc] = NULL;
|
||||
for (int i = 0; i < envc; i++) {
|
||||
for (int i = 0; i < envc; i++)
|
||||
{
|
||||
std::string pair = env_[i].utf8().get_data();
|
||||
env[i] = strdup(pair.c_str());
|
||||
}
|
||||
|
@ -276,14 +300,15 @@ Dictionary PTYUnix::fork(
|
|||
#if !defined(__APPLE__)
|
||||
// uid / gid
|
||||
int uid = p_uid;
|
||||
int gid = p_gid;
|
||||
int gid = p_gid;
|
||||
#endif
|
||||
|
||||
// termios
|
||||
struct termios t = termios();
|
||||
struct termios *term = &t;
|
||||
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
||||
if (p_utf8) {
|
||||
if (p_utf8)
|
||||
{
|
||||
#if defined(IUTF8)
|
||||
term->c_iflag |= IUTF8;
|
||||
#endif
|
||||
|
@ -309,10 +334,10 @@ Dictionary PTYUnix::fork(
|
|||
term->c_cc[VMIN] = 1;
|
||||
term->c_cc[VTIME] = 0;
|
||||
|
||||
#if (__APPLE__)
|
||||
#if (__APPLE__)
|
||||
term->c_cc[VDSUSP] = 25;
|
||||
term->c_cc[VSTATUS] = 20;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
cfsetispeed(term, B38400);
|
||||
cfsetospeed(term, B38400);
|
||||
|
@ -331,27 +356,31 @@ Dictionary PTYUnix::fork(
|
|||
argv[1] = strdup(cwd_.c_str());
|
||||
argv[2] = strdup(file.c_str());
|
||||
argv[argl - 1] = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
std::string arg = argv_[i].utf8().get_data();
|
||||
argv[i + 3] = strdup(arg.c_str());
|
||||
}
|
||||
|
||||
int err = -1;
|
||||
pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);
|
||||
if (err != 0) {
|
||||
if (err != 0)
|
||||
{
|
||||
ERR_FAIL_V_MSG(result, "posix_spawnp failed with error: " + String(strerror(err)));
|
||||
}
|
||||
if (pty_nonblock(master) == -1) {
|
||||
if (pty_nonblock(master) == -1)
|
||||
{
|
||||
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
||||
}
|
||||
#else
|
||||
int argc = argv_.size();
|
||||
int argl = argc + 2;
|
||||
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
||||
char** argv = argv_unique_ptr.get();
|
||||
char **argv = argv_unique_ptr.get();
|
||||
argv[0] = strdup(file.c_str());
|
||||
argv[argl - 1] = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
std::string arg = argv_[i].utf8().get_data();
|
||||
argv[i + 1] = strdup(arg.c_str());
|
||||
}
|
||||
|
@ -365,14 +394,16 @@ Dictionary PTYUnix::fork(
|
|||
sigfillset(&newmask);
|
||||
pthread_sigmask(SIG_SETMASK, &newmask, &oldmask);
|
||||
|
||||
pid = forkpty(&master, nullptr, static_cast<termios*>(term), static_cast<winsize*>(&winp));
|
||||
pid = forkpty(&master, nullptr, static_cast<termios *>(term), static_cast<winsize *>(&winp));
|
||||
|
||||
if (!pid) {
|
||||
if (!pid)
|
||||
{
|
||||
// remove all signal handler from child
|
||||
sig_action.sa_handler = SIG_DFL;
|
||||
sig_action.sa_flags = 0;
|
||||
sigemptyset(&sig_action.sa_mask);
|
||||
for (int i = 0 ; i < NSIG ; i++) { // NSIG is a macro for all signals + 1
|
||||
for (int i = 0; i < NSIG; i++)
|
||||
{ // NSIG is a macro for all signals + 1
|
||||
sigaction(i, &sig_action, NULL);
|
||||
}
|
||||
}
|
||||
|
@ -380,44 +411,51 @@ Dictionary PTYUnix::fork(
|
|||
// re-enable signals
|
||||
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
|
||||
|
||||
switch (pid) {
|
||||
case -1:
|
||||
ERR_FAIL_V_MSG(result, "forkpty(3) failed.");
|
||||
case 0:
|
||||
if (strlen(cwd_.c_str())) {
|
||||
if (chdir(cwd_.c_str()) == -1) {
|
||||
perror("chdir(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (uid != -1 && gid != -1) {
|
||||
if (setgid(gid) == -1) {
|
||||
perror("setgid(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
if (setuid(uid) == -1) {
|
||||
perror("setuid(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
switch (pid)
|
||||
{
|
||||
case -1:
|
||||
ERR_FAIL_V_MSG(result, "forkpty(3) failed.");
|
||||
case 0:
|
||||
if (strlen(cwd_.c_str()))
|
||||
{
|
||||
if (chdir(cwd_.c_str()) == -1)
|
||||
{
|
||||
char **old = environ;
|
||||
environ = env;
|
||||
execvp(argv[0], argv);
|
||||
environ = old;
|
||||
perror("execvp(3) failed.");
|
||||
perror("chdir(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
default:
|
||||
if (pty_nonblock(master) == -1) {
|
||||
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
||||
}
|
||||
|
||||
if (uid != -1 && gid != -1)
|
||||
{
|
||||
if (setgid(gid) == -1)
|
||||
{
|
||||
perror("setgid(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
if (setuid(uid) == -1)
|
||||
{
|
||||
perror("setuid(2) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
char **old = environ;
|
||||
environ = env;
|
||||
execvp(argv[0], argv);
|
||||
environ = old;
|
||||
perror("execvp(3) failed.");
|
||||
_exit(1);
|
||||
}
|
||||
default:
|
||||
if (pty_nonblock(master) == -1)
|
||||
{
|
||||
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
result["fd"] = master;
|
||||
result["fd"] = master;
|
||||
result["pid"] = pid;
|
||||
result["pty"] = ptsname(master);
|
||||
|
||||
|
@ -430,9 +468,9 @@ Dictionary PTYUnix::fork(
|
|||
}
|
||||
|
||||
Dictionary PTYUnix::open(
|
||||
const int &p_cols,
|
||||
const int &p_rows
|
||||
) {
|
||||
const int &p_cols,
|
||||
const int &p_rows)
|
||||
{
|
||||
Dictionary result;
|
||||
result["error"] = FAILED;
|
||||
|
||||
|
@ -445,51 +483,56 @@ Dictionary PTYUnix::open(
|
|||
|
||||
// pty
|
||||
int master, slave;
|
||||
int ret = openpty(&master, &slave, nullptr, NULL, static_cast<winsize*>(&winp));
|
||||
int ret = openpty(&master, &slave, nullptr, NULL, static_cast<winsize *>(&winp));
|
||||
|
||||
if (ret == -1) {
|
||||
if (ret == -1)
|
||||
{
|
||||
ERR_FAIL_V_MSG(result, "openpty(3) failed.");
|
||||
}
|
||||
|
||||
if (pty_nonblock(master) == -1) {
|
||||
if (pty_nonblock(master) == -1)
|
||||
{
|
||||
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
||||
}
|
||||
|
||||
if (pty_nonblock(slave) == -1) {
|
||||
if (pty_nonblock(slave) == -1)
|
||||
{
|
||||
ERR_FAIL_V_MSG(result, "Could not set slave fd to nonblocking.");
|
||||
}
|
||||
|
||||
result["master"] = master;
|
||||
result["slave"] = slave;
|
||||
result["pty"] = ptsname(master);
|
||||
result["pty"] = ptsname(master);
|
||||
|
||||
result["error"] = OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
void PTYUnix::resize(
|
||||
const int &p_fd,
|
||||
const int &p_cols,
|
||||
const int &p_rows
|
||||
) {
|
||||
const int &p_fd,
|
||||
const int &p_cols,
|
||||
const int &p_rows)
|
||||
{
|
||||
int fd = p_fd;
|
||||
|
||||
struct winsize winp;
|
||||
winp.ws_col = p_cols;
|
||||
winp.ws_row = p_rows;
|
||||
winp.ws_col = p_cols;
|
||||
winp.ws_row = p_rows;
|
||||
winp.ws_xpixel = 0;
|
||||
winp.ws_ypixel = 0;
|
||||
|
||||
if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
|
||||
switch (errno) {
|
||||
case EBADF:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, EBADF");
|
||||
case EFAULT:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, EFAULT");
|
||||
case EINVAL:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, EINVAL");
|
||||
case ENOTTY:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, ENOTTY");
|
||||
if (ioctl(fd, TIOCSWINSZ, &winp) == -1)
|
||||
{
|
||||
switch (errno)
|
||||
{
|
||||
case EBADF:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, EBADF");
|
||||
case EFAULT:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, EFAULT");
|
||||
case EINVAL:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, EINVAL");
|
||||
case ENOTTY:
|
||||
ERR_FAIL_MSG("ioctl(2) failed, ENOTTY");
|
||||
}
|
||||
ERR_FAIL_MSG("ioctl(2) failed");
|
||||
}
|
||||
|
@ -501,14 +544,14 @@ void PTYUnix::resize(
|
|||
* Foreground Process Name
|
||||
*/
|
||||
String process(
|
||||
const int &p_fd,
|
||||
const String &p_tty
|
||||
) {
|
||||
const int &p_fd,
|
||||
const String &p_tty)
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
int fd = p_fd;
|
||||
char *name = pty_getproc(fd);
|
||||
#else
|
||||
int fd = p_fd;
|
||||
int fd = p_fd;
|
||||
|
||||
std::string tty_ = p_tty.utf8().get_data();
|
||||
char *tty = strdup(tty_.c_str());
|
||||
|
@ -516,7 +559,8 @@ String process(
|
|||
free(tty);
|
||||
#endif
|
||||
|
||||
if (name == NULL) {
|
||||
if (name == NULL)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -530,9 +574,11 @@ String process(
|
|||
*/
|
||||
|
||||
static int
|
||||
pty_nonblock(int fd) {
|
||||
pty_nonblock(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1) return -1;
|
||||
if (flags == -1)
|
||||
return -1;
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
|
@ -561,7 +607,8 @@ pty_nonblock(int fd) {
|
|||
#if defined(__linux__)
|
||||
|
||||
static char *
|
||||
pty_getproc(int fd, char *tty) {
|
||||
pty_getproc(int fd, char *tty)
|
||||
{
|
||||
FILE *f;
|
||||
char *path, *buf;
|
||||
size_t len;
|
||||
|
@ -569,14 +616,17 @@ pty_getproc(int fd, char *tty) {
|
|||
pid_t pgrp;
|
||||
int r;
|
||||
|
||||
if ((pgrp = tcgetpgrp(fd)) == -1) {
|
||||
if ((pgrp = tcgetpgrp(fd)) == -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp);
|
||||
if (r == -1 || path == NULL) return NULL;
|
||||
if (r == -1 || path == NULL)
|
||||
return NULL;
|
||||
|
||||
if ((f = fopen(path, "r")) == NULL) {
|
||||
if ((f = fopen(path, "r")) == NULL)
|
||||
{
|
||||
free(path);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -585,14 +635,18 @@ pty_getproc(int fd, char *tty) {
|
|||
|
||||
len = 0;
|
||||
buf = NULL;
|
||||
while ((ch = fgetc(f)) != EOF) {
|
||||
if (ch == '\0') break;
|
||||
while ((ch = fgetc(f)) != EOF)
|
||||
{
|
||||
if (ch == '\0')
|
||||
break;
|
||||
buf = (char *)realloc(buf, len + 2);
|
||||
if (buf == NULL) return NULL;
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
buf[len++] = ch;
|
||||
}
|
||||
|
||||
if (buf != NULL) {
|
||||
if (buf != NULL)
|
||||
{
|
||||
buf[len] = '\0';
|
||||
}
|
||||
|
||||
|
@ -603,21 +657,25 @@ pty_getproc(int fd, char *tty) {
|
|||
#elif defined(__APPLE__)
|
||||
|
||||
static char *
|
||||
pty_getproc(int fd) {
|
||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
|
||||
pty_getproc(int fd)
|
||||
{
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};
|
||||
size_t size;
|
||||
struct kinfo_proc kp;
|
||||
|
||||
if ((mib[3] = tcgetpgrp(fd)) == -1) {
|
||||
if ((mib[3] = tcgetpgrp(fd)) == -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = sizeof kp;
|
||||
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
|
||||
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0') {
|
||||
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0')
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -627,7 +685,8 @@ pty_getproc(int fd) {
|
|||
#else
|
||||
|
||||
static char *
|
||||
pty_getproc(int fd, char *tty) {
|
||||
pty_getproc(int fd, char *tty)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -635,16 +694,18 @@ pty_getproc(int fd, char *tty) {
|
|||
|
||||
#if defined(__APPLE__)
|
||||
static void
|
||||
pty_posix_spawn(char** argv, char** env,
|
||||
pty_posix_spawn(char **argv, char **env,
|
||||
const struct termios *termp,
|
||||
const struct winsize *winp,
|
||||
int* master,
|
||||
pid_t* pid,
|
||||
int* err) {
|
||||
int *master,
|
||||
pid_t *pid,
|
||||
int *err)
|
||||
{
|
||||
int low_fds[3];
|
||||
size_t count = 0;
|
||||
|
||||
for (; count < 3; count++) {
|
||||
for (; count < 3; count++)
|
||||
{
|
||||
low_fds[count] = posix_openpt(O_RDWR);
|
||||
if (low_fds[count] >= STDERR_FILENO)
|
||||
break;
|
||||
|
@ -655,12 +716,14 @@ pty_posix_spawn(char** argv, char** env,
|
|||
POSIX_SPAWN_SETSIGMASK |
|
||||
POSIX_SPAWN_SETSID;
|
||||
*master = posix_openpt(O_RDWR);
|
||||
if (*master == -1) {
|
||||
if (*master == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int res = grantpt(*master) || unlockpt(*master);
|
||||
if (res == -1) {
|
||||
if (res == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -668,25 +731,31 @@ pty_posix_spawn(char** argv, char** env,
|
|||
int slave;
|
||||
char slave_pty_name[128];
|
||||
res = ioctl(*master, TIOCPTYGNAME, slave_pty_name);
|
||||
if (res == -1) {
|
||||
if (res == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
slave = open(slave_pty_name, O_RDWR | O_NOCTTY);
|
||||
if (slave == -1) {
|
||||
if (slave == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (termp) {
|
||||
if (termp)
|
||||
{
|
||||
res = tcsetattr(slave, TCSANOW, termp);
|
||||
if (res == -1) {
|
||||
if (res == -1)
|
||||
{
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (winp) {
|
||||
if (winp)
|
||||
{
|
||||
res = ioctl(slave, TIOCSWINSZ, winp);
|
||||
if (res == -1) {
|
||||
if (res == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -702,7 +771,8 @@ pty_posix_spawn(char** argv, char** env,
|
|||
posix_spawnattr_t attrs;
|
||||
posix_spawnattr_init(&attrs);
|
||||
*err = posix_spawnattr_setflags(&attrs, flags);
|
||||
if (*err != 0) {
|
||||
if (*err != 0)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
@ -710,25 +780,29 @@ pty_posix_spawn(char** argv, char** env,
|
|||
/* Reset all signal the child to their default behavior */
|
||||
sigfillset(&signal_set);
|
||||
*err = posix_spawnattr_setsigdefault(&attrs, &signal_set);
|
||||
if (*err != 0) {
|
||||
if (*err != 0)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Reset the signal mask for all signals */
|
||||
sigemptyset(&signal_set);
|
||||
*err = posix_spawnattr_setsigmask(&attrs, &signal_set);
|
||||
if (*err != 0) {
|
||||
if (*err != 0)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
*err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env);
|
||||
} while (*err == EINTR);
|
||||
done:
|
||||
posix_spawn_file_actions_destroy(&acts);
|
||||
posix_spawnattr_destroy(&attrs);
|
||||
|
||||
for (; count > 0; count--) {
|
||||
for (; count > 0; count--)
|
||||
{
|
||||
close(low_fds[count]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,28 +12,25 @@ namespace godot
|
|||
{
|
||||
public:
|
||||
static Dictionary fork(
|
||||
const String &p_file,
|
||||
const PackedStringArray &p_args,
|
||||
const PackedStringArray &p_env,
|
||||
const String &p_cwd,
|
||||
const int &p_cols,
|
||||
const int &p_rows,
|
||||
const int &p_uid,
|
||||
const int &p_gid,
|
||||
const bool &p_utf8,
|
||||
const String &p_helper_path,
|
||||
const Callable &p_on_exit
|
||||
);
|
||||
const String &p_file,
|
||||
const PackedStringArray &p_args,
|
||||
const PackedStringArray &p_env,
|
||||
const String &p_cwd,
|
||||
const int &p_cols,
|
||||
const int &p_rows,
|
||||
const int &p_uid,
|
||||
const int &p_gid,
|
||||
const bool &p_utf8,
|
||||
const String &p_helper_path,
|
||||
const Callable &p_on_exit);
|
||||
|
||||
static Dictionary open(
|
||||
const int &p_cols,
|
||||
const int &p_rows
|
||||
);
|
||||
const int &p_cols,
|
||||
const int &p_rows);
|
||||
|
||||
static void resize(
|
||||
const int &p_fd,
|
||||
const int &p_cols,
|
||||
const int &p_rows
|
||||
);
|
||||
const int &p_fd,
|
||||
const int &p_cols,
|
||||
const int &p_rows);
|
||||
};
|
||||
} // namespace godot
|
||||
|
|
443
addons/godot_xterm/native/src/pty_win.cpp
Normal file
443
addons/godot_xterm/native/src/pty_win.cpp
Normal file
|
@ -0,0 +1,443 @@
|
|||
/**
|
||||
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
||||
* Copyright (c) 2017, Daniel Imms (MIT License)
|
||||
* Copyright (c) 2024, Leroy Hopson (MIT License)
|
||||
* Copyright (c) 2024, Alexander Treml (MIT License)
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* pty.cc:
|
||||
* This file is responsible for starting processes
|
||||
* with pseudo-terminal file descriptors.
|
||||
*
|
||||
* See:
|
||||
* conpty (https://github.com/microsoft/terminal/blob/main/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Includes
|
||||
*/
|
||||
|
||||
#if defined(_WIN32) && !defined(_PTY_DISABLED)
|
||||
|
||||
#include "pty_win.h"
|
||||
|
||||
#include <godot_cpp/classes/thread.hpp>
|
||||
#include <godot_cpp/variant/callable_method_pointer.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
|
||||
#include <thread>
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
HRESULT CreatePseudoConsoleAndPipes(COORD, HPCON *, int &, int &);
|
||||
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *, HPCON);
|
||||
void setup_exit_callback(Callable, int64_t);
|
||||
|
||||
struct DelBuf
|
||||
{
|
||||
int len;
|
||||
DelBuf(int len) : len(len) {}
|
||||
void operator()(char **p)
|
||||
{
|
||||
if (p == nullptr)
|
||||
return;
|
||||
for (int i = 0; i < len; i++)
|
||||
free(p[i]);
|
||||
delete[] p;
|
||||
}
|
||||
};
|
||||
|
||||
Dictionary PTYWin::fork(
|
||||
const String &p_file,
|
||||
const PackedStringArray &p_args,
|
||||
const PackedStringArray &p_env,
|
||||
const String &p_cwd,
|
||||
const int &p_cols,
|
||||
const int &p_rows,
|
||||
const int &p_uid,
|
||||
const int &p_gid,
|
||||
const bool &p_utf8,
|
||||
const String &p_helper_path,
|
||||
const Callable &p_on_exit)
|
||||
{
|
||||
Dictionary result;
|
||||
|
||||
// file
|
||||
std::string file = p_file.utf8().get_data();
|
||||
|
||||
// args
|
||||
PackedStringArray argv_ = p_args;
|
||||
|
||||
// env
|
||||
PackedStringArray env_ = p_env;
|
||||
int envc = env_.size();
|
||||
std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));
|
||||
char **env = env_unique_ptr.get();
|
||||
env[envc] = NULL;
|
||||
for (int i = 0; i < envc; i++)
|
||||
{
|
||||
std::string pair = env_[i].utf8().get_data();
|
||||
env[i] = strdup(pair.c_str());
|
||||
}
|
||||
|
||||
std::string cwd_ = p_cwd.utf8().get_data();
|
||||
|
||||
// Determine required size of Pseudo Console
|
||||
COORD winp{};
|
||||
winp.X = p_cols;
|
||||
winp.Y = p_rows;
|
||||
|
||||
int uid = p_uid;
|
||||
int gid = p_gid;
|
||||
|
||||
std::string helper_path = p_helper_path.utf8().get_data();
|
||||
|
||||
// Build argv
|
||||
int argc = argv_.size();
|
||||
int argl = argc + 2;
|
||||
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
||||
char **argv = argv_unique_ptr.get();
|
||||
argv[0] = strdup(file.c_str());
|
||||
argv[argl - 1] = NULL;
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
std::string arg = argv_[i].utf8().get_data();
|
||||
argv[i + 1] = strdup(arg.c_str());
|
||||
}
|
||||
|
||||
// Aggregate argv into command string
|
||||
std::string cmd;
|
||||
for (int i = 0; i < argl - 1; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
cmd += " ";
|
||||
cmd += argv[i];
|
||||
}
|
||||
LPSTR lpcmd = const_cast<char *>(cmd.c_str());
|
||||
|
||||
HRESULT ret{E_UNEXPECTED};
|
||||
HPCON hPC{INVALID_HANDLE_VALUE};
|
||||
|
||||
// Create the Pseudo Console and pipes to it
|
||||
int fd{-1};
|
||||
int fd_out{-1};
|
||||
ret = CreatePseudoConsoleAndPipes(winp, &hPC, fd, fd_out);
|
||||
|
||||
if (S_OK != ret)
|
||||
{
|
||||
result["error"] = ERR_CANT_FORK;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Initialize the necessary startup info struct
|
||||
STARTUPINFOEX startupInfo{};
|
||||
if (S_OK != InitializeStartupInfoAttachedToPseudoConsole(&startupInfo, hPC))
|
||||
{
|
||||
result["error"] = ERR_UNCONFIGURED;
|
||||
return result;
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION pi{};
|
||||
ret = CreateProcess(
|
||||
NULL, // No module name - use Command Line
|
||||
lpcmd, // Command Line
|
||||
NULL, // Process handle not inheritable
|
||||
NULL, // Thread handle not inheritable
|
||||
FALSE, // Inherit handles
|
||||
EXTENDED_STARTUPINFO_PRESENT, // Creation flags
|
||||
NULL, // Use parent's environment block
|
||||
NULL, // Use parent's starting directory
|
||||
&startupInfo.StartupInfo, // Pointer to STARTUPINFO
|
||||
&pi) // Pointer to PROCESS_INFORMATION
|
||||
? S_OK
|
||||
: GetLastError();
|
||||
|
||||
result["fd"] = fd;
|
||||
result["fd_out"] = fd_out;
|
||||
result["pid"] = static_cast<int64_t>(pi.dwProcessId);
|
||||
// TODO there is no equivalent to ptsname under windows. If this value is needed to support certain use cases, a workaround needs to be found
|
||||
result["pty"] = "ConPTY";
|
||||
result["hpc"] = reinterpret_cast<int64_t>(hPC);
|
||||
|
||||
// Set up process exit callback.
|
||||
Callable cb = p_on_exit;
|
||||
setup_exit_callback(cb, pi.dwProcessId);
|
||||
result["error"] = OK;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Dictionary PTYWin::open(
|
||||
const int &p_cols,
|
||||
const int &p_rows)
|
||||
{
|
||||
HRESULT ret{E_UNEXPECTED};
|
||||
Dictionary result;
|
||||
result["error"] = FAILED;
|
||||
|
||||
// TODO since the ConPTY API differs in many aspects from the unix PTY, this would need to look somewhat different
|
||||
// Not currently supported because unsure about how the interface should look, and what the use cases are.
|
||||
godot::UtilityFunctions::printerr("Not supported under windows!");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PTYWin::close(uint64_t hpc, int fd, int fd_out)
|
||||
{
|
||||
ClosePseudoConsole(reinterpret_cast<HPCON>(hpc));
|
||||
|
||||
// TODO(ast) this causes assertion errors. Might be related to this: https://stackoverflow.com/questions/5984144/assertion-error-in-crt-calling-osfile-in-vs-2008
|
||||
// TODO(ast) leave for now and return when the linking issues are figured out (see SConstruct file)
|
||||
// // Drain remaining data
|
||||
// char drain_buf[4096];
|
||||
// int bytes_read = 0;
|
||||
|
||||
// // Check if fd is valid before reading
|
||||
// if (fd >= 0) {
|
||||
// do {
|
||||
// bytes_read = read(fd, drain_buf, sizeof(drain_buf));
|
||||
// } while (bytes_read > 0);
|
||||
// godot::UtilityFunctions::print("Draining!");
|
||||
// } else {
|
||||
// godot::UtilityFunctions::print("fd is not valid, skipping drain.");
|
||||
// }
|
||||
|
||||
// // Optionally close file descriptors if they are valid
|
||||
// if (fd >= 0) _close(fd);
|
||||
// if (fd_out >= 0) _close(fd_out);
|
||||
}
|
||||
|
||||
// TODO(ast) repeatedly resizing sometimes crashes the terminal
|
||||
void PTYWin::resize(
|
||||
int64_t p_hpc,
|
||||
const int &p_cols,
|
||||
const int &p_rows)
|
||||
{
|
||||
COORD winp{};
|
||||
winp.X = p_cols;
|
||||
winp.Y = p_rows;
|
||||
|
||||
HPCON hpc = reinterpret_cast<HPCON>(p_hpc);
|
||||
HRESULT hr = ResizePseudoConsole(hpc, winp);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
godot::UtilityFunctions::printerr("ResizePseudoConsole failed. HRESULT: ", String::num_int64(hr), ", GetLastError: ", String::num_int64(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived from https://github.com/microsoft/terminal/blob/main/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp
|
||||
* Copyright (c) Microsoft Corporation (MIT License)
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* **/
|
||||
HRESULT CreatePseudoConsoleAndPipes(COORD size, HPCON *phPC, int &pFd, int &pFd_out)
|
||||
{
|
||||
HRESULT hr{E_UNEXPECTED};
|
||||
|
||||
// Generate unique pipe names using process id and tick count
|
||||
DWORD pid = GetCurrentProcessId();
|
||||
DWORD ticks = GetTickCount();
|
||||
wchar_t pipe_name_in[128];
|
||||
wchar_t pipe_name_out[128];
|
||||
swprintf(pipe_name_in, 128, L"\\\\.\\pipe\\godot_conpty_pipe_%lu_%lu_in", (unsigned long)pid, (unsigned long)ticks);
|
||||
swprintf(pipe_name_out, 128, L"\\\\.\\pipe\\godot_conpty_pipe_%lu_%lu_out", (unsigned long)pid, (unsigned long)ticks);
|
||||
|
||||
// Create bidirectional named pipes. Only one direction will be used on each.
|
||||
// This is done to achieve compatibility with pty.cpp since uv_pipe_open only works on named pipes
|
||||
HANDLE hPipeIn = INVALID_HANDLE_VALUE;
|
||||
HANDLE hPipeOut = INVALID_HANDLE_VALUE;
|
||||
HANDLE hPipeInFile = INVALID_HANDLE_VALUE;
|
||||
HANDLE hPipeOutFile = INVALID_HANDLE_VALUE;
|
||||
bool hPC_created = false;
|
||||
|
||||
hPipeIn = CreateNamedPipeW(
|
||||
pipe_name_in,
|
||||
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
1, // Max instances
|
||||
4096, 4096, // Out/in buffer size
|
||||
0, // Default timeout
|
||||
NULL // Default security
|
||||
);
|
||||
|
||||
if (hPipeIn == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hPipeOut = CreateNamedPipeW(
|
||||
pipe_name_out,
|
||||
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
1, 4096, 4096, 0, NULL);
|
||||
|
||||
if (hPipeOut == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Connect to the named pipes
|
||||
hPipeInFile = CreateFileW(
|
||||
pipe_name_in,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
|
||||
|
||||
if (hPipeInFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hPipeOutFile = CreateFileW(
|
||||
pipe_name_out,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
|
||||
|
||||
if (hPipeOutFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Create the Pseudo Console
|
||||
hr = CreatePseudoConsole(size, hPipeOut, hPipeIn, 0, phPC);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
hPC_created = true;
|
||||
|
||||
// Convert HANDLE to C file descriptor
|
||||
int fd = _open_osfhandle((intptr_t)hPipeInFile, 0);
|
||||
if (fd == -1)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pFd = fd;
|
||||
hPipeInFile = INVALID_HANDLE_VALUE; // Ownership transferred to fd
|
||||
|
||||
fd = _open_osfhandle((intptr_t)hPipeOutFile, 0);
|
||||
if (fd == -1)
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pFd_out = fd;
|
||||
hPipeOutFile = INVALID_HANDLE_VALUE; // Ownership transferred
|
||||
|
||||
hr = S_OK;
|
||||
|
||||
cleanup:
|
||||
// Only close handles that are still valid and not transferred
|
||||
if (hPipeIn != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hPipeIn);
|
||||
if (hPipeOut != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hPipeOut);
|
||||
if (hPipeInFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hPipeInFile);
|
||||
if (hPipeOutFile != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hPipeOutFile);
|
||||
if (FAILED(hr) && hPC_created && phPC)
|
||||
ClosePseudoConsole(*phPC);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copied from https://github.com/microsoft/terminal/blob/main/samples/ConPTY/EchoCon/EchoCon/EchoCon.cpp
|
||||
* Copyright (c) Microsoft Corporation (MIT License)
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* **/
|
||||
// Initializes the specified startup info struct with the required properties and
|
||||
// updates its thread attribute list with the specified ConPTY handle
|
||||
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC)
|
||||
{
|
||||
HRESULT hr{E_UNEXPECTED};
|
||||
|
||||
if (pStartupInfo)
|
||||
{
|
||||
size_t attrListSize{};
|
||||
|
||||
pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
|
||||
|
||||
// Get the size of the thread attribute list.
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
|
||||
|
||||
// Allocate a thread attribute list of the correct size
|
||||
pStartupInfo->lpAttributeList =
|
||||
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attrListSize));
|
||||
|
||||
// Initialize thread attribute list
|
||||
if (pStartupInfo->lpAttributeList && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize))
|
||||
{
|
||||
// Set Pseudo Console attribute
|
||||
hr = UpdateProcThreadAttribute(
|
||||
pStartupInfo->lpAttributeList,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
|
||||
hPC,
|
||||
sizeof(HPCON),
|
||||
NULL,
|
||||
NULL)
|
||||
? S_OK
|
||||
: HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
static void await_exit(Callable cb, int64_t pid)
|
||||
{
|
||||
DWORD exit_code = 0;
|
||||
int signal_code = 0;
|
||||
|
||||
HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, static_cast<DWORD>(pid));
|
||||
if (hProcess != NULL)
|
||||
{
|
||||
WaitForSingleObject(hProcess, INFINITE);
|
||||
GetExitCodeProcess(hProcess, &exit_code);
|
||||
CloseHandle(hProcess);
|
||||
cb.call_deferred(static_cast<int>(exit_code), signal_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
godot::UtilityFunctions::printerr("Could not open process!");
|
||||
}
|
||||
}
|
||||
|
||||
static void on_exit(int exit_code, int signal_code, Callable cb, Thread *thread)
|
||||
{
|
||||
cb.call(exit_code, signal_code);
|
||||
thread->wait_to_finish();
|
||||
}
|
||||
|
||||
void setup_exit_callback(Callable cb, int64_t pid)
|
||||
{
|
||||
Thread *thread = memnew(Thread);
|
||||
|
||||
Callable exit_func = create_custom_callable_static_function_pointer(&on_exit).bind(cb, thread);
|
||||
Callable thread_func = create_custom_callable_static_function_pointer(&await_exit).bind(exit_func, pid);
|
||||
|
||||
thread->start(thread_func);
|
||||
}
|
||||
|
||||
#endif
|
41
addons/godot_xterm/native/src/pty_win.h
Normal file
41
addons/godot_xterm/native/src/pty_win.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
// SPDX-FileCopyrightText: 2024 Leroy Hopson <godot-xterm@leroy.nix.nz>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/variant/callable.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
|
||||
namespace godot
|
||||
{
|
||||
class PTYWin
|
||||
{
|
||||
public:
|
||||
static Dictionary fork(
|
||||
const String &p_file,
|
||||
const PackedStringArray &p_args,
|
||||
const PackedStringArray &p_env,
|
||||
const String &p_cwd,
|
||||
const int &p_cols,
|
||||
const int &p_rows,
|
||||
const int &p_uid,
|
||||
const int &p_gid,
|
||||
const bool &p_utf8,
|
||||
const String &p_helper_path,
|
||||
const Callable &p_on_exit);
|
||||
|
||||
static Dictionary open(
|
||||
const int &p_cols,
|
||||
const int &p_rows);
|
||||
|
||||
static void resize(
|
||||
int64_t p_hpc,
|
||||
const int &p_cols,
|
||||
const int &p_rows);
|
||||
|
||||
static void close(
|
||||
uint64_t hpc,
|
||||
int fd,
|
||||
int fd_out);
|
||||
};
|
||||
} // namespace godot
|
|
@ -34,9 +34,9 @@ using namespace godot;
|
|||
|
||||
void Terminal::_bind_methods()
|
||||
{
|
||||
ADD_SIGNAL(MethodInfo("data_sent", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
|
||||
ADD_SIGNAL(MethodInfo("key_pressed", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"), PropertyInfo(Variant::OBJECT, "event")));
|
||||
ADD_SIGNAL(MethodInfo("size_changed", PropertyInfo(Variant::VECTOR2I, "new_size")));
|
||||
ADD_SIGNAL(MethodInfo("data_sent", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
|
||||
ADD_SIGNAL(MethodInfo("key_pressed", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"), PropertyInfo(Variant::OBJECT, "event")));
|
||||
ADD_SIGNAL(MethodInfo("size_changed", PropertyInfo(Variant::VECTOR2I, "new_size")));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_cols"), &Terminal::get_cols);
|
||||
ClassDB::bind_method(D_METHOD("get_rows"), &Terminal::get_rows);
|
||||
|
@ -146,11 +146,13 @@ int Terminal::get_rows() const
|
|||
return rows;
|
||||
}
|
||||
|
||||
Vector2i Terminal::get_cursor_pos() const {
|
||||
Vector2i Terminal::get_cursor_pos() const
|
||||
{
|
||||
return Vector2i(tsm_screen_get_cursor_x(screen), tsm_screen_get_cursor_y(screen));
|
||||
}
|
||||
|
||||
Vector2 Terminal::get_cell_size() const {
|
||||
Vector2 Terminal::get_cell_size() const
|
||||
{
|
||||
return cell_size;
|
||||
}
|
||||
|
||||
|
@ -181,7 +183,8 @@ String Terminal::write(const Variant data)
|
|||
ERR_FAIL_V_MSG("", "Data must be a String or PackedByteArray.");
|
||||
}
|
||||
|
||||
if (bytes.is_empty()) return "";
|
||||
if (bytes.is_empty())
|
||||
return "";
|
||||
|
||||
response.clear();
|
||||
tsm_vte_input(vte, (char *)bytes.ptr(), bytes.size());
|
||||
|
@ -190,7 +193,8 @@ String Terminal::write(const Variant data)
|
|||
return response.get_string_from_utf8();
|
||||
}
|
||||
|
||||
void Terminal::_gui_input(const Ref<InputEvent> &event) {
|
||||
void Terminal::_gui_input(const Ref<InputEvent> &event)
|
||||
{
|
||||
_handle_key_input(event);
|
||||
_handle_selection(event);
|
||||
_handle_mouse_wheel(event);
|
||||
|
@ -235,13 +239,15 @@ void Terminal::_write_cb(tsm_vte *vte, const char *u8, size_t len, void *data)
|
|||
{
|
||||
Terminal *term = static_cast<Terminal *>(data);
|
||||
|
||||
if (len > 0) {
|
||||
if (len > 0)
|
||||
{
|
||||
PackedByteArray data;
|
||||
data.resize(len);
|
||||
memcpy(data.ptrw(), u8, len);
|
||||
term->response.append_array(data);
|
||||
|
||||
if (term->last_input_event_key.is_valid()) {
|
||||
if (term->last_input_event_key.is_valid())
|
||||
{
|
||||
term->emit_signal("key_pressed", data.get_string_from_utf8(), term->last_input_event_key);
|
||||
term->last_input_event_key.unref();
|
||||
}
|
||||
|
@ -263,10 +269,11 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
|||
{
|
||||
Terminal *term = static_cast<Terminal *>(data);
|
||||
|
||||
if (age != 0 && age <= term->framebuffer_age) return OK;
|
||||
if (age != 0 && age <= term->framebuffer_age)
|
||||
return OK;
|
||||
|
||||
if (width < 1)
|
||||
{ // No foreground or background to draw.
|
||||
{ // No foreground or background to draw.
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
@ -276,20 +283,22 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
|||
attr_flags |= AttrFlag::INVERSE;
|
||||
if (attr->blink)
|
||||
attr_flags |= AttrFlag::BLINK;
|
||||
if (term->cursor_position.x == posx && term->cursor_position.y == posy) {
|
||||
if (term->cursor_position.x == posx && term->cursor_position.y == posy)
|
||||
{
|
||||
attr_flags |= AttrFlag::CURSOR;
|
||||
}
|
||||
|
||||
// Collect colors.
|
||||
Color fgcol = std::min(attr->fccode, (int8_t)TSM_COLOR_FOREGROUND) >= 0
|
||||
? term->palette[attr->fccode]
|
||||
: Color(attr->fr / 255.0f, attr->fg / 255.0f, attr->fb / 255.0f);
|
||||
? term->palette[attr->fccode]
|
||||
: Color(attr->fr / 255.0f, attr->fg / 255.0f, attr->fb / 255.0f);
|
||||
|
||||
Color bgcol = std::min(attr->bccode, (int8_t)TSM_COLOR_BACKGROUND) >= 0
|
||||
? term->palette[attr->bccode]
|
||||
: Color(attr->br / 255.0f, attr->bg / 255.0f, attr->bb / 255.0f);
|
||||
? term->palette[attr->bccode]
|
||||
: Color(attr->br / 255.0f, attr->bg / 255.0f, attr->bb / 255.0f);
|
||||
|
||||
if (attr->inverse && term->inverse_mode == InverseMode::INVERSE_MODE_SWAP) {
|
||||
if (attr->inverse && term->inverse_mode == InverseMode::INVERSE_MODE_SWAP)
|
||||
{
|
||||
std::swap(fgcol.r, bgcol.r);
|
||||
std::swap(fgcol.g, bgcol.g);
|
||||
std::swap(fgcol.b, bgcol.b);
|
||||
|
@ -300,7 +309,8 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
|||
bgcol.a = 0;
|
||||
|
||||
// Update images (accounting for ultra-wide characters).
|
||||
for (int i = 0; i < width && (posx + i) < term->cols; i++) {
|
||||
for (int i = 0; i < width && (posx + i) < term->cols; i++)
|
||||
{
|
||||
term->back_image->set_pixel(posx + i, posy, bgcol);
|
||||
term->attr_image->set_pixel(posx + i, posy, Color(attr_flags / 255.0f, 0, 0, 0));
|
||||
}
|
||||
|
@ -312,7 +322,7 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
|||
term->rs->canvas_item_add_rect(term->char_canvas_item, cell_rect, Color(1, 1, 1, 0));
|
||||
|
||||
if (len < 1)
|
||||
{ // No foreground to draw.
|
||||
{ // No foreground to draw.
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
@ -323,8 +333,7 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
|||
Vector2i(cell_position.x, cell_position.y + term->font_offset),
|
||||
static_cast<uint64_t>(*ch),
|
||||
term->font_size,
|
||||
fgcol
|
||||
);
|
||||
fgcol);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
@ -333,7 +342,8 @@ void Terminal::_bell_cb(struct tsm_vte *vte, void *data)
|
|||
{
|
||||
Terminal *term = static_cast<Terminal *>(data);
|
||||
|
||||
if (!term->bell_muted && term->bell_timer->is_stopped()) {
|
||||
if (!term->bell_muted && term->bell_timer->is_stopped())
|
||||
{
|
||||
term->emit_signal("bell");
|
||||
|
||||
if (term->bell_cooldown > 0)
|
||||
|
@ -393,12 +403,15 @@ bool Terminal::_set(const StringName &p_name, const Variant &p_value)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Terminal::_get(const StringName &p_name, Variant &r_value) {
|
||||
if (p_name == String("cols")) {
|
||||
bool Terminal::_get(const StringName &p_name, Variant &r_value)
|
||||
{
|
||||
if (p_name == String("cols"))
|
||||
{
|
||||
r_value = cols;
|
||||
return true;
|
||||
}
|
||||
if (p_name == String("rows")) {
|
||||
if (p_name == String("rows"))
|
||||
{
|
||||
r_value = rows;
|
||||
return true;
|
||||
}
|
||||
|
@ -485,20 +498,24 @@ void Terminal::update_sizes(bool force)
|
|||
|
||||
void Terminal::set_shader_parameters(const String ¶m, const Variant &value)
|
||||
{
|
||||
if (param.is_empty()) {
|
||||
if (param.is_empty())
|
||||
{
|
||||
set_shader_parameters("cols", cols);
|
||||
set_shader_parameters("rows", rows);
|
||||
set_shader_parameters("size", size);
|
||||
set_shader_parameters("cell_size", cell_size);
|
||||
set_shader_parameters("grid_size", Vector2(cols * cell_size.x, rows * cell_size.y));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
back_material->set_shader_parameter(param, value);
|
||||
fore_material->set_shader_parameter(param, value);
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::initialize_rendering() {
|
||||
ResourceLoader* rl = ResourceLoader::get_singleton();
|
||||
void Terminal::initialize_rendering()
|
||||
{
|
||||
ResourceLoader *rl = ResourceLoader::get_singleton();
|
||||
|
||||
rs = RenderingServer::get_singleton();
|
||||
attr_texture.instantiate();
|
||||
|
@ -542,7 +559,7 @@ void Terminal::initialize_rendering() {
|
|||
canvas = rs->canvas_create();
|
||||
rs->canvas_item_set_parent(char_canvas_item, canvas);
|
||||
|
||||
viewport = rs-> viewport_create();
|
||||
viewport = rs->viewport_create();
|
||||
rs->viewport_attach_canvas(viewport, canvas);
|
||||
rs->viewport_set_disable_3d(viewport, true);
|
||||
rs->viewport_set_transparent_background(viewport, true);
|
||||
|
@ -563,16 +580,19 @@ void Terminal::initialize_rendering() {
|
|||
rs->connect("frame_post_draw", Callable(this, "_on_frame_post_draw"));
|
||||
}
|
||||
|
||||
void Terminal::update_theme() {
|
||||
void Terminal::update_theme()
|
||||
{
|
||||
// Update colors.
|
||||
palette.resize(TSM_COLOR_NUM);
|
||||
for (int i = 0; i < TSM_COLOR_NUM; i++) {
|
||||
for (int i = 0; i < TSM_COLOR_NUM; i++)
|
||||
{
|
||||
tsm_vte_color color = static_cast<tsm_vte_color>(i);
|
||||
palette[color] = get_theme_color(String(COLOR_NAMES[i]));
|
||||
}
|
||||
|
||||
// Update fonts.
|
||||
for (int i = FontType::NORMAL; i <= FontType::BOLD_ITALICS; i++) {
|
||||
for (int i = FontType::NORMAL; i <= FontType::BOLD_ITALICS; i++)
|
||||
{
|
||||
FontType type = static_cast<FontType>(i);
|
||||
fonts[type] = has_theme_font(FONT_TYPES[type]) ? get_theme_font(FONT_TYPES[type]) : get_theme_font(FONT_TYPES[FontType::NORMAL]);
|
||||
}
|
||||
|
@ -581,25 +601,30 @@ void Terminal::update_theme() {
|
|||
style_normal = get_theme_stylebox("normal");
|
||||
style_focus = get_theme_stylebox("focus");
|
||||
|
||||
if (dynamic_cast<StyleBoxFlat*>(style_normal.ptr()) != nullptr) {
|
||||
if (dynamic_cast<StyleBoxFlat *>(style_normal.ptr()) != nullptr)
|
||||
{
|
||||
// Blend the background color with the style box's background color to get the "true" background color.
|
||||
Color style_background_color = style_normal->get("bg_color");
|
||||
palette[TSM_COLOR_BACKGROUND] = style_background_color.blend(palette[TSM_COLOR_BACKGROUND]);
|
||||
palette[TSM_COLOR_BACKGROUND] = style_background_color.blend(palette[TSM_COLOR_BACKGROUND]);
|
||||
}
|
||||
back_material->set_shader_parameter("background_color", palette[TSM_COLOR_BACKGROUND]);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void Terminal::_on_frame_post_draw() {
|
||||
if (redraw_requested) {
|
||||
void Terminal::_on_frame_post_draw()
|
||||
{
|
||||
if (redraw_requested)
|
||||
{
|
||||
queue_redraw();
|
||||
redraw_requested = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::draw_screen() {
|
||||
if (framebuffer_age == 0) {
|
||||
void Terminal::draw_screen()
|
||||
{
|
||||
if (framebuffer_age == 0)
|
||||
{
|
||||
Rect2 rect = Rect2(Vector2(), size);
|
||||
|
||||
rs->viewport_set_clear_mode(viewport, RenderingServer::ViewportClearMode::VIEWPORT_CLEAR_ONLY_NEXT_FRAME);
|
||||
|
@ -628,12 +653,14 @@ void Terminal::draw_screen() {
|
|||
back_texture->update(back_image);
|
||||
}
|
||||
|
||||
void Terminal::refresh() {
|
||||
void Terminal::refresh()
|
||||
{
|
||||
framebuffer_age = 0;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void Terminal::cleanup_rendering() {
|
||||
void Terminal::cleanup_rendering()
|
||||
{
|
||||
// StyleBox.
|
||||
rs->free_rid(style_canvas_item);
|
||||
|
||||
|
@ -649,18 +676,22 @@ void Terminal::cleanup_rendering() {
|
|||
rs->free_rid(char_shader);
|
||||
}
|
||||
|
||||
void Terminal::set_bell_muted(const bool muted) {
|
||||
void Terminal::set_bell_muted(const bool muted)
|
||||
{
|
||||
bell_muted = muted;
|
||||
}
|
||||
|
||||
bool Terminal::get_bell_muted() const {
|
||||
bool Terminal::get_bell_muted() const
|
||||
{
|
||||
return bell_muted;
|
||||
}
|
||||
|
||||
void Terminal::set_bell_cooldown(const double time) {
|
||||
void Terminal::set_bell_cooldown(const double time)
|
||||
{
|
||||
bell_cooldown = time;
|
||||
|
||||
if (!bell_timer->is_stopped()) {
|
||||
if (!bell_timer->is_stopped())
|
||||
{
|
||||
bell_timer->stop();
|
||||
|
||||
double remaining_time = std::max(0.0, bell_cooldown - bell_timer->get_time_left());
|
||||
|
@ -669,7 +700,8 @@ void Terminal::set_bell_cooldown(const double time) {
|
|||
}
|
||||
}
|
||||
|
||||
double Terminal::get_bell_cooldown() const {
|
||||
double Terminal::get_bell_cooldown() const
|
||||
{
|
||||
return bell_cooldown;
|
||||
}
|
||||
|
||||
|
@ -695,25 +727,28 @@ double Terminal::get_blink_off_time() const
|
|||
return blink_off_time;
|
||||
}
|
||||
|
||||
void Terminal::clear() {
|
||||
// Resize the terminal to a single row, forcing content above in to the scrollback buffer.
|
||||
tsm_screen_resize(screen, cols, 1);
|
||||
void Terminal::clear()
|
||||
{
|
||||
// Resize the terminal to a single row, forcing content above in to the scrollback buffer.
|
||||
tsm_screen_resize(screen, cols, 1);
|
||||
|
||||
// Clear the scrollback buffer (hence clearing the content that was above).
|
||||
tsm_screen_clear_sb(screen);
|
||||
// Clear the scrollback buffer (hence clearing the content that was above).
|
||||
tsm_screen_clear_sb(screen);
|
||||
|
||||
// Resize the screen to its original size.
|
||||
tsm_screen_resize(screen, cols, rows);
|
||||
// Resize the screen to its original size.
|
||||
tsm_screen_resize(screen, cols, rows);
|
||||
|
||||
refresh();
|
||||
refresh();
|
||||
}
|
||||
|
||||
String Terminal::_copy_screen(ScreenCopyFunction func) {
|
||||
String Terminal::_copy_screen(ScreenCopyFunction func)
|
||||
{
|
||||
char *out;
|
||||
PackedByteArray data;
|
||||
|
||||
data.resize(std::max(func(screen, &out), 0));
|
||||
if (data.size() > 0) {
|
||||
if (data.size() > 0)
|
||||
{
|
||||
memcpy(data.ptrw(), out, data.size());
|
||||
std::free(out);
|
||||
}
|
||||
|
@ -721,16 +756,20 @@ String Terminal::_copy_screen(ScreenCopyFunction func) {
|
|||
return data.get_string_from_utf8();
|
||||
}
|
||||
|
||||
void Terminal::select(const int p_from_line, const int p_from_column, const int p_to_line, const int p_to_column) {
|
||||
void Terminal::select(const int p_from_line, const int p_from_column, const int p_to_line, const int p_to_column)
|
||||
{
|
||||
int from_line = std::clamp((int)p_from_line, 0, (int)rows);
|
||||
int from_column = std::clamp((int)p_from_column, 0, (int)cols);
|
||||
int to_line = std::clamp((int)p_to_line, 0, (int)rows);
|
||||
int to_column = std::clamp((int)p_to_column, 0, (int)cols);
|
||||
|
||||
if (from_line > to_line) {
|
||||
if (from_line > to_line)
|
||||
{
|
||||
std::swap(to_line, from_line);
|
||||
std::swap(to_column, from_column);
|
||||
} else if ((from_line == to_line) && (from_column > to_column)) {
|
||||
}
|
||||
else if ((from_line == to_line) && (from_column > to_column))
|
||||
{
|
||||
std::swap(to_column, from_column);
|
||||
}
|
||||
|
||||
|
@ -742,34 +781,40 @@ void Terminal::select(const int p_from_line, const int p_from_column, const int
|
|||
|
||||
String selection = copy_selection();
|
||||
|
||||
#if defined(__linux__)
|
||||
#if defined(__linux__)
|
||||
if (copy_on_selection)
|
||||
DisplayServer::get_singleton()->clipboard_set_primary(selection);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (selection.length() > 0) {
|
||||
if (selection.length() > 0)
|
||||
{
|
||||
selecting = true;
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
String Terminal::copy_all() {
|
||||
String Terminal::copy_all()
|
||||
{
|
||||
return _copy_screen(&tsm_screen_copy_all);
|
||||
}
|
||||
|
||||
String Terminal::copy_selection() {
|
||||
String Terminal::copy_selection()
|
||||
{
|
||||
return _copy_screen(&tsm_screen_selection_copy);
|
||||
}
|
||||
|
||||
void Terminal::set_copy_on_selection(const bool p_enabled) {
|
||||
void Terminal::set_copy_on_selection(const bool p_enabled)
|
||||
{
|
||||
copy_on_selection = p_enabled;
|
||||
}
|
||||
|
||||
bool Terminal::get_copy_on_selection() const {
|
||||
bool Terminal::get_copy_on_selection() const
|
||||
{
|
||||
return copy_on_selection;
|
||||
}
|
||||
|
||||
void Terminal::set_inverse_mode(const int mode) {
|
||||
void Terminal::set_inverse_mode(const int mode)
|
||||
{
|
||||
inverse_mode = static_cast<InverseMode>(mode);
|
||||
|
||||
bool inverse_enabled = inverse_mode == InverseMode::INVERSE_MODE_INVERT;
|
||||
|
@ -778,11 +823,13 @@ void Terminal::set_inverse_mode(const int mode) {
|
|||
refresh();
|
||||
}
|
||||
|
||||
int Terminal::get_inverse_mode() const {
|
||||
int Terminal::get_inverse_mode() const
|
||||
{
|
||||
return static_cast<int>(inverse_mode);
|
||||
}
|
||||
|
||||
void Terminal::initialize_input() {
|
||||
void Terminal::initialize_input()
|
||||
{
|
||||
selecting = false;
|
||||
selection_mode = SelectionMode::NONE;
|
||||
selection_timer = memnew(Timer);
|
||||
|
@ -791,129 +838,144 @@ void Terminal::initialize_input() {
|
|||
add_child(selection_timer, false, INTERNAL_MODE_FRONT);
|
||||
}
|
||||
|
||||
void Terminal::_handle_key_input(Ref<InputEventKey> event) {
|
||||
if (!event.is_valid() || !event->is_pressed())
|
||||
return;
|
||||
void Terminal::_handle_key_input(Ref<InputEventKey> event)
|
||||
{
|
||||
if (!event.is_valid() || !event->is_pressed())
|
||||
return;
|
||||
|
||||
const Key keycode = event->get_keycode();
|
||||
char32_t unicode = event->get_unicode();
|
||||
uint32_t ascii = unicode <= 127 ? unicode : 0;
|
||||
const Key keycode = event->get_keycode();
|
||||
char32_t unicode = event->get_unicode();
|
||||
uint32_t ascii = unicode <= 127 ? unicode : 0;
|
||||
|
||||
unsigned int mods = 0;
|
||||
if (event->is_alt_pressed())
|
||||
mods |= TSM_SHIFT_MASK;
|
||||
if (event->is_ctrl_pressed())
|
||||
mods |= TSM_CONTROL_MASK;
|
||||
if (event->is_shift_pressed())
|
||||
mods |= TSM_SHIFT_MASK;
|
||||
unsigned int mods = 0;
|
||||
if (event->is_alt_pressed())
|
||||
mods |= TSM_ALT_MASK;
|
||||
if (event->is_ctrl_pressed())
|
||||
mods |= TSM_CONTROL_MASK;
|
||||
if (event->is_shift_pressed())
|
||||
mods |= TSM_SHIFT_MASK;
|
||||
|
||||
std::pair<Key, char32_t> key = {keycode, unicode};
|
||||
uint32_t keysym = (KEY_MAP.count(key) > 0) ? KEY_MAP.at(key) : XKB_KEY_NoSymbol;
|
||||
std::pair<Key, char32_t> key = {keycode, unicode};
|
||||
uint32_t keysym = (KEY_MAP.count(key) > 0) ? KEY_MAP.at(key) : XKB_KEY_NoSymbol;
|
||||
|
||||
last_input_event_key = event;
|
||||
tsm_vte_handle_keyboard(vte, keysym, ascii, mods, unicode ? unicode : TSM_VTE_INVALID);
|
||||
last_input_event_key = event;
|
||||
tsm_vte_handle_keyboard(vte, keysym, ascii, mods, unicode ? unicode : TSM_VTE_INVALID);
|
||||
|
||||
// Return to the bottom of the scrollback buffer if we scrolled up. Ignore
|
||||
// modifier keys pressed in isolation or if Ctrl+Shift modifier keys are
|
||||
// pressed.
|
||||
std::set<Key> mod_keys = {KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META};
|
||||
if (mod_keys.find(keycode) == mod_keys.end() &&
|
||||
!(event->is_ctrl_pressed() && event->is_shift_pressed())) {
|
||||
tsm_screen_sb_reset(screen);
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
// Prevent focus changing to other inputs when pressing Tab or Arrow keys.
|
||||
std::set<Key> tab_arrow_keys = {KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_TAB};
|
||||
if (tab_arrow_keys.find(keycode) != tab_arrow_keys.end())
|
||||
accept_event();
|
||||
}
|
||||
|
||||
void Terminal::_handle_mouse_wheel(Ref<InputEventMouseButton> event) {
|
||||
if (!event.is_valid() || !event->is_pressed())
|
||||
return;
|
||||
|
||||
void (*scroll_func)(tsm_screen *, unsigned int) = nullptr;
|
||||
|
||||
switch (event->get_button_index()) {
|
||||
case MOUSE_BUTTON_WHEEL_UP:
|
||||
scroll_func = &tsm_screen_sb_up;
|
||||
break;
|
||||
case MOUSE_BUTTON_WHEEL_DOWN:
|
||||
scroll_func = &tsm_screen_sb_down;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
if (scroll_func != nullptr) {
|
||||
// Scroll 5 times as fast as normal if alt is pressed (like TextEdit).
|
||||
// Otherwise, just scroll 3 lines.
|
||||
int speed = event->is_alt_pressed() ? 15 : 3;
|
||||
double factor = event->get_factor();
|
||||
(*scroll_func)(screen, speed * factor);
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::_handle_selection(Ref<InputEventMouse> event) {
|
||||
if (!event.is_valid())
|
||||
return;
|
||||
|
||||
Ref<InputEventMouseButton> mb = event;
|
||||
if (mb.is_valid()) {
|
||||
if (!mb->is_pressed() || mb->get_button_index() != MOUSE_BUTTON_LEFT)
|
||||
return;
|
||||
|
||||
if (selecting) {
|
||||
selecting = false;
|
||||
selection_mode = SelectionMode::NONE;
|
||||
tsm_screen_selection_reset(screen);
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
selecting = false;
|
||||
selection_mode = SelectionMode::POINTER;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mm = event;
|
||||
if (mm.is_valid()) {
|
||||
if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) && selection_mode != SelectionMode::NONE && !selecting) {
|
||||
selecting = true;
|
||||
Vector2 start = event->get_position() / cell_size;
|
||||
tsm_screen_selection_start(screen, start.x, start.y);
|
||||
queue_redraw();
|
||||
selection_timer->start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::_on_selection_held() {
|
||||
if (!(Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) || selection_mode == SelectionMode::NONE) {
|
||||
#if defined(__linux__)
|
||||
if (copy_on_selection) {
|
||||
DisplayServer::get_singleton()->clipboard_set_primary(copy_selection());
|
||||
// Return to the bottom of the scrollback buffer if we scrolled up. Ignore
|
||||
// modifier keys pressed in isolation or if Ctrl+Shift modifier keys are
|
||||
// pressed.
|
||||
std::set<Key> mod_keys = {KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META};
|
||||
if (mod_keys.find(keycode) == mod_keys.end() &&
|
||||
!(event->is_ctrl_pressed() && event->is_shift_pressed()))
|
||||
{
|
||||
tsm_screen_sb_reset(screen);
|
||||
queue_redraw();
|
||||
}
|
||||
#endif
|
||||
|
||||
selection_timer->stop();
|
||||
return;
|
||||
}
|
||||
// Prevent focus changing to other inputs when pressing Tab or Arrow keys.
|
||||
std::set<Key> tab_arrow_keys = {KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_TAB};
|
||||
if (tab_arrow_keys.find(keycode) != tab_arrow_keys.end())
|
||||
accept_event();
|
||||
}
|
||||
|
||||
Vector2 target = get_local_mouse_position() / cell_size;
|
||||
tsm_screen_selection_target(screen, target.x, target.y);
|
||||
queue_redraw();
|
||||
selection_timer->start();
|
||||
void Terminal::_handle_mouse_wheel(Ref<InputEventMouseButton> event)
|
||||
{
|
||||
if (!event.is_valid() || !event->is_pressed())
|
||||
return;
|
||||
|
||||
void (*scroll_func)(tsm_screen *, unsigned int) = nullptr;
|
||||
|
||||
switch (event->get_button_index())
|
||||
{
|
||||
case MOUSE_BUTTON_WHEEL_UP:
|
||||
scroll_func = &tsm_screen_sb_up;
|
||||
break;
|
||||
case MOUSE_BUTTON_WHEEL_DOWN:
|
||||
scroll_func = &tsm_screen_sb_down;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
if (scroll_func != nullptr)
|
||||
{
|
||||
// Scroll 5 times as fast as normal if alt is pressed (like TextEdit).
|
||||
// Otherwise, just scroll 3 lines.
|
||||
int speed = event->is_alt_pressed() ? 15 : 3;
|
||||
double factor = event->get_factor();
|
||||
(*scroll_func)(screen, speed *factor);
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::_handle_selection(Ref<InputEventMouse> event)
|
||||
{
|
||||
if (!event.is_valid())
|
||||
return;
|
||||
|
||||
Ref<InputEventMouseButton> mb = event;
|
||||
if (mb.is_valid())
|
||||
{
|
||||
if (!mb->is_pressed() || mb->get_button_index() != MOUSE_BUTTON_LEFT)
|
||||
return;
|
||||
|
||||
if (selecting)
|
||||
{
|
||||
selecting = false;
|
||||
selection_mode = SelectionMode::NONE;
|
||||
tsm_screen_selection_reset(screen);
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
selecting = false;
|
||||
selection_mode = SelectionMode::POINTER;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mm = event;
|
||||
if (mm.is_valid())
|
||||
{
|
||||
if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) && selection_mode != SelectionMode::NONE && !selecting)
|
||||
{
|
||||
selecting = true;
|
||||
Vector2 start = event->get_position() / cell_size;
|
||||
tsm_screen_selection_start(screen, start.x, start.y);
|
||||
queue_redraw();
|
||||
selection_timer->start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::_on_selection_held()
|
||||
{
|
||||
if (!(Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) || selection_mode == SelectionMode::NONE)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
if (copy_on_selection)
|
||||
{
|
||||
DisplayServer::get_singleton()->clipboard_set_primary(copy_selection());
|
||||
}
|
||||
#endif
|
||||
|
||||
selection_timer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 target = get_local_mouse_position() / cell_size;
|
||||
tsm_screen_selection_target(screen, target.x, target.y);
|
||||
queue_redraw();
|
||||
selection_timer->start();
|
||||
}
|
||||
|
||||
// Add default theme items for the "Terminal" theme type if they don't exist.
|
||||
// These defaults match Godot's built-in default theme (note: this is different from the default editor theme).
|
||||
void Terminal::set_default_theme_items() {
|
||||
void Terminal::set_default_theme_items()
|
||||
{
|
||||
Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme();
|
||||
if (default_theme->get_type_list().has("Terminal")) return;
|
||||
if (default_theme->get_type_list().has("Terminal"))
|
||||
return;
|
||||
|
||||
// As a workaround, create a new theme and then merge it with the default theme at the end.
|
||||
// See: https://github.com/godotengine/godot-cpp/issues/1332#issuecomment-2041060614.
|
||||
|
@ -927,16 +989,16 @@ void Terminal::set_default_theme_items() {
|
|||
|
||||
// Default ANSI colors based on xterm defaults: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors.
|
||||
// Some discussion about the slight difference of the blue colors: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=241717.
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_0_color" , "Terminal", Color::hex(0x000000FF)); // Black
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_1_color" , "Terminal", Color::hex(0xCD0000FF)); // Red
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_2_color" , "Terminal", Color::hex(0x00CD00FF)); // Green
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_3_color" , "Terminal", Color::hex(0xCDCD00FF)); // Yellow
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_4_color" , "Terminal", Color::hex(0x0000EEFF)); // Blue
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_5_color" , "Terminal", Color::hex(0xCD00CDFF)); // Magenta
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_6_color" , "Terminal", Color::hex(0x00CDCDFF)); // Cyan
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_7_color" , "Terminal", Color::hex(0xE5E5E5FF)); // White
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_8_color" , "Terminal", Color::hex(0x7F7F7FFF)); // Bright Black (Gray)
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_9_color" , "Terminal", Color::hex(0xFF0000FF)); // Bright Red
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_0_color", "Terminal", Color::hex(0x000000FF)); // Black
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_1_color", "Terminal", Color::hex(0xCD0000FF)); // Red
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_2_color", "Terminal", Color::hex(0x00CD00FF)); // Green
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_3_color", "Terminal", Color::hex(0xCDCD00FF)); // Yellow
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_4_color", "Terminal", Color::hex(0x0000EEFF)); // Blue
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_5_color", "Terminal", Color::hex(0xCD00CDFF)); // Magenta
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_6_color", "Terminal", Color::hex(0x00CDCDFF)); // Cyan
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_7_color", "Terminal", Color::hex(0xE5E5E5FF)); // White
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_8_color", "Terminal", Color::hex(0x7F7F7FFF)); // Bright Black (Gray)
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_9_color", "Terminal", Color::hex(0xFF0000FF)); // Bright Red
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_10_color", "Terminal", Color::hex(0x00FF00FF)); // Bright Green
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_11_color", "Terminal", Color::hex(0xFFFF00FF)); // Bright Yellow
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_12_color", "Terminal", Color::hex(0x5C5CFFFF)); // Bright Blue
|
||||
|
@ -945,11 +1007,13 @@ void Terminal::set_default_theme_items() {
|
|||
custom_theme->set_theme_item(Theme::DATA_TYPE_COLOR, "ansi_15_color", "Terminal", Color::hex(0xFFFFFFFF)); // Bright White
|
||||
|
||||
// No monospaced font in the default theme, so try to import our own. Will default to a non-monospace font otherwise.
|
||||
ResourceLoader* rl = ResourceLoader::get_singleton();
|
||||
ResourceLoader *rl = ResourceLoader::get_singleton();
|
||||
String const font_path = "res://addons/godot_xterm/themes/fonts/regular.tres";
|
||||
if (rl->exists(font_path)) {
|
||||
if (rl->exists(font_path))
|
||||
{
|
||||
Ref<Font> default_font = rl->load(font_path);
|
||||
for (int i = FontType::NORMAL; i <= FontType::BOLD_ITALICS; i++) {
|
||||
for (int i = FontType::NORMAL; i <= FontType::BOLD_ITALICS; i++)
|
||||
{
|
||||
FontType type = static_cast<FontType>(i);
|
||||
custom_theme->set_theme_item(Theme::DATA_TYPE_FONT, FONT_TYPES[type], "Terminal", default_font);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ namespace godot
|
|||
private:
|
||||
typedef std::map<std::pair<Key, char32_t>, uint32_t> KeyMap;
|
||||
|
||||
enum FontType {
|
||||
enum FontType
|
||||
{
|
||||
NORMAL,
|
||||
BOLD,
|
||||
ITALICS,
|
||||
|
@ -37,6 +38,7 @@ namespace godot
|
|||
static const char *COLOR_NAMES[18];
|
||||
static const char *FONT_TYPES[4];
|
||||
static const KeyMap KEY_MAP;
|
||||
|
||||
public:
|
||||
enum AttrFlag
|
||||
{
|
||||
|
@ -45,7 +47,8 @@ namespace godot
|
|||
CURSOR = 1 << 2,
|
||||
};
|
||||
|
||||
enum InverseMode {
|
||||
enum InverseMode
|
||||
{
|
||||
INVERSE_MODE_INVERT,
|
||||
INVERSE_MODE_SWAP,
|
||||
};
|
||||
|
@ -89,6 +92,7 @@ namespace godot
|
|||
String write(const Variant data);
|
||||
|
||||
void _gui_input(const Ref<InputEvent> &event) override;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
|
@ -123,7 +127,7 @@ namespace godot
|
|||
// This can be useful in cases where the bell character is being written too
|
||||
// frequently such as `while true; do echo -e "\a"; done`.
|
||||
double bell_cooldown;
|
||||
Timer* bell_timer;
|
||||
Timer *bell_timer;
|
||||
static void _bell_cb(struct tsm_vte *vte, void *data);
|
||||
|
||||
static int _draw_cb(struct tsm_screen *con, uint64_t id, const uint32_t *ch,
|
||||
|
@ -184,14 +188,18 @@ namespace godot
|
|||
|
||||
void _handle_mouse_wheel(Ref<InputEventMouseButton> event);
|
||||
|
||||
enum SelectionMode { NONE, POINTER };
|
||||
enum SelectionMode
|
||||
{
|
||||
NONE,
|
||||
POINTER
|
||||
};
|
||||
bool selecting = false;
|
||||
SelectionMode selection_mode = SelectionMode::NONE;
|
||||
Timer *selection_timer;
|
||||
void _handle_selection(Ref<InputEventMouse> event);
|
||||
void _on_selection_held();
|
||||
|
||||
typedef std::function<int(struct tsm_screen*, char**)> ScreenCopyFunction;
|
||||
typedef std::function<int(struct tsm_screen *, char **)> ScreenCopyFunction;
|
||||
String _copy_screen(ScreenCopyFunction func);
|
||||
|
||||
void set_default_theme_items();
|
||||
|
|
58
addons/godot_xterm/themes/default_green.tres
Normal file
58
addons/godot_xterm/themes/default_green.tres
Normal file
|
@ -0,0 +1,58 @@
|
|||
[gd_resource type="Theme" load_steps=4 format=3 uid="uid://dtpro3m7sdgvg"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/godot_xterm/resources/xrdb_theme.gd" id="1"]
|
||||
[ext_resource type="FontFile" uid="uid://c51gnbjamppg" path="res://addons/godot_xterm/themes/fonts/jet_brains_mono/jet_brains_mono_nl-regular-2.304.ttf" id="2"]
|
||||
|
||||
[sub_resource type="FontFile" id="FontFile_tdf0u"]
|
||||
fallbacks = Array[Font]([ExtResource("2")])
|
||||
cache/0/16/0/ascent = 0.0
|
||||
cache/0/16/0/descent = 0.0
|
||||
cache/0/16/0/underline_position = 0.0
|
||||
cache/0/16/0/underline_thickness = 0.0
|
||||
cache/0/16/0/scale = 1.0
|
||||
cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0)
|
||||
cache/0/16/0/kerning_overrides/11/0 = Vector2(0, 0)
|
||||
cache/0/16/0/kerning_overrides/14/0 = Vector2(0, 0)
|
||||
cache/0/11/0/ascent = 0.0
|
||||
cache/0/11/0/descent = 0.0
|
||||
cache/0/11/0/underline_position = 0.0
|
||||
cache/0/11/0/underline_thickness = 0.0
|
||||
cache/0/11/0/scale = 1.0
|
||||
cache/0/11/0/kerning_overrides/16/0 = Vector2(0, 0)
|
||||
cache/0/11/0/kerning_overrides/11/0 = Vector2(0, 0)
|
||||
cache/0/11/0/kerning_overrides/14/0 = Vector2(0, 0)
|
||||
cache/0/14/0/ascent = 0.0
|
||||
cache/0/14/0/descent = 0.0
|
||||
cache/0/14/0/underline_position = 0.0
|
||||
cache/0/14/0/underline_thickness = 0.0
|
||||
cache/0/14/0/scale = 1.0
|
||||
cache/0/14/0/kerning_overrides/16/0 = Vector2(0, 0)
|
||||
cache/0/14/0/kerning_overrides/11/0 = Vector2(0, 0)
|
||||
cache/0/14/0/kerning_overrides/14/0 = Vector2(0, 0)
|
||||
|
||||
[resource]
|
||||
Terminal/colors/ansi_0_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_10_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_11_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_12_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_13_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_14_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_15_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_1_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_2_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_3_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_4_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_5_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_6_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_7_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_8_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/ansi_9_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/background_color = Color(0.156863, 0.156863, 0.156863, 1)
|
||||
Terminal/colors/cursor = Color(0, 1, 0.4, 1)
|
||||
Terminal/colors/foreground_color = Color(0, 1, 0.4, 1)
|
||||
Terminal/font_sizes/font_size = 14
|
||||
Terminal/fonts/bold_font = null
|
||||
Terminal/fonts/bold_italics_font = null
|
||||
Terminal/fonts/italics_font = null
|
||||
Terminal/fonts/normal_font = SubResource("FontFile_tdf0u")
|
||||
script = ExtResource("1")
|
26
addons/godot_xterm/themes/default_white.tres
Normal file
26
addons/godot_xterm/themes/default_white.tres
Normal file
|
@ -0,0 +1,26 @@
|
|||
[gd_resource type="Theme" load_steps=3 format=3 uid="uid://0gk8swmcldbg"]
|
||||
|
||||
[ext_resource type="FontVariation" uid="uid://vmgmcu8gc6nt" path="res://addons/godot_xterm/themes/fonts/regular.tres" id="1_aigbn"]
|
||||
[ext_resource type="StyleBox" uid="uid://cxaclm5pavuv6" path="res://themes/normal.stylebox" id="1_bj7pu"]
|
||||
|
||||
[resource]
|
||||
default_font = ExtResource("1_aigbn")
|
||||
Terminal/colors/ansi_0_color = Color(0, 0, 0, 1)
|
||||
Terminal/colors/ansi_10_color = Color(0.258824, 1, 0.760784, 1)
|
||||
Terminal/colors/ansi_11_color = Color(1, 0.929412, 0.631373, 1)
|
||||
Terminal/colors/ansi_12_color = Color(0.341176, 0.701961, 1, 1)
|
||||
Terminal/colors/ansi_13_color = Color(0.639216, 0.639216, 0.960784, 1)
|
||||
Terminal/colors/ansi_14_color = Color(0.4, 0.901961, 1, 1)
|
||||
Terminal/colors/ansi_15_color = Color(1, 1, 1, 1)
|
||||
Terminal/colors/ansi_1_color = Color(1, 0.470588, 0.419608, 1)
|
||||
Terminal/colors/ansi_2_color = Color(0.388235, 0.760784, 0.34902, 1)
|
||||
Terminal/colors/ansi_3_color = Color(0.980392, 0.890196, 0.270588, 1)
|
||||
Terminal/colors/ansi_4_color = Color(0.0784314, 0.490196, 0.980392, 1)
|
||||
Terminal/colors/ansi_5_color = Color(1, 0.54902, 0.8, 1)
|
||||
Terminal/colors/ansi_6_color = Color(0.560784, 1, 0.858824, 1)
|
||||
Terminal/colors/ansi_7_color = Color(0.803922, 0.811765, 0.823529, 0.501961)
|
||||
Terminal/colors/ansi_8_color = Color(0.211765, 0.239216, 0.290196, 1)
|
||||
Terminal/colors/ansi_9_color = Color(1, 0.439216, 0.521569, 1)
|
||||
Terminal/colors/foreground_color = Color(0.803922, 0.811765, 0.823529, 1)
|
||||
Terminal/font_sizes/font_size = 14
|
||||
Terminal/styles/normal = ExtResource("1_bj7pu")
|
|
@ -145,17 +145,6 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
|
|||
$Terminal.grab_focus()
|
||||
scene.queue_free()
|
||||
"Terminal":
|
||||
if OS.get_name() == "Windows":
|
||||
OS.call_deferred(
|
||||
"alert",
|
||||
(
|
||||
"Psuedoterminal node currently"
|
||||
+ " uses pty.h but needs to use either winpty or conpty"
|
||||
+ " to work on Windows."
|
||||
),
|
||||
"Terminal not Supported on Windows"
|
||||
)
|
||||
return
|
||||
var scene = item.scene.instantiate()
|
||||
var pty = scene if OS.has_feature("web") else scene.get_node("PTY")
|
||||
add_child(scene)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://brjrtf5fpptw8"]
|
||||
|
||||
[ext_resource type="Script" path="res://examples/menu/menu.gd" id="2"]
|
||||
[ext_resource type="Theme" uid="uid://0gk8swmcldbg" path="res://themes/demo.tres" id="2_7f2wl"]
|
||||
[ext_resource type="Theme" uid="uid://0gk8swmcldbg" path="res://themes/demo.tres" id="2_pr2sv"]
|
||||
|
||||
[node name="Menu" type="Control"]
|
||||
layout_mode = 3
|
||||
|
@ -19,4 +19,4 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("2_7f2wl")
|
||||
theme = ExtResource("2_pr2sv")
|
||||
|
|
|
@ -2,6 +2,5 @@ extends Terminal
|
|||
|
||||
@onready var pty = $PTY
|
||||
|
||||
|
||||
func _ready():
|
||||
pty.fork(OS.get_environment("SHELL"))
|
||||
pty.fork()
|
||||
|
|
|
@ -21,7 +21,7 @@ window/vsync/use_vsync=false
|
|||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/godot_xterm/plugin.cfg", "res://addons/zylann.editor_debugger/plugin.cfg")
|
||||
enabled=PackedStringArray("res://addons/godot_xterm/plugin.cfg")
|
||||
|
||||
[rendering]
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ func before_each():
|
|||
|
||||
|
||||
func test_bell() -> void:
|
||||
watch_signals(terminal)
|
||||
terminal.bell_cooldown = 0
|
||||
terminal.write(char(7))
|
||||
terminal.write(char(0x07))
|
||||
|
@ -42,8 +43,8 @@ class TestTheme:
|
|||
|
||||
const TestScene := preload("../scenes/theme.tscn")
|
||||
|
||||
const default_theme := preload("res://addons/godot_xterm/themes/default.tres")
|
||||
const alt_theme := preload("res://addons/godot_xterm/themes/default_light.tres")
|
||||
const default_theme := preload("res://addons/godot_xterm/themes/default_green.tres")
|
||||
const alt_theme := preload("res://addons/godot_xterm/themes/default_white.tres")
|
||||
|
||||
const COLORS := [
|
||||
"ansi_0_color",
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
extends "res://addons/gut/test.gd"
|
||||
|
||||
const EMPTY_VAR = "GODOT_XTERM_TEST_EMPTY_ENV_VAR"
|
||||
const TEST_VAR = "GODOT_XTERM_TEST_ENV_VAR"
|
||||
const TEST_VAL = "TEST123"
|
||||
|
||||
var env: Dictionary
|
||||
|
||||
|
||||
func before_each():
|
||||
OS.set_environment(EMPTY_VAR, "")
|
||||
OS.set_environment(TEST_VAR, TEST_VAL)
|
||||
env = LibuvUtils.get_os_environ()
|
||||
|
||||
|
||||
func test_has_empty_var():
|
||||
assert_has(env, EMPTY_VAR)
|
||||
|
||||
|
||||
func test_empty_var_has_empty_val():
|
||||
assert_eq(env[EMPTY_VAR], "")
|
||||
|
||||
|
||||
func test_has_test_var():
|
||||
assert_has(env, TEST_VAR)
|
||||
|
||||
|
||||
func test_test_var_has_correct_val():
|
||||
assert_eq(env[TEST_VAR], TEST_VAL)
|
|
@ -96,7 +96,7 @@ func test_emits_exit_code_on_failure():
|
|||
func test_emits_exited_on_kill():
|
||||
subject.call("fork", "yes")
|
||||
await wait_frames(1)
|
||||
subject.call_deferred("kill", PTY.SIGNAL_SIGKILL)
|
||||
subject.call_deferred("kill", PTY.IPCSIGNAL_SIGKILL)
|
||||
await wait_for_signal(subject.exited, 1)
|
||||
assert_signal_emitted(subject, "exited")
|
||||
|
||||
|
@ -104,9 +104,9 @@ func test_emits_exited_on_kill():
|
|||
func test_emits_exited_with_signal():
|
||||
subject.call("fork", "yes")
|
||||
await wait_frames(1)
|
||||
subject.call_deferred("kill", PTY.SIGNAL_SIGSEGV)
|
||||
subject.call_deferred("kill", PTY.IPCSIGNAL_SIGSEGV)
|
||||
await wait_for_signal(subject.exited, 1)
|
||||
assert_signal_emitted_with_parameters(subject, "exited", [0, PTY.SIGNAL_SIGSEGV])
|
||||
assert_signal_emitted_with_parameters(subject, "exited", [0, PTY.IPCSIGNAL_SIGSEGV])
|
||||
|
||||
|
||||
# Run the same tests, but with use_threads = false.
|
||||
|
@ -189,7 +189,7 @@ class TestPTYSize:
|
|||
await wait_for_signal(subject.data_received, 1)
|
||||
|
||||
func after_each():
|
||||
subject.call_deferred("kill", PTY.SIGNAL_SIGHUP)
|
||||
subject.call_deferred("kill", PTY.IPCSIGNAL_SIGHUP)
|
||||
await wait_for_signal(subject.exited, 1)
|
||||
|
||||
func test_pty_default_size():
|
||||
|
|
|
@ -64,18 +64,18 @@ class TestInterface:
|
|||
# Enums.
|
||||
|
||||
func test_has_enum_signal():
|
||||
assert_eq(described_class.SIGNAL_SIGHUP, 1)
|
||||
assert_eq(described_class.SIGNAL_SIGINT, 2)
|
||||
assert_eq(described_class.SIGNAL_SIGQUIT, 3)
|
||||
assert_eq(described_class.SIGNAL_SIGILL, 4)
|
||||
assert_eq(described_class.SIGNAL_SIGTRAP, 5)
|
||||
assert_eq(described_class.SIGNAL_SIGABRT, 6)
|
||||
assert_eq(described_class.SIGNAL_SIGFPE, 8)
|
||||
assert_eq(described_class.SIGNAL_SIGKILL, 9)
|
||||
assert_eq(described_class.SIGNAL_SIGSEGV, 11)
|
||||
assert_eq(described_class.SIGNAL_SIGPIPE, 13)
|
||||
assert_eq(described_class.SIGNAL_SIGALRM, 14)
|
||||
assert_eq(described_class.SIGNAL_SIGTERM, 15)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGHUP, 1)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGINT, 2)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGQUIT, 3)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGILL, 4)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGTRAP, 5)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGABRT, 6)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGFPE, 8)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGKILL, 9)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGSEGV, 11)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGPIPE, 13)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGALRM, 14)
|
||||
assert_eq(described_class.IPCSIGNAL_SIGTERM, 15)
|
||||
|
||||
## Other tests.
|
||||
|
||||
|
|
|
@ -1,23 +1,14 @@
|
|||
extends "res://addons/gut/test.gd"
|
||||
|
||||
|
||||
class MockPTY:
|
||||
extends "res://addons/godot_xterm/nodes/pty/pty_native.gd"
|
||||
|
||||
func write(data):
|
||||
emit_signal("data_received", data)
|
||||
|
||||
|
||||
class BaseTest:
|
||||
extends "res://addons/gut/test.gd"
|
||||
|
||||
var pty
|
||||
var mock_pty_native: MockPTY
|
||||
var mock_pty_native: PTY
|
||||
|
||||
func before_each():
|
||||
pty = add_child_autofree(PTY.new())
|
||||
mock_pty_native = autofree(MockPTY.new())
|
||||
pty._pty_native = mock_pty_native
|
||||
mock_pty_native = autofree(PTY.new())
|
||||
watch_signals(mock_pty_native)
|
||||
|
||||
|
||||
|
@ -70,33 +61,17 @@ class TestPTYInterfaceGodotXterm2_0_0:
|
|||
func test_has_signal_exited():
|
||||
assert_has_signal(pty, "exited")
|
||||
|
||||
# NOTE: This differs from the GodotXterm 2.x API which uses Signal rather than IPCSignal.
|
||||
# In Godot 4.x, enums are no longer dictionaries and thus need to be inspected individually.
|
||||
func test_has_enum_Signal():
|
||||
assert_true("IPCSignal" in pty, "Expected pty to have enum IPCSignal.")
|
||||
assert_typeof(pty.IPCSignal, typeof(Dictionary()))
|
||||
var signals = {
|
||||
SIGHUP = 1,
|
||||
SIGINT = 2,
|
||||
SIGQUIT = 3,
|
||||
SIGILL = 4,
|
||||
SIGTRAP = 5,
|
||||
SIGABRT = 6,
|
||||
SIGFPE = 8,
|
||||
SIGKILL = 9,
|
||||
SIGSEGV = 11,
|
||||
SIGPIPE = 13,
|
||||
SIGALRM = 14,
|
||||
SIGTERM = 15,
|
||||
}
|
||||
assert_gt(
|
||||
pty.IPCSignal.size(),
|
||||
signals.size() - 1,
|
||||
"Expected Signal enum to have at least %d members." % signals.size()
|
||||
)
|
||||
for signame in signals.keys():
|
||||
assert_has(pty.IPCSignal, signame, "Expected Signal enum to have member %s." % signame)
|
||||
assert_eq(
|
||||
pty.IPCSignal[signame],
|
||||
signals[signame],
|
||||
"Expected Signal enum member %s to have value %d." % [signame, signals[signame]]
|
||||
)
|
||||
assert_eq(pty.IPCSIGNAL_SIGHUP, 1, "Expected pty to have IPCSIGNAL_SIGHUP.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGINT, 2, "Expected pty to have IPCSIGNAL_SIGINT.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGQUIT, 3, "Expected pty to have IPCSIGNAL_SIGQUIT.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGILL, 4, "Expected pty to have IPCSIGNAL_SIGILL.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGTRAP, 5, "Expected pty to have IPCSIGNAL_SIGTRAP.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGABRT, 6, "Expected pty to have IPCSIGNAL_SIGABRT.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGFPE, 8, "Expected pty to have IPCSIGNAL_SIGFPE.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGKILL, 9, "Expected pty to have IPCSIGNAL_SIGKILL.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGSEGV, 11, "Expected pty to have IPCSIGNAL_SIGSEGV.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGPIPE, 13, "Expected pty to have IPCSIGNAL_SIGPIPE.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGALRM, 14, "Expected pty to have IPCSIGNAL_SIGALRM.")
|
||||
assert_eq(pty.IPCSIGNAL_SIGTERM, 15, "Expected pty to have IPCSIGNAL_SIGTERM.")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue