todos:
	- Read more than one byte at a time before emitting signal.
	- Set correct termios and winp values.
	- Handle SIGWINCH stuff when resizing window.
This commit is contained in:
Leroy Hopson 2020-07-12 00:35:12 +07:00
parent 6b92606d99
commit 78eeacdc22
8 changed files with 169 additions and 1 deletions

View file

@ -98,7 +98,7 @@ cpp_library += '.' + str(bits)
# make sure our binding library is properly includes # 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(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(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. # tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=['src/']) env.Append(CPPPATH=['src/'])

View file

@ -1,4 +1,6 @@
#include "pseudoterminal.h" #include "pseudoterminal.h"
#include <pty.h>
#include <unistd.h>
using namespace godot; using namespace godot;
@ -23,6 +25,111 @@ Pseudoterminal::~Pseudoterminal()
void Pseudoterminal::_init() 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<std::mutex> 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<std::mutex> guard(write_buffer_mutex);
if (bytes_to_write > 0)
{
write(fd, write_buffer, bytes_to_write);
bytes_to_write = 0;
}
}
}
}
}
} }
void Pseudoterminal::_ready() void Pseudoterminal::_ready()
@ -31,4 +138,7 @@ void Pseudoterminal::_ready()
void Pseudoterminal::put_data(PoolByteArray data) void Pseudoterminal::put_data(PoolByteArray data)
{ {
std::lock_guard<std::mutex> guard(write_buffer_mutex);
bytes_to_write = data.size();
memcpy(write_buffer, data.read().ptr(), bytes_to_write);
} }

View file

@ -3,6 +3,8 @@
#include <Godot.hpp> #include <Godot.hpp>
#include <Node.hpp> #include <Node.hpp>
#include <thread>
#include <mutex>
namespace godot namespace godot
{ {
@ -11,6 +13,20 @@ class Pseudoterminal : public Node
{ {
GODOT_CLASS(Pseudoterminal, 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: public:
static void _register_methods(); static void _register_methods();

View file

@ -3,16 +3,58 @@ extends Container
# the window and/or screen. It also connects the terminal # the window and/or screen. It also connects the terminal
# to the input/output of the Psuedoterminal. # 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() onready var viewport = get_viewport()
func _ready(): func _ready():
$Pseudoterminal.connect("data_received", $Terminal, "write") $Pseudoterminal.connect("data_received", $Terminal, "write")
$Pseudoterminal.connect("data_received", self, "_on_data_received")
viewport.connect("size_changed", self, "_resize") viewport.connect("size_changed", self, "_resize")
_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(): func _resize():
rect_size = viewport.size rect_size = viewport.size
$Terminal.rect_size = rect_size $Terminal.rect_size = rect_size