mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-06-29 02:35: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"]
|
[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 = {
|
||||||
"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),
|
"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",
|
"format": "RGBA8",
|
||||||
|
@ -12,7 +12,7 @@ data = {
|
||||||
}
|
}
|
||||||
|
|
||||||
[sub_resource type="ImageTexture" id="ImageTexture_q1uu0"]
|
[sub_resource type="ImageTexture" id="ImageTexture_q1uu0"]
|
||||||
image = SubResource("Image_84jui")
|
image = SubResource("Image_law8x")
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_osmrc"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_osmrc"]
|
||||||
bg_color = Color(0.113329, 0.129458, 0.156802, 1)
|
bg_color = Color(0.113329, 0.129458, 0.156802, 1)
|
||||||
|
|
|
@ -28,7 +28,15 @@ sources.append([
|
||||||
if env['platform'] == 'linux' or env['platform'] == 'macos':
|
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:
|
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":
|
if env["platform"] == "macos":
|
||||||
library = env.SharedLibrary(
|
library = env.SharedLibrary(
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2020-2023 Leroy Hopson <godot-xterm@leroy.geek.nz>
|
# SPDX-FileCopyrightText: 2020-2023 Leroy Hopson <godot-xterm@leroy.geek.nz>
|
||||||
# SPDX-License-Identifier: MIT
|
# 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
|
set -e
|
||||||
|
|
||||||
# Parse args.
|
# Parse args.
|
||||||
|
@ -27,8 +35,10 @@ done
|
||||||
target=${target:-template_debug}
|
target=${target:-template_debug}
|
||||||
if [ "$target" == "template_debug" ]; then
|
if [ "$target" == "template_debug" ]; then
|
||||||
debug_symbols="yes"
|
debug_symbols="yes"
|
||||||
|
uv_target="debug"
|
||||||
else
|
else
|
||||||
debug_symbols="no"
|
debug_symbols="no"
|
||||||
|
uv_target="release"
|
||||||
fi
|
fi
|
||||||
nproc=$(nproc || sysctl -n hw.ncpu)
|
nproc=$(nproc || sysctl -n hw.ncpu)
|
||||||
|
|
||||||
|
@ -52,6 +62,7 @@ updateSubmodules() {
|
||||||
fi
|
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 LIBUV_DIR ${NATIVE_DIR}/thirdparty/libuv
|
||||||
updateSubmodules LIBTSM_DIR ${NATIVE_DIR}/thirdparty/libtsm
|
updateSubmodules LIBTSM_DIR ${NATIVE_DIR}/thirdparty/libtsm
|
||||||
updateSubmodules GODOT_CPP_DIR ${NATIVE_DIR}/thirdparty/godot-cpp
|
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}
|
cd ${LIBUV_DIR}
|
||||||
mkdir build || true
|
mkdir build || true
|
||||||
cd build
|
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)"
|
-DCMAKE_OSX_ARCHITECTURES=$(uname -m)"
|
||||||
if [ "$target" == "template_release" ]; then
|
if [ "$target" == "template_release" ]; then
|
||||||
args="$args -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL"
|
args="$args -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL"
|
||||||
else
|
else
|
||||||
args="$args -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL"
|
args="$args -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL"
|
||||||
fi
|
fi
|
||||||
cmake .. $args
|
cmake .. $args || fail
|
||||||
cd ..
|
cd ..
|
||||||
cmake --build build --config $target -j$nproc
|
cmake --build build --config $uv_target -j$nproc || fail
|
||||||
|
|
||||||
# Build libgodot-xterm.
|
# Build libgodot-xterm.
|
||||||
cd ${NATIVE_DIR}
|
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.
|
# Use Docker to build libgodot-xterm javascript.
|
||||||
#if [ -x "$(command -v docker-compose)" ]; then
|
#if [ -x "$(command -v docker-compose)" ]; then
|
||||||
# UID_GID="0:0" TARGET=$target docker-compose build javascript
|
# UID_GID="0:0" TARGET=$target docker-compose build javascript
|
||||||
# UID_GID="$(id -u):$(id -g)" TARGET=$target docker-compose run --rm javascript
|
# UID_GID="$(id -u):$(id -g)" TARGET=$target docker-compose run --rm javascript
|
||||||
#fi
|
#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},
|
||||||
{{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_0, '0'}, XKB_KEY_0},
|
||||||
{{KEY_1, '1'}, XKB_KEY_1},
|
{{KEY_1, '1'}, XKB_KEY_1},
|
||||||
{{KEY_2, '2'}, XKB_KEY_2},
|
{{KEY_2, '2'}, XKB_KEY_2},
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
#if (defined(__linux__) || defined(__APPLE__)) && !defined(_PTY_DISABLED)
|
#if (defined(__linux__) || defined(__APPLE__)) && !defined(_PTY_DISABLED)
|
||||||
#include "pty_unix.h"
|
#include "pty_unix.h"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#elif defined(_WIN32) && !defined(_PTY_DISABLED)
|
||||||
|
#include "pty_win.h"
|
||||||
|
#include <io.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Require buffer to be flushed after reaching this size.
|
// Require buffer to be flushed after reaching this size.
|
||||||
|
@ -30,19 +33,20 @@ 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 _write_cb(uv_write_t *req, int status) { std::free(req); }
|
||||||
void _close_cb(uv_handle_t *handle) { /* no-op */ };
|
void _close_cb(uv_handle_t *handle) { /* no-op */ };
|
||||||
|
|
||||||
void PTY::_bind_methods() {
|
void PTY::_bind_methods()
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGHUP);
|
{
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGINT);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGHUP);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGQUIT);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGINT);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGILL);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGQUIT);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGTRAP);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGILL);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGABRT);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGTRAP);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGFPE);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGABRT);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGKILL);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGFPE);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGSEGV);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGKILL);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGPIPE);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGSEGV);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGALRM);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGPIPE);
|
||||||
BIND_ENUM_CONSTANT(SIGNAL_SIGTERM);
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGALRM);
|
||||||
|
BIND_ENUM_CONSTANT(IPCSIGNAL_SIGTERM);
|
||||||
|
|
||||||
ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
|
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")));
|
ADD_SIGNAL(MethodInfo("exited", PropertyInfo(Variant::INT, "exit_code"), PropertyInfo(Variant::INT, "signal_code")));
|
||||||
|
@ -83,7 +87,8 @@ void PTY::_bind_methods() {
|
||||||
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;
|
use_threads = true;
|
||||||
|
|
||||||
set_process_internal(false);
|
set_process_internal(false);
|
||||||
|
@ -94,76 +99,92 @@ PTY::PTY() {
|
||||||
env["TERM"] = "xterm-256color";
|
env["TERM"] = "xterm-256color";
|
||||||
env["COLORTERM"] = "truecolor";
|
env["COLORTERM"] = "truecolor";
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
uv_loop_init(&loop);
|
uv_loop_init(&loop);
|
||||||
uv_async_init(&loop, &async_handle, [](uv_async_t *handle) {});
|
uv_async_init(&loop, &async_handle, [](uv_async_t *handle) {});
|
||||||
uv_pipe_init(&loop, &pipe, false);
|
uv_pipe_init(&loop, &pipe, false);
|
||||||
|
#ifdef _WIN32
|
||||||
|
uv_pipe_init(&loop, &pipe_out, false);
|
||||||
|
#endif
|
||||||
pipe.data = this;
|
pipe.data = this;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::set_cols(const int num_cols) {
|
void PTY::set_cols(const int num_cols)
|
||||||
if (cols != num_cols) {
|
{
|
||||||
|
if (cols != num_cols)
|
||||||
|
{
|
||||||
cols = num_cols;
|
cols = num_cols;
|
||||||
resize(cols, rows);
|
resize(cols, rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PTY::get_cols() const {
|
int PTY::get_cols() const
|
||||||
|
{
|
||||||
return cols;
|
return cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::set_rows(const int num_rows) {
|
void PTY::set_rows(const int num_rows)
|
||||||
if (rows != num_rows) {
|
{
|
||||||
|
if (rows != num_rows)
|
||||||
|
{
|
||||||
rows = num_rows;
|
rows = num_rows;
|
||||||
resize(cols, rows);
|
resize(cols, rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PTY::get_rows() const {
|
int PTY::get_rows() const
|
||||||
|
{
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary PTY::get_env() const {
|
Dictionary PTY::get_env() const
|
||||||
|
{
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::set_env(const Dictionary &value) {
|
void PTY::set_env(const Dictionary &value)
|
||||||
|
{
|
||||||
env = value;
|
env = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PTY::get_use_os_env() const {
|
bool PTY::get_use_os_env() const
|
||||||
|
{
|
||||||
return use_os_env;
|
return use_os_env;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::set_use_os_env(const bool value) {
|
void PTY::set_use_os_env(const bool value)
|
||||||
|
{
|
||||||
use_os_env = 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);
|
ERR_FAIL_COND(status != STATUS_CLOSED);
|
||||||
use_threads = p_use;
|
use_threads = p_use;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PTY::is_using_threads() const {
|
bool PTY::is_using_threads() const
|
||||||
|
{
|
||||||
return use_threads;
|
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;
|
terminal_path = p_terminal_path;
|
||||||
|
|
||||||
Callable write = Callable(this, "write");
|
Callable write = Callable(this, "write");
|
||||||
Callable resizev = Callable(this, "resizev");
|
Callable resizev = Callable(this, "resizev");
|
||||||
|
|
||||||
// Disconnect the current terminal, if any.
|
// Disconnect the current terminal, if any.
|
||||||
if (terminal != nullptr) {
|
if (terminal != nullptr)
|
||||||
|
{
|
||||||
disconnect("data_received", Callable(terminal, "write"));
|
disconnect("data_received", Callable(terminal, "write"));
|
||||||
terminal->disconnect("data_sent", write);
|
terminal->disconnect("data_sent", write);
|
||||||
terminal->disconnect("size_changed", resizev);
|
terminal->disconnect("size_changed", resizev);
|
||||||
}
|
}
|
||||||
|
|
||||||
terminal = Object::cast_to<Terminal>(get_node_or_null(terminal_path));
|
terminal = Object::cast_to<Terminal>(get_node_or_null(terminal_path));
|
||||||
if (terminal == nullptr) return;
|
if (terminal == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
// Connect the new terminal.
|
// Connect the new terminal.
|
||||||
resize(terminal->get_cols(), terminal->get_rows());
|
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);
|
connect("data_received", Callable(terminal, "write"), CONNECT_PERSIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
NodePath PTY::get_terminal_path() const {
|
NodePath PTY::get_terminal_path() const
|
||||||
|
{
|
||||||
return terminal_path;
|
return terminal_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
String PTY::get_pts_name() const {
|
String PTY::get_pts_name() const
|
||||||
|
{
|
||||||
return pts_name;
|
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);
|
String fork_file = _get_fork_file(file);
|
||||||
Dictionary fork_env = _get_fork_env();
|
Dictionary fork_env = _get_fork_env();
|
||||||
Dictionary result;
|
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");
|
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"));
|
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"]);
|
Error err = static_cast<Error>((int)result["error"]);
|
||||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to fork.");
|
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"];
|
fd = result["fd"];
|
||||||
pid = result["pid"];
|
pid = result["pid"];
|
||||||
pts_name = result["pty"];
|
pts_name = result["pty"];
|
||||||
|
#ifdef _WIN32
|
||||||
|
fd_out = result["fd_out"];
|
||||||
|
hpc = result["hpc"];
|
||||||
|
#endif
|
||||||
|
|
||||||
status = STATUS_OPEN;
|
status = STATUS_OPEN;
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
_pipe_open(fd, &pipe);
|
||||||
_pipe_open(fd);
|
#ifdef _WIN32
|
||||||
uv_read_start((uv_stream_t *)&pipe, _alloc_buffer, _read_cb);
|
_pipe_open(fd_out, &pipe_out);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (use_threads) {
|
uv_read_start((uv_stream_t *)&pipe, _alloc_buffer, _read_cb);
|
||||||
|
|
||||||
|
if (use_threads)
|
||||||
|
{
|
||||||
stop_thread.clear();
|
stop_thread.clear();
|
||||||
thread->start(callable_mp(this, &PTY::_thread_func));
|
thread->start(callable_mp(this, &PTY::_thread_func));
|
||||||
}
|
}
|
||||||
|
@ -216,20 +252,27 @@ Error PTY::fork(const String &file, const PackedStringArray &args, const String
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::kill(const int signal) {
|
void PTY::kill(const int signal)
|
||||||
#if (defined(__linux__) || defined(__APPLE__)) && !defined(_PTY_DISABLED)
|
{
|
||||||
if (pid > 0) {
|
#if !defined(_PTY_DISABLED)
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
uv_kill(pid, signal);
|
uv_kill(pid, signal);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Error PTY::open(const int cols, const int rows) {
|
Error PTY::open(const int cols, const int rows)
|
||||||
|
{
|
||||||
Dictionary result;
|
Dictionary result;
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
result = PTYUnix::open(cols, rows);
|
result = PTYUnix::open(cols, rows);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
result = PTYWin::open(cols, rows);
|
||||||
|
#endif
|
||||||
|
|
||||||
Error err = static_cast<Error>((int)result["error"]);
|
Error err = static_cast<Error>((int)result["error"]);
|
||||||
ERR_FAIL_COND_V(err != OK, err);
|
ERR_FAIL_COND_V(err != OK, err);
|
||||||
|
@ -240,18 +283,28 @@ Error PTY::open(const int cols, const int rows) {
|
||||||
return OK;
|
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;
|
cols = p_cols;
|
||||||
rows = p_rows;
|
rows = p_rows;
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
if (fd > -1) {
|
if (fd > -1)
|
||||||
|
{
|
||||||
PTYUnix::resize(fd, cols, rows);
|
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;
|
PackedByteArray bytes;
|
||||||
|
|
||||||
switch (data.get_type())
|
switch (data.get_type())
|
||||||
|
@ -266,30 +319,30 @@ void PTY::write(const Variant &data) const {
|
||||||
ERR_FAIL_MSG("Data must be a String or PackedByteArray.");
|
ERR_FAIL_MSG("Data must be a String or PackedByteArray.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == STATUS_OPEN) {
|
if (status == STATUS_OPEN)
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
{
|
||||||
uv_buf_t buf;
|
uv_buf_t buf;
|
||||||
buf.base = (char *)bytes.ptr();
|
buf.base = (char *)bytes.ptr();
|
||||||
buf.len = bytes.size();
|
buf.len = bytes.size();
|
||||||
uv_write_t *req = (uv_write_t *)malloc(sizeof(uv_write_t));
|
uv_write_t *req = (uv_write_t *)malloc(sizeof(uv_write_t));
|
||||||
req->data = (void *)buf.base;
|
req->data = (void *)buf.base;
|
||||||
uv_write(req, (uv_stream_t *)&pipe, &buf, 1, _write_cb);
|
uv_write(req, (uv_stream_t *)&pipe_out, &buf, 1, _write_cb);
|
||||||
uv_run((uv_loop_t*)&loop, UV_RUN_NOWAIT);
|
uv_run((uv_loop_t *)&loop, UV_RUN_NOWAIT);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::_notification(int p_what) {
|
void PTY::_notification(int p_what)
|
||||||
|
{
|
||||||
switch (p_what)
|
switch (p_what)
|
||||||
{
|
{
|
||||||
case NOTIFICATION_INTERNAL_PROCESS:
|
case NOTIFICATION_INTERNAL_PROCESS:
|
||||||
{
|
{
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
if (!use_threads)
|
||||||
if (!use_threads) uv_run(&loop, UV_RUN_NOWAIT);
|
uv_run(&loop, UV_RUN_NOWAIT);
|
||||||
#endif
|
|
||||||
|
|
||||||
buffer_write_mutex->lock();
|
buffer_write_mutex->lock();
|
||||||
if (buffer.size() > 0) {
|
if (buffer.size() > 0)
|
||||||
|
{
|
||||||
emit_signal("data_received", buffer);
|
emit_signal("data_received", buffer);
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
buffer_cleared->post();
|
buffer_cleared->post();
|
||||||
|
@ -304,46 +357,59 @@ void PTY::_notification(int p_what) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::_thread_func() {
|
void PTY::_thread_func()
|
||||||
while (!stop_thread.is_set()) {
|
{
|
||||||
if (buffer.size() < BUFFER_LIMIT) {
|
while (!stop_thread.is_set())
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
{
|
||||||
|
if (buffer.size() < BUFFER_LIMIT)
|
||||||
|
{
|
||||||
uv_run(&loop, UV_RUN_ONCE);
|
uv_run(&loop, UV_RUN_ONCE);
|
||||||
#endif
|
}
|
||||||
} else {
|
else
|
||||||
|
{
|
||||||
buffer_cleared->wait();
|
buffer_cleared->wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PTY::_close() {
|
void PTY::_close()
|
||||||
if (use_threads) {
|
{
|
||||||
if (thread->is_started()) {
|
if (use_threads)
|
||||||
|
{
|
||||||
|
if (thread->is_started())
|
||||||
|
{
|
||||||
stop_thread.set();
|
stop_thread.set();
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
uv_async_send(&async_handle);
|
uv_async_send(&async_handle);
|
||||||
#endif
|
|
||||||
thread->wait_to_finish();
|
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);
|
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);
|
uv_close((uv_handle_t *)&async_handle, _close_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == STATUS_OPEN) {
|
if (status == STATUS_OPEN)
|
||||||
|
{
|
||||||
uv_run(&loop, UV_RUN_NOWAIT);
|
uv_run(&loop, UV_RUN_NOWAIT);
|
||||||
uv_loop_close(&loop);
|
uv_loop_close(&loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fd > 0) close(fd);
|
#ifdef _WIN32
|
||||||
if (pid > 0) kill(SIGNAL_SIGHUP);
|
PTYWin::close(hpc, fd, fd_out);
|
||||||
#endif
|
fd_out = -1;
|
||||||
|
hpc = -1;
|
||||||
|
#else
|
||||||
|
if (fd > 0)
|
||||||
|
close(fd);
|
||||||
|
if (pid > 0)
|
||||||
|
kill(IPCSIGNAL_SIGHUP);
|
||||||
|
#endif
|
||||||
|
|
||||||
fd = -1;
|
fd = -1;
|
||||||
pid = -1;
|
pid = -1;
|
||||||
|
@ -352,38 +418,46 @@ void PTY::_close() {
|
||||||
status = STATUS_CLOSED;
|
status = STATUS_CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
String PTY::_get_fork_file(const String &file) const {
|
String PTY::_get_fork_file(const String &file) const
|
||||||
if (!file.is_empty()) return file;
|
{
|
||||||
|
if (!file.is_empty())
|
||||||
|
return file;
|
||||||
|
|
||||||
String shell_env = OS::get_singleton()->get_environment("SHELL");
|
String shell_env = OS::get_singleton()->get_environment("SHELL");
|
||||||
if (!shell_env.is_empty()) {
|
if (!shell_env.is_empty())
|
||||||
|
{
|
||||||
return shell_env;
|
return shell_env;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
return "sh";
|
return "sh";
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
return "zsh";
|
return "zsh";
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
return "cmd.exe";
|
return "cmd.exe";
|
||||||
#else
|
#else
|
||||||
return "";
|
return "";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary PTY::_get_fork_env() const {
|
Dictionary PTY::_get_fork_env() const
|
||||||
if (!use_os_env) return env;
|
{
|
||||||
|
if (!use_os_env)
|
||||||
#if defined(_PTY_DISABLED)
|
|
||||||
return env;
|
return env;
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#if defined(_PTY_DISABLED)
|
||||||
|
return env;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// TODO This might need windows specific adjustment
|
||||||
|
return env;
|
||||||
Dictionary os_env;
|
Dictionary os_env;
|
||||||
uv_env_item_t *uv_env;
|
uv_env_item_t *uv_env;
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
uv_os_environ(&uv_env, &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;
|
os_env[uv_env[i].name] = uv_env[i].value;
|
||||||
}
|
}
|
||||||
uv_os_free_environ(uv_env, count);
|
uv_os_free_environ(uv_env, count);
|
||||||
|
@ -405,7 +479,8 @@ Dictionary PTY::_get_fork_env() const {
|
||||||
|
|
||||||
// Merge in our custom environment.
|
// Merge in our custom environment.
|
||||||
PackedStringArray keys = PackedStringArray(env.keys());
|
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];
|
String key = keys[i];
|
||||||
os_env[key] = env[key];
|
os_env[key] = env[key];
|
||||||
}
|
}
|
||||||
|
@ -413,11 +488,13 @@ Dictionary PTY::_get_fork_env() const {
|
||||||
return os_env;
|
return os_env;
|
||||||
}
|
}
|
||||||
|
|
||||||
PackedStringArray PTY::_parse_env(const Dictionary &env) const {
|
PackedStringArray PTY::_parse_env(const Dictionary &env) const
|
||||||
|
{
|
||||||
PackedStringArray parsed_env;
|
PackedStringArray parsed_env;
|
||||||
PackedStringArray keys = PackedStringArray(env.keys());
|
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];
|
String key = keys[i];
|
||||||
parsed_env.push_back(key + "=" + String(env[key]));
|
parsed_env.push_back(key + "=" + String(env[key]));
|
||||||
}
|
}
|
||||||
|
@ -425,22 +502,25 @@ PackedStringArray PTY::_parse_env(const Dictionary &env) const {
|
||||||
return parsed_env;
|
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);
|
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)
|
||||||
|
{
|
||||||
void _alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
|
||||||
buf->base = (char *)malloc(suggested_size);
|
buf->base = (char *)malloc(suggested_size);
|
||||||
buf->len = 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);
|
PTY *pty = static_cast<PTY *>(pipe->data);
|
||||||
|
|
||||||
if (nread < 0) {
|
if (nread < 0)
|
||||||
switch (nread) {
|
{
|
||||||
|
switch (nread)
|
||||||
|
{
|
||||||
case UV_EOF:
|
case UV_EOF:
|
||||||
// Normal after shell exits.
|
// Normal after shell exits.
|
||||||
case UV_EIO:
|
case UV_EIO:
|
||||||
|
@ -455,7 +535,8 @@ void PTY::_read_cb(uv_stream_t *pipe, ssize_t nread, const uv_buf_t *buf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nread > 0) {
|
if (nread > 0)
|
||||||
|
{
|
||||||
MutexLock lock(*pty->buffer_write_mutex.ptr());
|
MutexLock lock(*pty->buffer_write_mutex.ptr());
|
||||||
|
|
||||||
int old_size = pty->buffer.size();
|
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_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_pipe_open(&pipe, fd));
|
ERR_FAIL_UV_ERR(uv_stream_set_blocking((uv_stream_t *)pipe, false));
|
||||||
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));
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -19,22 +19,24 @@ namespace godot
|
||||||
GDCLASS(PTY, Node)
|
GDCLASS(PTY, Node)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Signal {
|
enum IPCSignal
|
||||||
SIGNAL_SIGHUP = 1,
|
{
|
||||||
SIGNAL_SIGINT = 2,
|
IPCSIGNAL_SIGHUP = 1,
|
||||||
SIGNAL_SIGQUIT = 3,
|
IPCSIGNAL_SIGINT = 2,
|
||||||
SIGNAL_SIGILL = 4,
|
IPCSIGNAL_SIGQUIT = 3,
|
||||||
SIGNAL_SIGTRAP = 5,
|
IPCSIGNAL_SIGILL = 4,
|
||||||
SIGNAL_SIGABRT = 6,
|
IPCSIGNAL_SIGTRAP = 5,
|
||||||
SIGNAL_SIGFPE = 8,
|
IPCSIGNAL_SIGABRT = 6,
|
||||||
SIGNAL_SIGKILL = 9,
|
IPCSIGNAL_SIGFPE = 8,
|
||||||
SIGNAL_SIGSEGV = 11,
|
IPCSIGNAL_SIGKILL = 9,
|
||||||
SIGNAL_SIGPIPE = 13,
|
IPCSIGNAL_SIGSEGV = 11,
|
||||||
SIGNAL_SIGALRM = 14,
|
IPCSIGNAL_SIGPIPE = 13,
|
||||||
SIGNAL_SIGTERM = 15,
|
IPCSIGNAL_SIGALRM = 14,
|
||||||
|
IPCSIGNAL_SIGTERM = 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Status {
|
enum Status
|
||||||
|
{
|
||||||
STATUS_CLOSED,
|
STATUS_CLOSED,
|
||||||
STATUS_OPEN,
|
STATUS_OPEN,
|
||||||
STATUS_PAUSED,
|
STATUS_PAUSED,
|
||||||
|
@ -65,7 +67,7 @@ namespace godot
|
||||||
String get_pts_name() const;
|
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);
|
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);
|
Error open(const int cols = 80, const int rows = 24);
|
||||||
void resize(const int cols, const int rows);
|
void resize(const int cols, const int rows);
|
||||||
void resizev(const Vector2i &size) { resize(size.x, size.y); };
|
void resizev(const Vector2i &size) { resize(size.x, size.y); };
|
||||||
|
@ -79,6 +81,10 @@ namespace godot
|
||||||
private:
|
private:
|
||||||
int pid = -1;
|
int pid = -1;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
#ifdef _WIN32
|
||||||
|
int fd_out = -1;
|
||||||
|
int64_t hpc = -1; // pseudoconsole handle
|
||||||
|
#endif
|
||||||
|
|
||||||
unsigned int cols = 80;
|
unsigned int cols = 80;
|
||||||
unsigned int rows = 24;
|
unsigned int rows = 24;
|
||||||
|
@ -106,15 +112,17 @@ namespace godot
|
||||||
bool use_threads;
|
bool use_threads;
|
||||||
void _thread_func();
|
void _thread_func();
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
uv_loop_t loop;
|
uv_loop_t loop;
|
||||||
uv_pipe_t pipe;
|
uv_pipe_t pipe;
|
||||||
Error _pipe_open(const int fd);
|
#ifdef _WIN32
|
||||||
#endif
|
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 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);
|
static Error uv_err_to_godot_err(const int uv_err);
|
||||||
};
|
};
|
||||||
} // namespace godot
|
} // namespace godot
|
||||||
|
|
||||||
VARIANT_ENUM_CAST(PTY::Signal);
|
VARIANT_ENUM_CAST(PTY::IPCSignal);
|
||||||
|
|
|
@ -82,7 +82,7 @@
|
||||||
|
|
||||||
/* macOS 10.14 back does not define this constant */
|
/* macOS 10.14 back does not define this constant */
|
||||||
#ifndef POSIX_SPAWN_SETSID
|
#ifndef POSIX_SPAWN_SETSID
|
||||||
#define POSIX_SPAWN_SETSID 1024
|
#define POSIX_SPAWN_SETSID 1024
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* environ for execvpe */
|
/* environ for execvpe */
|
||||||
|
@ -91,19 +91,21 @@ extern char **environ;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
extern "C" {
|
extern "C"
|
||||||
// Changes the current thread's directory to a path or directory file
|
{
|
||||||
// descriptor. libpthread only exposes a syscall wrapper starting in
|
// Changes the current thread's directory to a path or directory file
|
||||||
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
|
// descriptor. libpthread only exposes a syscall wrapper starting in
|
||||||
// the syscall is issued directly.
|
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
|
||||||
int pthread_chdir_np(const char* dir) API_AVAILABLE(macosx(10.12));
|
// the syscall is issued directly.
|
||||||
int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
|
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) ({ \
|
#define HANDLE_EINTR(x) ({ \
|
||||||
int eintr_wrapper_counter = 0; \
|
int eintr_wrapper_counter = 0; \
|
||||||
decltype(x) eintr_wrapper_result; \
|
decltype(x) eintr_wrapper_result; \
|
||||||
do { \
|
do \
|
||||||
|
{ \
|
||||||
eintr_wrapper_result = (x); \
|
eintr_wrapper_result = (x); \
|
||||||
} while (eintr_wrapper_result == -1 && errno == EINTR && \
|
} while (eintr_wrapper_result == -1 && errno == EINTR && \
|
||||||
eintr_wrapper_counter++ < 100); \
|
eintr_wrapper_counter++ < 100); \
|
||||||
|
@ -113,7 +115,8 @@ int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
|
||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
static void await_exit(Callable cb, pid_t pid) {
|
static void await_exit(Callable cb, pid_t pid)
|
||||||
|
{
|
||||||
int ret;
|
int ret;
|
||||||
int stat_loc;
|
int stat_loc;
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
|
@ -123,8 +126,10 @@ static void await_exit(Callable cb, pid_t pid) {
|
||||||
struct kevent change = {0};
|
struct kevent change = {0};
|
||||||
EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
|
EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
|
||||||
ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));
|
ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));
|
||||||
if (ret == -1) {
|
if (ret == -1)
|
||||||
if (errno == ESRCH) {
|
{
|
||||||
|
if (errno == ESRCH)
|
||||||
|
{
|
||||||
// At this point, one of the following has occurred:
|
// At this point, one of the following has occurred:
|
||||||
// 1. The process has died but has not yet been reaped.
|
// 1. The process has died but has not yet been reaped.
|
||||||
// 2. The process has died and has already 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
|
// kqueueable, but it may not be waitable yet either. Mark calls
|
||||||
// this case the "zombie death race".
|
// this case the "zombie death race".
|
||||||
ret = HANDLE_EINTR(waitpid(pid, &stat_loc, WNOHANG));
|
ret = HANDLE_EINTR(waitpid(pid, &stat_loc, WNOHANG));
|
||||||
if (ret == 0) {
|
if (ret == 0)
|
||||||
|
{
|
||||||
ret = kill(pid, SIGKILL);
|
ret = kill(pid, SIGKILL);
|
||||||
if (ret != -1) {
|
if (ret != -1)
|
||||||
|
{
|
||||||
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
struct kevent event = {0};
|
struct kevent event = {0};
|
||||||
ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));
|
ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));
|
||||||
if (ret == 1) {
|
if (ret == 1)
|
||||||
|
{
|
||||||
if ((event.fflags & NOTE_EXIT) &&
|
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
|
// The process is dead or dying. This won't block for long, if at
|
||||||
// all.
|
// all.
|
||||||
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
||||||
|
@ -152,16 +163,22 @@ static void await_exit(Callable cb, pid_t pid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
while (true) {
|
while (true)
|
||||||
|
{
|
||||||
errno = 0;
|
errno = 0;
|
||||||
if ((ret = waitpid(pid, &stat_loc, 0)) != pid) {
|
if ((ret = waitpid(pid, &stat_loc, 0)) != pid)
|
||||||
if (ret == -1 && errno == EINTR) {
|
{
|
||||||
|
if (ret == -1 && errno == EINTR)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ret == -1 && errno == ECHILD) {
|
if (ret == -1 && errno == ECHILD)
|
||||||
|
{
|
||||||
// waitpid is already handled elsewhere.
|
// waitpid is already handled elsewhere.
|
||||||
;
|
;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,22 +186,26 @@ static void await_exit(Callable cb, pid_t pid) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
int exit_code = 0, signal_code = 0;
|
int exit_code = 0, signal_code = 0;
|
||||||
if (WIFEXITED(stat_loc)) {
|
if (WIFEXITED(stat_loc))
|
||||||
|
{
|
||||||
exit_code = WEXITSTATUS(stat_loc); // errno?
|
exit_code = WEXITSTATUS(stat_loc); // errno?
|
||||||
}
|
}
|
||||||
if (WIFSIGNALED(stat_loc)) {
|
if (WIFSIGNALED(stat_loc))
|
||||||
|
{
|
||||||
signal_code = WTERMSIG(stat_loc);
|
signal_code = WTERMSIG(stat_loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
cb.call_deferred(exit_code, signal_code);
|
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);
|
cb.call(exit_code, signal_code);
|
||||||
thread->wait_to_finish();
|
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);
|
Thread *thread = memnew(Thread);
|
||||||
|
|
||||||
Callable exit_func = create_custom_callable_static_function_pointer(&on_exit).bind(cb, 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__)
|
#if defined(__APPLE__) || defined(__OpenBSD__)
|
||||||
static void
|
static void
|
||||||
pty_posix_spawn(char** argv, char** env,
|
pty_posix_spawn(char **argv, char **env,
|
||||||
const struct termios *termp,
|
const struct termios *termp,
|
||||||
const struct winsize *winp,
|
const struct winsize *winp,
|
||||||
int* master,
|
int *master,
|
||||||
pid_t* pid,
|
pid_t *pid,
|
||||||
int* err);
|
int *err);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct DelBuf {
|
struct DelBuf
|
||||||
|
{
|
||||||
int len;
|
int len;
|
||||||
DelBuf(int len) : len(len) {}
|
DelBuf(int len) : len(len) {}
|
||||||
void operator()(char **p) {
|
void operator()(char **p)
|
||||||
|
{
|
||||||
if (p == nullptr)
|
if (p == nullptr)
|
||||||
return;
|
return;
|
||||||
for (int i = 0; i < len; i++)
|
for (int i = 0; i < len; i++)
|
||||||
|
@ -241,8 +264,8 @@ Dictionary PTYUnix::fork(
|
||||||
const int &p_gid,
|
const int &p_gid,
|
||||||
const bool &p_utf8,
|
const bool &p_utf8,
|
||||||
const String &p_helper_path,
|
const String &p_helper_path,
|
||||||
const Callable &p_on_exit
|
const Callable &p_on_exit)
|
||||||
) {
|
{
|
||||||
Dictionary result;
|
Dictionary result;
|
||||||
result["error"] = FAILED;
|
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));
|
std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));
|
||||||
char **env = env_unique_ptr.get();
|
char **env = env_unique_ptr.get();
|
||||||
env[envc] = NULL;
|
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();
|
std::string pair = env_[i].utf8().get_data();
|
||||||
env[i] = strdup(pair.c_str());
|
env[i] = strdup(pair.c_str());
|
||||||
}
|
}
|
||||||
|
@ -283,7 +307,8 @@ Dictionary PTYUnix::fork(
|
||||||
struct termios t = termios();
|
struct termios t = termios();
|
||||||
struct termios *term = &t;
|
struct termios *term = &t;
|
||||||
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
||||||
if (p_utf8) {
|
if (p_utf8)
|
||||||
|
{
|
||||||
#if defined(IUTF8)
|
#if defined(IUTF8)
|
||||||
term->c_iflag |= IUTF8;
|
term->c_iflag |= IUTF8;
|
||||||
#endif
|
#endif
|
||||||
|
@ -309,10 +334,10 @@ Dictionary PTYUnix::fork(
|
||||||
term->c_cc[VMIN] = 1;
|
term->c_cc[VMIN] = 1;
|
||||||
term->c_cc[VTIME] = 0;
|
term->c_cc[VTIME] = 0;
|
||||||
|
|
||||||
#if (__APPLE__)
|
#if (__APPLE__)
|
||||||
term->c_cc[VDSUSP] = 25;
|
term->c_cc[VDSUSP] = 25;
|
||||||
term->c_cc[VSTATUS] = 20;
|
term->c_cc[VSTATUS] = 20;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cfsetispeed(term, B38400);
|
cfsetispeed(term, B38400);
|
||||||
cfsetospeed(term, B38400);
|
cfsetospeed(term, B38400);
|
||||||
|
@ -331,27 +356,31 @@ Dictionary PTYUnix::fork(
|
||||||
argv[1] = strdup(cwd_.c_str());
|
argv[1] = strdup(cwd_.c_str());
|
||||||
argv[2] = strdup(file.c_str());
|
argv[2] = strdup(file.c_str());
|
||||||
argv[argl - 1] = NULL;
|
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();
|
std::string arg = argv_[i].utf8().get_data();
|
||||||
argv[i + 3] = strdup(arg.c_str());
|
argv[i + 3] = strdup(arg.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = -1;
|
int err = -1;
|
||||||
pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);
|
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)));
|
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.");
|
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int argc = argv_.size();
|
int argc = argv_.size();
|
||||||
int argl = argc + 2;
|
int argl = argc + 2;
|
||||||
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
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[0] = strdup(file.c_str());
|
||||||
argv[argl - 1] = NULL;
|
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();
|
std::string arg = argv_[i].utf8().get_data();
|
||||||
argv[i + 1] = strdup(arg.c_str());
|
argv[i + 1] = strdup(arg.c_str());
|
||||||
}
|
}
|
||||||
|
@ -365,14 +394,16 @@ Dictionary PTYUnix::fork(
|
||||||
sigfillset(&newmask);
|
sigfillset(&newmask);
|
||||||
pthread_sigmask(SIG_SETMASK, &newmask, &oldmask);
|
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
|
// remove all signal handler from child
|
||||||
sig_action.sa_handler = SIG_DFL;
|
sig_action.sa_handler = SIG_DFL;
|
||||||
sig_action.sa_flags = 0;
|
sig_action.sa_flags = 0;
|
||||||
sigemptyset(&sig_action.sa_mask);
|
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);
|
sigaction(i, &sig_action, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,23 +411,29 @@ Dictionary PTYUnix::fork(
|
||||||
// re-enable signals
|
// re-enable signals
|
||||||
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
|
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
|
||||||
|
|
||||||
switch (pid) {
|
switch (pid)
|
||||||
|
{
|
||||||
case -1:
|
case -1:
|
||||||
ERR_FAIL_V_MSG(result, "forkpty(3) failed.");
|
ERR_FAIL_V_MSG(result, "forkpty(3) failed.");
|
||||||
case 0:
|
case 0:
|
||||||
if (strlen(cwd_.c_str())) {
|
if (strlen(cwd_.c_str()))
|
||||||
if (chdir(cwd_.c_str()) == -1) {
|
{
|
||||||
|
if (chdir(cwd_.c_str()) == -1)
|
||||||
|
{
|
||||||
perror("chdir(2) failed.");
|
perror("chdir(2) failed.");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uid != -1 && gid != -1) {
|
if (uid != -1 && gid != -1)
|
||||||
if (setgid(gid) == -1) {
|
{
|
||||||
|
if (setgid(gid) == -1)
|
||||||
|
{
|
||||||
perror("setgid(2) failed.");
|
perror("setgid(2) failed.");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
if (setuid(uid) == -1) {
|
if (setuid(uid) == -1)
|
||||||
|
{
|
||||||
perror("setuid(2) failed.");
|
perror("setuid(2) failed.");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
@ -411,7 +448,8 @@ Dictionary PTYUnix::fork(
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (pty_nonblock(master) == -1) {
|
if (pty_nonblock(master) == -1)
|
||||||
|
{
|
||||||
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
ERR_FAIL_V_MSG(result, "Could not set master fd to nonblocking.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,8 +469,8 @@ Dictionary PTYUnix::fork(
|
||||||
|
|
||||||
Dictionary PTYUnix::open(
|
Dictionary PTYUnix::open(
|
||||||
const int &p_cols,
|
const int &p_cols,
|
||||||
const int &p_rows
|
const int &p_rows)
|
||||||
) {
|
{
|
||||||
Dictionary result;
|
Dictionary result;
|
||||||
result["error"] = FAILED;
|
result["error"] = FAILED;
|
||||||
|
|
||||||
|
@ -445,17 +483,20 @@ Dictionary PTYUnix::open(
|
||||||
|
|
||||||
// pty
|
// pty
|
||||||
int master, slave;
|
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.");
|
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.");
|
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.");
|
ERR_FAIL_V_MSG(result, "Could not set slave fd to nonblocking.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,8 +511,8 @@ Dictionary PTYUnix::open(
|
||||||
void PTYUnix::resize(
|
void PTYUnix::resize(
|
||||||
const int &p_fd,
|
const int &p_fd,
|
||||||
const int &p_cols,
|
const int &p_cols,
|
||||||
const int &p_rows
|
const int &p_rows)
|
||||||
) {
|
{
|
||||||
int fd = p_fd;
|
int fd = p_fd;
|
||||||
|
|
||||||
struct winsize winp;
|
struct winsize winp;
|
||||||
|
@ -480,8 +521,10 @@ void PTYUnix::resize(
|
||||||
winp.ws_xpixel = 0;
|
winp.ws_xpixel = 0;
|
||||||
winp.ws_ypixel = 0;
|
winp.ws_ypixel = 0;
|
||||||
|
|
||||||
if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
|
if (ioctl(fd, TIOCSWINSZ, &winp) == -1)
|
||||||
switch (errno) {
|
{
|
||||||
|
switch (errno)
|
||||||
|
{
|
||||||
case EBADF:
|
case EBADF:
|
||||||
ERR_FAIL_MSG("ioctl(2) failed, EBADF");
|
ERR_FAIL_MSG("ioctl(2) failed, EBADF");
|
||||||
case EFAULT:
|
case EFAULT:
|
||||||
|
@ -502,8 +545,8 @@ void PTYUnix::resize(
|
||||||
*/
|
*/
|
||||||
String process(
|
String process(
|
||||||
const int &p_fd,
|
const int &p_fd,
|
||||||
const String &p_tty
|
const String &p_tty)
|
||||||
) {
|
{
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
int fd = p_fd;
|
int fd = p_fd;
|
||||||
char *name = pty_getproc(fd);
|
char *name = pty_getproc(fd);
|
||||||
|
@ -516,7 +559,8 @@ String process(
|
||||||
free(tty);
|
free(tty);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (name == NULL) {
|
if (name == NULL)
|
||||||
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,9 +574,11 @@ String process(
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pty_nonblock(int fd) {
|
pty_nonblock(int fd)
|
||||||
|
{
|
||||||
int flags = fcntl(fd, F_GETFL, 0);
|
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);
|
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +607,8 @@ pty_nonblock(int fd) {
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
pty_getproc(int fd, char *tty) {
|
pty_getproc(int fd, char *tty)
|
||||||
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
char *path, *buf;
|
char *path, *buf;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -569,14 +616,17 @@ pty_getproc(int fd, char *tty) {
|
||||||
pid_t pgrp;
|
pid_t pgrp;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
if ((pgrp = tcgetpgrp(fd)) == -1) {
|
if ((pgrp = tcgetpgrp(fd)) == -1)
|
||||||
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp);
|
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);
|
free(path);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -585,14 +635,18 @@ pty_getproc(int fd, char *tty) {
|
||||||
|
|
||||||
len = 0;
|
len = 0;
|
||||||
buf = NULL;
|
buf = NULL;
|
||||||
while ((ch = fgetc(f)) != EOF) {
|
while ((ch = fgetc(f)) != EOF)
|
||||||
if (ch == '\0') break;
|
{
|
||||||
|
if (ch == '\0')
|
||||||
|
break;
|
||||||
buf = (char *)realloc(buf, len + 2);
|
buf = (char *)realloc(buf, len + 2);
|
||||||
if (buf == NULL) return NULL;
|
if (buf == NULL)
|
||||||
|
return NULL;
|
||||||
buf[len++] = ch;
|
buf[len++] = ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buf != NULL) {
|
if (buf != NULL)
|
||||||
|
{
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,21 +657,25 @@ pty_getproc(int fd, char *tty) {
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
pty_getproc(int fd) {
|
pty_getproc(int fd)
|
||||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
|
{
|
||||||
|
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, 0};
|
||||||
size_t size;
|
size_t size;
|
||||||
struct kinfo_proc kp;
|
struct kinfo_proc kp;
|
||||||
|
|
||||||
if ((mib[3] = tcgetpgrp(fd)) == -1) {
|
if ((mib[3] = tcgetpgrp(fd)) == -1)
|
||||||
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
size = sizeof kp;
|
size = sizeof kp;
|
||||||
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
|
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1)
|
||||||
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0') {
|
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0')
|
||||||
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,7 +685,8 @@ pty_getproc(int fd) {
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
pty_getproc(int fd, char *tty) {
|
pty_getproc(int fd, char *tty)
|
||||||
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,16 +694,18 @@ pty_getproc(int fd, char *tty) {
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
static void
|
static void
|
||||||
pty_posix_spawn(char** argv, char** env,
|
pty_posix_spawn(char **argv, char **env,
|
||||||
const struct termios *termp,
|
const struct termios *termp,
|
||||||
const struct winsize *winp,
|
const struct winsize *winp,
|
||||||
int* master,
|
int *master,
|
||||||
pid_t* pid,
|
pid_t *pid,
|
||||||
int* err) {
|
int *err)
|
||||||
|
{
|
||||||
int low_fds[3];
|
int low_fds[3];
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
|
||||||
for (; count < 3; count++) {
|
for (; count < 3; count++)
|
||||||
|
{
|
||||||
low_fds[count] = posix_openpt(O_RDWR);
|
low_fds[count] = posix_openpt(O_RDWR);
|
||||||
if (low_fds[count] >= STDERR_FILENO)
|
if (low_fds[count] >= STDERR_FILENO)
|
||||||
break;
|
break;
|
||||||
|
@ -655,12 +716,14 @@ pty_posix_spawn(char** argv, char** env,
|
||||||
POSIX_SPAWN_SETSIGMASK |
|
POSIX_SPAWN_SETSIGMASK |
|
||||||
POSIX_SPAWN_SETSID;
|
POSIX_SPAWN_SETSID;
|
||||||
*master = posix_openpt(O_RDWR);
|
*master = posix_openpt(O_RDWR);
|
||||||
if (*master == -1) {
|
if (*master == -1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int res = grantpt(*master) || unlockpt(*master);
|
int res = grantpt(*master) || unlockpt(*master);
|
||||||
if (res == -1) {
|
if (res == -1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,25 +731,31 @@ pty_posix_spawn(char** argv, char** env,
|
||||||
int slave;
|
int slave;
|
||||||
char slave_pty_name[128];
|
char slave_pty_name[128];
|
||||||
res = ioctl(*master, TIOCPTYGNAME, slave_pty_name);
|
res = ioctl(*master, TIOCPTYGNAME, slave_pty_name);
|
||||||
if (res == -1) {
|
if (res == -1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
slave = open(slave_pty_name, O_RDWR | O_NOCTTY);
|
slave = open(slave_pty_name, O_RDWR | O_NOCTTY);
|
||||||
if (slave == -1) {
|
if (slave == -1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (termp) {
|
if (termp)
|
||||||
|
{
|
||||||
res = tcsetattr(slave, TCSANOW, termp);
|
res = tcsetattr(slave, TCSANOW, termp);
|
||||||
if (res == -1) {
|
if (res == -1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (winp) {
|
if (winp)
|
||||||
|
{
|
||||||
res = ioctl(slave, TIOCSWINSZ, winp);
|
res = ioctl(slave, TIOCSWINSZ, winp);
|
||||||
if (res == -1) {
|
if (res == -1)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -702,7 +771,8 @@ pty_posix_spawn(char** argv, char** env,
|
||||||
posix_spawnattr_t attrs;
|
posix_spawnattr_t attrs;
|
||||||
posix_spawnattr_init(&attrs);
|
posix_spawnattr_init(&attrs);
|
||||||
*err = posix_spawnattr_setflags(&attrs, flags);
|
*err = posix_spawnattr_setflags(&attrs, flags);
|
||||||
if (*err != 0) {
|
if (*err != 0)
|
||||||
|
{
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,25 +780,29 @@ pty_posix_spawn(char** argv, char** env,
|
||||||
/* Reset all signal the child to their default behavior */
|
/* Reset all signal the child to their default behavior */
|
||||||
sigfillset(&signal_set);
|
sigfillset(&signal_set);
|
||||||
*err = posix_spawnattr_setsigdefault(&attrs, &signal_set);
|
*err = posix_spawnattr_setsigdefault(&attrs, &signal_set);
|
||||||
if (*err != 0) {
|
if (*err != 0)
|
||||||
|
{
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset the signal mask for all signals */
|
/* Reset the signal mask for all signals */
|
||||||
sigemptyset(&signal_set);
|
sigemptyset(&signal_set);
|
||||||
*err = posix_spawnattr_setsigmask(&attrs, &signal_set);
|
*err = posix_spawnattr_setsigmask(&attrs, &signal_set);
|
||||||
if (*err != 0) {
|
if (*err != 0)
|
||||||
|
{
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do
|
||||||
|
{
|
||||||
*err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env);
|
*err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env);
|
||||||
} while (*err == EINTR);
|
} while (*err == EINTR);
|
||||||
done:
|
done:
|
||||||
posix_spawn_file_actions_destroy(&acts);
|
posix_spawn_file_actions_destroy(&acts);
|
||||||
posix_spawnattr_destroy(&attrs);
|
posix_spawnattr_destroy(&attrs);
|
||||||
|
|
||||||
for (; count > 0; count--) {
|
for (; count > 0; count--)
|
||||||
|
{
|
||||||
close(low_fds[count]);
|
close(low_fds[count]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,18 +22,15 @@ namespace godot
|
||||||
const int &p_gid,
|
const int &p_gid,
|
||||||
const bool &p_utf8,
|
const bool &p_utf8,
|
||||||
const String &p_helper_path,
|
const String &p_helper_path,
|
||||||
const Callable &p_on_exit
|
const Callable &p_on_exit);
|
||||||
);
|
|
||||||
|
|
||||||
static Dictionary open(
|
static Dictionary open(
|
||||||
const int &p_cols,
|
const int &p_cols,
|
||||||
const int &p_rows
|
const int &p_rows);
|
||||||
);
|
|
||||||
|
|
||||||
static void resize(
|
static void resize(
|
||||||
const int &p_fd,
|
const int &p_fd,
|
||||||
const int &p_cols,
|
const int &p_cols,
|
||||||
const int &p_rows
|
const int &p_rows);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
} // namespace godot
|
} // 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
|
|
@ -146,11 +146,13 @@ int Terminal::get_rows() const
|
||||||
return rows;
|
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));
|
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;
|
return cell_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +183,8 @@ String Terminal::write(const Variant data)
|
||||||
ERR_FAIL_V_MSG("", "Data must be a String or PackedByteArray.");
|
ERR_FAIL_V_MSG("", "Data must be a String or PackedByteArray.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes.is_empty()) return "";
|
if (bytes.is_empty())
|
||||||
|
return "";
|
||||||
|
|
||||||
response.clear();
|
response.clear();
|
||||||
tsm_vte_input(vte, (char *)bytes.ptr(), bytes.size());
|
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();
|
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_key_input(event);
|
||||||
_handle_selection(event);
|
_handle_selection(event);
|
||||||
_handle_mouse_wheel(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);
|
Terminal *term = static_cast<Terminal *>(data);
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0)
|
||||||
|
{
|
||||||
PackedByteArray data;
|
PackedByteArray data;
|
||||||
data.resize(len);
|
data.resize(len);
|
||||||
memcpy(data.ptrw(), u8, len);
|
memcpy(data.ptrw(), u8, len);
|
||||||
term->response.append_array(data);
|
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->emit_signal("key_pressed", data.get_string_from_utf8(), term->last_input_event_key);
|
||||||
term->last_input_event_key.unref();
|
term->last_input_event_key.unref();
|
||||||
}
|
}
|
||||||
|
@ -263,7 +269,8 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
||||||
{
|
{
|
||||||
Terminal *term = static_cast<Terminal *>(data);
|
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)
|
if (width < 1)
|
||||||
{ // No foreground or background to draw.
|
{ // No foreground or background to draw.
|
||||||
|
@ -276,7 +283,8 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
||||||
attr_flags |= AttrFlag::INVERSE;
|
attr_flags |= AttrFlag::INVERSE;
|
||||||
if (attr->blink)
|
if (attr->blink)
|
||||||
attr_flags |= AttrFlag::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;
|
attr_flags |= AttrFlag::CURSOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +297,8 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
||||||
? term->palette[attr->bccode]
|
? term->palette[attr->bccode]
|
||||||
: Color(attr->br / 255.0f, attr->bg / 255.0f, attr->bb / 255.0f);
|
: 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.r, bgcol.r);
|
||||||
std::swap(fgcol.g, bgcol.g);
|
std::swap(fgcol.g, bgcol.g);
|
||||||
std::swap(fgcol.b, bgcol.b);
|
std::swap(fgcol.b, bgcol.b);
|
||||||
|
@ -300,7 +309,8 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
||||||
bgcol.a = 0;
|
bgcol.a = 0;
|
||||||
|
|
||||||
// Update images (accounting for ultra-wide characters).
|
// 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->back_image->set_pixel(posx + i, posy, bgcol);
|
||||||
term->attr_image->set_pixel(posx + i, posy, Color(attr_flags / 255.0f, 0, 0, 0));
|
term->attr_image->set_pixel(posx + i, posy, Color(attr_flags / 255.0f, 0, 0, 0));
|
||||||
}
|
}
|
||||||
|
@ -323,8 +333,7 @@ int Terminal::_draw_cb(struct tsm_screen *con,
|
||||||
Vector2i(cell_position.x, cell_position.y + term->font_offset),
|
Vector2i(cell_position.x, cell_position.y + term->font_offset),
|
||||||
static_cast<uint64_t>(*ch),
|
static_cast<uint64_t>(*ch),
|
||||||
term->font_size,
|
term->font_size,
|
||||||
fgcol
|
fgcol);
|
||||||
);
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
@ -333,7 +342,8 @@ void Terminal::_bell_cb(struct tsm_vte *vte, void *data)
|
||||||
{
|
{
|
||||||
Terminal *term = static_cast<Terminal *>(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");
|
term->emit_signal("bell");
|
||||||
|
|
||||||
if (term->bell_cooldown > 0)
|
if (term->bell_cooldown > 0)
|
||||||
|
@ -393,12 +403,15 @@ bool Terminal::_set(const StringName &p_name, const Variant &p_value)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Terminal::_get(const StringName &p_name, Variant &r_value) {
|
bool Terminal::_get(const StringName &p_name, Variant &r_value)
|
||||||
if (p_name == String("cols")) {
|
{
|
||||||
|
if (p_name == String("cols"))
|
||||||
|
{
|
||||||
r_value = cols;
|
r_value = cols;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (p_name == String("rows")) {
|
if (p_name == String("rows"))
|
||||||
|
{
|
||||||
r_value = rows;
|
r_value = rows;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -485,20 +498,24 @@ void Terminal::update_sizes(bool force)
|
||||||
|
|
||||||
void Terminal::set_shader_parameters(const String ¶m, const Variant &value)
|
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("cols", cols);
|
||||||
set_shader_parameters("rows", rows);
|
set_shader_parameters("rows", rows);
|
||||||
set_shader_parameters("size", size);
|
set_shader_parameters("size", size);
|
||||||
set_shader_parameters("cell_size", cell_size);
|
set_shader_parameters("cell_size", cell_size);
|
||||||
set_shader_parameters("grid_size", Vector2(cols * cell_size.x, rows * cell_size.y));
|
set_shader_parameters("grid_size", Vector2(cols * cell_size.x, rows * cell_size.y));
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
back_material->set_shader_parameter(param, value);
|
back_material->set_shader_parameter(param, value);
|
||||||
fore_material->set_shader_parameter(param, value);
|
fore_material->set_shader_parameter(param, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::initialize_rendering() {
|
void Terminal::initialize_rendering()
|
||||||
ResourceLoader* rl = ResourceLoader::get_singleton();
|
{
|
||||||
|
ResourceLoader *rl = ResourceLoader::get_singleton();
|
||||||
|
|
||||||
rs = RenderingServer::get_singleton();
|
rs = RenderingServer::get_singleton();
|
||||||
attr_texture.instantiate();
|
attr_texture.instantiate();
|
||||||
|
@ -542,7 +559,7 @@ void Terminal::initialize_rendering() {
|
||||||
canvas = rs->canvas_create();
|
canvas = rs->canvas_create();
|
||||||
rs->canvas_item_set_parent(char_canvas_item, canvas);
|
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_attach_canvas(viewport, canvas);
|
||||||
rs->viewport_set_disable_3d(viewport, true);
|
rs->viewport_set_disable_3d(viewport, true);
|
||||||
rs->viewport_set_transparent_background(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"));
|
rs->connect("frame_post_draw", Callable(this, "_on_frame_post_draw"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::update_theme() {
|
void Terminal::update_theme()
|
||||||
|
{
|
||||||
// Update colors.
|
// Update colors.
|
||||||
palette.resize(TSM_COLOR_NUM);
|
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);
|
tsm_vte_color color = static_cast<tsm_vte_color>(i);
|
||||||
palette[color] = get_theme_color(String(COLOR_NAMES[i]));
|
palette[color] = get_theme_color(String(COLOR_NAMES[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fonts.
|
// 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);
|
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]);
|
fonts[type] = has_theme_font(FONT_TYPES[type]) ? get_theme_font(FONT_TYPES[type]) : get_theme_font(FONT_TYPES[FontType::NORMAL]);
|
||||||
}
|
}
|
||||||
|
@ -581,7 +601,8 @@ void Terminal::update_theme() {
|
||||||
style_normal = get_theme_stylebox("normal");
|
style_normal = get_theme_stylebox("normal");
|
||||||
style_focus = get_theme_stylebox("focus");
|
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.
|
// 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");
|
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]);
|
||||||
|
@ -591,15 +612,19 @@ void Terminal::update_theme() {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::_on_frame_post_draw() {
|
void Terminal::_on_frame_post_draw()
|
||||||
if (redraw_requested) {
|
{
|
||||||
|
if (redraw_requested)
|
||||||
|
{
|
||||||
queue_redraw();
|
queue_redraw();
|
||||||
redraw_requested = false;
|
redraw_requested = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::draw_screen() {
|
void Terminal::draw_screen()
|
||||||
if (framebuffer_age == 0) {
|
{
|
||||||
|
if (framebuffer_age == 0)
|
||||||
|
{
|
||||||
Rect2 rect = Rect2(Vector2(), size);
|
Rect2 rect = Rect2(Vector2(), size);
|
||||||
|
|
||||||
rs->viewport_set_clear_mode(viewport, RenderingServer::ViewportClearMode::VIEWPORT_CLEAR_ONLY_NEXT_FRAME);
|
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);
|
back_texture->update(back_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::refresh() {
|
void Terminal::refresh()
|
||||||
|
{
|
||||||
framebuffer_age = 0;
|
framebuffer_age = 0;
|
||||||
queue_redraw();
|
queue_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::cleanup_rendering() {
|
void Terminal::cleanup_rendering()
|
||||||
|
{
|
||||||
// StyleBox.
|
// StyleBox.
|
||||||
rs->free_rid(style_canvas_item);
|
rs->free_rid(style_canvas_item);
|
||||||
|
|
||||||
|
@ -649,18 +676,22 @@ void Terminal::cleanup_rendering() {
|
||||||
rs->free_rid(char_shader);
|
rs->free_rid(char_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::set_bell_muted(const bool muted) {
|
void Terminal::set_bell_muted(const bool muted)
|
||||||
|
{
|
||||||
bell_muted = muted;
|
bell_muted = muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Terminal::get_bell_muted() const {
|
bool Terminal::get_bell_muted() const
|
||||||
|
{
|
||||||
return bell_muted;
|
return bell_muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::set_bell_cooldown(const double time) {
|
void Terminal::set_bell_cooldown(const double time)
|
||||||
|
{
|
||||||
bell_cooldown = time;
|
bell_cooldown = time;
|
||||||
|
|
||||||
if (!bell_timer->is_stopped()) {
|
if (!bell_timer->is_stopped())
|
||||||
|
{
|
||||||
bell_timer->stop();
|
bell_timer->stop();
|
||||||
|
|
||||||
double remaining_time = std::max(0.0, bell_cooldown - bell_timer->get_time_left());
|
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;
|
return bell_cooldown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -695,7 +727,8 @@ double Terminal::get_blink_off_time() const
|
||||||
return blink_off_time;
|
return blink_off_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::clear() {
|
void Terminal::clear()
|
||||||
|
{
|
||||||
// Resize the terminal to a single row, forcing content above in to the scrollback buffer.
|
// Resize the terminal to a single row, forcing content above in to the scrollback buffer.
|
||||||
tsm_screen_resize(screen, cols, 1);
|
tsm_screen_resize(screen, cols, 1);
|
||||||
|
|
||||||
|
@ -708,12 +741,14 @@ void Terminal::clear() {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
String Terminal::_copy_screen(ScreenCopyFunction func) {
|
String Terminal::_copy_screen(ScreenCopyFunction func)
|
||||||
|
{
|
||||||
char *out;
|
char *out;
|
||||||
PackedByteArray data;
|
PackedByteArray data;
|
||||||
|
|
||||||
data.resize(std::max(func(screen, &out), 0));
|
data.resize(std::max(func(screen, &out), 0));
|
||||||
if (data.size() > 0) {
|
if (data.size() > 0)
|
||||||
|
{
|
||||||
memcpy(data.ptrw(), out, data.size());
|
memcpy(data.ptrw(), out, data.size());
|
||||||
std::free(out);
|
std::free(out);
|
||||||
}
|
}
|
||||||
|
@ -721,16 +756,20 @@ String Terminal::_copy_screen(ScreenCopyFunction func) {
|
||||||
return data.get_string_from_utf8();
|
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_line = std::clamp((int)p_from_line, 0, (int)rows);
|
||||||
int from_column = std::clamp((int)p_from_column, 0, (int)cols);
|
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_line = std::clamp((int)p_to_line, 0, (int)rows);
|
||||||
int to_column = std::clamp((int)p_to_column, 0, (int)cols);
|
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_line, from_line);
|
||||||
std::swap(to_column, from_column);
|
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);
|
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();
|
String selection = copy_selection();
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
if (copy_on_selection)
|
if (copy_on_selection)
|
||||||
DisplayServer::get_singleton()->clipboard_set_primary(selection);
|
DisplayServer::get_singleton()->clipboard_set_primary(selection);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (selection.length() > 0) {
|
if (selection.length() > 0)
|
||||||
|
{
|
||||||
selecting = true;
|
selecting = true;
|
||||||
queue_redraw();
|
queue_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String Terminal::copy_all() {
|
String Terminal::copy_all()
|
||||||
|
{
|
||||||
return _copy_screen(&tsm_screen_copy_all);
|
return _copy_screen(&tsm_screen_copy_all);
|
||||||
}
|
}
|
||||||
|
|
||||||
String Terminal::copy_selection() {
|
String Terminal::copy_selection()
|
||||||
|
{
|
||||||
return _copy_screen(&tsm_screen_selection_copy);
|
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;
|
copy_on_selection = p_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Terminal::get_copy_on_selection() const {
|
bool Terminal::get_copy_on_selection() const
|
||||||
|
{
|
||||||
return copy_on_selection;
|
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);
|
inverse_mode = static_cast<InverseMode>(mode);
|
||||||
|
|
||||||
bool inverse_enabled = inverse_mode == InverseMode::INVERSE_MODE_INVERT;
|
bool inverse_enabled = inverse_mode == InverseMode::INVERSE_MODE_INVERT;
|
||||||
|
@ -778,11 +823,13 @@ void Terminal::set_inverse_mode(const int mode) {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Terminal::get_inverse_mode() const {
|
int Terminal::get_inverse_mode() const
|
||||||
|
{
|
||||||
return static_cast<int>(inverse_mode);
|
return static_cast<int>(inverse_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::initialize_input() {
|
void Terminal::initialize_input()
|
||||||
|
{
|
||||||
selecting = false;
|
selecting = false;
|
||||||
selection_mode = SelectionMode::NONE;
|
selection_mode = SelectionMode::NONE;
|
||||||
selection_timer = memnew(Timer);
|
selection_timer = memnew(Timer);
|
||||||
|
@ -791,7 +838,8 @@ void Terminal::initialize_input() {
|
||||||
add_child(selection_timer, false, INTERNAL_MODE_FRONT);
|
add_child(selection_timer, false, INTERNAL_MODE_FRONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::_handle_key_input(Ref<InputEventKey> event) {
|
void Terminal::_handle_key_input(Ref<InputEventKey> event)
|
||||||
|
{
|
||||||
if (!event.is_valid() || !event->is_pressed())
|
if (!event.is_valid() || !event->is_pressed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -801,7 +849,7 @@ void Terminal::_handle_key_input(Ref<InputEventKey> event) {
|
||||||
|
|
||||||
unsigned int mods = 0;
|
unsigned int mods = 0;
|
||||||
if (event->is_alt_pressed())
|
if (event->is_alt_pressed())
|
||||||
mods |= TSM_SHIFT_MASK;
|
mods |= TSM_ALT_MASK;
|
||||||
if (event->is_ctrl_pressed())
|
if (event->is_ctrl_pressed())
|
||||||
mods |= TSM_CONTROL_MASK;
|
mods |= TSM_CONTROL_MASK;
|
||||||
if (event->is_shift_pressed())
|
if (event->is_shift_pressed())
|
||||||
|
@ -818,7 +866,8 @@ void Terminal::_handle_key_input(Ref<InputEventKey> event) {
|
||||||
// pressed.
|
// pressed.
|
||||||
std::set<Key> mod_keys = {KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META};
|
std::set<Key> mod_keys = {KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META};
|
||||||
if (mod_keys.find(keycode) == mod_keys.end() &&
|
if (mod_keys.find(keycode) == mod_keys.end() &&
|
||||||
!(event->is_ctrl_pressed() && event->is_shift_pressed())) {
|
!(event->is_ctrl_pressed() && event->is_shift_pressed()))
|
||||||
|
{
|
||||||
tsm_screen_sb_reset(screen);
|
tsm_screen_sb_reset(screen);
|
||||||
queue_redraw();
|
queue_redraw();
|
||||||
}
|
}
|
||||||
|
@ -829,13 +878,15 @@ void Terminal::_handle_key_input(Ref<InputEventKey> event) {
|
||||||
accept_event();
|
accept_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::_handle_mouse_wheel(Ref<InputEventMouseButton> event) {
|
void Terminal::_handle_mouse_wheel(Ref<InputEventMouseButton> event)
|
||||||
|
{
|
||||||
if (!event.is_valid() || !event->is_pressed())
|
if (!event.is_valid() || !event->is_pressed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
void (*scroll_func)(tsm_screen *, unsigned int) = nullptr;
|
void (*scroll_func)(tsm_screen *, unsigned int) = nullptr;
|
||||||
|
|
||||||
switch (event->get_button_index()) {
|
switch (event->get_button_index())
|
||||||
|
{
|
||||||
case MOUSE_BUTTON_WHEEL_UP:
|
case MOUSE_BUTTON_WHEEL_UP:
|
||||||
scroll_func = &tsm_screen_sb_up;
|
scroll_func = &tsm_screen_sb_up;
|
||||||
break;
|
break;
|
||||||
|
@ -846,26 +897,30 @@ void Terminal::_handle_mouse_wheel(Ref<InputEventMouseButton> event) {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (scroll_func != nullptr) {
|
if (scroll_func != nullptr)
|
||||||
|
{
|
||||||
// Scroll 5 times as fast as normal if alt is pressed (like TextEdit).
|
// Scroll 5 times as fast as normal if alt is pressed (like TextEdit).
|
||||||
// Otherwise, just scroll 3 lines.
|
// Otherwise, just scroll 3 lines.
|
||||||
int speed = event->is_alt_pressed() ? 15 : 3;
|
int speed = event->is_alt_pressed() ? 15 : 3;
|
||||||
double factor = event->get_factor();
|
double factor = event->get_factor();
|
||||||
(*scroll_func)(screen, speed * factor);
|
(*scroll_func)(screen, speed *factor);
|
||||||
queue_redraw();
|
queue_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::_handle_selection(Ref<InputEventMouse> event) {
|
void Terminal::_handle_selection(Ref<InputEventMouse> event)
|
||||||
|
{
|
||||||
if (!event.is_valid())
|
if (!event.is_valid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Ref<InputEventMouseButton> mb = event;
|
Ref<InputEventMouseButton> mb = event;
|
||||||
if (mb.is_valid()) {
|
if (mb.is_valid())
|
||||||
|
{
|
||||||
if (!mb->is_pressed() || mb->get_button_index() != MOUSE_BUTTON_LEFT)
|
if (!mb->is_pressed() || mb->get_button_index() != MOUSE_BUTTON_LEFT)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (selecting) {
|
if (selecting)
|
||||||
|
{
|
||||||
selecting = false;
|
selecting = false;
|
||||||
selection_mode = SelectionMode::NONE;
|
selection_mode = SelectionMode::NONE;
|
||||||
tsm_screen_selection_reset(screen);
|
tsm_screen_selection_reset(screen);
|
||||||
|
@ -879,8 +934,10 @@ void Terminal::_handle_selection(Ref<InputEventMouse> event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<InputEventMouseMotion> mm = event;
|
Ref<InputEventMouseMotion> mm = event;
|
||||||
if (mm.is_valid()) {
|
if (mm.is_valid())
|
||||||
if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) && selection_mode != SelectionMode::NONE && !selecting) {
|
{
|
||||||
|
if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) && selection_mode != SelectionMode::NONE && !selecting)
|
||||||
|
{
|
||||||
selecting = true;
|
selecting = true;
|
||||||
Vector2 start = event->get_position() / cell_size;
|
Vector2 start = event->get_position() / cell_size;
|
||||||
tsm_screen_selection_start(screen, start.x, start.y);
|
tsm_screen_selection_start(screen, start.x, start.y);
|
||||||
|
@ -891,13 +948,16 @@ void Terminal::_handle_selection(Ref<InputEventMouse> event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::_on_selection_held() {
|
void Terminal::_on_selection_held()
|
||||||
if (!(Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) || selection_mode == SelectionMode::NONE) {
|
{
|
||||||
#if defined(__linux__)
|
if (!(Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) || selection_mode == SelectionMode::NONE)
|
||||||
if (copy_on_selection) {
|
{
|
||||||
|
#if defined(__linux__)
|
||||||
|
if (copy_on_selection)
|
||||||
|
{
|
||||||
DisplayServer::get_singleton()->clipboard_set_primary(copy_selection());
|
DisplayServer::get_singleton()->clipboard_set_primary(copy_selection());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
selection_timer->stop();
|
selection_timer->stop();
|
||||||
return;
|
return;
|
||||||
|
@ -911,9 +971,11 @@ void Terminal::_on_selection_held() {
|
||||||
|
|
||||||
// Add default theme items for the "Terminal" theme type if they don't exist.
|
// 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).
|
// 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();
|
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.
|
// 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.
|
// 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.
|
// 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.
|
// 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_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_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_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_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_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_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_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_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_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_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_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_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
|
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
|
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.
|
// 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";
|
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);
|
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);
|
FontType type = static_cast<FontType>(i);
|
||||||
custom_theme->set_theme_item(Theme::DATA_TYPE_FONT, FONT_TYPES[type], "Terminal", default_font);
|
custom_theme->set_theme_item(Theme::DATA_TYPE_FONT, FONT_TYPES[type], "Terminal", default_font);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ namespace godot
|
||||||
private:
|
private:
|
||||||
typedef std::map<std::pair<Key, char32_t>, uint32_t> KeyMap;
|
typedef std::map<std::pair<Key, char32_t>, uint32_t> KeyMap;
|
||||||
|
|
||||||
enum FontType {
|
enum FontType
|
||||||
|
{
|
||||||
NORMAL,
|
NORMAL,
|
||||||
BOLD,
|
BOLD,
|
||||||
ITALICS,
|
ITALICS,
|
||||||
|
@ -37,6 +38,7 @@ namespace godot
|
||||||
static const char *COLOR_NAMES[18];
|
static const char *COLOR_NAMES[18];
|
||||||
static const char *FONT_TYPES[4];
|
static const char *FONT_TYPES[4];
|
||||||
static const KeyMap KEY_MAP;
|
static const KeyMap KEY_MAP;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum AttrFlag
|
enum AttrFlag
|
||||||
{
|
{
|
||||||
|
@ -45,7 +47,8 @@ namespace godot
|
||||||
CURSOR = 1 << 2,
|
CURSOR = 1 << 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum InverseMode {
|
enum InverseMode
|
||||||
|
{
|
||||||
INVERSE_MODE_INVERT,
|
INVERSE_MODE_INVERT,
|
||||||
INVERSE_MODE_SWAP,
|
INVERSE_MODE_SWAP,
|
||||||
};
|
};
|
||||||
|
@ -89,6 +92,7 @@ namespace godot
|
||||||
String write(const Variant data);
|
String write(const Variant data);
|
||||||
|
|
||||||
void _gui_input(const Ref<InputEvent> &event) override;
|
void _gui_input(const Ref<InputEvent> &event) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
|
@ -123,7 +127,7 @@ namespace godot
|
||||||
// This can be useful in cases where the bell character is being written too
|
// This can be useful in cases where the bell character is being written too
|
||||||
// frequently such as `while true; do echo -e "\a"; done`.
|
// frequently such as `while true; do echo -e "\a"; done`.
|
||||||
double bell_cooldown;
|
double bell_cooldown;
|
||||||
Timer* bell_timer;
|
Timer *bell_timer;
|
||||||
static void _bell_cb(struct tsm_vte *vte, void *data);
|
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,
|
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);
|
void _handle_mouse_wheel(Ref<InputEventMouseButton> event);
|
||||||
|
|
||||||
enum SelectionMode { NONE, POINTER };
|
enum SelectionMode
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
POINTER
|
||||||
|
};
|
||||||
bool selecting = false;
|
bool selecting = false;
|
||||||
SelectionMode selection_mode = SelectionMode::NONE;
|
SelectionMode selection_mode = SelectionMode::NONE;
|
||||||
Timer *selection_timer;
|
Timer *selection_timer;
|
||||||
void _handle_selection(Ref<InputEventMouse> event);
|
void _handle_selection(Ref<InputEventMouse> event);
|
||||||
void _on_selection_held();
|
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);
|
String _copy_screen(ScreenCopyFunction func);
|
||||||
|
|
||||||
void set_default_theme_items();
|
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()
|
$Terminal.grab_focus()
|
||||||
scene.queue_free()
|
scene.queue_free()
|
||||||
"Terminal":
|
"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 scene = item.scene.instantiate()
|
||||||
var pty = scene if OS.has_feature("web") else scene.get_node("PTY")
|
var pty = scene if OS.has_feature("web") else scene.get_node("PTY")
|
||||||
add_child(scene)
|
add_child(scene)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[gd_scene load_steps=3 format=3 uid="uid://brjrtf5fpptw8"]
|
[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="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"]
|
[node name="Menu" type="Control"]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
|
@ -19,4 +19,4 @@ anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme = ExtResource("2_7f2wl")
|
theme = ExtResource("2_pr2sv")
|
||||||
|
|
|
@ -2,6 +2,5 @@ extends Terminal
|
||||||
|
|
||||||
@onready var pty = $PTY
|
@onready var pty = $PTY
|
||||||
|
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
pty.fork(OS.get_environment("SHELL"))
|
pty.fork()
|
||||||
|
|
|
@ -21,7 +21,7 @@ window/vsync/use_vsync=false
|
||||||
|
|
||||||
[editor_plugins]
|
[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]
|
[rendering]
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ func before_each():
|
||||||
|
|
||||||
|
|
||||||
func test_bell() -> void:
|
func test_bell() -> void:
|
||||||
|
watch_signals(terminal)
|
||||||
terminal.bell_cooldown = 0
|
terminal.bell_cooldown = 0
|
||||||
terminal.write(char(7))
|
terminal.write(char(7))
|
||||||
terminal.write(char(0x07))
|
terminal.write(char(0x07))
|
||||||
|
@ -42,8 +43,8 @@ class TestTheme:
|
||||||
|
|
||||||
const TestScene := preload("../scenes/theme.tscn")
|
const TestScene := preload("../scenes/theme.tscn")
|
||||||
|
|
||||||
const default_theme := preload("res://addons/godot_xterm/themes/default.tres")
|
const default_theme := preload("res://addons/godot_xterm/themes/default_green.tres")
|
||||||
const alt_theme := preload("res://addons/godot_xterm/themes/default_light.tres")
|
const alt_theme := preload("res://addons/godot_xterm/themes/default_white.tres")
|
||||||
|
|
||||||
const COLORS := [
|
const COLORS := [
|
||||||
"ansi_0_color",
|
"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():
|
func test_emits_exited_on_kill():
|
||||||
subject.call("fork", "yes")
|
subject.call("fork", "yes")
|
||||||
await wait_frames(1)
|
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)
|
await wait_for_signal(subject.exited, 1)
|
||||||
assert_signal_emitted(subject, "exited")
|
assert_signal_emitted(subject, "exited")
|
||||||
|
|
||||||
|
@ -104,9 +104,9 @@ func test_emits_exited_on_kill():
|
||||||
func test_emits_exited_with_signal():
|
func test_emits_exited_with_signal():
|
||||||
subject.call("fork", "yes")
|
subject.call("fork", "yes")
|
||||||
await wait_frames(1)
|
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)
|
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.
|
# Run the same tests, but with use_threads = false.
|
||||||
|
@ -189,7 +189,7 @@ class TestPTYSize:
|
||||||
await wait_for_signal(subject.data_received, 1)
|
await wait_for_signal(subject.data_received, 1)
|
||||||
|
|
||||||
func after_each():
|
func after_each():
|
||||||
subject.call_deferred("kill", PTY.SIGNAL_SIGHUP)
|
subject.call_deferred("kill", PTY.IPCSIGNAL_SIGHUP)
|
||||||
await wait_for_signal(subject.exited, 1)
|
await wait_for_signal(subject.exited, 1)
|
||||||
|
|
||||||
func test_pty_default_size():
|
func test_pty_default_size():
|
||||||
|
|
|
@ -64,18 +64,18 @@ class TestInterface:
|
||||||
# Enums.
|
# Enums.
|
||||||
|
|
||||||
func test_has_enum_signal():
|
func test_has_enum_signal():
|
||||||
assert_eq(described_class.SIGNAL_SIGHUP, 1)
|
assert_eq(described_class.IPCSIGNAL_SIGHUP, 1)
|
||||||
assert_eq(described_class.SIGNAL_SIGINT, 2)
|
assert_eq(described_class.IPCSIGNAL_SIGINT, 2)
|
||||||
assert_eq(described_class.SIGNAL_SIGQUIT, 3)
|
assert_eq(described_class.IPCSIGNAL_SIGQUIT, 3)
|
||||||
assert_eq(described_class.SIGNAL_SIGILL, 4)
|
assert_eq(described_class.IPCSIGNAL_SIGILL, 4)
|
||||||
assert_eq(described_class.SIGNAL_SIGTRAP, 5)
|
assert_eq(described_class.IPCSIGNAL_SIGTRAP, 5)
|
||||||
assert_eq(described_class.SIGNAL_SIGABRT, 6)
|
assert_eq(described_class.IPCSIGNAL_SIGABRT, 6)
|
||||||
assert_eq(described_class.SIGNAL_SIGFPE, 8)
|
assert_eq(described_class.IPCSIGNAL_SIGFPE, 8)
|
||||||
assert_eq(described_class.SIGNAL_SIGKILL, 9)
|
assert_eq(described_class.IPCSIGNAL_SIGKILL, 9)
|
||||||
assert_eq(described_class.SIGNAL_SIGSEGV, 11)
|
assert_eq(described_class.IPCSIGNAL_SIGSEGV, 11)
|
||||||
assert_eq(described_class.SIGNAL_SIGPIPE, 13)
|
assert_eq(described_class.IPCSIGNAL_SIGPIPE, 13)
|
||||||
assert_eq(described_class.SIGNAL_SIGALRM, 14)
|
assert_eq(described_class.IPCSIGNAL_SIGALRM, 14)
|
||||||
assert_eq(described_class.SIGNAL_SIGTERM, 15)
|
assert_eq(described_class.IPCSIGNAL_SIGTERM, 15)
|
||||||
|
|
||||||
## Other tests.
|
## Other tests.
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,14 @@
|
||||||
extends "res://addons/gut/test.gd"
|
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:
|
class BaseTest:
|
||||||
extends "res://addons/gut/test.gd"
|
extends "res://addons/gut/test.gd"
|
||||||
|
|
||||||
var pty
|
var pty
|
||||||
var mock_pty_native: MockPTY
|
var mock_pty_native: PTY
|
||||||
|
|
||||||
func before_each():
|
func before_each():
|
||||||
pty = add_child_autofree(PTY.new())
|
pty = add_child_autofree(PTY.new())
|
||||||
mock_pty_native = autofree(MockPTY.new())
|
mock_pty_native = autofree(PTY.new())
|
||||||
pty._pty_native = mock_pty_native
|
|
||||||
watch_signals(mock_pty_native)
|
watch_signals(mock_pty_native)
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,33 +61,17 @@ class TestPTYInterfaceGodotXterm2_0_0:
|
||||||
func test_has_signal_exited():
|
func test_has_signal_exited():
|
||||||
assert_has_signal(pty, "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():
|
func test_has_enum_Signal():
|
||||||
assert_true("IPCSignal" in pty, "Expected pty to have enum IPCSignal.")
|
assert_eq(pty.IPCSIGNAL_SIGHUP, 1, "Expected pty to have IPCSIGNAL_SIGHUP.")
|
||||||
assert_typeof(pty.IPCSignal, typeof(Dictionary()))
|
assert_eq(pty.IPCSIGNAL_SIGINT, 2, "Expected pty to have IPCSIGNAL_SIGINT.")
|
||||||
var signals = {
|
assert_eq(pty.IPCSIGNAL_SIGQUIT, 3, "Expected pty to have IPCSIGNAL_SIGQUIT.")
|
||||||
SIGHUP = 1,
|
assert_eq(pty.IPCSIGNAL_SIGILL, 4, "Expected pty to have IPCSIGNAL_SIGILL.")
|
||||||
SIGINT = 2,
|
assert_eq(pty.IPCSIGNAL_SIGTRAP, 5, "Expected pty to have IPCSIGNAL_SIGTRAP.")
|
||||||
SIGQUIT = 3,
|
assert_eq(pty.IPCSIGNAL_SIGABRT, 6, "Expected pty to have IPCSIGNAL_SIGABRT.")
|
||||||
SIGILL = 4,
|
assert_eq(pty.IPCSIGNAL_SIGFPE, 8, "Expected pty to have IPCSIGNAL_SIGFPE.")
|
||||||
SIGTRAP = 5,
|
assert_eq(pty.IPCSIGNAL_SIGKILL, 9, "Expected pty to have IPCSIGNAL_SIGKILL.")
|
||||||
SIGABRT = 6,
|
assert_eq(pty.IPCSIGNAL_SIGSEGV, 11, "Expected pty to have IPCSIGNAL_SIGSEGV.")
|
||||||
SIGFPE = 8,
|
assert_eq(pty.IPCSIGNAL_SIGPIPE, 13, "Expected pty to have IPCSIGNAL_SIGPIPE.")
|
||||||
SIGKILL = 9,
|
assert_eq(pty.IPCSIGNAL_SIGALRM, 14, "Expected pty to have IPCSIGNAL_SIGALRM.")
|
||||||
SIGSEGV = 11,
|
assert_eq(pty.IPCSIGNAL_SIGTERM, 15, "Expected pty to have IPCSIGNAL_SIGTERM.")
|
||||||
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]]
|
|
||||||
)
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue