Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@ export_credentials.cfg
.mono/
data_*/
mono_crash.*.json

# C++ build artifacts
*.os
*.o
*.so
*.dll
*.dylib
*.framework
.sconsign.dblite
bin/
demo/bin/
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "godot-cpp"]
path = godot-cpp
url = https://github.com/godotengine/godot-cpp.git
[submodule "libvterm"]
path = libvterm
url = https://github.com/neovim/libvterm.git
40 changes: 40 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env python

env = SConscript("godot-cpp/SConstruct")

env.Append(CPPPATH=["src/", "libvterm/include"])
sources = Glob("src/*.cpp")

libvterm_sources = Glob("libvterm/src/*.c")
sources += libvterm_sources

if env["platform"] in ["linux", "linuxbsd"]:
env.Append(LIBS=["util"])
env.Append(CPPDEFINES=["_GNU_SOURCE"])

if env["platform"] == "macos":
library = env.SharedLibrary(
"demo/bin/libshell.{}.{}.framework/libshell.{}.{}".format(
env["platform"], env["target"], env["platform"], env["target"]
),
source=sources,
)
elif env["platform"] == "ios":
if env["ios_simulator"]:
library = env.StaticLibrary(
"demo/bin/libshell.{}.{}.simulator.a".format(env["platform"], env["target"]),
source=sources,
)
else:
library = env.StaticLibrary(
"demo/bin/libshell.{}.{}.a".format(env["platform"], env["target"]),
source=sources,
)
else:
library = env.SharedLibrary(
"demo/bin/libshell{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
source=sources,
)

env.NoCache(library)
Default(library)
21 changes: 21 additions & 0 deletions demo/shell.gdextension
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[gd_resource type="GDExtension" format=3]

[configuration]

entry_symbol = "shell_library_init"
compatibility_minimum = "4.2"

[libraries]

linux.debug.x86_64 = "res://bin/libshell.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://bin/libshell.linux.template_release.x86_64.so"
linux.editor.x86_64 = "res://bin/libshell.linux.editor.x86_64.so"
windows.debug.x86_64 = "res://bin/libshell.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libshell.windows.template_release.x86_64.dll"
windows.editor.x86_64 = "res://bin/libshell.windows.editor.x86_64.dll"
macos.debug = "res://bin/libshell.macos.template_debug.framework"
macos.release = "res://bin/libshell.macos.template_release.framework"
macos.editor = "res://bin/libshell.macos.editor.framework"
macos.debug.arm64 = "res://bin/libshell.macos.template_debug.arm64.framework"
macos.release.arm64 = "res://bin/libshell.macos.template_release.arm64.framework"
macos.editor.arm64 = "res://bin/libshell.macos.editor.arm64.framework"
1 change: 1 addition & 0 deletions godot-cpp
Submodule godot-cpp added at 9ae37a
1 change: 1 addition & 0 deletions libvterm
Submodule libvterm added at 934bc2
152 changes: 152 additions & 0 deletions src/shell_pty.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include "shell_pty.h"

#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

#if defined(_WIN32)
// Windows ConPTY placeholder (requires Windows 10 SDK)
#else
#include <unistd.h>
#include <fcntl.h>
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <util.h>
#else
#include <pty.h>
#endif
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <string>
#endif

using namespace godot;

void PTY::_bind_methods() {
ClassDB::bind_method(D_METHOD("start", "command", "rows", "cols"), &PTY::start, DEFVAL(24), DEFVAL(80));
ClassDB::bind_method(D_METHOD("stop"), &PTY::stop);
ClassDB::bind_method(D_METHOD("read_data"), &PTY::read_data);
ClassDB::bind_method(D_METHOD("write_data", "data"), &PTY::write_data);
ClassDB::bind_method(D_METHOD("resize", "rows", "cols"), &PTY::resize);
ClassDB::bind_method(D_METHOD("is_running"), &PTY::is_running);
}

PTY::PTY() {
master_fd = -1;
pid = -1;
}

PTY::~PTY() {
stop();
}

bool PTY::start(const String &p_command, int p_rows, int p_cols) {
if (is_running()) {
stop();
}

#if defined(_WIN32)
UtilityFunctions::printerr("Windows ConPTY not yet implemented in this proof-of-concept.");
return false;
#else
struct winsize winp;
winp.ws_row = p_rows;
winp.ws_col = p_cols;
winp.ws_xpixel = 0;
winp.ws_ypixel = 0;

pid = forkpty(&master_fd, nullptr, nullptr, &winp);

if (pid < 0) {
UtilityFunctions::printerr("forkpty failed");
return false;
} else if (pid == 0) {
// Child process
setenv("TERM", "xterm-256color", 1);

std::string cmd = p_command.utf8().get_data();
if (cmd.empty()) {
cmd = "/bin/sh";
}

const char *args[] = { cmd.c_str(), nullptr };
execvp(args[0], (char *const *)args);

// If execvp fails
exit(1);
}

// Parent process
// Set master_fd to non-blocking
int flags = fcntl(master_fd, F_GETFL, 0);
fcntl(master_fd, F_SETFL, flags | O_NONBLOCK);

return true;
#endif
}

void PTY::stop() {
#if !defined(_WIN32)
if (pid > 0) {
kill(pid, SIGTERM);
waitpid(pid, nullptr, 0);
pid = -1;
}
if (master_fd != -1) {
close(master_fd);
master_fd = -1;
}
#endif
}

PackedByteArray PTY::read_data() {
PackedByteArray pba;
#if !defined(_WIN32)
if (master_fd != -1) {
char buffer[4096];
ssize_t bytes_read = read(master_fd, buffer, sizeof(buffer));
if (bytes_read > 0) {
pba.resize(bytes_read);
memcpy(pba.ptrw(), buffer, bytes_read);
}
}
#endif
return pba;
}

void PTY::write_data(const PackedByteArray &p_data) {
#if !defined(_WIN32)
if (master_fd != -1 && p_data.size() > 0) {
// We ignore write errors for this simple example
[[maybe_unused]] ssize_t result = write(master_fd, p_data.ptr(), p_data.size());
}
#endif
}

void PTY::resize(int p_rows, int p_cols) {
#if !defined(_WIN32)
if (master_fd != -1) {
struct winsize winp;
winp.ws_row = p_rows;
winp.ws_col = p_cols;
winp.ws_xpixel = 0;
winp.ws_ypixel = 0;
ioctl(master_fd, TIOCSWINSZ, &winp);
}
#endif
}

bool PTY::is_running() const {
#if !defined(_WIN32)
if (pid > 0) {
int status;
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
return true;
} else if (result == pid) {
// Process died, waitpid consumed the zombie
return false;
}
}
#endif
return false;
}
36 changes: 36 additions & 0 deletions src/shell_pty.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef SHELL_PTY_H
#define SHELL_PTY_H

#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/string.hpp>

namespace godot {

class PTY : public RefCounted {
GDCLASS(PTY, RefCounted)

private:
int master_fd;
int pid;

protected:
static void _bind_methods();

public:
PTY();
~PTY();

bool start(const String &p_command, int p_rows, int p_cols);
void stop();

PackedByteArray read_data();
void write_data(const PackedByteArray &p_data);
void resize(int p_rows, int p_cols);

bool is_running() const;
};

} // namespace godot

#endif // SHELL_PTY_H
Loading