diff --git a/addons/godot_xterm_native/.sconsign.dblite b/addons/godot_xterm_native/.sconsign.dblite index 6274a54..bacc365 100644 Binary files a/addons/godot_xterm_native/.sconsign.dblite and b/addons/godot_xterm_native/.sconsign.dblite differ diff --git a/addons/godot_xterm_native/SConstruct b/addons/godot_xterm_native/SConstruct index 0115286..59c71a0 100644 --- a/addons/godot_xterm_native/SConstruct +++ b/addons/godot_xterm_native/SConstruct @@ -98,7 +98,7 @@ cpp_library += '.' + str(bits) # make sure our binding library is properly includes env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/']) env.Append(LIBPATH=[cpp_bindings_path + 'bin/'] + os.environ['LD_LIBRARY_PATH'].split(':')) -env.Append(LIBS=[cpp_library, 'tsm']) +env.Append(LIBS=[cpp_library, 'tsm', 'util']) # Note util used by pseudoterminal, tsm used by terminal. # tweak this if you want to use different folders, or more folders, to store your source code in. env.Append(CPPPATH=['src/']) diff --git a/addons/godot_xterm_native/bin/x11/libgodotxtermnative.so b/addons/godot_xterm_native/bin/x11/libgodotxtermnative.so index 0ecaef6..da09712 100755 Binary files a/addons/godot_xterm_native/bin/x11/libgodotxtermnative.so and b/addons/godot_xterm_native/bin/x11/libgodotxtermnative.so differ diff --git a/addons/godot_xterm_native/src/libgodotxtermnative.os b/addons/godot_xterm_native/src/libgodotxtermnative.os index c70e3d0..e5180ca 100644 Binary files a/addons/godot_xterm_native/src/libgodotxtermnative.os and b/addons/godot_xterm_native/src/libgodotxtermnative.os differ diff --git a/addons/godot_xterm_native/src/pseudoterminal.cpp b/addons/godot_xterm_native/src/pseudoterminal.cpp index 2e6fdc1..47a309d 100644 --- a/addons/godot_xterm_native/src/pseudoterminal.cpp +++ b/addons/godot_xterm_native/src/pseudoterminal.cpp @@ -1,4 +1,6 @@ #include "pseudoterminal.h" +#include +#include using namespace godot; @@ -23,6 +25,111 @@ Pseudoterminal::~Pseudoterminal() void Pseudoterminal::_init() { + pty_thread = std::thread(&Pseudoterminal::process_pty, this); + bytes_to_write = 0; +} + +void Pseudoterminal::process_pty() +{ + int fd; + char *name; + + should_process_pty = true; + + pid_t pty_pid = forkpty(&fd, NULL, NULL, NULL); + + if (pty_pid == -1) + { + ERR_PRINT(String("Error forking pty: {0}").format(Array::make(strerror(errno)))); + should_process_pty = false; + return; + } + else if (pty_pid == 0) + { + /* Child */ + + char termenv[11] = {"TERM=xterm"}; + putenv(termenv); + + char colortermenv[20] = {"COLORTERM=truecolor"}; + putenv(colortermenv); + + char *shell = getenv("SHELL"); + execvp(shell, NULL); + } + else + { + /* Parent */ + while (1) + { + int ready = -1; + fd_set read_fds; + fd_set write_fds; + + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + FD_SET(fd, &write_fds); + + struct timeval timeout; + timeout.tv_sec = 5; + timeout.tv_usec = 0; + + ready = select(fd + 1, &read_fds, &write_fds, NULL, &timeout); + + if (ready > 0) + { + if (FD_ISSET(fd, &read_fds)) + { + std::lock_guard guard(read_buffer_mutex); + + int ret; + int bytes_read = 0; + + bytes_read = read(fd, read_buffer, 1); + + if (bytes_read <= 0) + continue; + + //while (1) + //{ + // ret = read(fd, read_buffer, 1); + + // if (ret == -1 || ret == 0) + // { + // break; + // } + // else + // { + // bytes_read += ret; + // } + //} + + PoolByteArray data = PoolByteArray(); + data.resize(bytes_read); + memcpy(data.write().ptr(), read_buffer, bytes_read); + + emit_signal("data_received", PoolByteArray(data)); + + if (bytes_read > 0) + { + Godot::print(String("read {0} bytes").format(Array::make(bytes_read))); + } + } + + if (FD_ISSET(fd, &write_fds)) + { + std::lock_guard guard(write_buffer_mutex); + + if (bytes_to_write > 0) + { + write(fd, write_buffer, bytes_to_write); + + bytes_to_write = 0; + } + } + } + } + } } void Pseudoterminal::_ready() @@ -31,4 +138,7 @@ void Pseudoterminal::_ready() void Pseudoterminal::put_data(PoolByteArray data) { + std::lock_guard guard(write_buffer_mutex); + bytes_to_write = data.size(); + memcpy(write_buffer, data.read().ptr(), bytes_to_write); } \ No newline at end of file diff --git a/addons/godot_xterm_native/src/pseudoterminal.h b/addons/godot_xterm_native/src/pseudoterminal.h index 739b33a..cfde6d1 100644 --- a/addons/godot_xterm_native/src/pseudoterminal.h +++ b/addons/godot_xterm_native/src/pseudoterminal.h @@ -3,6 +3,8 @@ #include #include +#include +#include namespace godot { @@ -11,6 +13,20 @@ class Pseudoterminal : public Node { GODOT_CLASS(Pseudoterminal, Node) +private: + std::thread pty_thread; + bool should_process_pty; + + char write_buffer[4096]; + int bytes_to_write; + std::mutex write_buffer_mutex; + + char read_buffer[4096]; + int bytes_to_read; + std::mutex read_buffer_mutex; + + void process_pty(); + public: static void _register_methods(); diff --git a/addons/godot_xterm_native/src/pseudoterminal.os b/addons/godot_xterm_native/src/pseudoterminal.os index 7be71aa..d659cf4 100644 Binary files a/addons/godot_xterm_native/src/pseudoterminal.os and b/addons/godot_xterm_native/src/pseudoterminal.os differ diff --git a/examples/terminal/container.gd b/examples/terminal/container.gd index 16e31ad..69f96cc 100644 --- a/examples/terminal/container.gd +++ b/examples/terminal/container.gd @@ -3,16 +3,58 @@ extends Container # the window and/or screen. It also connects the terminal # to the input/output of the Psuedoterminal. +const ESCAPE = 27 +const BACKSPACE = 8 +const BEEP = 7 +const SPACE = 32 +const LEFT_BRACKET = 91 +const ENTER = 10 +const BACKSPACE_ALT = 127 onready var viewport = get_viewport() func _ready(): $Pseudoterminal.connect("data_received", $Terminal, "write") + $Pseudoterminal.connect("data_received", self, "_on_data_received") viewport.connect("size_changed", self, "_resize") _resize() +func _input(event): + #return + if event is InputEventKey and event.pressed: + var data = PoolByteArray([]) + accept_event() + + # TODO: Handle more of these. + if (event.control and event.scancode == KEY_C): + data.append(3) + elif event.unicode: + data.append(event.unicode) + elif event.scancode == KEY_ENTER: + data.append(ENTER) + elif event.scancode == KEY_BACKSPACE: + data.append(BACKSPACE_ALT) + elif event.scancode == KEY_ESCAPE: + data.append(27) + elif event.scancode == KEY_TAB: + data.append(9) + elif OS.get_scancode_string(event.scancode) == "Shift": + pass + elif OS.get_scancode_string(event.scancode) == "Control": + pass + else: + pass + #push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode))) + #emit_signal("output", data) + $Pseudoterminal.put_data(data) + + +func _on_data_received(data: PoolByteArray): + print("Got data: %s" % data.get_string_from_utf8()) + + func _resize(): rect_size = viewport.size $Terminal.rect_size = rect_size