Further progress towards Godot 4.0 support

- Primary example scenes (menu, terminal, and asciicast) working but
  still a lot of warning/error messages and some regressions.
- Editor integrated terminal works, but still a lot of warning/error
  messages and some regressions.
- Added support for "blink" display attribute.
- Removed GDScript terminal code. Terminal node is now purely a
  GDExtension. So is LibuvUtils.
- GUT tests not working yet.
- Still a lot of things to fix.
- So far, only built for and manually tested on Linux x86_64.
This commit is contained in:
Leroy Hopson 2023-01-08 22:41:48 +13:00
parent aad8e39dae
commit ad7f97e493
No known key found for this signature in database
GPG key ID: D2747312A6DB51AA
30 changed files with 1385 additions and 1459 deletions

View file

@ -0,0 +1,211 @@
#include "terminal.h"
#include <xkbcommon/xkbcommon-keysyms.h>
using namespace godot;
const std::map<const char *, const char *> Terminal::FONTS = {
{"normal_font", "res://addons/godot_xterm/fonts/normal.ttf"},
{"bold_font", "res://addons/godot_xterm/fonts/bold.ttf"},
{"italics_font", "res://addons/godot_xterm/fonts/italics.ttf"},
{"bold_italics_font", "res://addons/godot_xterm/fonts/bold_italics.ttf"},
};
// TODO: Ensure it is default Xterm dark theme.
const Terminal::ColorMap Terminal::COLORS = {
{"ansi_0_color", {"#000000", TSM_COLOR_BLACK}},
{"ansi_1_color", {"#CD0000", TSM_COLOR_RED}},
{"ansi_2_color", {"#00CD00", TSM_COLOR_GREEN}},
{"ansi_3_color", {"#CDCD00", TSM_COLOR_YELLOW}},
{"ansi_4_color", {"#0000EE", TSM_COLOR_BLUE}},
{"ansi_5_color", {"#CD00CD", TSM_COLOR_MAGENTA}},
{"ansi_6_color", {"#00CDCD", TSM_COLOR_CYAN}},
{"ansi_7_color", {"#E5E5E5", TSM_COLOR_LIGHT_GREY}},
{"ansi_8_color", {"#7F7F7F", TSM_COLOR_DARK_GREY}},
{"ansi_9_color", {"#FF0000", TSM_COLOR_LIGHT_RED}},
{"ansi_10_color", {"#00FF00", TSM_COLOR_LIGHT_GREEN}},
{"ansi_11_color", {"#FFFF00", TSM_COLOR_LIGHT_YELLOW}},
{"ansi_12_color", {"#0000FC", TSM_COLOR_LIGHT_BLUE}},
{"ansi_13_color", {"#FF00FF", TSM_COLOR_LIGHT_MAGENTA}},
{"ansi_14_color", {"#00FFFF", TSM_COLOR_LIGHT_CYAN}},
{"ansi_15_color", {"#FFFFFF", TSM_COLOR_WHITE}},
{"foreground_color", {"#FFFFFF", TSM_COLOR_FOREGROUND}},
{"background_color", {"#000000", TSM_COLOR_BACKGROUND}},
};
const Terminal::KeyMap Terminal::KEY_MAP = {
// 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 appropriate scancode. Based on the patch which adds support for
// this to TextEdit/LineEdit:
// https://github.com/godotengine/godot/pull/3269/files
{{KEY_KP_0, '0'}, XKB_KEY_KP_0},
{{KEY_KP_0, '\0'}, XKB_KEY_KP_Insert},
{{KEY_KP_1, '1'}, XKB_KEY_KP_1},
{{KEY_KP_1, '\0'}, XKB_KEY_KP_End},
{{KEY_KP_2, '2'}, XKB_KEY_KP_2},
{{KEY_KP_2, '\0'}, XKB_KEY_KP_Down},
{{KEY_KP_3, '3'}, XKB_KEY_KP_3},
{{KEY_KP_3, '\0'}, XKB_KEY_KP_Page_Down},
{{KEY_KP_4, '4'}, XKB_KEY_KP_4},
{{KEY_KP_4, '\0'}, XKB_KEY_KP_Left},
{{KEY_KP_5, '5'}, XKB_KEY_KP_5},
{{KEY_KP_5, '\0'}, XKB_KEY_KP_Begin},
{{KEY_KP_6, '6'}, XKB_KEY_KP_6},
{{KEY_KP_6, '\0'}, XKB_KEY_KP_Right},
{{KEY_KP_7, '7'}, XKB_KEY_KP_7},
{{KEY_KP_7, '\0'}, XKB_KEY_KP_Home},
{{KEY_KP_8, '8'}, XKB_KEY_KP_8},
{{KEY_KP_8, '\0'}, XKB_KEY_KP_Up},
{{KEY_KP_9, '9'}, XKB_KEY_KP_9},
{{KEY_KP_9, '\0'}, XKB_KEY_KP_Page_Up},
{{KEY_KP_PERIOD, '.'}, XKB_KEY_KP_Decimal},
{{KEY_KP_PERIOD, '\0'}, XKB_KEY_KP_Delete},
{{KEY_KP_DIVIDE, '/'}, XKB_KEY_KP_Divide},
{{KEY_KP_MULTIPLY, '*'}, XKB_KEY_KP_Multiply},
{{KEY_KP_SUBTRACT, '-'}, XKB_KEY_KP_Subtract},
{{KEY_KP_ADD, '+'}, XKB_KEY_KP_Add},
{{KEY_KP_ENTER, '\0'}, XKB_KEY_KP_Enter},
//{{ , }, XKB_KEY_KP_Equal},
//{{ , }, XKB_KEY_KP_Separator},
//{{ , }, XKB_KEY_KP_Tab},
//{{ , }, XKB_KEY_KP_F1},
//{{ , }, XKB_KEY_KP_F2},
//{{ , }, XKB_KEY_KP_F3},
//{{ , }, XKB_KEY_KP_F4},
// Godot scancodes do not distinguish between uppercase and lowercase
// letters, so we can check the unicode value to determine this.
{{KEY_A, 'a'}, XKB_KEY_a},
{{KEY_A, 'A'}, XKB_KEY_A},
{{KEY_B, 'b'}, XKB_KEY_b},
{{KEY_B, 'B'}, XKB_KEY_B},
{{KEY_C, 'c'}, XKB_KEY_c},
{{KEY_C, 'C'}, XKB_KEY_C},
{{KEY_D, 'd'}, XKB_KEY_d},
{{KEY_D, 'D'}, XKB_KEY_D},
{{KEY_E, 'e'}, XKB_KEY_e},
{{KEY_E, 'E'}, XKB_KEY_E},
{{KEY_F, 'f'}, XKB_KEY_f},
{{KEY_F, 'F'}, XKB_KEY_F},
{{KEY_G, 'g'}, XKB_KEY_g},
{{KEY_G, 'G'}, XKB_KEY_G},
{{KEY_H, 'h'}, XKB_KEY_h},
{{KEY_H, 'H'}, XKB_KEY_H},
{{KEY_I, 'i'}, XKB_KEY_i},
{{KEY_I, 'I'}, XKB_KEY_I},
{{KEY_J, 'j'}, XKB_KEY_j},
{{KEY_J, 'J'}, XKB_KEY_J},
{{KEY_K, 'k'}, XKB_KEY_k},
{{KEY_K, 'K'}, XKB_KEY_K},
{{KEY_L, 'l'}, XKB_KEY_l},
{{KEY_L, 'L'}, XKB_KEY_L},
{{KEY_M, 'm'}, XKB_KEY_m},
{{KEY_M, 'M'}, XKB_KEY_M},
{{KEY_N, 'n'}, XKB_KEY_n},
{{KEY_N, 'N'}, XKB_KEY_N},
{{KEY_O, 'o'}, XKB_KEY_o},
{{KEY_O, 'O'}, XKB_KEY_O},
{{KEY_P, 'p'}, XKB_KEY_p},
{{KEY_P, 'P'}, XKB_KEY_P},
{{KEY_Q, 'q'}, XKB_KEY_q},
{{KEY_Q, 'Q'}, XKB_KEY_Q},
{{KEY_R, 'r'}, XKB_KEY_r},
{{KEY_R, 'R'}, XKB_KEY_R},
{{KEY_S, 's'}, XKB_KEY_s},
{{KEY_S, 'S'}, XKB_KEY_S},
{{KEY_T, 't'}, XKB_KEY_t},
{{KEY_T, 'T'}, XKB_KEY_T},
{{KEY_U, 'u'}, XKB_KEY_u},
{{KEY_U, 'U'}, XKB_KEY_U},
{{KEY_V, 'v'}, XKB_KEY_v},
{{KEY_V, 'V'}, XKB_KEY_V},
{{KEY_W, 'w'}, XKB_KEY_w},
{{KEY_W, 'W'}, XKB_KEY_W},
{{KEY_X, 'x'}, XKB_KEY_x},
{{KEY_X, 'X'}, XKB_KEY_X},
{{KEY_Y, 'y'}, XKB_KEY_y},
{{KEY_Y, 'Y'}, XKB_KEY_Y},
{{KEY_Z, 'z'}, XKB_KEY_z},
{{KEY_Z, 'Z'}, XKB_KEY_Z},
{{KEY_0, '0'}, XKB_KEY_0},
{{KEY_1, '1'}, XKB_KEY_1},
{{KEY_2, '2'}, XKB_KEY_2},
{{KEY_3, '3'}, XKB_KEY_3},
{{KEY_4, '4'}, XKB_KEY_4},
{{KEY_5, '5'}, XKB_KEY_5},
{{KEY_6, '6'}, XKB_KEY_6},
{{KEY_7, '7'}, XKB_KEY_7},
{{KEY_8, '8'}, XKB_KEY_8},
{{KEY_9, '9'}, XKB_KEY_9},
{{KEY_BRACKETLEFT, '['}, XKB_KEY_bracketleft},
{{KEY_BRACKETLEFT, ']'}, XKB_KEY_bracketright},
{{KEY_BRACELEFT, '{'}, XKB_KEY_braceleft},
{{KEY_BRACERIGHT, '}'}, XKB_KEY_braceright},
{{KEY_BACKSLASH, '\\'}, XKB_KEY_backslash},
{{KEY_BAR, '|'}, XKB_KEY_bar},
{{KEY_QUOTELEFT, '`'}, XKB_KEY_grave},
{{KEY_ASCIITILDE, '~'}, XKB_KEY_asciitilde},
{{KEY_SLASH, '/'}, XKB_KEY_slash},
{{KEY_QUESTION, '?'}, XKB_KEY_question},
{{KEY_HOME, '\0'}, XKB_KEY_Home},
{{KEY_BACKSPACE, '\0'}, XKB_KEY_BackSpace},
{{KEY_BACKTAB, '\0'}, XKB_KEY_ISO_Left_Tab},
{{KEY_CLEAR, '\0'}, XKB_KEY_Clear},
{{KEY_PAUSE, '\0'}, XKB_KEY_Pause},
{{KEY_SCROLLLOCK, '\0'}, XKB_KEY_Scroll_Lock},
{{KEY_SYSREQ, '\0'}, XKB_KEY_Sys_Req},
{{KEY_ESCAPE, '\0'}, XKB_KEY_Escape},
{{KEY_ENTER, '\0'}, XKB_KEY_Return},
{{KEY_INSERT, '\0'}, XKB_KEY_Insert},
{{KEY_DELETE, '\0'}, XKB_KEY_Delete},
{{KEY_PAGEUP, '\0'}, XKB_KEY_Page_Up},
{{KEY_PAGEDOWN, '\0'}, XKB_KEY_Page_Down},
{{KEY_UP, '\0'}, XKB_KEY_Up},
{{KEY_DOWN, '\0'}, XKB_KEY_Down},
{{KEY_RIGHT, '\0'}, XKB_KEY_Right},
{{KEY_LEFT, '\0'}, XKB_KEY_Left},
{{KEY_TAB, '\0'}, XKB_KEY_Tab},
//{{ , }, XKB_KEY_Linefeed},
//{{ , }, XKB_KEY_Find},
//{{ , }, XKB_KEY_Select},
{{KEY_F1, '\0'}, XKB_KEY_F1},
{{KEY_F2, '\0'}, XKB_KEY_F2},
{{KEY_F3, '\0'}, XKB_KEY_F3},
{{KEY_F4, '\0'}, XKB_KEY_F4},
{{KEY_F5, '\0'}, XKB_KEY_F5},
{{KEY_F6, '\0'}, XKB_KEY_F6},
{{KEY_F7, '\0'}, XKB_KEY_F7},
{{KEY_F8, '\0'}, XKB_KEY_F8},
{{KEY_F9, '\0'}, XKB_KEY_F9},
{{KEY_F10, '\0'}, XKB_KEY_F10},
{{KEY_F11, '\0'}, XKB_KEY_F11},
{{KEY_F12, '\0'}, XKB_KEY_F12},
{{KEY_F13, '\0'}, XKB_KEY_F13},
{{KEY_F14, '\0'}, XKB_KEY_F14},
{{KEY_F15, '\0'}, XKB_KEY_F15},
{{KEY_F16, '\0'}, XKB_KEY_F16},
{{KEY_F17, '\0'}, XKB_KEY_F17},
{{KEY_F18, '\0'}, XKB_KEY_F18},
{{KEY_F19, '\0'}, XKB_KEY_F19},
{{KEY_F20, '\0'}, XKB_KEY_F20},
{{KEY_F21, '\0'}, XKB_KEY_F21},
{{KEY_F22, '\0'}, XKB_KEY_F22},
{{KEY_F23, '\0'}, XKB_KEY_F23},
{{KEY_F24, '\0'}, XKB_KEY_F24},
{{KEY_F25, '\0'}, XKB_KEY_F25},
{{KEY_F26, '\0'}, XKB_KEY_F26},
{{KEY_F27, '\0'}, XKB_KEY_F27},
{{KEY_F28, '\0'}, XKB_KEY_F28},
{{KEY_F29, '\0'}, XKB_KEY_F29},
{{KEY_F30, '\0'}, XKB_KEY_F30},
{{KEY_F31, '\0'}, XKB_KEY_F31},
{{KEY_F32, '\0'}, XKB_KEY_F32},
{{KEY_F33, '\0'}, XKB_KEY_F33},
{{KEY_F34, '\0'}, XKB_KEY_F34},
{{KEY_F35, '\0'}, XKB_KEY_F35},
};

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#include "libuv_utils.h"
@ -8,11 +8,15 @@
using namespace godot;
void LibuvUtils::_bind_methods() {
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);
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);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("kill", "pid", "signum"), &LibuvUtils::kill);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("kill", "pid", "signum"),
&LibuvUtils::kill);
}
LibuvUtils::LibuvUtils() {}
@ -108,14 +112,14 @@ Error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_ENOENT: // no such file or directory
return ERR_FILE_NOT_FOUND;
case UV_EAI_BADFLAGS: // bad ai_flags value
case UV_EAI_BADHINTS: // invalid value for hints
case UV_EFAULT: // bad address in system call argument
case UV_EFTYPE: // inappropriate file type or format
case UV_EINVAL: // invalid argument
case UV_ENOTTY: // inappropriate ioctl for device
case UV_EPROTOTYPE: // protocol wrong type for socket
return ERR_INVALID_PARAMETER; // Parameter passed is invalid
case UV_EAI_BADFLAGS: // bad ai_flags value
case UV_EAI_BADHINTS: // invalid value for hints
case UV_EFAULT: // bad address in system call argument
case UV_EFTYPE: // inappropriate file type or format
case UV_EINVAL: // invalid argument
case UV_ENOTTY: // inappropriate ioctl for device
case UV_EPROTOTYPE: // protocol wrong type for socket
return ERR_INVALID_PARAMETER; // Parameter passed is invalid
case UV_ENOSYS: // function not implemented
return ERR_METHOD_NOT_FOUND;
@ -123,12 +127,12 @@ Error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_EAI_MEMORY: // out of memory
return ERR_OUT_OF_MEMORY;
case UV_E2BIG: // argument list too long
case UV_EFBIG: // file too large
case UV_EMSGSIZE: // message too long
case UV_ENAMETOOLONG: // name too long
case UV_EOVERFLOW: // value too large for defined data type
case UV_ERANGE: // result too large
case UV_E2BIG: // argument list too long
case UV_EFBIG: // file too large
case UV_EMSGSIZE: // message too long
case UV_ENAMETOOLONG: // name too long
case UV_EOVERFLOW: // value too large for defined data type
case UV_ERANGE: // result too large
return ERR_PARAMETER_RANGE_ERROR; // Parameter given out of range
case UV_ETIMEDOUT:
@ -139,19 +143,19 @@ Error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_EXDEV: // cross-device link not permitted
return ERR_UNAUTHORIZED;
case UV_EADDRNOTAVAIL: // address not available
case UV_EAFNOSUPPORT: // address family not supported
case UV_EAGAIN: // resource temporarily unavailable
case UV_EAI_ADDRFAMILY: // address family not supported
case UV_EAI_FAMILY: // ai_family not supported
case UV_EAI_SERVICE: // service not available for socket type
case UV_EAI_SOCKTYPE: // socket type not supported
case UV_ENOPROTOOPT: // protocol not available
case UV_ENOTSUP: // operation not supported on socket
case UV_EPROTONOSUPPORT: // protocol not supported
case UV_ESOCKTNOSUPPORT: // socket type not supported
return ERR_UNAVAILABLE; // What is requested is
// unsupported/unavailable
case UV_EADDRNOTAVAIL: // address not available
case UV_EAFNOSUPPORT: // address family not supported
case UV_EAGAIN: // resource temporarily unavailable
case UV_EAI_ADDRFAMILY: // address family not supported
case UV_EAI_FAMILY: // ai_family not supported
case UV_EAI_SERVICE: // service not available for socket type
case UV_EAI_SOCKTYPE: // socket type not supported
case UV_ENOPROTOOPT: // protocol not available
case UV_ENOTSUP: // operation not supported on socket
case UV_EPROTONOSUPPORT: // protocol not supported
case UV_ESOCKTNOSUPPORT: // socket type not supported
return ERR_UNAVAILABLE; // What is requested is
// unsupported/unavailable
case UV_EAI_NODATA: // no address
case UV_EDESTADDRREQ: // destination address required
@ -191,6 +195,6 @@ Error LibuvUtils::translate_uv_errno(int uv_err) {
case UV_ESPIPE: // invalid seek
case UV_UNKNOWN: // unknown error
default:
return FAILED; // Generic fail error
return FAILED; // Generic fail error
}
}

