diff --git a/CHANGELOG.md b/CHANGELOG.md index d0cec55..f05b14f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Implementation of Terminal node from GDScript to GDNative using [Aetf's patched version of libtsm](https://github.com/Aetf/libtsm). +- Move input handling to the Terminal node itself, rather than handling it in a seperate Control node. diff --git a/addons/godot_xterm/native/SConstruct b/addons/godot_xterm/native/SConstruct index 1338f0b..582f04d 100644 --- a/addons/godot_xterm/native/SConstruct +++ b/addons/godot_xterm/native/SConstruct @@ -99,7 +99,7 @@ else: cpp_library += '.' + str(bits) # make sure our binding library is properly includes -env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/', libtsm_path + 'src/tsm']) +env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/', libtsm_path + 'src/tsm', libtsm_path + 'external']) env.Append(LIBPATH=[cpp_bindings_path + 'bin/', libtsm_path + 'build/src/tsm']) env.Append(LIBS=[cpp_library, 'tsm', 'util']) # Note util used by pseudoterminal, tsm used by terminal. diff --git a/addons/godot_xterm/native/src/terminal.cpp b/addons/godot_xterm/native/src/terminal.cpp index a40371c..e41b7d8 100644 --- a/addons/godot_xterm/native/src/terminal.cpp +++ b/addons/godot_xterm/native/src/terminal.cpp @@ -1,6 +1,10 @@ #include "terminal.h" #include +#include +#include +#include #include +#include using namespace godot; @@ -27,6 +31,170 @@ const uint8_t Terminal::default_color_palette[TSM_COLOR_NUM][3] = { [TSM_COLOR_BACKGROUND] = {0x00, 0x00, 0x00}, }; +const std::map, uint32_t> Terminal::keymap = { + + // 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 + {{'0', GlobalConstants::KEY_KP_0}, XKB_KEY_KP_0}, + {{0b0, GlobalConstants::KEY_KP_0}, XKB_KEY_KP_Insert}, + {{'1', GlobalConstants::KEY_KP_1}, XKB_KEY_KP_1}, + {{0b0, GlobalConstants::KEY_KP_1}, XKB_KEY_KP_End}, + {{'2', GlobalConstants::KEY_KP_2}, XKB_KEY_KP_2}, + {{0b0, GlobalConstants::KEY_KP_2}, XKB_KEY_KP_Down}, + {{'3', GlobalConstants::KEY_KP_3}, XKB_KEY_KP_3}, + {{0b0, GlobalConstants::KEY_KP_3}, XKB_KEY_KP_Page_Down}, + {{'4', GlobalConstants::KEY_KP_4}, XKB_KEY_KP_4}, + {{0b0, GlobalConstants::KEY_KP_4}, XKB_KEY_KP_Left}, + {{'5', GlobalConstants::KEY_KP_5}, XKB_KEY_KP_5}, + {{0b0, GlobalConstants::KEY_KP_5}, XKB_KEY_KP_Begin}, + {{'6', GlobalConstants::KEY_KP_6}, XKB_KEY_KP_6}, + {{0b0, GlobalConstants::KEY_KP_6}, XKB_KEY_KP_Right}, + {{'7', GlobalConstants::KEY_KP_7}, XKB_KEY_KP_7}, + {{0b0, GlobalConstants::KEY_KP_7}, XKB_KEY_KP_Home}, + {{'8', GlobalConstants::KEY_KP_8}, XKB_KEY_KP_8}, + {{0b0, GlobalConstants::KEY_KP_8}, XKB_KEY_KP_Up}, + {{'9', GlobalConstants::KEY_KP_9}, XKB_KEY_KP_9}, + {{0b0, GlobalConstants::KEY_KP_9}, XKB_KEY_KP_Page_Up}, + {{'.', GlobalConstants::KEY_KP_PERIOD}, XKB_KEY_KP_Decimal}, + {{0b0, GlobalConstants::KEY_KP_PERIOD}, XKB_KEY_KP_Delete}, + {{'/', GlobalConstants::KEY_KP_DIVIDE}, XKB_KEY_KP_Divide}, + {{'*', GlobalConstants::KEY_KP_MULTIPLY}, XKB_KEY_KP_Multiply}, + {{'-', GlobalConstants::KEY_KP_SUBTRACT}, XKB_KEY_KP_Subtract}, + {{'+', GlobalConstants::KEY_KP_ADD}, XKB_KEY_KP_Add}, + {{0b0, GlobalConstants::KEY_KP_ENTER}, 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. + {{'a', GlobalConstants::KEY_A}, XKB_KEY_a}, + {{'A', GlobalConstants::KEY_A}, XKB_KEY_A}, + {{'b', GlobalConstants::KEY_B}, XKB_KEY_b}, + {{'B', GlobalConstants::KEY_B}, XKB_KEY_B}, + {{'c', GlobalConstants::KEY_C}, XKB_KEY_c}, + {{'C', GlobalConstants::KEY_C}, XKB_KEY_C}, + {{'d', GlobalConstants::KEY_D}, XKB_KEY_d}, + {{'D', GlobalConstants::KEY_D}, XKB_KEY_D}, + {{'e', GlobalConstants::KEY_E}, XKB_KEY_e}, + {{'E', GlobalConstants::KEY_E}, XKB_KEY_E}, + {{'f', GlobalConstants::KEY_F}, XKB_KEY_f}, + {{'F', GlobalConstants::KEY_F}, XKB_KEY_F}, + {{'g', GlobalConstants::KEY_G}, XKB_KEY_g}, + {{'G', GlobalConstants::KEY_G}, XKB_KEY_G}, + {{'h', GlobalConstants::KEY_H}, XKB_KEY_h}, + {{'H', GlobalConstants::KEY_H}, XKB_KEY_H}, + {{'i', GlobalConstants::KEY_I}, XKB_KEY_i}, + {{'I', GlobalConstants::KEY_I}, XKB_KEY_I}, + {{'j', GlobalConstants::KEY_J}, XKB_KEY_j}, + {{'J', GlobalConstants::KEY_J}, XKB_KEY_J}, + {{'k', GlobalConstants::KEY_K}, XKB_KEY_k}, + {{'K', GlobalConstants::KEY_K}, XKB_KEY_K}, + {{'l', GlobalConstants::KEY_L}, XKB_KEY_l}, + {{'L', GlobalConstants::KEY_L}, XKB_KEY_L}, + {{'m', GlobalConstants::KEY_M}, XKB_KEY_m}, + {{'M', GlobalConstants::KEY_M}, XKB_KEY_M}, + {{'n', GlobalConstants::KEY_N}, XKB_KEY_n}, + {{'N', GlobalConstants::KEY_N}, XKB_KEY_N}, + {{'o', GlobalConstants::KEY_O}, XKB_KEY_o}, + {{'O', GlobalConstants::KEY_O}, XKB_KEY_O}, + {{'p', GlobalConstants::KEY_P}, XKB_KEY_p}, + {{'P', GlobalConstants::KEY_P}, XKB_KEY_P}, + {{'q', GlobalConstants::KEY_Q}, XKB_KEY_q}, + {{'Q', GlobalConstants::KEY_Q}, XKB_KEY_Q}, + {{'r', GlobalConstants::KEY_R}, XKB_KEY_r}, + {{'R', GlobalConstants::KEY_R}, XKB_KEY_R}, + {{'s', GlobalConstants::KEY_S}, XKB_KEY_s}, + {{'S', GlobalConstants::KEY_S}, XKB_KEY_S}, + {{'t', GlobalConstants::KEY_T}, XKB_KEY_t}, + {{'T', GlobalConstants::KEY_T}, XKB_KEY_T}, + {{'u', GlobalConstants::KEY_U}, XKB_KEY_u}, + {{'U', GlobalConstants::KEY_U}, XKB_KEY_U}, + {{'v', GlobalConstants::KEY_V}, XKB_KEY_v}, + {{'V', GlobalConstants::KEY_V}, XKB_KEY_V}, + {{'w', GlobalConstants::KEY_W}, XKB_KEY_w}, + {{'W', GlobalConstants::KEY_W}, XKB_KEY_W}, + {{'x', GlobalConstants::KEY_X}, XKB_KEY_x}, + {{'X', GlobalConstants::KEY_X}, XKB_KEY_X}, + {{'y', GlobalConstants::KEY_Y}, XKB_KEY_y}, + {{'Y', GlobalConstants::KEY_Y}, XKB_KEY_Y}, + {{'z', GlobalConstants::KEY_Z}, XKB_KEY_z}, + {{'Z', GlobalConstants::KEY_Z}, XKB_KEY_Z}, + + {{'0', GlobalConstants::KEY_0}, XKB_KEY_0}, + {{'1', GlobalConstants::KEY_1}, XKB_KEY_1}, + {{'2', GlobalConstants::KEY_2}, XKB_KEY_2}, + {{'3', GlobalConstants::KEY_3}, XKB_KEY_3}, + {{'4', GlobalConstants::KEY_4}, XKB_KEY_4}, + {{'5', GlobalConstants::KEY_5}, XKB_KEY_5}, + {{'6', GlobalConstants::KEY_6}, XKB_KEY_6}, + {{'7', GlobalConstants::KEY_7}, XKB_KEY_7}, + {{'8', GlobalConstants::KEY_8}, XKB_KEY_8}, + {{'9', GlobalConstants::KEY_9}, XKB_KEY_9}, + + {{'[', GlobalConstants::KEY_BRACKETLEFT}, XKB_KEY_bracketleft}, + {{'[', GlobalConstants::KEY_BRACKETLEFT}, XKB_KEY_bracketright}, + {{'{', GlobalConstants::KEY_BRACELEFT}, XKB_KEY_braceleft}, + {{'}', GlobalConstants::KEY_BRACERIGHT}, XKB_KEY_braceright}, + + {{'\\', GlobalConstants::KEY_BACKSLASH}, XKB_KEY_backslash}, + {{'|', GlobalConstants::KEY_BAR}, XKB_KEY_bar}, + {{'`', GlobalConstants::KEY_QUOTELEFT}, XKB_KEY_grave}, + {{'~', GlobalConstants::KEY_ASCIITILDE}, XKB_KEY_asciitilde}, + {{'/', GlobalConstants::KEY_SLASH}, XKB_KEY_slash}, + {{'?', GlobalConstants::KEY_QUESTION}, XKB_KEY_question}, + + {{0, GlobalConstants::KEY_HOME}, XKB_KEY_Home}, + {{0, GlobalConstants::KEY_BACKSPACE}, XKB_KEY_BackSpace}, + {{0, GlobalConstants::KEY_BACKTAB}, XKB_KEY_ISO_Left_Tab}, + {{0, GlobalConstants::KEY_CLEAR}, XKB_KEY_Clear}, + {{0, GlobalConstants::KEY_PAUSE}, XKB_KEY_Pause}, + {{0, GlobalConstants::KEY_SCROLLLOCK}, XKB_KEY_Scroll_Lock}, + {{0, GlobalConstants::KEY_SYSREQ}, XKB_KEY_Sys_Req}, + {{0, GlobalConstants::KEY_ESCAPE}, XKB_KEY_Escape}, + {{0, GlobalConstants::KEY_ENTER}, XKB_KEY_Return}, + {{0, GlobalConstants::KEY_INSERT}, XKB_KEY_Insert}, + {{0, GlobalConstants::KEY_DELETE}, XKB_KEY_Delete}, + {{0, GlobalConstants::KEY_PAGEUP}, XKB_KEY_Page_Up}, + {{0, GlobalConstants::KEY_PAGEDOWN}, XKB_KEY_Page_Down}, + {{0, GlobalConstants::KEY_UP}, XKB_KEY_Up}, + {{0, GlobalConstants::KEY_DOWN}, XKB_KEY_Down}, + {{0, GlobalConstants::KEY_RIGHT}, XKB_KEY_Right}, + {{0, GlobalConstants::KEY_LEFT}, XKB_KEY_Left}, + {{0, GlobalConstants::KEY_TAB}, XKB_KEY_Tab}, + //{{ , }, XKB_KEY_Linefeed}, + //{{ , }, XKB_KEY_Find}, + //{{ , }, XKB_KEY_Select}, + + {{0, GlobalConstants::KEY_F1}, XKB_KEY_F1}, + {{0, GlobalConstants::KEY_F2}, XKB_KEY_F2}, + {{0, GlobalConstants::KEY_F3}, XKB_KEY_F3}, + {{0, GlobalConstants::KEY_F4}, XKB_KEY_F4}, + {{0, GlobalConstants::KEY_F5}, XKB_KEY_F5}, + {{0, GlobalConstants::KEY_F6}, XKB_KEY_F6}, + {{0, GlobalConstants::KEY_F7}, XKB_KEY_F7}, + {{0, GlobalConstants::KEY_F8}, XKB_KEY_F8}, + {{0, GlobalConstants::KEY_F9}, XKB_KEY_F9}, + {{0, GlobalConstants::KEY_F10}, XKB_KEY_F10}, + {{0, GlobalConstants::KEY_F11}, XKB_KEY_F11}, + {{0, GlobalConstants::KEY_F12}, XKB_KEY_F12}, + {{0, GlobalConstants::KEY_F13}, XKB_KEY_F13}, + {{0, GlobalConstants::KEY_F14}, XKB_KEY_F14}, + {{0, GlobalConstants::KEY_F15}, XKB_KEY_F15}, + {{0, GlobalConstants::KEY_F16}, XKB_KEY_F16}, + //{{0, GlobalConstants::KEY_F17}, XKB_KEY_F17}, + //{{0, GlobalConstants::KEY_F18}, XKB_KEY_F18}, + //{{0, GlobalConstants::KEY_F19}, XKB_KEY_F19}, + //{{0, GlobalConstants::KEY_F20}, XKB_KEY_F20}, +}; + static struct { Color col; @@ -41,6 +209,13 @@ static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data { Terminal *term = static_cast(data); + + PoolByteArray bytes = PoolByteArray(); + + for (int i = 0; i < len; i++) + bytes.append(u8[i]); + + term->emit_signal("data_read", bytes); } static int text_draw_cb(struct tsm_screen *con, @@ -86,7 +261,7 @@ void Terminal::_register_methods() register_method("_init", &Terminal::_init); register_method("_ready", &Terminal::_ready); - register_method("_input", &Terminal::_input); + register_method("_gui_input", &Terminal::_gui_input); register_method("_draw", &Terminal::_draw); register_method("write", &Terminal::write); @@ -94,6 +269,8 @@ void Terminal::_register_methods() //register_property("rows", &Terminal::rows, 24); //register_property("cols", &Terminal::cols, 80); + + register_signal("data_read", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY); } Terminal::Terminal() @@ -148,8 +325,34 @@ void Terminal::_notification(int what) } } -void Terminal::_input(Variant event) +void Terminal::_gui_input(Variant event) { + Ref k = event; + + if (k.is_valid()) + { + if (!k->is_pressed()) + { + return; + } + + int64_t scancode = k->get_scancode(); + int64_t unicode = k->get_unicode(); + uint32_t ascii = unicode <= 127 ? unicode : 0; + + unsigned int mods = 0; + if (k->get_alt()) + mods |= TSM_ALT_MASK; + if (k->get_control()) + mods |= TSM_CONTROL_MASK; + if (k->get_shift()) + mods |= TSM_SHIFT_MASK; + + auto iter = keymap.find({unicode, scancode}); + uint32_t keysym = (iter != keymap.end() ? iter->second : XKB_KEY_NoSymbol); + + tsm_vte_handle_keyboard(vte, keysym, ascii, mods, unicode ? unicode : TSM_VTE_INVALID); + } } void Terminal::_draw() diff --git a/addons/godot_xterm/native/src/terminal.h b/addons/godot_xterm/native/src/terminal.h index e15d1b6..ac79ef8 100644 --- a/addons/godot_xterm/native/src/terminal.h +++ b/addons/godot_xterm/native/src/terminal.h @@ -34,6 +34,7 @@ namespace godot private: static const uint8_t default_color_palette[TSM_COLOR_NUM][3]; + static const std::map, uint32_t> keymap; Vector2 cell_size; std::map palette = {}; @@ -54,7 +55,7 @@ namespace godot void _init(); void _ready(); void _notification(int what); - void _input(Variant event); + void _gui_input(Variant event); void _draw(); void write(PoolByteArray bytes); diff --git a/examples/terminal/Terminal.tscn b/examples/terminal/Terminal.tscn index 46fbd06..c54c563 100644 --- a/examples/terminal/Terminal.tscn +++ b/examples/terminal/Terminal.tscn @@ -1,23 +1,22 @@ -[gd_scene load_steps=5 format=2] +[gd_scene load_steps=4 format=2] [ext_resource path="res://addons/godot_xterm/nodes/terminal/terminal.gdns" type="Script" id=1] -[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=2] -[ext_resource path="res://addons/godot_xterm/nodes/pseudoterminal/pseudoterminal.gdns" type="Script" id=3] -[ext_resource path="res://examples/terminal/container.gd" type="Script" id=4] +[ext_resource path="res://addons/godot_xterm/nodes/pseudoterminal/pseudoterminal.gdns" type="Script" id=2] +[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=4] -[node name="Container" type="Container"] -margin_right = 40.0 -margin_bottom = 40.0 -script = ExtResource( 4 ) +[node name="Terminal" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +focus_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme = ExtResource( 4 ) +script = ExtResource( 1 ) __meta__ = { "_edit_use_anchors_": false } -[node name="Terminal" type="Control" parent="."] -margin_right = 40.0 -margin_bottom = 40.0 -theme = ExtResource( 2 ) -script = ExtResource( 1 ) - [node name="Pseudoterminal" type="Node" parent="."] -script = ExtResource( 3 ) +script = ExtResource( 2 ) +[connection signal="data_read" from="." to="Pseudoterminal" method="put_data"] +[connection signal="data_received" from="Pseudoterminal" to="." method="write"] diff --git a/examples/terminal/container.gd b/examples/terminal/container.gd deleted file mode 100644 index eac725a..0000000 --- a/examples/terminal/container.gd +++ /dev/null @@ -1,55 +0,0 @@ -extends Container -# This Container ensures that the terminal always fills -# the window and/or screen. It also connects the terminal -# to the input/output of the Psuedoterminal. - -const ESCAPE = 27 -const BACKSPACE = 8 -const BEEP = 7 -const SPACE = 32 -const LEFT_BRACKET = 91 -const ENTER = 10 -const BACKSPACE_ALT = 127 - -onready var viewport = get_viewport() - -func _ready(): - $Pseudoterminal.connect("data_received", $Terminal, "write") - - viewport.connect("size_changed", self, "_resize") - _resize() - - -func _input(event): - #return - if event is InputEventKey and event.pressed: - var data = PoolByteArray([]) - accept_event() - - # TODO: Handle more of these. - if (event.control and event.scancode == KEY_C): - data.append(3) - elif event.unicode: - data.append(event.unicode) - elif event.scancode == KEY_ENTER: - data.append(ENTER) - elif event.scancode == KEY_BACKSPACE: - data.append(BACKSPACE_ALT) - elif event.scancode == KEY_ESCAPE: - data.append(27) - elif event.scancode == KEY_TAB: - data.append(9) - elif OS.get_scancode_string(event.scancode) == "Shift": - pass - elif OS.get_scancode_string(event.scancode) == "Control": - pass - else: - pass - #push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode))) - #emit_signal("output", data) - $Pseudoterminal.put_data(data) - - -func _resize(): - rect_size = viewport.size - $Terminal.rect_size = rect_size