Convert from GDNative to GDExtension

Work in progress.
This commit is contained in:
Leroy Hopson 2022-12-29 22:52:13 +13:00
parent 6b47d35835
commit 44f7e3801c
No known key found for this signature in database
GPG key ID: D2747312A6DB51AA
25 changed files with 408 additions and 304 deletions

View file

@ -346,6 +346,9 @@ platform/windows/godot_res.res
# Scons construction environment dump # Scons construction environment dump
.scons_env.json .scons_env.json
# Scons cache
.cache
# Scons progress indicator # Scons progress indicator
.scons_node_count .scons_node_count

View file

@ -0,0 +1,6 @@
[configuration]
entry_symbol = "godot_xterm_library_init"
[libraries]
linux.debug.x86_64 = "res://addons/godot_xterm/native/bin/libgodot-xterm.linux.64.so"

View file

@ -14,6 +14,8 @@ import os
import sys import sys
import subprocess import subprocess
EnsureSConsVersion(4, 0)
# Try to detect the host platform automatically. # Try to detect the host platform automatically.
# This is used if no `platform` argument is passed. # This is used if no `platform` argument is passed.
if sys.platform.startswith('linux'): if sys.platform.startswith('linux'):
@ -58,8 +60,8 @@ opts.Add(EnumVariable(
opts.Add(EnumVariable( opts.Add(EnumVariable(
'target', 'target',
'Compilation target', 'Compilation target',
'debug', 'editor',
allowed_values=('debug', 'release'), allowed_values=('editor', 'release'),
ignorecase=2 ignorecase=2
)) ))
opts.Add(EnumVariable("macos_arch", "Target macOS architecture", opts.Add(EnumVariable("macos_arch", "Target macOS architecture",
@ -73,6 +75,15 @@ opts.Add(BoolVariable(
opts.Update(env) opts.Update(env)
Help(opts.GenerateHelpText(env)) Help(opts.GenerateHelpText(env))
# Cache if SCONS_CACHE env var set.
scons_cache_path = os.environ.get("SCONS_CACHE")
if scons_cache_path is not None:
CacheDir(scons_cache_path)
Decider("MD5")
print("caching to " + scons_cache_path)
exit
# Allows 32bit builds on windows 64bit. # Allows 32bit builds on windows 64bit.
if env['platform'] == 'windows': if env['platform'] == 'windows':
if env['bits'] == '64': if env['bits'] == '64':
@ -96,7 +107,7 @@ if env['platform'] == 'linux':
env.Append(CCFLAGS=['-fPIC', '-Wwrite-strings']) env.Append(CCFLAGS=['-fPIC', '-Wwrite-strings'])
env.Append(LINKFLAGS=["-Wl,-R'$$ORIGIN'", '-static-libstdc++']) env.Append(LINKFLAGS=["-Wl,-R'$$ORIGIN'", '-static-libstdc++'])
if env['target'] == 'debug': if env['target'] == 'editor':
env.Append(CCFLAGS=['-Og', '-g']) env.Append(CCFLAGS=['-Og', '-g'])
elif env['target'] == 'release': elif env['target'] == 'release':
env.Append(CCFLAGS=['-O3']) env.Append(CCFLAGS=['-O3'])
@ -153,7 +164,7 @@ elif env['platform'] == 'osx':
env.Append(LINKFLAGS=['-Wl,-undefined,dynamic_lookup']) env.Append(LINKFLAGS=['-Wl,-undefined,dynamic_lookup'])
if env['target'] == 'debug': if env['target'] == 'editor':
env.Append(CCFLAGS=['-Og', '-g']) env.Append(CCFLAGS=['-Og', '-g'])
elif env['target'] == 'release': elif env['target'] == 'release':
env.Append(CCFLAGS=['-O3']) env.Append(CCFLAGS=['-O3'])
@ -166,14 +177,14 @@ elif env['platform'] == 'windows':
# On Windows using MSVC. # On Windows using MSVC.
if host_platform == 'windows': if host_platform == 'windows':
if env['target'] == 'debug': if env['target'] == 'editor':
env.Append(CCFLAGS=['/Z7', '/Od', '/EHsc', '/D_DEBUG', '/MDd']) env.Append(CCFLAGS=['/Z7', '/Od', '/EHsc', '/D_DEBUG', '/MDd'])
elif env['target'] == 'release': elif env['target'] == 'release':
env.Append(CCFLAGS=['/O2', '/EHsc', '/DNDEBUG', '/MD']) env.Append(CCFLAGS=['/O2', '/EHsc', '/DNDEBUG', '/MD'])
# On Windows, Linux, or MacOS using MinGW. # On Windows, Linux, or MacOS using MinGW.
elif host_platform == 'linux' or host_platform == 'osx': elif host_platform == 'linux' or host_platform == 'osx':
env.Append(CCFLAGS=['-std=c++14', '-Wwrite-strings']) env.Append(CCFLAGS=['-std=c++17', '-Wwrite-strings'])
env.Append(LINKFLAGS=[ env.Append(LINKFLAGS=[
'--static', '--static',
'-Wl,--no-undefined', '-Wl,--no-undefined',
@ -181,7 +192,7 @@ elif env['platform'] == 'windows':
'-static-libstdc++', '-static-libstdc++',
]) ])
if env['target'] == 'debug': if env['target'] == 'editor':
env.Append(CCFLAGS=['-Og', '-g']) env.Append(CCFLAGS=['-Og', '-g'])
elif env['target'] == 'release': elif env['target'] == 'release':
env.Append(CCFLAGS=['-O3']) env.Append(CCFLAGS=['-O3'])
@ -231,16 +242,15 @@ Default(libtsm)
# Build libgodot-xterm. # Build libgodot-xterm.
if env['platform'] != 'windows': if env['platform'] != 'windows':
env.Append(CXXFLAGS=['-std=c++14']) env.Append(CXXFLAGS=['-std=c++17'])
env.Append(CPPPATH=[ env.Append(CPPPATH=[
'src/', 'src/',
'thirdparty/libtsm/build/src/tsm', 'thirdparty/libtsm/build/src/tsm',
'thirdparty/libtsm/build/src/shared', 'thirdparty/libtsm/build/src/shared',
'thirdparty/godot-cpp/include/', 'thirdparty/godot-cpp/gdextension',
'thirdparty/godot-cpp/include/core/', 'thirdparty/godot-cpp/include',
'thirdparty/godot-cpp/include/gen/', 'thirdparty/godot-cpp/gen/include',
'thirdparty/godot-cpp/godot-headers/',
'thirdparty/libuv/src', 'thirdparty/libuv/src',
'thirdparty/libuv/include' 'thirdparty/libuv/include'
]) ])
@ -254,7 +264,7 @@ env.Append(LIBS=[
env['platform'], env['platform'],
env['target'], env['target'],
'wasm' if env['platform'] == 'javascript' else env['macos_arch'] if ( 'wasm' if env['platform'] == 'javascript' else env['macos_arch'] if (
env['macos_arch'] != 'universal' and env['platform'] == 'osx') else env['bits'], env['macos_arch'] != 'universal' and env['platform'] == 'osx') else 'x86_64', # FIXME use correct arch.
env['LIBSUFFIX'], env['LIBSUFFIX'],
)), )),
env.File('thirdparty/libtsm/build/bin/libtsm.{}.{}.{}{}'.format( env.File('thirdparty/libtsm/build/bin/libtsm.{}.{}.{}{}'.format(
@ -266,7 +276,7 @@ env.Append(LIBS=[
]) ])
sources = [] sources = []
sources.append('src/libgodotxtermnative.cpp') sources.append('src/register_types.cpp')
sources.append('src/terminal.cpp') sources.append('src/terminal.cpp')
@ -280,7 +290,7 @@ else:
sources.append('src/node_pty/unix/pty.cc') sources.append('src/node_pty/unix/pty.cc')
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:
#sources.append('src/node_pty/win/conpty.cc') sources.append('src/node_pty/win/conpty.cc')
env.Append(LIBS=[ env.Append(LIBS=[
env.File('thirdparty/libuv/build/{}/uv_a.lib'.format(env["target"].capitalize())), env.File('thirdparty/libuv/build/{}/uv_a.lib'.format(env["target"].capitalize())),
'Advapi32.lib', 'Advapi32.lib',

View file

@ -16,14 +16,14 @@ while [[ $# -gt 0 ]]; do
shift shift
;; ;;
*) *)
echo "Usage: ./build.sh [-t|--target <release|debug>] [--disable_pty]"; echo "Usage: ./build.sh [-t|--target <release|editor>] [--disable_pty]";
exit 128 exit 128
shift shift
;; ;;
esac esac
done done
# Set defaults. # Set defaults.
target=${target:-debug} target=${target:-editor}
disable_pty=${disable_pty:-no} disable_pty=${disable_pty:-no}
nproc=$(nproc || sysctl -n hw.ncpu) nproc=$(nproc || sysctl -n hw.ncpu)
@ -31,6 +31,9 @@ nproc=$(nproc || sysctl -n hw.ncpu)
#GODOT_DIR Get the absolute path to the directory this script is in. #GODOT_DIR Get the absolute path to the directory this script is in.
NATIVE_DIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" NATIVE_DIR="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
# Cache built files.
export SCONS_CACHE=${NATIVE_DIR}/.cache
# Run script inside a nix shell if it is available. # Run script inside a nix shell if it is available.
if command -v nix-shell && [ $NIX_PATH ] && [ -z $IN_NIX_SHELL ]; then if command -v nix-shell && [ $NIX_PATH ] && [ -z $IN_NIX_SHELL ]; then
@ -56,9 +59,10 @@ updateSubmodules GODOT_CPP_DIR ${NATIVE_DIR}/thirdparty/godot-cpp
# Build godot-cpp bindings. # Build godot-cpp bindings.
cd ${GODOT_CPP_DIR} # FIXME: Commented out to improve build time, but needs to be uncommented and run for initial build of godot-cpp.
echo "scons generate_bindings=yes target=$target -j$nproc" #cd ${GODOT_CPP_DIR}
scons generate_bindings=yes macos_arch=$(uname -m) target=$target -j$nproc #echo "scons generate_bindings=yes target=$target -j$nproc"
#scons generate_bindings=yes macos_arch=$(uname -m) target=$target -j$nproc
# Build libuv as a static library. # Build libuv as a static library.
cd ${LIBUV_DIR} cd ${LIBUV_DIR}
@ -80,7 +84,7 @@ cd ${NATIVE_DIR}
scons target=$target macos_arch=$(uname -m) disable_pty=$disable_pty -j$nproc scons target=$target macos_arch=$(uname -m) disable_pty=$disable_pty -j$nproc
# 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

View file

@ -1,23 +1,23 @@
// SPDX-FileCopyrightText: 2021-2022 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#include "libuv_utils.h" #include "libuv_utils.h"
#include <godot_cpp/classes/global_constants.hpp>
#include <uv.h> #include <uv.h>
using namespace godot; using namespace godot;
void LibuvUtils::_register_methods() { void LibuvUtils::_bind_methods() {
register_method("_init", &LibuvUtils::_init); ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_os_environ"), &LibuvUtils::get_os_environ);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_os_release"), &LibuvUtils::get_os_release);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_cwd"), &LibuvUtils::get_cwd);
register_method("get_os_environ", &LibuvUtils::get_os_environ); ClassDB::bind_static_method("LibuvUtils", D_METHOD("kill", "pid", "signum"), &LibuvUtils::kill);
register_method("get_os_release", &LibuvUtils::get_os_release);
register_method("get_cwd", &LibuvUtils::get_cwd);
register_method("kill", &LibuvUtils::kill);
} }
LibuvUtils::LibuvUtils() {} LibuvUtils::LibuvUtils() {}
LibuvUtils::~LibuvUtils() {} LibuvUtils::~LibuvUtils() {}
void LibuvUtils::_init() {}
Dictionary LibuvUtils::get_os_environ() { Dictionary LibuvUtils::get_os_environ() {
Dictionary result; Dictionary result;
@ -63,50 +63,50 @@ String LibuvUtils::get_cwd() {
return result; return result;
} }
godot_error LibuvUtils::kill(int pid, int signum) { Error LibuvUtils::kill(int pid, int signum) {
RETURN_UV_ERR(uv_kill(pid, signum)); RETURN_UV_ERR(uv_kill(pid, signum));
} }
godot_error LibuvUtils::translate_uv_errno(int uv_err) { Error LibuvUtils::translate_uv_errno(int uv_err) {
if (uv_err >= 0) if (uv_err >= 0)
return GODOT_OK; return OK;
// Rough translation of libuv error to godot error. // Rough translation of libuv error to godot error.
// Not necessarily accurate. // Not necessarily accurate.
switch (uv_err) { switch (uv_err) {
case UV_EEXIST: // file already exists case UV_EEXIST: // file already exists
return GODOT_ERR_ALREADY_EXISTS; return ERR_ALREADY_EXISTS;
case UV_EADDRINUSE: // address already in use case UV_EADDRINUSE: // address already in use
return GODOT_ERR_ALREADY_IN_USE; return ERR_ALREADY_IN_USE;
case UV_EBUSY: // resource busy or locked case UV_EBUSY: // resource busy or locked
case UV_ETXTBSY: // text file is busy case UV_ETXTBSY: // text file is busy
return GODOT_ERR_BUSY; return ERR_BUSY;
case UV_ECONNREFUSED: // connection refused case UV_ECONNREFUSED: // connection refused
return GODOT_ERR_CANT_CONNECT; return ERR_CANT_CONNECT;
case UV_ECONNABORTED: // software caused connection abort case UV_ECONNABORTED: // software caused connection abort
case UV_ECONNRESET: // connection reset by peer case UV_ECONNRESET: // connection reset by peer
case UV_EISCONN: // socket is already connected case UV_EISCONN: // socket is already connected
case UV_ENOTCONN: // socket is not connected case UV_ENOTCONN: // socket is not connected
return GODOT_ERR_CONNECTION_ERROR; return ERR_CONNECTION_ERROR;
case UV_ENODEV: // no such device case UV_ENODEV: // no such device
case UV_ENXIO: // no such device or address case UV_ENXIO: // no such device or address
case UV_ESRCH: // no such process case UV_ESRCH: // no such process
return GODOT_ERR_DOES_NOT_EXIST; return ERR_DOES_NOT_EXIST;
case UV_EROFS: // read-only file system case UV_EROFS: // read-only file system
return GODOT_ERR_FILE_CANT_WRITE; return ERR_FILE_CANT_WRITE;
case UV_EOF: // end of file case UV_EOF: // end of file
return GODOT_ERR_FILE_EOF; return ERR_FILE_EOF;
case UV_ENOENT: // no such file or directory case UV_ENOENT: // no such file or directory
return GODOT_ERR_FILE_NOT_FOUND; return ERR_FILE_NOT_FOUND;
case UV_EAI_BADFLAGS: // bad ai_flags value case UV_EAI_BADFLAGS: // bad ai_flags value
case UV_EAI_BADHINTS: // invalid value for hints case UV_EAI_BADHINTS: // invalid value for hints
@ -115,13 +115,13 @@ godot_error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_EINVAL: // invalid argument case UV_EINVAL: // invalid argument
case UV_ENOTTY: // inappropriate ioctl for device case UV_ENOTTY: // inappropriate ioctl for device
case UV_EPROTOTYPE: // protocol wrong type for socket case UV_EPROTOTYPE: // protocol wrong type for socket
return GODOT_ERR_INVALID_PARAMETER; // Parameter passed is invalid return ERR_INVALID_PARAMETER; // Parameter passed is invalid
case UV_ENOSYS: // function not implemented case UV_ENOSYS: // function not implemented
return GODOT_ERR_METHOD_NOT_FOUND; return ERR_METHOD_NOT_FOUND;
case UV_EAI_MEMORY: // out of memory case UV_EAI_MEMORY: // out of memory
return GODOT_ERR_OUT_OF_MEMORY; return ERR_OUT_OF_MEMORY;
case UV_E2BIG: // argument list too long case UV_E2BIG: // argument list too long
case UV_EFBIG: // file too large case UV_EFBIG: // file too large
@ -129,15 +129,15 @@ godot_error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_ENAMETOOLONG: // name too long case UV_ENAMETOOLONG: // name too long
case UV_EOVERFLOW: // value too large for defined data type case UV_EOVERFLOW: // value too large for defined data type
case UV_ERANGE: // result too large case UV_ERANGE: // result too large
return GODOT_ERR_PARAMETER_RANGE_ERROR; // Parameter given out of range return ERR_PARAMETER_RANGE_ERROR; // Parameter given out of range
case UV_ETIMEDOUT: case UV_ETIMEDOUT:
return GODOT_ERR_TIMEOUT; // connection timed out return ERR_TIMEOUT; // connection timed out
case UV_EACCES: // permission denied case UV_EACCES: // permission denied
case UV_EPERM: // operation not permitted case UV_EPERM: // operation not permitted
case UV_EXDEV: // cross-device link not permitted case UV_EXDEV: // cross-device link not permitted
return GODOT_ERR_UNAUTHORIZED; return ERR_UNAUTHORIZED;
case UV_EADDRNOTAVAIL: // address not available case UV_EADDRNOTAVAIL: // address not available
case UV_EAFNOSUPPORT: // address family not supported case UV_EAFNOSUPPORT: // address family not supported
@ -150,12 +150,12 @@ godot_error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_ENOTSUP: // operation not supported on socket case UV_ENOTSUP: // operation not supported on socket
case UV_EPROTONOSUPPORT: // protocol not supported case UV_EPROTONOSUPPORT: // protocol not supported
case UV_ESOCKTNOSUPPORT: // socket type not supported case UV_ESOCKTNOSUPPORT: // socket type not supported
return GODOT_ERR_UNAVAILABLE; // What is requested is return ERR_UNAVAILABLE; // What is requested is
// unsupported/unavailable // unsupported/unavailable
case UV_EAI_NODATA: // no address case UV_EAI_NODATA: // no address
case UV_EDESTADDRREQ: // destination address required case UV_EDESTADDRREQ: // destination address required
return GODOT_ERR_UNCONFIGURED; return ERR_UNCONFIGURED;
case UV_EAI_AGAIN: // temporary failure case UV_EAI_AGAIN: // temporary failure
case UV_EAI_CANCELED: // request canceled case UV_EAI_CANCELED: // request canceled
@ -191,6 +191,6 @@ godot_error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_ESPIPE: // invalid seek case UV_ESPIPE: // invalid seek
case UV_UNKNOWN: // unknown error case UV_UNKNOWN: // unknown error
default: default:
return GODOT_FAILED; // Generic fail error return FAILED; // Generic fail error
} }
} }

View file

@ -1,7 +1,7 @@
#ifndef GODOT_XTERM_UV_UTILS_H #ifndef GODOT_XTERM_UV_UTILS_H
#define GODOT_XTERM_UV_UTILS_H #define GODOT_XTERM_UV_UTILS_H
#include <Godot.hpp> #include <godot_cpp/classes/ref_counted.hpp>
#include <uv.h> #include <uv.h>
#define UV_ERR_PRINT(uv_err) \ #define UV_ERR_PRINT(uv_err) \
@ -21,27 +21,26 @@
namespace godot { namespace godot {
class LibuvUtils : public Reference { class LibuvUtils : public RefCounted {
GODOT_CLASS(LibuvUtils, Reference) GDCLASS(LibuvUtils, RefCounted)
public: public:
static void _register_methods();
LibuvUtils(); LibuvUtils();
~LibuvUtils(); ~LibuvUtils();
void _init(); static Dictionary get_os_environ();
static String get_os_release();
static String get_cwd();
Dictionary get_os_environ(); static Error kill(int pid, int signum);
String get_os_release();
String get_cwd();
godot_error kill(int pid, int signum);
public: public:
static godot_error translate_uv_errno(int uv_err); static Error translate_uv_errno(int uv_err);
protected:
static void _bind_methods();
}; };
} // namespace godot } // namespace godot
#endif // GODOT_XTERM_UV_UTILS_H #endif // GODOT_XTERM_UV_UTILS_H

View file

@ -20,7 +20,7 @@
#include "pty.h" #include "pty.h"
#include "libuv_utils.h" #include "libuv_utils.h"
#include <FuncRef.hpp> #include <godot_cpp/variant/callable.hpp>
#include <uv.h> #include <uv.h>
#include <errno.h> #include <errno.h>
@ -89,7 +89,7 @@ using namespace godot;
*/ */
struct pty_baton { struct pty_baton {
Ref<FuncRef> cb; Callable cb;
int exit_code; int exit_code;
int signal_code; int signal_code;
pid_t pid; pid_t pid;
@ -119,11 +119,11 @@ static void pty_after_waitpid(uv_async_t *);
static void pty_after_close(uv_handle_t *); static void pty_after_close(uv_handle_t *);
Array PTYUnix::fork(String p_file, int _ignored, PoolStringArray p_args, Array PTYUnix::fork(String p_file, int _ignored, PackedStringArray p_args,
PoolStringArray p_env, String p_cwd, int p_cols, int p_rows, PackedStringArray p_env, String p_cwd, int p_cols, int p_rows,
int p_uid, int p_gid, bool p_utf8, Ref<FuncRef> p_on_exit) { int p_uid, int p_gid, bool p_utf8, Callable p_on_exit) {
// file // file
char *file = p_file.alloc_c_string(); char *file = strdup(p_file.utf8().get_data());
// args // args
int i = 0; int i = 0;
@ -133,7 +133,7 @@ Array PTYUnix::fork(String p_file, int _ignored, PoolStringArray p_args,
argv[0] = strdup(file); argv[0] = strdup(file);
argv[argl - 1] = NULL; argv[argl - 1] = NULL;
for (; i < argc; i++) { for (; i < argc; i++) {
char *arg = p_args[i].alloc_c_string(); char *arg = strdup(p_args[i].utf8().get_data());
argv[i + 1] = strdup(arg); argv[i + 1] = strdup(arg);
} }
@ -143,12 +143,12 @@ Array PTYUnix::fork(String p_file, int _ignored, PoolStringArray p_args,
char **env = new char *[envc + 1]; char **env = new char *[envc + 1];
env[envc] = NULL; env[envc] = NULL;
for (; i < envc; i++) { for (; i < envc; i++) {
char *pairs = p_env[i].alloc_c_string(); char *pairs = strdup(p_env[i].utf8().get_data());
env[i] = strdup(pairs); env[i] = strdup(pairs);
} }
// cwd // cwd
char *cwd = strdup(p_cwd.alloc_c_string()); char *cwd = strdup(p_cwd.utf8().get_data());
// size // size
struct winsize winp; struct winsize winp;
@ -240,7 +240,7 @@ Array PTYUnix::fork(String p_file, int _ignored, PoolStringArray p_args,
switch (pid) { switch (pid) {
case -1: case -1:
ERR_PRINT("forkpty(3) failed."); ERR_PRINT("forkpty(3) failed.");
return Array::make(GODOT_FAILED); return Array::make(FAILED);
case 0: case 0:
if (strlen(cwd)) { if (strlen(cwd)) {
if (chdir(cwd) == -1) { if (chdir(cwd) == -1) {
@ -267,10 +267,10 @@ Array PTYUnix::fork(String p_file, int _ignored, PoolStringArray p_args,
default: default:
if (pty_nonblock(master) == -1) { if (pty_nonblock(master) == -1) {
ERR_PRINT("Could not set master fd to nonblocking."); ERR_PRINT("Could not set master fd to nonblocking.");
return Array::make(GODOT_FAILED); return Array::make(FAILED);
} }
Dictionary result = Dictionary::make(); Dictionary result;
result["fd"] = (int)master; result["fd"] = (int)master;
result["pid"] = (int)pid; result["pid"] = (int)pid;
result["pty"] = ptsname(master); result["pty"] = ptsname(master);
@ -286,10 +286,10 @@ Array PTYUnix::fork(String p_file, int _ignored, PoolStringArray p_args,
uv_thread_create(&baton->tid, pty_waitpid, static_cast<void *>(baton)); uv_thread_create(&baton->tid, pty_waitpid, static_cast<void *>(baton));
return Array::make(GODOT_OK, result); return Array::make(OK, result);
} }
return Array::make(GODOT_FAILED); return Array::make(FAILED);
} }
Array PTYUnix::open(int p_cols, int p_rows) { Array PTYUnix::open(int p_cols, int p_rows) {
@ -306,28 +306,28 @@ Array PTYUnix::open(int p_cols, int p_rows) {
if (ret == -1) { if (ret == -1) {
ERR_PRINT("openpty(3) failed."); ERR_PRINT("openpty(3) failed.");
return Array::make(GODOT_FAILED); return Array::make(FAILED);
} }
if (pty_nonblock(master) == -1) { if (pty_nonblock(master) == -1) {
ERR_PRINT("Could not set master fd to nonblocking."); ERR_PRINT("Could not set master fd to nonblocking.");
return Array::make(GODOT_FAILED); return Array::make(FAILED);
} }
if (pty_nonblock(slave) == -1) { if (pty_nonblock(slave) == -1) {
ERR_PRINT("Could not set slave fd to nonblocking."); ERR_PRINT("Could not set slave fd to nonblocking.");
return Array::make(GODOT_FAILED); return Array::make(FAILED);
} }
Dictionary dict = Dictionary::make(); Dictionary dict;
dict["master"] = master; dict["master"] = master;
dict["slave"] = slave; dict["slave"] = slave;
dict["pty"] = ptsname(master); dict["pty"] = ptsname(master);
return Array::make(GODOT_OK, dict); return Array::make(OK, dict);
} }
godot_error PTYUnix::resize(int p_fd, int p_cols, int p_rows) { Error PTYUnix::resize(int p_fd, int p_cols, int p_rows) {
int fd = p_fd; int fd = p_fd;
struct winsize winp; struct winsize winp;
@ -348,10 +348,10 @@ godot_error PTYUnix::resize(int p_fd, int p_cols, int p_rows) {
RETURN_UV_ERR(UV_ENOTTY); RETURN_UV_ERR(UV_ENOTTY);
} }
ERR_PRINT("ioctl(2) failed"); ERR_PRINT("ioctl(2) failed");
return GODOT_FAILED; return FAILED;
} }
return GODOT_OK; return OK;
} }
/** /**
@ -360,7 +360,7 @@ godot_error PTYUnix::resize(int p_fd, int p_cols, int p_rows) {
String PTYUnix::process(int p_fd, String p_tty) { String PTYUnix::process(int p_fd, String p_tty) {
int fd = p_fd; int fd = p_fd;
char *tty = p_tty.alloc_c_string(); char *tty = strdup(p_tty.utf8().get_data());
char *name = pty_getproc(fd, tty); char *name = pty_getproc(fd, tty);
std::free(tty); std::free(tty);
@ -445,9 +445,9 @@ static void pty_after_waitpid(uv_async_t *async) {
Array argv = Array::make(baton->exit_code, baton->signal_code); Array argv = Array::make(baton->exit_code, baton->signal_code);
if (baton->cb != nullptr && baton->cb->is_valid()) { if (baton->cb != nullptr && baton->cb.is_valid()) {
baton->cb->call_funcv(argv); baton->cb.callv(argv);
baton->cb = (Ref<FuncRef>)nullptr; baton->cb = (Variant)nullptr;
} }
uv_close((uv_handle_t *)async, pty_after_close); uv_close((uv_handle_t *)async, pty_after_close);
@ -663,12 +663,12 @@ static pid_t pty_forkpty(int *amaster, char *name, const struct termios *termp,
* Init * Init
*/ */
void PTYUnix::_register_methods() { void PTYUnix::_bind_methods() {
register_method("_init", &PTYUnix::_init); ClassDB::bind_method(D_METHOD("_init"), &PTYUnix::_init);
register_method("fork", &PTYUnix::fork); ClassDB::bind_method(D_METHOD("fork"), &PTYUnix::fork);
register_method("open", &PTYUnix::open); ClassDB::bind_method(D_METHOD("open"), &PTYUnix::open);
register_method("resize", &PTYUnix::resize); ClassDB::bind_method(D_METHOD("resize"), &PTYUnix::resize);
register_method("process", &PTYUnix::process); ClassDB::bind_method(D_METHOD("process"), &PTYUnix::process);
} }
void PTYUnix::_init() {} void PTYUnix::_init() {}

View file

@ -1,29 +1,32 @@
// Copyright (c) 2021, Leroy Hopson (MIT License). // SPDX-FileCopyrightText: 2021-2022 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#ifndef GODOT_XTERM_PTY_H #ifndef GODOT_XTERM_PTY_H
#define GODOT_XTERM_PTY_H #define GODOT_XTERM_PTY_H
#include <FuncRef.hpp> #include <godot_cpp/classes/ref_counted.hpp>
#include <Godot.hpp> #include <godot_cpp/variant/callable.hpp>
namespace godot { namespace godot {
class PTYUnix : public Reference { class PTYUnix : public RefCounted {
GODOT_CLASS(PTYUnix, Reference) GDCLASS(PTYUnix, RefCounted)
public: public:
Array fork(String file, Array fork(String file,
int _ignored, /* FIXME: For some reason Pipe throws int _ignored, /* FIXME: For some reason Pipe throws
ENOTSOCK in read callback if args (or another non-empty, ENOTSOCK in read callback if args (or another non-empty,
non-zero) value is in this position. */ non-zero) value is in this position. */
PoolStringArray args, PoolStringArray env, String cwd, int cols, PackedStringArray args, PackedStringArray env, String cwd, int cols,
int rows, int uid, int gid, bool utf8, Ref<FuncRef> on_exit); int rows, int uid, int gid, bool utf8, Callable on_exit);
Array open(int cols, int rows); Array open(int cols, int rows);
godot_error resize(int fd, int cols, int rows); Error resize(int fd, int cols, int rows);
String process(int fd, String tty); String process(int fd, String tty);
void _init(); void _init();
static void _register_methods();
protected:
static void _bind_methods();
}; };
} // namespace godot } // namespace godot

View file

@ -1,15 +1,16 @@
// Copyright (c) 2021, Leroy Hopson (MIT License). // SPDX-FileCopyrightText: 2021 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#ifndef GODOT_XTERM_CONPTY_H #ifndef GODOT_XTERM_CONPTY_H
#define GODOT_XTERM_CONPTY_H #define GODOT_XTERM_CONPTY_H
#include <FuncRef.hpp> #include <godot_cpp/classes/ref_counted.hpp>
#include <Godot.hpp> #include <godot_cpp/variant/callable.hpp>
namespace godot { namespace godot {
class ConPTY : public Reference { class ConPTY : public RefCounted {
GODOT_CLASS(ConPTY, Reference) GDCLASS(ConPTY, RefCounted)
public: public:
// Array fork(String file, // Array fork(String file,
@ -25,9 +26,11 @@ public:
// String process(int fd, String tty); // String process(int fd, String tty);
void _init(); void _init();
static void _register_methods();
protected:
static void _bind_methods();
}; };
} // namespace godot } // namespace godot
#endif // GODOT_XTERM_CONPTY_H #endif // GODOT_XTERM_CONPTY_H

View file

@ -2,12 +2,14 @@
#include "pipe.h" #include "pipe.h"
#include "libuv_utils.h" #include "libuv_utils.h"
#include <Dictionary.hpp> #include <godot_cpp/variant/dictionary.hpp>
#include <InputEventKey.hpp> #include <godot_cpp/variant/packed_byte_array.hpp>
#include <OS.hpp> #include <godot_cpp/classes/global_constants.hpp>
#include <ResourceLoader.hpp> #include <godot_cpp/classes/input_event_key.hpp>
#include <Theme.hpp> #include <godot_cpp/classes/os.hpp>
#include <Timer.hpp> #include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/theme.hpp>
#include <godot_cpp/classes/timer.hpp>
#include <algorithm> #include <algorithm>
#include <thread> #include <thread>
#include <vector> #include <vector>
@ -19,17 +21,16 @@
using namespace godot; using namespace godot;
void Pipe::_register_methods() { void Pipe::_bind_methods() {
register_method("_init", &Pipe::_init); ClassDB::bind_method(D_METHOD("_init"), &Pipe::_init);
register_method("poll", &Pipe::_poll_connection); ClassDB::bind_method(D_METHOD("poll"), &Pipe::_poll_connection);
register_method("open", &Pipe::open); ClassDB::bind_method(D_METHOD("open"), &Pipe::open);
register_method("write", &Pipe::write); ClassDB::bind_method(D_METHOD("write"), &Pipe::write);
register_method("get_status", &Pipe::get_status); ClassDB::bind_method(D_METHOD("get_status"), &Pipe::get_status);
register_method("close", &Pipe::close); ClassDB::bind_method(D_METHOD("close"), &Pipe::close);
register_signal<Pipe>("data_received", "data", ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
} }
Pipe::Pipe() {} Pipe::Pipe() {}
@ -47,7 +48,7 @@ void _write_cb(uv_write_t *req, int status);
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);
godot_error Pipe::open(int fd, bool ipc = false) { Error Pipe::open(int fd, bool ipc = false) {
RETURN_IF_UV_ERR(uv_pipe_init(uv_default_loop(), &handle, ipc)); RETURN_IF_UV_ERR(uv_pipe_init(uv_default_loop(), &handle, ipc));
handle.data = this; handle.data = this;
@ -58,7 +59,7 @@ godot_error Pipe::open(int fd, bool ipc = false) {
uv_read_start((uv_stream_t *)&handle, _alloc_buffer, _read_cb)); uv_read_start((uv_stream_t *)&handle, _alloc_buffer, _read_cb));
status = 1; status = 1;
return GODOT_OK; return OK;
} }
void Pipe::close() { void Pipe::close() {
@ -66,8 +67,8 @@ void Pipe::close() {
uv_run(uv_default_loop(), UV_RUN_NOWAIT); uv_run(uv_default_loop(), UV_RUN_NOWAIT);
} }
godot_error Pipe::write(PoolByteArray data) { Error Pipe::write(PackedByteArray data) {
char *s = (char *)data.read().ptr(); char *s = (char *)data.ptr();
ULONG len = data.size(); ULONG len = data.size();
uv_buf_t buf; uv_buf_t buf;
@ -80,7 +81,7 @@ godot_error Pipe::write(PoolByteArray data) {
uv_write(req, (uv_stream_t *)&handle, &buf, 1, _write_cb); uv_write(req, (uv_stream_t *)&handle, &buf, 1, _write_cb);
uv_run(uv_default_loop(), UV_RUN_NOWAIT); uv_run(uv_default_loop(), UV_RUN_NOWAIT);
return GODOT_OK; return OK;
} }
int Pipe::get_status() { int Pipe::get_status() {
@ -116,9 +117,9 @@ void _read_cb(uv_stream_t *handle, ssize_t nread, const uv_buf_t *buf) {
return; return;
} }
PoolByteArray data; PackedByteArray data;
data.resize(nread); data.resize(nread);
{ memcpy(data.write().ptr(), buf->base, nread); } { memcpy(data.ptrw(), buf->base, nread); }
std::free((char *)buf->base); std::free((char *)buf->base);
pipe->emit_signal("data_received", data); pipe->emit_signal("data_received", data);

View file

@ -1,32 +1,31 @@
// Copyright (c) 2021, Leroy Hopson (MIT License). // SPDX-FileCopyrightText: 2021-2022 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#ifndef GODOT_XTERM_PIPE_H #ifndef GODOT_XTERM_PIPE_H
#define GODOT_XTERM_PIPE_H #define GODOT_XTERM_PIPE_H
#include <Godot.hpp> #include <godot_cpp/classes/ref_counted.hpp>
#include <Reference.hpp> #include <godot_cpp/variant/packed_byte_array.hpp>
#include <uv.h> #include <uv.h>
namespace godot { namespace godot {
class Pipe : public Reference { class Pipe : public RefCounted {
GODOT_CLASS(Pipe, Reference) GDCLASS(Pipe, RefCounted)
public: public:
uv_pipe_t handle; uv_pipe_t handle;
static void _register_methods();
Pipe(); Pipe();
~Pipe(); ~Pipe();
void _init(); void _init();
godot_error open(int fd, bool ipc); Error open(int fd, bool ipc);
void close(); void close();
int get_status(); int get_status();
godot_error write(PoolByteArray data); Error write(PackedByteArray data);
void pause(); void pause();
void resume(); void resume();
@ -34,10 +33,13 @@ public:
public: public:
int status; int status;
protected:
static void _bind_methods();
private: private:
void _poll_connection(); void _poll_connection();
static godot_error _translate_error(int err); static Error _translate_error(int err);
}; };
} // namespace godot } // namespace godot

View file

@ -0,0 +1,60 @@
// Copyright (c) 2022, Leroy Hopson (MIT License).
#include "register_types.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
#include "terminal.h"
#if !defined(_PTY_DISABLED)
#include "libuv_utils.h"
#include "pipe.h"
#if defined(__linux__) || defined(__APPLE__)
#include "node_pty/unix/pty.h"
#endif
#if defined(__WIN32)
//#include "node_pty/win/conpty.h"
#endif
#endif
using namespace godot;
void initialize_godot_xterm_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ClassDB::register_class<Terminal>();
#if !defined(_PTY_DISABLED)
ClassDB::register_class<Pipe>();
ClassDB::register_class<LibuvUtils>();
#if defined(__linux__) || defined(__APPLE__)
ClassDB::register_class<PTYUnix>();
#endif
#if defined(__WIN32)
//ClassDB::register_class<ConPTY>();
#endif
#endif
}
void uninitialize_godot_xterm_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
extern "C"
// Initialization
GDExtensionBool GDE_EXPORT godot_xterm_library_init(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization);
init_obj.register_initializer(initialize_godot_xterm_module);
init_obj.register_terminator(uninitialize_godot_xterm_module);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}

View file

@ -0,0 +1,11 @@
#ifndef GODOT_XTERM_REGISTER_TYPES_H
#define GODOT_XTERM_REGISTER_TYPES_H
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_godot_xterm_module(ModuleInitializationLevel p_level);
void uninitialize_godot_xterm_module(ModuleInitializationLevel p_level);
#endif // GODOT_XTERM_REGISTER_TYPES_H

View file

@ -1,19 +1,19 @@
// Copyright (c) 2021, Leroy Hopson (MIT License). // Copyright (c) 2021, Leroy Hopson (MIT License).
#include "terminal.h" #include "terminal.h"
#include <Dictionary.hpp> #include <godot_cpp/variant/dictionary.hpp>
#include <InputEventKey.hpp> #include <godot_cpp/classes/input_event_key.hpp>
#include <InputEventMouseButton.hpp> #include <godot_cpp/classes/input_event_mouse_button.hpp>
#include <OS.hpp> #include <godot_cpp/classes/os.hpp>
#include <ResourceLoader.hpp> #include <godot_cpp/classes/resource_loader.hpp>
#include <Theme.hpp> #include <godot_cpp/classes/theme.hpp>
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <xkbcommon/xkbcommon-keysyms.h> #include <xkbcommon/xkbcommon-keysyms.h>
// For _populate_key_list(), see below. // For _populate_key_list(), see below.
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__) #if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
#include <GlobalConstants.hpp> #include <godot_cpp/classes/global_constants.hpp>
#endif #endif
using namespace godot; using namespace godot;
@ -23,22 +23,8 @@ void Terminal::_populate_key_list() {
if (!_key_list.empty()) if (!_key_list.empty())
return; return;
// The following error is thrown on the javascript platform when using // TODO: Remove GLOBAL_CONSTANT macro.
// GlobalConstants from the header: abort(Assertion failed: bad export type for #define GLOBAL_CONSTANT(VAR) VAR
// `_ZN5godot15GlobalConstants8KEY_KP_0E`: undefined). Build with -s
// ASSERTIONS=1 for more info.
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
#define GLOBAL_CONSTANT(VAR) GlobalConstants::VAR
#else
#define GLOBAL_CONSTANT(VAR) get_constant(#VAR)
const godot_dictionary _constants = godot::api->godot_get_global_constants();
const Dictionary *constants = (Dictionary *)&_constants;
auto get_constant = [constants](std::string name) -> int64_t {
godot::Variant key = (godot::Variant)(godot::String(name.c_str()));
return constants->operator[](key);
};
#endif
// Godot does not have seperate scancodes for keypad keys when NumLock is off. // Godot does not have seperate scancodes for keypad keys when NumLock is off.
// We can check the unicode value to determine whether it is off and set the // We can check the unicode value to determine whether it is off and set the
@ -226,9 +212,9 @@ static void write_cb(struct tsm_vte *vte, const char *u8, size_t len,
void *data) { void *data) {
Terminal *term = static_cast<Terminal *>(data); Terminal *term = static_cast<Terminal *>(data);
PoolByteArray bytes = PoolByteArray(); PackedByteArray bytes;
bytes.resize(len); bytes.resize(len);
{ memcpy(bytes.write().ptr(), u8, len); } { memcpy(bytes.ptrw(), u8, len); }
if (len > 0) { if (len > 0) {
if (term->input_event_key.is_valid()) { if (term->input_event_key.is_valid()) {
@ -276,51 +262,53 @@ static void bell_cb(tsm_vte *_vte, void *data) {
terminal->emit_signal("bell"); terminal->emit_signal("bell");
} }
void Terminal::_register_methods() { void Terminal::_bind_methods() {
register_method("_init", &Terminal::_init); //ClassDB::bind_method(D_METHOD("_ready"), &Terminal::_ready);
register_method("_ready", &Terminal::_ready); ClassDB::bind_method(D_METHOD("_notification"), &Terminal::_notification);
register_method("_notification", &Terminal::_notification); ClassDB::bind_method(D_METHOD("__gui_input"), &Terminal::_gui_input);
register_method("_gui_input", &Terminal::_gui_input); //ClassDB::bind_method(D_METHOD("_draw"), &Terminal::_draw);
register_method("_draw", &Terminal::_draw);
register_method("write", &Terminal::write); ClassDB::bind_method(D_METHOD("write"), &Terminal::write);
register_method("sb_up", &Terminal::sb_up); ClassDB::bind_method(D_METHOD("sb_up"), &Terminal::sb_up);
register_method("sb_down", &Terminal::sb_down); ClassDB::bind_method(D_METHOD("sb_down"), &Terminal::sb_down);
register_method("sb_reset", &Terminal::sb_reset); ClassDB::bind_method(D_METHOD("sb_reset"), &Terminal::sb_reset);
register_method("clear_sb", &Terminal::clear_sb); ClassDB::bind_method(D_METHOD("clear_sb"), &Terminal::clear_sb);
register_method("start_selection", &Terminal::start_selection); ClassDB::bind_method(D_METHOD("start_selection"), &Terminal::start_selection);
register_method("select_to_pointer", &Terminal::select_to_pointer); ClassDB::bind_method(D_METHOD("select_to_pointer"), &Terminal::select_to_pointer);
register_method("reset_selection", &Terminal::reset_selection); ClassDB::bind_method(D_METHOD("reset_selection"), &Terminal::reset_selection);
register_method("copy_selection", &Terminal::copy_selection); ClassDB::bind_method(D_METHOD("copy_selection"), &Terminal::copy_selection);
register_method("copy_all", &Terminal::copy_all); ClassDB::bind_method(D_METHOD("copy_all"), &Terminal::copy_all);
register_method("_update_theme", &Terminal::update_theme); ClassDB::bind_method(D_METHOD("_update_theme"), &Terminal::update_theme);
register_method("_update_size", &Terminal::update_theme); ClassDB::bind_method(D_METHOD("_update_size"), &Terminal::update_theme);
register_property<Terminal, Vector2>("cell_size", &Terminal::cell_size, ClassDB::bind_method(D_METHOD("get_cell_size"), &Terminal::get_cell_size);
Vector2(0, 0)); ClassDB::bind_method(D_METHOD("get_rows"), &Terminal::get_rows);
register_property<Terminal, int>("rows", &Terminal::rows, 24); ClassDB::bind_method(D_METHOD("get_cols"), &Terminal::get_cols);
register_property<Terminal, int>("cols", &Terminal::cols, 80); ClassDB::bind_method(D_METHOD("get_update_mode"), &Terminal::get_update_mode);
register_property<Terminal, int>("update_mode", &Terminal::update_mode, ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &Terminal::set_update_mode);
UpdateMode::AUTO); ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode"), "set_update_mode", "get_update_mode");
register_signal<Terminal>("data_sent", "data", ADD_SIGNAL(MethodInfo("data_sent", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY); ADD_SIGNAL(MethodInfo("key_pressed", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"),
register_signal<Terminal>("key_pressed", "data", PropertyInfo(Variant::OBJECT, "event")));
GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY, "event", ADD_SIGNAL(MethodInfo("size_changed", PropertyInfo(Variant::VECTOR2, "new_size")));
GODOT_VARIANT_TYPE_OBJECT); ADD_SIGNAL(MethodInfo("bell"));
register_signal<Terminal>("size_changed", "new_size",
GODOT_VARIANT_TYPE_VECTOR2);
register_signal<Terminal>("bell", Dictionary());
} }
Terminal::Terminal() {}
Terminal::~Terminal() {} Terminal::~Terminal() {}
void Terminal::_init() { // TODO: Investigate using UPDATE_MODE enum instead of int.
int Terminal::get_update_mode() { return update_mode; }
void Terminal::set_update_mode(int p_update_mode) { update_mode = p_update_mode; };
Vector2 Terminal::get_cell_size() { return cell_size; }
int Terminal::get_rows() { return rows; }
int Terminal::get_cols() { return cols; }
Terminal::Terminal() {
framebuffer_age = 0; framebuffer_age = 0;
update_mode = UpdateMode::AUTO; update_mode = UpdateMode::AUTO;
@ -358,16 +346,16 @@ void Terminal::_gui_input(Variant event) {
return; return;
} }
int64_t scancode = k->get_scancode(); int64_t scancode = k->get_keycode();
int64_t unicode = k->get_unicode(); int64_t unicode = k->get_unicode();
uint32_t ascii = unicode <= 127 ? unicode : 0; uint32_t ascii = unicode <= 127 ? unicode : 0;
unsigned int mods = 0; unsigned int mods = 0;
if (k->get_alt()) if (k->is_alt_pressed())
mods |= TSM_ALT_MASK; mods |= TSM_ALT_MASK;
if (k->get_control()) if (k->is_ctrl_pressed())
mods |= TSM_CONTROL_MASK; mods |= TSM_CONTROL_MASK;
if (k->get_shift()) if (k->is_shift_pressed())
mods |= TSM_SHIFT_MASK; mods |= TSM_SHIFT_MASK;
uint32_t keysym = mapkey({unicode, scancode}); uint32_t keysym = mapkey({unicode, scancode});
@ -418,8 +406,8 @@ void Terminal::update_theme() {
Color default_color) -> void { Color default_color) -> void {
Color c; Color c;
c = has_color(theme_color, "Terminal") ? get_color(theme_color, "Terminal") c = has_theme_color(theme_color, "Terminal") ? get_theme_color(theme_color, "Terminal")
: has_color_override(theme_color) ? get_color(theme_color, "") : has_theme_color_override(theme_color) ? get_theme_color(theme_color, "")
: (default_theme != nullptr && : (default_theme != nullptr &&
default_theme->has_color(theme_color, "Terminal")) default_theme->has_color(theme_color, "Terminal"))
? default_theme->get_color(theme_color, "Terminal") ? default_theme->get_color(theme_color, "Terminal")
@ -488,17 +476,17 @@ void Terminal::update_theme() {
auto load_font = [this, default_theme](String font_style) -> void { auto load_font = [this, default_theme](String font_style) -> void {
Ref<Font> fontref; Ref<Font> fontref;
if (has_font(font_style, "Terminal")) { if (has_theme_font(font_style, "Terminal")) {
fontref = get_font(font_style, "Terminal"); fontref = get_theme_font(font_style, "Terminal");
} else if (has_font_override(font_style)) { } else if (has_theme_font_override(font_style)) {
fontref = get_font(font_style, ""); fontref = get_theme_font(font_style, "");
} else if (has_font("regular", "Terminal")) { } else if (has_theme_font("regular", "Terminal")) {
fontref = get_font("regular", "Terminal"); fontref = get_theme_font("regular", "Terminal");
} else if (default_theme != nullptr && } else if (default_theme != nullptr &&
default_theme->has_font("regular", "Terminal")) { default_theme->has_font("regular", "Terminal")) {
fontref = default_theme->get_font("regular", "Terminal"); fontref = default_theme->get_font("regular", "Terminal");
} else { } else {
fontref = get_font(""); fontref = get_theme_font("");
} }
fontmap.insert(std::pair<String, Ref<Font>>(font_style, fontref)); fontmap.insert(std::pair<String, Ref<Font>>(font_style, fontref));
@ -523,7 +511,7 @@ void Terminal::draw_foreground(int row, int col, char *ch,
const tsm_screen_attr *attr, Color fgcolor) { const tsm_screen_attr *attr, Color fgcolor) {
/* Set the font */ /* Set the font */
Ref<Font> fontref = get_font(""); Ref<Font> fontref = get_theme_font("");
if (attr->bold && attr->italic) { if (attr->bold && attr->italic) {
fontref = fontmap["bold_italic"]; fontref = fontmap["bold_italic"];
@ -543,10 +531,10 @@ void Terminal::draw_foreground(int row, int col, char *ch,
int font_height = fontref.ptr()->get_height(); int font_height = fontref.ptr()->get_height();
Vector2 foreground_pos = Vector2 foreground_pos =
Vector2(col * cell_size.x, row * cell_size.y + font_height / 1.25); Vector2(col * cell_size.x, row * cell_size.y + font_height / 1.25);
draw_string(fontref, foreground_pos, ch, fgcolor); draw_string(fontref, foreground_pos, ch, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, fgcolor); // FIXME
if (attr->underline) if (attr->underline)
draw_string(fontref, foreground_pos, "_", fgcolor); draw_string(fontref, foreground_pos, "_", HORIZONTAL_ALIGNMENT_LEFT, -1, 16, fgcolor); // FIXME
} }
std::pair<Color, Color> Terminal::get_cell_colors(const tsm_screen_attr *attr) { std::pair<Color, Color> Terminal::get_cell_colors(const tsm_screen_attr *attr) {
@ -596,10 +584,10 @@ void Terminal::update_size() {
Ref<Font> fontref; Ref<Font> fontref;
if (fontmap.count("regular")) if (fontmap.count("regular"))
fontref = fontmap["regular"]; fontref = fontmap["regular"];
else if (has_font("regular", "Terminal")) else if (has_theme_font("regular", "Terminal"))
fontref = get_font("regular", "Terminal"); fontref = get_theme_font("regular", "Terminal");
else else
fontref = get_font(""); fontref = get_theme_font("");
cell_size = fontref->get_string_size("W"); cell_size = fontref->get_string_size("W");
@ -611,43 +599,43 @@ void Terminal::update_size() {
emit_signal("size_changed", Vector2(cols, rows)); emit_signal("size_changed", Vector2(cols, rows));
} }
void Terminal::write(PoolByteArray data) { void Terminal::write(PackedByteArray data) {
tsm_vte_input(vte, (char *)data.read().ptr(), data.size()); tsm_vte_input(vte, (char *)data.ptr(), data.size());
} }
void Terminal::sb_up(int num) { void Terminal::sb_up(int num) {
tsm_screen_sb_up(screen, num); tsm_screen_sb_up(screen, num);
update(); queue_redraw();
} }
void Terminal::sb_down(int num) { void Terminal::sb_down(int num) {
tsm_screen_sb_down(screen, num); tsm_screen_sb_down(screen, num);
update(); queue_redraw();
} }
void Terminal::sb_reset() { void Terminal::sb_reset() {
tsm_screen_sb_reset(screen); tsm_screen_sb_reset(screen);
update(); queue_redraw();
} }
void Terminal::clear_sb() { void Terminal::clear_sb() {
tsm_screen_clear_sb(screen); tsm_screen_clear_sb(screen);
update(); queue_redraw();
} }
void Terminal::start_selection(Vector2 position) { void Terminal::start_selection(Vector2 position) {
tsm_screen_selection_start(screen, position.x, position.y); tsm_screen_selection_start(screen, position.x, position.y);
update(); queue_redraw();
} }
void Terminal::select_to_pointer(Vector2 position) { void Terminal::select_to_pointer(Vector2 position) {
tsm_screen_selection_target(screen, position.x, position.y); tsm_screen_selection_target(screen, position.x, position.y);
update(); queue_redraw();
} }
void Terminal::reset_selection() { void Terminal::reset_selection() {
tsm_screen_selection_reset(screen); tsm_screen_selection_reset(screen);
update(); queue_redraw();
} }
String Terminal::copy_selection() { String Terminal::copy_selection() {

View file

@ -1,11 +1,13 @@
// Copyright (c) 2021, Leroy Hopson (MIT License). // SPDX-FileCopyrightText: 2021-2022 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#ifndef TERMINAL_H #ifndef TERMINAL_H
#define TERMINAL_H #define TERMINAL_H
#include <Control.hpp> #include <godot_cpp/classes/control.hpp>
#include <Font.hpp> #include <godot_cpp/classes/font.hpp>
#include <Godot.hpp> #include <godot_cpp/classes/input_event_key.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <libtsm.h> #include <libtsm.h>
#include <map> #include <map>
#include <vector> #include <vector>
@ -13,12 +15,14 @@
namespace godot { namespace godot {
class Terminal : public Control { class Terminal : public Control {
GODOT_CLASS(Terminal, Control) GDCLASS(Terminal, Control)
public: public:
Ref<InputEventKey> input_event_key; Ref<InputEventKey> input_event_key;
protected: protected:
static void _bind_methods();
tsm_screen *screen; tsm_screen *screen;
tsm_vte *vte; tsm_vte *vte;
@ -42,18 +46,15 @@ public:
Color fgcol); Color fgcol);
public: public:
static void _register_methods();
Terminal(); Terminal();
~Terminal(); ~Terminal();
void _init();
void _ready(); void _ready();
void _notification(int what); void _notification(int what);
void _gui_input(Variant event); void _gui_input(Variant event);
void _draw(); void _draw();
void write(PoolByteArray data); void write(PackedByteArray data);
void sb_up(int num); void sb_up(int num);
void sb_down(int num); void sb_down(int num);
@ -72,11 +73,16 @@ public:
ALL, ALL,
ALL_NEXT_FRAME, ALL_NEXT_FRAME,
}; };
int update_mode = UpdateMode::AUTO;
int get_update_mode();
void set_update_mode(int update_mode);
Vector2 cell_size; Vector2 cell_size = Vector2(0, 0);
int rows; Vector2 get_cell_size();
int cols; int rows = 24;
int update_mode; int get_rows();
int cols = 80;
int get_cols();
uint8_t color_palette[TSM_COLOR_NUM][3]; uint8_t color_palette[TSM_COLOR_NUM][3];

@ -1 +1 @@
Subproject commit 867374da404887337909e8b7b9de5a8acbc47569 Subproject commit 3da6db4fe41b8f3d3aaeb0dc80f1556f83fd7fe6

View file

@ -6,14 +6,12 @@ extends Object
# GDNative does not currently support registering static functions so we fake it. # GDNative does not currently support registering static functions so we fake it.
# Only the static functions of this class should be called. # Only the static functions of this class should be called.
const LibuvUtils = preload("./libuv_utils.gdns")
static func get_os_environ() -> Dictionary: static func get_os_environ() -> Dictionary:
# While Godot has OS.get_environment(), I could see a way to get all environent # While Godot has OS.get_environment(), I could see a way to get all environent
# variables, other than by OS.execute() which would require to much platform # variables, other than by OS.execute() which would require to much platform
# specific code. Easier to use libuv's utility function. # specific code. Easier to use libuv's utility function.
return LibuvUtils.new().get_os_environ() return LibuvUtils.get_os_environ()
static func get_cwd() -> String: static func get_cwd() -> String:

View file

@ -6,10 +6,6 @@
@tool @tool
extends "../pty_native.gd" extends "../pty_native.gd"
const LibuvUtils := preload("../libuv_utils.gd")
const Pipe := preload("../pipe.gdns")
const PTYUnix = preload("./pty_unix.gdns")
const DEFAULT_NAME := "xterm-256color" const DEFAULT_NAME := "xterm-256color"
const DEFAULT_COLS := 80 const DEFAULT_COLS := 80
const DEFAULT_ROWS := 24 const DEFAULT_ROWS := 24
@ -25,7 +21,7 @@ const FALLBACK_FILE = "sh"
# Any signal_number can be sent to the pty's process using the kill() function, # Any signal_number can be sent to the pty's process using the kill() function,
# these are just the signals with numbers specified in the POSIX standard. # these are just the signals with numbers specified in the POSIX standard.
enum Signal { enum IPCSignal {
SIGHUP = 1, # Hangup SIGHUP = 1, # Hangup
SIGINT = 2, # Terminal interrupt signal SIGINT = 2, # Terminal interrupt signal
SIGQUIT = 3, # Terminal quit signal SIGQUIT = 3, # Terminal quit signal
@ -68,10 +64,7 @@ var _exit_cb: Callable
# Writes data to the socket. # Writes data to the socket.
# data: The data to write. # data: The data to write.
func write(data) -> void: func write(data) -> void:
assert( assert(data is PackedByteArray or data is String, "Invalid type for argument 'data'. Should be of type PackedByteArray or String")
data is PackedByteArray or data is String,
"Invalid type for argument 'data'. Should be of type PackedByteArray or String"
)
if _pipe: if _pipe:
_pipe.write(data if data is PackedByteArray else data.to_utf8_buffer()) _pipe.write(data if data is PackedByteArray else data.to_utf8_buffer())
@ -81,7 +74,7 @@ func resize(cols: int, rows: int) -> void:
PTYUnix.new().resize(_fd, cols, rows) PTYUnix.new().resize(_fd, cols, rows)
func kill(signum: int = Signal.SIGHUP) -> void: func kill(signum: int = IPCSignal.SIGHUP) -> void:
if _pipe: if _pipe:
_pipe.close() _pipe.close()
if _pid > 0: if _pid > 0:
@ -133,13 +126,12 @@ func fork(
var parsed_env: PackedStringArray = _parse_env(final_env) var parsed_env: PackedStringArray = _parse_env(final_env)
# Exit callback. # Exit callback.
_exit_cb = Callable.new() _exit_cb = Callable(self, "on_exit")
_exit_cb.set_instance(self)
_exit_cb.set_function("_on_exit")
# Actual fork. # Actual fork.
var result = PTYUnix.new().fork( # VERY IMPORTANT: The must be set null or 0, otherwise will get an ENOTSOCK error after connecting our pipe to the fd. var result = PTYUnix.new().fork(
file, null, args, parsed_env, cwd, cols, rows, uid, gid, utf8, _exit_cb # VERY IMPORTANT: The second argument must be 0, otherwise will get an ENOTSOCK error after connecting our pipe to the fd.
file, 0, args, parsed_env, cwd, cols, rows, uid, gid, utf8, _exit_cb
) )
if result[0] != OK: if result[0] != OK:
@ -154,7 +146,7 @@ func fork(
_pid = result[1].pid _pid = result[1].pid
_pipe = Pipe.new() _pipe = Pipe.new()
_pipe.open(_fd) _pipe.open(_fd, true) # FIXME: _pipe.open(_fd) should be sufficient but requires two args.
# Must connect to signal AFTER opening, otherwise we will get error ENOTSOCK. # Must connect to signal AFTER opening, otherwise we will get error ENOTSOCK.
_pipe.connect("data_received",Callable(self,"_on_pipe_data_received")) _pipe.connect("data_received",Callable(self,"_on_pipe_data_received"))
@ -169,7 +161,7 @@ func open(cols: int = DEFAULT_COLS, rows: int = DEFAULT_ROWS) -> Array:
func _exit_tree(): func _exit_tree():
_exit_cb = null _exit_cb = null
if _pid > 1: if _pid > 1:
LibuvUtils.kill(_pid, Signal.SIGHUP) LibuvUtils.kill(_pid, IPCSignal.SIGHUP)
if _pipe: if _pipe:
while _pipe.get_status() != 0: while _pipe.get_status() != 0:
continue continue

View file

@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://c62updkby54f3"] [gd_scene load_steps=2 format=3 uid="uid://c62updkby54f3"]
[sub_resource type="GDScript" id="1"] [sub_resource type="GDScript" id="GDScript_d8lvm"]
script/source = "@tool script/source = "@tool
extends SubViewport extends SubViewport
" "
@ -11,12 +11,13 @@ handle_input_locally = false
gui_snap_controls_to_pixels = false gui_snap_controls_to_pixels = false
size = Vector2i(2, 2) size = Vector2i(2, 2)
render_target_clear_mode = 1 render_target_clear_mode = 1
script = SubResource("1") script = SubResource("GDScript_d8lvm")
[node name="Terminal" type="Control" parent="."] [node name="Terminal" type="Terminal" parent="."]
layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
offset_right = -2.0 offset_right = -2.0
offset_bottom = -2.0 offset_bottom = -2.0
grow_horizontal = 2
grow_vertical = 2

View file

@ -18,7 +18,7 @@ const DEFAULT_ENV := {TERM = DEFAULT_NAME, COLORTERM = "truecolor"}
# Any signal_number can be sent to the pty's process using the kill() function, # Any signal_number can be sent to the pty's process using the kill() function,
# these are just the signals with numbers specified in the POSIX standard. # these are just the signals with numbers specified in the POSIX standard.
const Signal = _PTYUnix.Signal const IPCSignal = _PTYUnix.IPCSignal
signal data_received(data) signal data_received(data)
signal exited(exit_code, signum) signal exited(exit_code, signum)
@ -78,8 +78,8 @@ func _init():
func _ready(): func _ready():
if terminal_path and not _terminal: if not (terminal_path.is_empty()) and not _terminal:
set_terminal_path(terminal_path) self.terminal_path = terminal_path
func set_cols(value: int): func set_cols(value: int):
@ -151,7 +151,7 @@ func resizev(size: Vector2) -> void:
# Kill the pty. # Kill the pty.
# sigint: The signal to send. By default this is SIGHUP. # sigint: The signal to send. By default this is SIGHUP.
# This is not supported on Windows. # This is not supported on Windows.
func kill(signum: int = Signal.SIGHUP) -> void: func kill(signum: int = IPCSignal.SIGHUP) -> void:
_pty_native.kill(signum) _pty_native.kill(signum)
@ -160,7 +160,7 @@ func _notification(what: int):
NOTIFICATION_PARENTED: NOTIFICATION_PARENTED:
var parent = get_parent() var parent = get_parent()
if parent is _Terminal: if parent is _Terminal:
set_terminal_path(get_path_to(parent)) self.terminal_path = get_path_to(parent)
func fork( func fork(

View file

@ -27,8 +27,8 @@ enum SelectionMode {
@export var update_mode: UpdateMode = UpdateMode.AUTO : @export var update_mode: UpdateMode = UpdateMode.AUTO :
get: get:
return update_mode # TODOConverter40 Non existent get function return update_mode # TODOConverter40 Non existent get function
set(mod_value): set(p_update_mode):
mod_value # TODOConverter40 Copy here content of set_update_mode set_update_mode(p_update_mode)
# If true, text in the terminal will be copied to the clipboard when selected. # If true, text in the terminal will be copied to the clipboard when selected.
@export var copy_on_selection: bool @export var copy_on_selection: bool
@ -90,7 +90,7 @@ func write(data) -> void:
func _flush(): func _flush():
for data in _buffer: for data in _buffer:
_native_terminal.write(data if data is PackedByteArray else data.to_utf8_buffer()) _native_terminal.write(data if data is PackedByteArray else data.to_utf8_buffer())
_native_terminal.update() _native_terminal.queue_redraw()
_buffer.clear() _buffer.clear()
@ -170,29 +170,30 @@ func _update_theme():
func _refresh(): func _refresh():
_screen.update() _screen.queue_redraw()
if update_mode == UpdateMode.AUTO: if update_mode == UpdateMode.AUTO:
_native_terminal.update_mode = UpdateMode.ALL_NEXT_FRAME _native_terminal.update_mode = UpdateMode.ALL_NEXT_FRAME
func _gui_input(event): func _gui_input(event):
_native_terminal._gui_input(event) _native_terminal.__gui_input(event) # FIXME: use _gui_input rather than __gui_input.
if event is InputEventKey and event.pressed: if event is InputEventKey and event.pressed:
# Return to bottom of scrollback buffer if we scrolled up. Ignore modifier # Return to bottom of scrollback buffer if we scrolled up. Ignore modifier
# keys pressed in isolation or if Ctrl+Shift modifier keys are pressed. # keys pressed in isolation or if Ctrl+Shift modifier keys are pressed.
if ( if (
not event.scancode in [KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META, KEY_MASK_CMD_OR_CTRL] not event.keycode in [KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META, KEY_MASK_CMD_OR_CTRL]
and not (event.control and event.shift) and not (event.ctrl_pressed and event.shift_pressed)
): ):
_native_terminal.sb_reset() _native_terminal.sb_reset()
# Prevent focus changing to other inputs when pressing Tab or Arrow keys. # Prevent focus changing to other inputs when pressing Tab or Arrow keys.
if event.scancode in [KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_TAB]: if event.keycode in [KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_TAB]:
accept_event() accept_event()
_handle_mouse_wheel(event) # FIXME
_handle_selection(event) #_handle_mouse_wheel(event)
#_handle_selection(event)
func _handle_mouse_wheel(event: InputEventMouseButton): func _handle_mouse_wheel(event: InputEventMouseButton):

View file

@ -110,10 +110,10 @@ func draw_menu():
func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void: func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
match data: match data:
tput.CURSOR_UP: # Up arrow key TPut.CURSOR_UP: # Up arrow key
selected_index = int(clamp(selected_index - 1, 0, menu_items.size() - 1)) selected_index = int(clamp(selected_index - 1, 0, menu_items.size() - 1))
draw_menu() draw_menu()
tput.CURSOR_DOWN: # Down arrow key TPut.CURSOR_DOWN: # Down arrow key
selected_index = int(clamp(selected_index + 1, 0, menu_items.size() - 1)) selected_index = int(clamp(selected_index + 1, 0, menu_items.size() - 1))
draw_menu() draw_menu()
"1": "1":
@ -127,7 +127,7 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
draw_menu() draw_menu()
# We can also match against the raw InputEventKey. # We can also match against the raw InputEventKey.
if event.scancode == KEY_ENTER: if event.keycode == KEY_ENTER:
var item = menu_items[selected_index] var item = menu_items[selected_index]
match item.name: match item.name:
@ -160,9 +160,11 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
$Terminal.grab_focus() $Terminal.grab_focus()
scene.queue_free() scene.queue_free()
"Exit": "Exit":
if OS.has_feature("JavaScript"): pass
JavaScript.eval("window.history.back() || window.close()") # FIXME
get_tree().quit() #if OS.has_feature("JavaScript"):
#JavaScript.eval("window.history.back() || window.close()")
#get_tree().quit()
func _on_Asciicast_key_pressed( func _on_Asciicast_key_pressed(

View file

@ -8,15 +8,17 @@ layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2") script = ExtResource("2")
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Terminal" type="Control" parent="."] [node name="Terminal" type="Control" parent="."]
layout_mode = 3 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
focus_mode = 1 focus_mode = 1
script = ExtResource("1") script = ExtResource("1")
copy_on_selection = false

View file

@ -29,10 +29,6 @@ config/icon="res://docs/media/icon.png"
window/vsync/use_vsync=false window/vsync/use_vsync=false
[editor_plugins]
enabled=PackedStringArray()
[rendering] [rendering]
quality/driver/driver_name="GLES2" quality/driver/driver_name="GLES2"

16
shell.nix Normal file
View file

@ -0,0 +1,16 @@
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [
vulkan-loader
alsa-lib
libGL
libGLU
xorg.libX11
xorg.libXcursor
xorg.libXext
xorg.libXfixes
xorg.libXi
xorg.libXinerama
xorg.libXrandr
];
}