View file

@ -1,19 +1,8 @@
// Copyright (c) 2021, Leroy Hopson (MIT License).
// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SDPX-License-Identifier: MIT
#include "pipe.h"
#include "libuv_utils.h"
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/classes/input_event_key.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/theme.hpp>
#include <godot_cpp/classes/timer.hpp>
#include <algorithm>
#include <thread>
#include <vector>
#include <xkbcommon/xkbcommon-keysyms.h>
#ifndef ULONG
#define ULONG size_t
@ -22,15 +11,16 @@
using namespace godot;
void Pipe::_bind_methods() {
ClassDB::bind_method(D_METHOD("_init"), &Pipe::_init);
ClassDB::bind_method(D_METHOD("_init"), &Pipe::_init);
ClassDB::bind_method(D_METHOD("poll"), &Pipe::_poll_connection);
ClassDB::bind_method(D_METHOD("open"), &Pipe::open);
ClassDB::bind_method(D_METHOD("open", "fd", "ipc"), &Pipe::open);
ClassDB::bind_method(D_METHOD("write"), &Pipe::write);
ClassDB::bind_method(D_METHOD("get_status"), &Pipe::get_status);
ClassDB::bind_method(D_METHOD("close"), &Pipe::close);
ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
ADD_SIGNAL(MethodInfo("data_received",
PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
}
Pipe::Pipe() {}

View file

@ -1,5 +1,3 @@
// Copyright (c) 2022, Leroy Hopson (MIT License).
#include "register_types.h"
#include <gdextension_interface.h>
@ -25,9 +23,9 @@ using namespace godot;
void initialize_godot_xterm_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
return;
}
ClassDB::register_class<Terminal>();
#if !defined(_PTY_DISABLED)
ClassDB::register_class<Pipe>();
@ -36,25 +34,30 @@ void initialize_godot_xterm_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<PTYUnix>();
#endif
#if defined(__WIN32)
//ClassDB::register_class<ConPTY>();
// ClassDB::register_class<ConPTY>();
#endif
#endif
}
void uninitialize_godot_xterm_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
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);
// 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);
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();
return init_obj.init();
}

File diff suppressed because it is too large Load diff

View file

@ -1,93 +1,165 @@
// SPDX-FileCopyrightText: 2021-2022 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson <godot-xterm@leroy.geek.nz>
// SPDX-License-Identifier: MIT
#ifndef TERMINAL_H
#define TERMINAL_H
#ifndef GODOT_XTERM_TERMINAL_H
#define GODOT_XTERM_TERMINAL_H
#include <godot_cpp/classes/control.hpp>
#include <godot_cpp/classes/font.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/input_event_key.hpp>
#include <godot_cpp/classes/input_event_mouse.hpp>
#include <godot_cpp/classes/input_event_mouse_button.hpp>
#include <godot_cpp/classes/sub_viewport.hpp>
#include <godot_cpp/classes/texture_rect.hpp>
#include <godot_cpp/classes/timer.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <libtsm.h>
#include <map>
#include <vector>
namespace godot {
using namespace godot;
class Terminal : public Control {
GDCLASS(Terminal, Control)
public:
Ref<InputEventKey> input_event_key;
protected:
static void _bind_methods();
tsm_screen *screen;
tsm_vte *vte;
private:
static const uint8_t default_color_palette[TSM_COLOR_NUM][3];
static std::map<std::pair<int64_t, int64_t>, int> _key_list;
static void _populate_key_list();
static uint32_t mapkey(std::pair<int64_t, int64_t> key);
std::map<int, Color> palette = {};
std::map<String, Ref<Font>> fontmap = {};
void update_size();
void update_theme();
public:
std::pair<Color, Color> get_cell_colors(const tsm_screen_attr *attr);
void draw_background(int row, int col, Color bgcol, int width);
void draw_foreground(int row, int col, char *ch, const tsm_screen_attr *attr,
Color fgcol);
public:
Terminal();
~Terminal();
void _ready();
void _notification(int what);
void _gui_input(Variant event);
void _draw();
void write(PackedByteArray data);
void sb_up(int num);
void sb_down(int num);
void sb_reset();
void clear_sb();
void start_selection(Vector2 position);
void select_to_pointer(Vector2 position);
void reset_selection();
String copy_selection();
String copy_all();
enum UpdateMode {
DISABLED,
AUTO,
ALL,
ALL_NEXT_FRAME,
};
int update_mode = UpdateMode::AUTO;
int get_update_mode();
void set_update_mode(int update_mode);
static const UpdateMode UPDATE_MODE_DISABLED = UpdateMode::DISABLED;
static const UpdateMode UPDATE_MODE_AUTO = UpdateMode::AUTO;
static const UpdateMode UPDATE_MODE_ALL = UpdateMode::ALL;
static const UpdateMode UPDATE_MODE_ALL_NEXT_FRAME =
UpdateMode::ALL_NEXT_FRAME;
bool copy_on_selection = false;
void set_copy_on_selection(bool value);
bool get_copy_on_selection();
UpdateMode update_mode = UPDATE_MODE_AUTO;
void set_update_mode(UpdateMode value);
UpdateMode get_update_mode();
double bell_cooldown = 0.1f;
void set_bell_cooldown(double value);
double get_bell_cooldown();
bool bell_muted = false;
void set_bell_muted(bool value);
bool get_bell_muted();
bool blink_enabled = true;
void set_blink_enabled(bool value);
bool get_blink_enabled();
double blink_time_on = 0.6;
void set_blink_time_on(double value);
double get_blink_time_on();
double blink_time_off = 0.3;
void set_blink_time_off(double value);
double get_blink_time_off();
void clear();
String copy_all();
String copy_selection();
int get_cols();
int get_rows();
void write(Variant data);
void _gui_input(Ref<InputEvent> event);
void _notification(int what);
void _flush();
void _on_back_buffer_draw();
void _on_selection_held();
void _toggle_blink();
protected:
static void _bind_methods();
private:
struct ColorDef {
const char *default_color;
tsm_vte_color tsm_color;
};
struct ThemeCache {
int font_size = 0;
std::map<String, Ref<Font>> fonts = std::map<String, Ref<Font>>{};
std::map<String, Color> colors = std::map<String, Color>{};
} theme_cache;
typedef std::map<const char *, ColorDef> ColorMap;
typedef std::pair<Color, Color> ColorPair;
typedef std::map<const char *, const char *> FontMap;
typedef std::map<std::pair<Key, char32_t>, uint32_t> KeyMap;
static const KeyMap KEY_MAP;
static const ColorMap COLORS;
static const FontMap FONTS;
enum SelectionMode { NONE, POINTER };
Control *back_buffer = new Control();
SubViewport *sub_viewport = new SubViewport();
TextureRect *front_buffer = new TextureRect();
Timer *bell_timer = new Timer();
Timer *blink_timer = new Timer();
Timer *selection_timer = new Timer();
tsm_screen *screen;
tsm_vte *vte;
Array write_buffer = Array();
int cols = 80;
int rows = 24;
// Whether blinking characters are visible. Not whether blinking is enabled
// which is determined by `blink_enabled`.
bool blink_on = true;
Vector2 cell_size = Vector2(0, 0);
Vector2 get_cell_size();
int rows = 24;
int get_rows();
int cols = 80;
int get_cols();
uint8_t color_palette[TSM_COLOR_NUM][3];
std::map<int, Color> palette = {};
tsm_age_t framebuffer_age;
};
} // namespace godot
#endif // TERMINAL_H
Ref<InputEventKey> last_input_event_key;
bool selecting = false;
SelectionMode selection_mode = SelectionMode::NONE;
static void _bell_cb(tsm_vte *vte, void *data);
static int _text_draw_cb(tsm_screen *con, uint64_t id, const uint32_t *ch,
size_t len, unsigned int width, unsigned int col,
unsigned int row, const tsm_screen_attr *attr,
tsm_age_t age, void *data);
static void _write_cb(tsm_vte *vte, const char *u8, size_t len, void *data);
void _draw_background(int row, int col, Color bgcol, int width);
void _draw_foreground(int row, int col, char *ch, const tsm_screen_attr *attr,
Color fgcol);
ColorPair _get_cell_colors(const tsm_screen_attr *attr);
void _handle_key_input(Ref<InputEventKey> event);
void _handle_mouse_wheel(Ref<InputEventMouseButton> event);
void _handle_selection(Ref<InputEventMouse> event);
void _recalculate_size();
void _refresh();
void _update_theme_item_cache();
};
VARIANT_ENUM_CAST(Terminal, UpdateMode);
#endif // GODOT_XTERM_TERMINAL_H