From ad7f97e493df8975a61845ead2f06e4e558778f1 Mon Sep 17 00:00:00 2001 From: Leroy Hopson Date: Sun, 8 Jan 2023 22:41:48 +1300 Subject: [PATCH] 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. --- .../terminal/editor_terminal.gd | 45 +- .../terminal/editor_terminal.tscn | 26 +- .../editor_plugins/terminal/terminal_panel.gd | 149 +- .../terminal/terminal_panel.tscn | 154 +-- .../import_plugins/xrdb_import_plugin.gd | 37 +- addons/godot_xterm/native/SConstruct | 1 + addons/godot_xterm/native/src/constants.cpp | 211 +++ addons/godot_xterm/native/src/libuv_utils.cpp | 70 +- addons/godot_xterm/native/src/pipe.cpp | 22 +- .../godot_xterm/native/src/register_types.cpp | 29 +- addons/godot_xterm/native/src/terminal.cpp | 1226 ++++++++--------- addons/godot_xterm/native/src/terminal.h | 196 ++- addons/godot_xterm/nodes/pty/libuv_utils.gd | 36 - addons/godot_xterm/nodes/pty/unix/pty_unix.gd | 33 +- addons/godot_xterm/nodes/terminal/viewport.gd | 2 - .../godot_xterm/nodes/terminal/viewport.tscn | 23 - addons/godot_xterm/plugin.gd | 7 +- addons/godot_xterm/pty.gd | 55 +- addons/godot_xterm/terminal.gd | 291 ---- addons/godot_xterm/themes/fonts/regular.tres | 25 +- examples/asciicast/asciicast.tscn | 25 +- examples/menu/menu.gd | 13 +- examples/menu/menu.tscn | 8 +- examples/retro_term/retro_term.tscn | 89 +- examples/socat_terminal/socat_terminal.gd | 14 +- examples/socat_terminal/socat_terminal.tscn | 17 +- examples/terminal/terminal.gd | 2 +- examples/terminal/terminal.tscn | 30 +- project.godot | 4 + themes/retro_green.tres | 4 +- 30 files changed, 1385 insertions(+), 1459 deletions(-) create mode 100644 addons/godot_xterm/native/src/constants.cpp delete mode 100644 addons/godot_xterm/nodes/pty/libuv_utils.gd delete mode 100644 addons/godot_xterm/nodes/terminal/viewport.gd delete mode 100644 addons/godot_xterm/nodes/terminal/viewport.tscn delete mode 100644 addons/godot_xterm/terminal.gd diff --git a/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd index b46903f..f89a446 100644 --- a/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd +++ b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd @@ -1,5 +1,5 @@ @tool -extends "../../terminal.gd" +extends Terminal signal exited(exit_code, signum) @@ -14,46 +14,43 @@ func _set_terminal_colors(color_map: Dictionary) -> void: for key in color_map.keys(): var val: String = color_map[key] var color: Color = editor_settings.get_setting("text_editor/highlighting/%s" % val) - theme.set_color(key, "Terminal", color) + add_theme_color_override(key, color) func _ready(): if not editor_settings: return - theme = Theme.new() - # Get colors from TextEdit theme. Created using the default (Adaptive) theme # for reference, but will probably cause strange results if using another theme # better to use a dedicated terminal theme, rather than relying on this. _set_terminal_colors( { - "black": "caret_background_color", - "red": "keyword_color", - "green": "gdscript/node_path_color", - "yellow": "string_color", - "blue": "function_color", - "magenta": "symbol_color", - "cyan": "gdscript/function_definition_color", - "white": "text_color", - "bright_black": "comment_color", - "bright_red": "breakpoint_color", - "bright_green": "base_type_color", - "bright_yellow": "search_result_color", - "bright_blue": "member_variable_color", - "bright_magenta": "code_folding_color", - "bright_cyan": "user_type_color", - "bright_white": "text_selected_color", - "background": "background_color", - "foreground": "caret_color", + "ansi_0_color": "completion_background_color", + "ansi_1_color": "keyword_color", + "ansi_2_color": "gdscript/node_path_color", + "ansi_3_color": "string_color", + "ansi_4_color": "function_color", + "ansi_5_color": "symbol_color", + "ansi_6_color": "gdscript/function_definition_color", + "ansi_7_color": "text_color", + "ansi_8_color": "comment_color", + "ansi_9_color": "breakpoint_color", + "ansi_10_color": "base_type_color", + "ansi_11_color": "search_result_color", + "ansi_12_color": "member_variable_color", + "ansi_13_color": "code_folding_color", + "ansi_14_color": "user_type_color", + "ansi_15_color": "text_selected_color", + "background_color": "background_color", + "foreground_color": "caret_color", } ) - _native_terminal._update_theme() func _input(event): if has_focus() and event is InputEventKey and event.is_pressed(): - if event.control and event.scancode in [KEY_PAGEUP, KEY_PAGEDOWN]: + if event.ctrl_pressed and event.scancode in [KEY_PAGEUP, KEY_PAGEDOWN]: # Handled by switch tabs shortcut. return diff --git a/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn index f21ab4c..0fdbc30 100644 --- a/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn +++ b/addons/godot_xterm/editor_plugins/terminal/editor_terminal.tscn @@ -1,27 +1,25 @@ -[gd_scene load_steps=4 format=2] +[gd_scene load_steps=4 format=3 uid="uid://bkcyv0w3setep"] -[ext_resource path="res://addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd" type="Script" id=1] -[ext_resource path="res://addons/godot_xterm/pty.gd" type="Script" id=2] -[ext_resource path="res://addons/godot_xterm/themes/default.tres" type="Theme" id=3] +[ext_resource type="Script" path="res://addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd" id="1"] +[ext_resource type="Script" path="res://addons/godot_xterm/pty.gd" id="2"] +[ext_resource type="Theme" uid="uid://c3ep6rm56qjeb" path="res://addons/godot_xterm/themes/default.tres" id="3"] -[node name="Terminal" type="Control"] +[node name="Terminal" type="Terminal"] +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -focus_mode = 1 +grow_horizontal = 2 +grow_vertical = 2 size_flags_horizontal = 4 size_flags_vertical = 4 -theme = ExtResource( 3 ) -script = ExtResource( 1 ) +focus_mode = 1 +theme = ExtResource("3") +script = ExtResource("1") [node name="PTY" type="Node" parent="."] -script = ExtResource( 2 ) +script = ExtResource("2") terminal_path = NodePath("..") -env = { -"COLORTERM": "truecolor", -"TERM": "xterm-256color" -} [node name="Bell" type="AudioStreamPlayer" parent="."] -[connection signal="bell" from="." to="Bell" method="play"] [connection signal="exited" from="PTY" to="." method="_on_PTY_exited"] diff --git a/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd index e36e828..2db7829 100644 --- a/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd +++ b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd @@ -28,9 +28,9 @@ var editor_plugin: EditorPlugin var editor_interface: EditorInterface @onready var editor_settings: EditorSettings = editor_interface.get_editor_settings() -@onready var tabs: TabBar = $VBoxContainer/TabbarContainer/TabBar +@onready var tabs: TabBar = $VBoxContainer/TabbarContainer/Tabs @onready var tabbar_container: HBoxContainer = $VBoxContainer/TabbarContainer -@onready var add_button: Button = $VBoxContainer/TabbarContainer/TabBar/AddButton +@onready var add_button: Button = $VBoxContainer/TabbarContainer/AddButton @onready var tab_container: TabContainer = $VBoxContainer/TabContainer @onready var terminal_popup_menu: PopupMenu = $VBoxContainer/TerminalPopupMenu @@ -39,7 +39,7 @@ var editor_interface: EditorInterface @onready var size_label: Label = $SizeLabel @onready var size_label_timer: Timer = $SizeLabel/SizeLabelTimer -@onready var ready := true +@onready var is_ready := true var _theme := Theme.new() var _settings: TerminalSettings @@ -47,7 +47,9 @@ var _tab_container_min_size func _ready(): - tab_container.add_theme_stylebox_override("panel", get_stylebox("background", "EditorStyles")) + tab_container.add_theme_stylebox_override( + "panel", get_theme_stylebox("background", "EditorStyles") + ) _update_settings() @@ -55,18 +57,6 @@ func _load_or_create_settings() -> void: # Use only default settings for now, until settings are properly defined # and documented. _settings = TerminalSettings.new() - return - - var dir := Directory.new() - - if not dir.dir_exists(SETTINGS_FILE_PATH.get_base_dir()): - dir.make_dir(SETTINGS_FILE_PATH.get_base_dir()) - - if not dir.file_exists(SETTINGS_FILE_PATH): - var settings := TerminalSettings.new() - ResourceSaver.save(SETTINGS_FILE_PATH, settings) - - _settings = load(SETTINGS_FILE_PATH) func _update_settings() -> void: @@ -76,36 +66,37 @@ func _update_settings() -> void: if editor_interface.has_method("get_editor_scale"): editor_scale = editor_interface.get_editor_scale() - minimum_size = Vector2(0, tabbar_container.size.y + 182) * editor_scale + custom_minimum_size = Vector2(0, tabbar_container.size.y + 182) * editor_scale size.y = 415 tabs.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ALWAYS - # Update shortcuts. - if _settings.new_terminal_shortcut: - terminal_popup_menu.set_item_shortcut( - TerminalPopupMenuOptions.NEW_TERMINAL, _settings.new_terminal_shortcut, true - ) - if _settings.kill_terminal_shortcut: - terminal_popup_menu.set_item_shortcut( - TerminalPopupMenuOptions.KILL_TERMINAL, _settings.kill_terminal_shortcut, false - ) - if _settings.copy_shortcut: - terminal_popup_menu.set_item_shortcut( - TerminalPopupMenuOptions.COPY, _settings.copy_shortcut, false - ) - if _settings.paste_shortcut: - terminal_popup_menu.set_item_shortcut( - TerminalPopupMenuOptions.PASTE, _settings.paste_shortcut, false - ) +#FIXME +# # Update shortcuts. +# if _settings.new_terminal_shortcut: +# terminal_popup_menu.set_item_shortcut( +# TerminalPopupMenuOptions.NEW_TERMINAL, _settings.new_terminal_shortcut, true +# ) +# if _settings.kill_terminal_shortcut: +# terminal_popup_menu.set_item_shortcut( +# TerminalPopupMenuOptions.KILL_TERMINAL, _settings.kill_terminal_shortcut, false +# ) +# if _settings.copy_shortcut: +# terminal_popup_menu.set_item_shortcut( +# TerminalPopupMenuOptions.COPY, _settings.copy_shortcut, false +# ) +# if _settings.paste_shortcut: +# terminal_popup_menu.set_item_shortcut( +# TerminalPopupMenuOptions.PASTE, _settings.paste_shortcut, false +# ) _update_terminal_tabs() func _update_terminal_tabs(): # Wait a couple of frames to allow everything to resize before updating. - await get_tree().idle_frame - await get_tree().idle_frame + await get_tree().process_frame + await get_tree().process_frame if tabs.get_offset_buttons_visible(): # Move add button to fixed position on the tabbar. @@ -116,7 +107,7 @@ func _update_terminal_tabs(): tabbar_container.move_child(add_button, 0) else: # Move add button after last tab. - if add_button.get_parent() == tabbar_container: + if tabs.tab_count > 0 and add_button.get_parent() == tabbar_container: tabbar_container.remove_child(add_button) tabs.add_child(add_button) var last_tab := Rect2() @@ -125,6 +116,9 @@ func _update_terminal_tabs(): add_button.position = Vector2( last_tab.position.x + last_tab.size.x + 3, last_tab.position.y ) + if tabs.tab_count == 0 and add_button.get_parent() == tabs: + tabs.remove_child(add_button) + tabbar_container.add_child(add_button) # Make sure we still own the button, so it gets saved with our scene. add_button.owner = self @@ -135,9 +129,9 @@ func _on_AddButton_pressed(): var terminal := EditorTerminal.instantiate() tabs.add_tab(shell.get_file()) terminal.editor_settings = editor_settings - terminal.set_anchors_preset(PRESET_WIDE) - terminal.connect("gui_input",Callable(self,"_on_TabContainer_gui_input")) - terminal.connect("exited",Callable(self,"_on_Terminal_exited").bind(terminal)) + terminal.set_anchors_preset(PRESET_BOTTOM_WIDE) + terminal.connect("gui_input", Callable(self, "_on_TabContainer_gui_input")) + terminal.connect("exited", Callable(self, "_on_Terminal_exited").bind(terminal)) tab_container.add_child(terminal) terminal.pty.fork(shell) terminal.grab_focus() @@ -163,7 +157,7 @@ func _on_Tabs_tab_close(tab_index): func _notification(what): - if not ready: + if not is_ready: return match what: @@ -181,46 +175,53 @@ func _input(event: InputEvent) -> void: return # Global shortcut to open new terminal and make terminal panel visible. - if _settings.new_terminal_shortcut and _settings.new_terminal_shortcut.shortcut: - if event.is_match(_settings.new_terminal_shortcut.shortcut): - get_tree().set_input_as_handled() - editor_plugin.make_bottom_panel_item_visible(self) - _on_AddButton_pressed() + # FIXME +# if _settings.new_terminal_shortcut and _settings.new_terminal_shortcut.shortcut: +# if event.is_match(_settings.new_terminal_shortcut.shortcut): +# get_tree().set_input_as_handled() +# editor_plugin.make_bottom_panel_item_visible(self) +# _on_AddButton_pressed() # Non-global shortcuts, only applied if terminal is active and focused. if ( tabs.get_tab_count() > 0 and tab_container.get_child(tabs.current_tab).has_focus() or terminal_popup_menu.has_focus() ): + pass # Kill terminal. - if _settings.kill_terminal_shortcut and _settings.kill_terminal_shortcut.shortcut: - if event.is_match(_settings.kill_terminal_shortcut.shortcut): - get_tree().set_input_as_handled() - _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.KILL_TERMINAL) + # FIXME shortcut - # Copy. - if _settings.copy_shortcut and _settings.copy_shortcut.shortcut: - if event.is_match(_settings.copy_shortcut.shortcut): - get_tree().set_input_as_handled() - _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.COPY) - # Paste. - if _settings.paste_shortcut and _settings.paste_shortcut.shortcut: - if event.is_match(_settings.paste_shortcut.shortcut): - get_tree().set_input_as_handled() - _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.PASTE) +# if _settings.kill_terminal_shortcut and _settings.kill_terminal_shortcut.shortcut: +# if event.is_match(_settings.kill_terminal_shortcut.shortcut): +# get_tree().set_input_as_handled() +# _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.KILL_TERMINAL) - # Next tab. - if _settings.next_tab_shortcut and _settings.next_tab_shortcut.shortcut: - if event.is_match(_settings.next_tab_shortcut.shortcut): - get_tree().set_input_as_handled() - tabs.current_tab = min(tabs.current_tab + 1, tabs.get_tab_count() - 1) +# Copy. +#FIXME +# if _settings.copy_shortcut and _settings.copy_shortcut.shortcut: +# if event.is_match(_settings.copy_shortcut.shortcut): +# get_tree().set_input_as_handled() +# _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.COPY) - # Previous tab. - if _settings.previous_tab_shortcut and _settings.previous_tab_shortcut.shortcut: - if event.is_match(_settings.previous_tab_shortcut.shortcut): - get_tree().set_input_as_handled() - tabs.current_tab = max(tabs.current_tab - 1, 0) +# Paste. +#FIXME +# if _settings.paste_shortcut and _settings.paste_shortcut.shortcut: +# if event.is_match(_settings.paste_shortcut.shortcut): +# get_tree().set_input_as_handled() +# _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.PASTE) +# +# # Next tab. +# if _settings.next_tab_shortcut and _settings.next_tab_shortcut.shortcut: +# if event.is_match(_settings.next_tab_shortcut.shortcut): +# get_tree().set_input_as_handled() +# tabs.current_tab = min(tabs.current_tab + 1, tabs.get_tab_count() - 1) +# +# # Previous tab. +# if _settings.previous_tab_shortcut and _settings.previous_tab_shortcut.shortcut: +# if event.is_match(_settings.previous_tab_shortcut.shortcut): +# get_tree().set_input_as_handled() +# tabs.current_tab = max(tabs.current_tab - 1, 0) func _on_TabContainer_gui_input(event): @@ -238,15 +239,15 @@ func _on_TerminalPopupMenu_id_pressed(id): var terminal = tab_container.get_child(tab_container.current_tab) match id: TerminalPopupMenuOptions.COPY: - OS.clipboard = terminal.copy_selection() + DisplayServer.clipboard_set(terminal.copy_selection()) TerminalPopupMenuOptions.PASTE: - for i in OS.clipboard.length(): + for i in DisplayServer.clipboard_get().length(): var event = InputEventKey.new() - event.unicode = ord(OS.clipboard[i]) + event.unicode = DisplayServer.clipboard_get().unicode_at(i) event.button_pressed = true terminal._gui_input(event) TerminalPopupMenuOptions.COPY_ALL: - OS.clipboard = terminal.copy_all() + DisplayServer.clipboard_set(terminal.copy_all()) TerminalPopupMenuOptions.CLEAR: terminal.clear() TerminalPopupMenuOptions.KILL_TERMINAL: diff --git a/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn index dd5b10d..c1f15f7 100644 --- a/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn +++ b/addons/godot_xterm/editor_plugins/terminal/terminal_panel.tscn @@ -1,119 +1,93 @@ -[gd_scene load_steps=7 format=2] +[gd_scene load_steps=4 format=3 uid="uid://cbxovnvw5o4mo"] -[ext_resource path="res://addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd" type="Script" id=1] +[ext_resource type="Script" path="res://addons/godot_xterm/editor_plugins/terminal/terminal_panel.gd" id="1"] -[sub_resource type="Image" id=6] +[sub_resource type="Image" id="Image_x7bb3"] data = { -"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), -"format": "LumAlpha8", -"height": 16, -"mipmaps": false, -"width": 16 -} - -[sub_resource type="ImageTexture" id=2] -flags = 4 -flags = 4 -image = SubResource( 6 ) -size = Vector2( 16, 16 ) - -[sub_resource type="StyleBoxTexture" id=3] -texture = SubResource( 2 ) -region_rect = Rect2( 0, 0, 16, 16 ) -offset_left = 2.0 -offset_right = 2.0 -offset_top = 2.0 -offset_bottom = 2.0 - -[sub_resource type="Image" id=7] -data = { -"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), +"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, 224, 0, 224, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), "format": "RGBA8", "height": 16, "mipmaps": false, "width": 16 } -[sub_resource type="ImageTexture" id=5] -flags = 0 -flags = 0 -image = SubResource( 7 ) -size = Vector2( 16, 16 ) +[sub_resource type="ImageTexture" id="ImageTexture_e8boo"] +image = SubResource("Image_x7bb3") [node name="Panel" type="Panel"] +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -offset_top = -3.0 -minimum_size = Vector2( 0, 34 ) -custom_styles/panel = SubResource( 3 ) -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} +offset_bottom = 3.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1") [node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -minimum_size = Vector2( 0, 24 ) -custom_constants/separation = 0 -__meta__ = { -"_edit_use_anchors_": false -} +grow_horizontal = 2 +grow_vertical = 2 [node name="TabbarContainer" type="HBoxContainer" parent="VBoxContainer"] -offset_right = 1024.0 -offset_bottom = 24.0 +layout_mode = 2 -[node name="TabBar" type="TabBar" parent="VBoxContainer/TabbarContainer"] -offset_right = 1024.0 -offset_bottom = 24.0 -size_flags_horizontal = 3 -tab_alignment = 0 -tab_close_display_policy = 1 +[node name="Tabs" type="TabBar" parent="VBoxContainer/TabbarContainer"] +layout_mode = 2 +clip_tabs = false drag_to_rearrange_enabled = true -__meta__ = { -"_edit_use_anchors_": false -} -[node name="AddButton" type="Button" parent="VBoxContainer/TabbarContainer/TabBar"] -offset_left = 3.0 -offset_right = 31.0 -offset_bottom = 24.0 +[node name="AddButton" type="Button" parent="VBoxContainer/TabbarContainer"] +layout_mode = 2 tooltip_text = "Add a new scene." -icon = SubResource( 5 ) -__meta__ = { -"_edit_use_anchors_": false -} +icon = SubResource("ImageTexture_e8boo") [node name="PopupMenu" type="PopupMenu" parent="VBoxContainer/TabbarContainer"] -offset_left = 906.0 -offset_right = 1024.0 -offset_bottom = 88.0 -items = [ "Kill", null, 0, false, false, 0, 0, null, "", false, "Kill Others", null, 0, false, false, 1, 0, null, "", false, "Kill to the Right", null, 0, false, false, 2, 0, null, "", false, "Kill All", null, 0, false, false, 3, 0, null, "", false ] +item_count = 4 +item_0/text = "Kill" +item_0/id = 0 +item_1/text = "Kill Others" +item_1/id = 1 +item_2/text = "Kill to the Right" +item_2/id = 2 +item_3/text = "Kill All" +item_3/id = 3 [node name="TabContainer" type="TabContainer" parent="VBoxContainer"] -offset_top = 24.0 -offset_right = 1024.0 -offset_bottom = 603.0 clip_contents = true +layout_mode = 2 size_flags_vertical = 3 -custom_constants/top_margin = 0 -custom_constants/side_margin = 0 -custom_styles/panel = SubResource( 3 ) tabs_visible = false [node name="TerminalPopupMenu" type="PopupMenu" parent="VBoxContainer"] -offset_right = 193.0 -offset_bottom = 160.0 -size_flags_horizontal = 0 -size_flags_vertical = 0 -items = [ "New Terminal", null, 0, false, false, 0, 0, null, "", false, "", null, 0, false, true, 1, 0, null, "", true, "Copy", null, 0, false, false, 2, 0, null, "", false, "Paste", null, 0, false, false, 3, 0, null, "", false, "Copy All", null, 0, false, false, 4, 0, null, "", false, "", null, 0, false, false, 5, 0, null, "", true, "Clear", null, 0, false, false, 6, 0, null, "", false, "Kill Terminal", null, 0, false, false, 7, 0, null, "", false ] -__meta__ = { -"_edit_use_anchors_": false -} +size = Vector2i(136, 178) +item_count = 8 +item_0/text = "New Terminal" +item_0/id = 0 +item_1/text = "" +item_1/id = 1 +item_1/disabled = true +item_1/separator = true +item_2/text = "Copy" +item_2/id = 2 +item_3/text = "Paste" +item_3/id = 3 +item_4/text = "Copy All" +item_4/id = 4 +item_5/text = "" +item_5/id = 5 +item_5/separator = true +item_6/text = "Clear" +item_6/id = 6 +item_7/text = "Kill Terminal" +item_7/id = 7 [node name="SizeLabel" type="Label" parent="."] visible = false +layout_mode = 1 +anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 @@ -122,15 +96,15 @@ offset_left = -52.0 offset_top = -15.5 offset_right = 52.0 offset_bottom = 15.5 +grow_horizontal = 2 +grow_vertical = 2 text = "Size: %d rows; %d cols (%d x %d)" -align = 1 -__meta__ = { -"_edit_use_anchors_": false -} [node name="Panel" type="Panel" parent="SizeLabel"] show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 offset_left = -5.0 @@ -139,17 +113,13 @@ offset_right = 5.0 offset_bottom = 5.0 grow_horizontal = 2 grow_vertical = 2 -__meta__ = { -"_edit_use_anchors_": false -} [node name="SizeLabelTimer" type="Timer" parent="SizeLabel"] [connection signal="resized" from="." to="." method="_on_Panel_resized"] -[connection signal="reposition_active_tab_request" from="VBoxContainer/TabbarContainer/TabBar" to="." method="_on_Tabs_reposition_active_tab_request"] -[connection signal="tab_changed" from="VBoxContainer/TabbarContainer/TabBar" to="." method="_on_Tabs_tab_changed"] -[connection signal="tab_closed" from="VBoxContainer/TabbarContainer/TabBar" to="." method="_on_Tabs_tab_close"] -[connection signal="pressed" from="VBoxContainer/TabbarContainer/TabBar/AddButton" to="." method="_on_AddButton_pressed"] +[connection signal="tab_changed" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_changed"] +[connection signal="tab_close_pressed" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_close"] +[connection signal="pressed" from="VBoxContainer/TabbarContainer/AddButton" to="." method="_on_AddButton_pressed"] [connection signal="gui_input" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_gui_input"] [connection signal="id_pressed" from="VBoxContainer/TerminalPopupMenu" to="." method="_on_TerminalPopupMenu_id_pressed"] [connection signal="timeout" from="SizeLabel/SizeLabelTimer" to="." method="_on_SizeLabelTimer_timeout"] diff --git a/addons/godot_xterm/import_plugins/xrdb_import_plugin.gd b/addons/godot_xterm/import_plugins/xrdb_import_plugin.gd index 1cf0222..4b9d74b 100644 --- a/addons/godot_xterm/import_plugins/xrdb_import_plugin.gd +++ b/addons/godot_xterm/import_plugins/xrdb_import_plugin.gd @@ -39,8 +39,9 @@ func _import(source_file, save_path, options, r_platform_variant, r_gen_files): return err var theme: Theme = XrdbTheme.new() - theme.set_font("regular", "Terminal", preload("../themes/fonts/regular.tres")) - for font in ["bold", "italic", "bold_italic"]: + theme.set_font_size("font_size", "Terminal", 14) + theme.set_font("normal_font", "Terminal", preload("../themes/fonts/regular.tres")) + for font in ["bold_font", "italic_font", "bold_italic_font"]: theme.set_font(font, "Terminal", null) var word_regex = RegEx.new() @@ -77,37 +78,37 @@ func _import(source_file, save_path, options, r_platform_variant, r_gen_files): match name: "color0", "ansi_0_color": - theme.set_color("black", "Terminal", color) + theme.set_color("ansi_0_color", "Terminal", color) "color1", "ansi_1_color": - theme.set_color("red", "Terminal", color) + theme.set_color("ansi_1_color", "Terminal", color) "color2", "ansi_2_color": - theme.set_color("green", "Terminal", color) + theme.set_color("ansi_2_color", "Terminal", color) "color3", "ansi_3_color": - theme.set_color("yellow", "Terminal", color) + theme.set_color("ansi_3_color", "Terminal", color) "color4", "ansi_4_color": - theme.set_color("blue", "Terminal", color) + theme.set_color("ansi_4_color", "Terminal", color) "color5", "ansi_5_color": - theme.set_color("magenta", "Terminal", color) + theme.set_color("ansi_5_color", "Terminal", color) "color6", "ansi_6_color": - theme.set_color("cyan", "Terminal", color) + theme.set_color("ansi_6_color", "Terminal", color) "color7", "ansi_7_color": - theme.set_color("white", "Terminal", color) + theme.set_color("ansi_7_color", "Terminal", color) "color8", "ansi_8_color": - theme.set_color("bright_black", "Terminal", color) + theme.set_color("ansi_8_color", "Terminal", color) "color9", "ansi_9_color": - theme.set_color("bright_red", "Terminal", color) + theme.set_color("ansi_9_color", "Terminal", color) "color10", "ansi_10_color": - theme.set_color("bright_green", "Terminal", color) + theme.set_color("ansi_10_color", "Terminal", color) "color11", "ansi_11_color": - theme.set_color("bright_yellow", "Terminal", color) + theme.set_color("ansi_11_color", "Terminal", color) "color12", "ansi_12_color": - theme.set_color("bright_blue", "Terminal", color) + theme.set_color("ansi_12_color", "Terminal", color) "color13", "ansi_13_color": - theme.set_color("bright_magenta", "Terminal", color) + theme.set_color("ansi_13_color", "Terminal", color) "color14", "ansi_14_color": - theme.set_color("bright_cyan", "Terminal", color) + theme.set_color("ansi_14_color", "Terminal", color) "color15", "ansi_15_color": - theme.set_color("bright_white", "Terminal", color) + theme.set_color("ansi_15_color", "Terminal", color) "foreground", "foreground_color": theme.set_color("foreground", "Terminal", color) "background", "background_color": diff --git a/addons/godot_xterm/native/SConstruct b/addons/godot_xterm/native/SConstruct index d66d77d..08c339b 100644 --- a/addons/godot_xterm/native/SConstruct +++ b/addons/godot_xterm/native/SConstruct @@ -277,6 +277,7 @@ env.Append(LIBS=[ sources = [] sources.append('src/register_types.cpp') +sources.append('src/constants.cpp') sources.append('src/terminal.cpp') diff --git a/addons/godot_xterm/native/src/constants.cpp b/addons/godot_xterm/native/src/constants.cpp new file mode 100644 index 0000000..ca30568 --- /dev/null +++ b/addons/godot_xterm/native/src/constants.cpp @@ -0,0 +1,211 @@ +#include "terminal.h" +#include + +using namespace godot; + +const std::map 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}, +}; diff --git a/addons/godot_xterm/native/src/libuv_utils.cpp b/addons/godot_xterm/native/src/libuv_utils.cpp index 6827af0..47ee934 100644 --- a/addons/godot_xterm/native/src/libuv_utils.cpp +++ b/addons/godot_xterm/native/src/libuv_utils.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Leroy Hopson +// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson // 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 } } diff --git a/addons/godot_xterm/native/src/pipe.cpp b/addons/godot_xterm/native/src/pipe.cpp index 7c4670d..d2ae6ba 100644 --- a/addons/godot_xterm/native/src/pipe.cpp +++ b/addons/godot_xterm/native/src/pipe.cpp @@ -1,19 +1,8 @@ -// Copyright (c) 2021, Leroy Hopson (MIT License). +// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson +// SDPX-License-Identifier: MIT #include "pipe.h" #include "libuv_utils.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #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() {} diff --git a/addons/godot_xterm/native/src/register_types.cpp b/addons/godot_xterm/native/src/register_types.cpp index bf5eece..0a45eef 100644 --- a/addons/godot_xterm/native/src/register_types.cpp +++ b/addons/godot_xterm/native/src/register_types.cpp @@ -1,5 +1,3 @@ -// Copyright (c) 2022, Leroy Hopson (MIT License). - #include "register_types.h" #include @@ -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(); #if !defined(_PTY_DISABLED) ClassDB::register_class(); @@ -36,25 +34,30 @@ void initialize_godot_xterm_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); #endif #if defined(__WIN32) - //ClassDB::register_class(); + // ClassDB::register_class(); #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(); } diff --git a/addons/godot_xterm/native/src/terminal.cpp b/addons/godot_xterm/native/src/terminal.cpp index 8a85cd7..4acef1c 100644 --- a/addons/godot_xterm/native/src/terminal.cpp +++ b/addons/godot_xterm/native/src/terminal.cpp @@ -1,641 +1,90 @@ -// Copyright (c) 2021, Leroy Hopson (MIT License). +// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson +// SPDX-License-Identifier: MIT #include "terminal.h" -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include #include #include -// For _populate_key_list(), see below. -#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__) -#include -#endif +#define UNICODE_MAX 0x10FFFF using namespace godot; -std::map, int> Terminal::_key_list = {}; -void Terminal::_populate_key_list() { - if (!_key_list.empty()) - return; +void Terminal::set_copy_on_selection(bool value) { copy_on_selection = value; } -// TODO: Remove GLOBAL_CONSTANT macro. -#define GLOBAL_CONSTANT(VAR) VAR +bool Terminal::get_copy_on_selection() { return copy_on_selection; } - // 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_list.insert({{'0', GLOBAL_CONSTANT(KEY_KP_0)}, XKB_KEY_KP_0}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_0)}, XKB_KEY_KP_Insert}); - _key_list.insert({{'1', GLOBAL_CONSTANT(KEY_KP_1)}, XKB_KEY_KP_1}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_1)}, XKB_KEY_KP_End}); - _key_list.insert({{'2', GLOBAL_CONSTANT(KEY_KP_2)}, XKB_KEY_KP_2}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_2)}, XKB_KEY_KP_Down}); - _key_list.insert({{'3', GLOBAL_CONSTANT(KEY_KP_3)}, XKB_KEY_KP_3}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_3)}, XKB_KEY_KP_Page_Down}); - _key_list.insert({{'4', GLOBAL_CONSTANT(KEY_KP_4)}, XKB_KEY_KP_4}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_4)}, XKB_KEY_KP_Left}); - _key_list.insert({{'5', GLOBAL_CONSTANT(KEY_KP_5)}, XKB_KEY_KP_5}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_5)}, XKB_KEY_KP_Begin}); - _key_list.insert({{'6', GLOBAL_CONSTANT(KEY_KP_6)}, XKB_KEY_KP_6}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_6)}, XKB_KEY_KP_Right}); - _key_list.insert({{'7', GLOBAL_CONSTANT(KEY_KP_7)}, XKB_KEY_KP_7}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_7)}, XKB_KEY_KP_Home}); - _key_list.insert({{'8', GLOBAL_CONSTANT(KEY_KP_8)}, XKB_KEY_KP_8}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_8)}, XKB_KEY_KP_Up}); - _key_list.insert({{'9', GLOBAL_CONSTANT(KEY_KP_9)}, XKB_KEY_KP_9}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_9)}, XKB_KEY_KP_Page_Up}); - _key_list.insert({{'.', GLOBAL_CONSTANT(KEY_KP_PERIOD)}, XKB_KEY_KP_Decimal}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_PERIOD)}, XKB_KEY_KP_Delete}); - _key_list.insert({{'/', GLOBAL_CONSTANT(KEY_KP_DIVIDE)}, XKB_KEY_KP_Divide}); - _key_list.insert( - {{'*', GLOBAL_CONSTANT(KEY_KP_MULTIPLY)}, XKB_KEY_KP_Multiply}); - _key_list.insert( - {{'-', GLOBAL_CONSTANT(KEY_KP_SUBTRACT)}, XKB_KEY_KP_Subtract}); - _key_list.insert({{'+', GLOBAL_CONSTANT(KEY_KP_ADD)}, XKB_KEY_KP_Add}); - _key_list.insert({{0b0, GLOBAL_CONSTANT(KEY_KP_ENTER)}, XKB_KEY_KP_Enter}); - //_key_list.insert({{ , }, XKB_KEY_KP_Equal}); - //_key_list.insert({{ , }, XKB_KEY_KP_Separator}); - //_key_list.insert({{ , }, XKB_KEY_KP_Tab}); - //_key_list.insert({{ , }, XKB_KEY_KP_F1}); - //_key_list.insert({{ , }, XKB_KEY_KP_F2}); - //_key_list.insert({{ , }, XKB_KEY_KP_F3}); - //_key_list.insert({{ , }, 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_list.insert({{'a', GLOBAL_CONSTANT(KEY_A)}, XKB_KEY_a}); - _key_list.insert({{'A', GLOBAL_CONSTANT(KEY_A)}, XKB_KEY_A}); - _key_list.insert({{'b', GLOBAL_CONSTANT(KEY_B)}, XKB_KEY_b}); - _key_list.insert({{'B', GLOBAL_CONSTANT(KEY_B)}, XKB_KEY_B}); - _key_list.insert({{'c', GLOBAL_CONSTANT(KEY_C)}, XKB_KEY_c}); - _key_list.insert({{'C', GLOBAL_CONSTANT(KEY_C)}, XKB_KEY_C}); - _key_list.insert({{'d', GLOBAL_CONSTANT(KEY_D)}, XKB_KEY_d}); - _key_list.insert({{'D', GLOBAL_CONSTANT(KEY_D)}, XKB_KEY_D}); - _key_list.insert({{'e', GLOBAL_CONSTANT(KEY_E)}, XKB_KEY_e}); - _key_list.insert({{'E', GLOBAL_CONSTANT(KEY_E)}, XKB_KEY_E}); - _key_list.insert({{'f', GLOBAL_CONSTANT(KEY_F)}, XKB_KEY_f}); - _key_list.insert({{'F', GLOBAL_CONSTANT(KEY_F)}, XKB_KEY_F}); - _key_list.insert({{'g', GLOBAL_CONSTANT(KEY_G)}, XKB_KEY_g}); - _key_list.insert({{'G', GLOBAL_CONSTANT(KEY_G)}, XKB_KEY_G}); - _key_list.insert({{'h', GLOBAL_CONSTANT(KEY_H)}, XKB_KEY_h}); - _key_list.insert({{'H', GLOBAL_CONSTANT(KEY_H)}, XKB_KEY_H}); - _key_list.insert({{'i', GLOBAL_CONSTANT(KEY_I)}, XKB_KEY_i}); - _key_list.insert({{'I', GLOBAL_CONSTANT(KEY_I)}, XKB_KEY_I}); - _key_list.insert({{'j', GLOBAL_CONSTANT(KEY_J)}, XKB_KEY_j}); - _key_list.insert({{'J', GLOBAL_CONSTANT(KEY_J)}, XKB_KEY_J}); - _key_list.insert({{'k', GLOBAL_CONSTANT(KEY_K)}, XKB_KEY_k}); - _key_list.insert({{'K', GLOBAL_CONSTANT(KEY_K)}, XKB_KEY_K}); - _key_list.insert({{'l', GLOBAL_CONSTANT(KEY_L)}, XKB_KEY_l}); - _key_list.insert({{'L', GLOBAL_CONSTANT(KEY_L)}, XKB_KEY_L}); - _key_list.insert({{'m', GLOBAL_CONSTANT(KEY_M)}, XKB_KEY_m}); - _key_list.insert({{'M', GLOBAL_CONSTANT(KEY_M)}, XKB_KEY_M}); - _key_list.insert({{'n', GLOBAL_CONSTANT(KEY_N)}, XKB_KEY_n}); - _key_list.insert({{'N', GLOBAL_CONSTANT(KEY_N)}, XKB_KEY_N}); - _key_list.insert({{'o', GLOBAL_CONSTANT(KEY_O)}, XKB_KEY_o}); - _key_list.insert({{'O', GLOBAL_CONSTANT(KEY_O)}, XKB_KEY_O}); - _key_list.insert({{'p', GLOBAL_CONSTANT(KEY_P)}, XKB_KEY_p}); - _key_list.insert({{'P', GLOBAL_CONSTANT(KEY_P)}, XKB_KEY_P}); - _key_list.insert({{'q', GLOBAL_CONSTANT(KEY_Q)}, XKB_KEY_q}); - _key_list.insert({{'Q', GLOBAL_CONSTANT(KEY_Q)}, XKB_KEY_Q}); - _key_list.insert({{'r', GLOBAL_CONSTANT(KEY_R)}, XKB_KEY_r}); - _key_list.insert({{'R', GLOBAL_CONSTANT(KEY_R)}, XKB_KEY_R}); - _key_list.insert({{'s', GLOBAL_CONSTANT(KEY_S)}, XKB_KEY_s}); - _key_list.insert({{'S', GLOBAL_CONSTANT(KEY_S)}, XKB_KEY_S}); - _key_list.insert({{'t', GLOBAL_CONSTANT(KEY_T)}, XKB_KEY_t}); - _key_list.insert({{'T', GLOBAL_CONSTANT(KEY_T)}, XKB_KEY_T}); - _key_list.insert({{'u', GLOBAL_CONSTANT(KEY_U)}, XKB_KEY_u}); - _key_list.insert({{'U', GLOBAL_CONSTANT(KEY_U)}, XKB_KEY_U}); - _key_list.insert({{'v', GLOBAL_CONSTANT(KEY_V)}, XKB_KEY_v}); - _key_list.insert({{'V', GLOBAL_CONSTANT(KEY_V)}, XKB_KEY_V}); - _key_list.insert({{'w', GLOBAL_CONSTANT(KEY_W)}, XKB_KEY_w}); - _key_list.insert({{'W', GLOBAL_CONSTANT(KEY_W)}, XKB_KEY_W}); - _key_list.insert({{'x', GLOBAL_CONSTANT(KEY_X)}, XKB_KEY_x}); - _key_list.insert({{'X', GLOBAL_CONSTANT(KEY_X)}, XKB_KEY_X}); - _key_list.insert({{'y', GLOBAL_CONSTANT(KEY_Y)}, XKB_KEY_y}); - _key_list.insert({{'Y', GLOBAL_CONSTANT(KEY_Y)}, XKB_KEY_Y}); - _key_list.insert({{'z', GLOBAL_CONSTANT(KEY_Z)}, XKB_KEY_z}); - _key_list.insert({{'Z', GLOBAL_CONSTANT(KEY_Z)}, XKB_KEY_Z}); - - _key_list.insert({{'0', GLOBAL_CONSTANT(KEY_0)}, XKB_KEY_0}); - _key_list.insert({{'1', GLOBAL_CONSTANT(KEY_1)}, XKB_KEY_1}); - _key_list.insert({{'2', GLOBAL_CONSTANT(KEY_2)}, XKB_KEY_2}); - _key_list.insert({{'3', GLOBAL_CONSTANT(KEY_3)}, XKB_KEY_3}); - _key_list.insert({{'4', GLOBAL_CONSTANT(KEY_4)}, XKB_KEY_4}); - _key_list.insert({{'5', GLOBAL_CONSTANT(KEY_5)}, XKB_KEY_5}); - _key_list.insert({{'6', GLOBAL_CONSTANT(KEY_6)}, XKB_KEY_6}); - _key_list.insert({{'7', GLOBAL_CONSTANT(KEY_7)}, XKB_KEY_7}); - _key_list.insert({{'8', GLOBAL_CONSTANT(KEY_8)}, XKB_KEY_8}); - _key_list.insert({{'9', GLOBAL_CONSTANT(KEY_9)}, XKB_KEY_9}); - - _key_list.insert( - {{'[', GLOBAL_CONSTANT(KEY_BRACKETLEFT)}, XKB_KEY_bracketleft}); - _key_list.insert( - {{'[', GLOBAL_CONSTANT(KEY_BRACKETLEFT)}, XKB_KEY_bracketright}); - _key_list.insert({{'{', GLOBAL_CONSTANT(KEY_BRACELEFT)}, XKB_KEY_braceleft}); - _key_list.insert( - {{'}', GLOBAL_CONSTANT(KEY_BRACERIGHT)}, XKB_KEY_braceright}); - - _key_list.insert({{'\\', GLOBAL_CONSTANT(KEY_BACKSLASH)}, XKB_KEY_backslash}); - _key_list.insert({{'|', GLOBAL_CONSTANT(KEY_BAR)}, XKB_KEY_bar}); - _key_list.insert({{'`', GLOBAL_CONSTANT(KEY_QUOTELEFT)}, XKB_KEY_grave}); - _key_list.insert( - {{'~', GLOBAL_CONSTANT(KEY_ASCIITILDE)}, XKB_KEY_asciitilde}); - _key_list.insert({{'/', GLOBAL_CONSTANT(KEY_SLASH)}, XKB_KEY_slash}); - _key_list.insert({{'?', GLOBAL_CONSTANT(KEY_QUESTION)}, XKB_KEY_question}); - - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_HOME)}, XKB_KEY_Home}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_BACKSPACE)}, XKB_KEY_BackSpace}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_BACKTAB)}, XKB_KEY_ISO_Left_Tab}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_CLEAR)}, XKB_KEY_Clear}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_PAUSE)}, XKB_KEY_Pause}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_SCROLLLOCK)}, XKB_KEY_Scroll_Lock}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_SYSREQ)}, XKB_KEY_Sys_Req}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_ESCAPE)}, XKB_KEY_Escape}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_ENTER)}, XKB_KEY_Return}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_INSERT)}, XKB_KEY_Insert}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_DELETE)}, XKB_KEY_Delete}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_PAGEUP)}, XKB_KEY_Page_Up}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_PAGEDOWN)}, XKB_KEY_Page_Down}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_UP)}, XKB_KEY_Up}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_DOWN)}, XKB_KEY_Down}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_RIGHT)}, XKB_KEY_Right}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_LEFT)}, XKB_KEY_Left}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_TAB)}, XKB_KEY_Tab}); - //_key_list.insert({{ , }, XKB_KEY_Linefeed}, - //_key_list.insert({{ , }, XKB_KEY_Find}, - //_key_list.insert({{ , }, XKB_KEY_Select}, - - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F1)}, XKB_KEY_F1}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F2)}, XKB_KEY_F2}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F3)}, XKB_KEY_F3}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F4)}, XKB_KEY_F4}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F5)}, XKB_KEY_F5}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F6)}, XKB_KEY_F6}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F7)}, XKB_KEY_F7}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F8)}, XKB_KEY_F8}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F9)}, XKB_KEY_F9}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F10)}, XKB_KEY_F10}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F11)}, XKB_KEY_F11}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F12)}, XKB_KEY_F12}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F13)}, XKB_KEY_F13}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F14)}, XKB_KEY_F14}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F15)}, XKB_KEY_F15}); - _key_list.insert({{0, GLOBAL_CONSTANT(KEY_F16)}, XKB_KEY_F16}); - //_key_list.insert({{0, GLOBAL_CONSTANT(KEY_F17)}, XKB_KEY_F17}); - //_key_list.insert({{0, GLOBAL_CONSTANT(KEY_F18)}, XKB_KEY_F18}); - //_key_list.insert({{0, GLOBAL_CONSTANT(KEY_F19)}, XKB_KEY_F19}); - //_key_list.insert({{0, GLOBAL_CONSTANT(KEY_F20)}, XKB_KEY_F20}); +void Terminal::set_update_mode(Terminal::UpdateMode value) { + update_mode = value; }; +Terminal::UpdateMode Terminal::get_update_mode() { return update_mode; } -uint32_t Terminal::mapkey(std::pair key) { - if (Terminal::_key_list.empty()) - Terminal::_populate_key_list(); - auto iter = _key_list.find(key); - return (iter != _key_list.end() ? iter->second : XKB_KEY_NoSymbol); +void Terminal::set_bell_cooldown(double value) { bell_cooldown = value; } + +double Terminal::get_bell_cooldown() { return bell_cooldown; } + +void Terminal::set_bell_muted(bool value) { bell_muted = value; } + +bool Terminal::get_bell_muted() { return bell_muted; } + +void Terminal::set_blink_enabled(bool value) { + blink_enabled = value; + + if (!blink_enabled) + blink_timer->stop(); + + _refresh(); } -static struct { - Color col; - bool is_set; -} colours[16]; +bool Terminal::get_blink_enabled() { return blink_enabled; } -static void term_output(const char *s, size_t len, void *user) {} +void Terminal::set_blink_time_off(double value) { + blink_time_off = value; -static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, - void *data) { - Terminal *term = static_cast(data); - - PackedByteArray bytes; - bytes.resize(len); - { memcpy(bytes.ptrw(), u8, len); } - - if (len > 0) { - if (term->input_event_key.is_valid()) { - // The callback was fired from a key press event so emit the "key_pressed" - // signal. - term->emit_signal("key_pressed", bytes, term->input_event_key); - term->input_event_key.unref(); - } - - term->emit_signal("data_sent", bytes); + if (!blink_on && !blink_timer->is_stopped()) { + double time_left = blink_timer->get_time_left(); + blink_timer->start(std::min(blink_time_off, time_left)); } } -static int text_draw_cb(struct tsm_screen *con, uint64_t id, const uint32_t *ch, - size_t len, unsigned int width, unsigned int col, - unsigned int row, const struct tsm_screen_attr *attr, - tsm_age_t age, void *data) { - Terminal *terminal = static_cast(data); +double Terminal::get_blink_time_off() { return blink_time_off; } - if (terminal->update_mode == Terminal::UpdateMode::AUTO && age != 0 && - age <= terminal->framebuffer_age) - return 0; +void Terminal::set_blink_time_on(double value) { + blink_time_on = value; - if (width < 1) // No foreground or background to draw. - return 0; - - std::pair color_pair = terminal->get_cell_colors(attr); - terminal->draw_background(row, col, color_pair.first, width); - - if (len < 1) // No foreground to draw. - return 0; - - size_t ulen = 0; - char buf[5] = {0}; - - char *utf8 = tsm_ucs4_to_utf8_alloc(ch, len, &ulen); - memcpy(buf, utf8, ulen); - terminal->draw_foreground(row, col, buf, attr, color_pair.second); - - return 0; -} - -static void bell_cb(tsm_vte *_vte, void *data) { - Terminal *terminal = static_cast(data); - terminal->emit_signal("bell"); -} - -void Terminal::_bind_methods() { - //ClassDB::bind_method(D_METHOD("_ready"), &Terminal::_ready); - ClassDB::bind_method(D_METHOD("_notification"), &Terminal::_notification); - ClassDB::bind_method(D_METHOD("__gui_input"), &Terminal::_gui_input); - //ClassDB::bind_method(D_METHOD("_draw"), &Terminal::_draw); - - ClassDB::bind_method(D_METHOD("write"), &Terminal::write); - - ClassDB::bind_method(D_METHOD("sb_up"), &Terminal::sb_up); - ClassDB::bind_method(D_METHOD("sb_down"), &Terminal::sb_down); - ClassDB::bind_method(D_METHOD("sb_reset"), &Terminal::sb_reset); - ClassDB::bind_method(D_METHOD("clear_sb"), &Terminal::clear_sb); - - ClassDB::bind_method(D_METHOD("start_selection"), &Terminal::start_selection); - ClassDB::bind_method(D_METHOD("select_to_pointer"), &Terminal::select_to_pointer); - ClassDB::bind_method(D_METHOD("reset_selection"), &Terminal::reset_selection); - ClassDB::bind_method(D_METHOD("copy_selection"), &Terminal::copy_selection); - ClassDB::bind_method(D_METHOD("copy_all"), &Terminal::copy_all); - - ClassDB::bind_method(D_METHOD("_update_theme"), &Terminal::update_theme); - ClassDB::bind_method(D_METHOD("_update_size"), &Terminal::update_theme); - - ClassDB::bind_method(D_METHOD("get_cell_size"), &Terminal::get_cell_size); - ClassDB::bind_method(D_METHOD("get_rows"), &Terminal::get_rows); - ClassDB::bind_method(D_METHOD("get_cols"), &Terminal::get_cols); - ClassDB::bind_method(D_METHOD("get_update_mode"), &Terminal::get_update_mode); - ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &Terminal::set_update_mode); - ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode"), "set_update_mode", "get_update_mode"); - - ADD_SIGNAL(MethodInfo("data_sent", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); - ADD_SIGNAL(MethodInfo("key_pressed", PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"), - PropertyInfo(Variant::OBJECT, "event"))); - ADD_SIGNAL(MethodInfo("size_changed", PropertyInfo(Variant::VECTOR2, "new_size"))); - ADD_SIGNAL(MethodInfo("bell")); -} - -Terminal::~Terminal() {} - -// 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; - update_mode = UpdateMode::AUTO; - - if (tsm_screen_new(&screen, NULL, NULL)) { - ERR_PRINT("Error creating new tsm screen"); - } - tsm_screen_set_max_sb(screen, - 1000); // TODO: Use config var for scrollback size. - - if (tsm_vte_new(&vte, screen, write_cb, this, NULL, NULL)) { - ERR_PRINT("Error creating new tsm vte"); - } - - tsm_vte_set_bell_cb(vte, bell_cb, this); -} - -void Terminal::_ready() { update_theme(); } - -void Terminal::_notification(int what) { - switch (what) { - case NOTIFICATION_RESIZED: - update_size(); - break; - case NOTIFICATION_THEME_CHANGED: - // update_theme(); - break; + if (blink_on && !blink_timer->is_stopped()) { + double time_left = blink_timer->get_time_left(); + blink_timer->start(std::min(blink_time_on, time_left)); } } -void Terminal::_gui_input(Variant event) { - Ref k = event; +double Terminal::get_blink_time_on() { return blink_time_on; } - if (k.is_valid()) { - if (!k->is_pressed()) { - return; - } - - int64_t scancode = k->get_keycode(); - int64_t unicode = k->get_unicode(); - uint32_t ascii = unicode <= 127 ? unicode : 0; - - unsigned int mods = 0; - if (k->is_alt_pressed()) - mods |= TSM_ALT_MASK; - if (k->is_ctrl_pressed()) - mods |= TSM_CONTROL_MASK; - if (k->is_shift_pressed()) - mods |= TSM_SHIFT_MASK; - - uint32_t keysym = mapkey({unicode, scancode}); - - input_event_key = k; - tsm_vte_handle_keyboard(vte, keysym, ascii, mods, - unicode ? unicode : TSM_VTE_INVALID); - } -} - -void Terminal::_draw() { - if (update_mode == UpdateMode::DISABLED) - return; - - if ((update_mode > UpdateMode::AUTO) || framebuffer_age == 0) { - /* Draw the full terminal rect background */ - // Draw the rectangle slightly larger, so it fills the entire viewport. - Color background_color = palette[TSM_COLOR_BACKGROUND]; - draw_rect(Rect2(Vector2(-4, -4), get_rect().size + Vector2(8, 8)), - background_color); - } - - framebuffer_age = tsm_screen_draw(screen, text_draw_cb, this); - - if (update_mode == UpdateMode::ALL_NEXT_FRAME) - update_mode = UpdateMode::AUTO; -} - -void Terminal::update_theme() { - ResourceLoader *rl = ResourceLoader::get_singleton(); - Ref default_theme; - - /* Load the default theme if it exists and no theme is set */ - // Don't actually set the theme to default (to allow inheritence of themes), - // but do load default values from it. - - const char *default_theme_path = - "res://addons/godot_xterm/themes/default.tres"; - - if (!get_theme().is_valid() && rl->exists(default_theme_path)) { - default_theme = rl->load(default_theme_path); - } - - /* Generate color palette based on theme */ - - auto set_pallete_color = [this, default_theme](tsm_vte_color color, - String theme_color, - Color default_color) -> void { - Color c; - - c = has_theme_color(theme_color, "Terminal") ? get_theme_color(theme_color, "Terminal") - : has_theme_color_override(theme_color) ? get_theme_color(theme_color, "") - : (default_theme != nullptr && - default_theme->has_color(theme_color, "Terminal")) - ? default_theme->get_color(theme_color, "Terminal") - : default_color; - - color_palette[color][0] = c.get_r8(); - color_palette[color][1] = c.get_g8(); - color_palette[color][2] = c.get_b8(); - - palette[color] = c; - }; - - /* Default to Xterm colors */ - - /* ANSI 0 */ - set_pallete_color(TSM_COLOR_BLACK, "black", Color::html("#000000")); - /* ANSI 1 */ - set_pallete_color(TSM_COLOR_RED, "red", Color::html("#CD0000")); - /* ANSI 2 */ - set_pallete_color(TSM_COLOR_GREEN, "green", Color::html("#00CD00")); - /* ANSI 3 */ - set_pallete_color(TSM_COLOR_YELLOW, "yellow", Color::html("#CDCD00")); - /* ANSI 4 */ - set_pallete_color(TSM_COLOR_BLUE, "blue", Color::html("#0000EE")); - /* ANSI 5 */ - set_pallete_color(TSM_COLOR_MAGENTA, "magenta", Color::html("#CD00CD")); - /* ANSI 6 */ - set_pallete_color(TSM_COLOR_CYAN, "cyan", Color::html("#00CDCD")); - /* ANSI 7 (White) */ - set_pallete_color(TSM_COLOR_LIGHT_GREY, "white", Color::html("#E5E5E5")); - /* ANSI 8 (Bright Black) */ - set_pallete_color(TSM_COLOR_DARK_GREY, "bright_black", - Color::html("#7F7F7F")); - /* ANSI 9 */ - set_pallete_color(TSM_COLOR_LIGHT_RED, "bright_red", Color::html("#FF0000")); - /* ANSI 10 */ - set_pallete_color(TSM_COLOR_LIGHT_GREEN, "bright_green", - Color::html("#00FF00")); - /* ANSI 11 */ - set_pallete_color(TSM_COLOR_LIGHT_YELLOW, "bright_yellow", - Color::html("#FFFF00")); - /* ANSI 12 */ - set_pallete_color(TSM_COLOR_LIGHT_BLUE, "bright_blue", - Color::html("#0000FC")); - /* ANSI 13 */ - set_pallete_color(TSM_COLOR_LIGHT_MAGENTA, "bright_magenta", - Color::html("#FF00FF")); - /* ANSI 14 */ - set_pallete_color(TSM_COLOR_LIGHT_CYAN, "bright_cyan", - Color::html("#00FFFF")); - /* ANSI 15 (Bright White) */ - set_pallete_color(TSM_COLOR_WHITE, "bright_white", Color::html("#FFFFFF")); - - set_pallete_color(TSM_COLOR_FOREGROUND, "foreground", Color::html("#000000")); - set_pallete_color(TSM_COLOR_BACKGROUND, "background", Color::html("#FFFFFF")); - - if (tsm_vte_set_custom_palette(vte, color_palette)) { - ERR_PRINT("Error setting custom palette"); - } - if (tsm_vte_set_palette(vte, "custom")) { - ERR_PRINT("Error setting palette"); - } - - /* Load fonts into the fontmap from theme */ - - auto load_font = [this, default_theme](String font_style) -> void { - Ref fontref; - - if (has_theme_font(font_style, "Terminal")) { - fontref = get_theme_font(font_style, "Terminal"); - } else if (has_theme_font_override(font_style)) { - fontref = get_theme_font(font_style, ""); - } else if (has_theme_font("regular", "Terminal")) { - fontref = get_theme_font("regular", "Terminal"); - } else if (default_theme != nullptr && - default_theme->has_font("regular", "Terminal")) { - fontref = default_theme->get_font("regular", "Terminal"); - } else { - fontref = get_theme_font(""); - } - - fontmap.insert(std::pair>(font_style, fontref)); - }; - - load_font("bold_italic"); - load_font("bold"); - load_font("italic"); - load_font("regular"); - - // update_size(); -} - -void Terminal::draw_background(int row, int col, Color bgcolor, int width = 1) { - /* Draw the background */ - Vector2 background_pos = Vector2(col * cell_size.x, row * cell_size.y); - Rect2 background_rect = Rect2(background_pos, cell_size * Vector2(width, 1)); - draw_rect(background_rect, bgcolor); -} - -void Terminal::draw_foreground(int row, int col, char *ch, - const tsm_screen_attr *attr, Color fgcolor) { - /* Set the font */ - - Ref fontref = get_theme_font(""); - - if (attr->bold && attr->italic) { - fontref = fontmap["bold_italic"]; - } else if (attr->bold) { - fontref = fontmap["bold"]; - } else if (attr->italic) { - fontref = fontmap["italic"]; - } else { - fontref = fontmap["regular"]; - } - - /* Draw the foreground */ - - if (attr->blink) - ; // TODO: Handle blink - - int font_height = fontref.ptr()->get_height(); - Vector2 foreground_pos = - Vector2(col * cell_size.x, row * cell_size.y + font_height / 1.25); - draw_string(fontref, foreground_pos, ch, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, fgcolor); // FIXME - - if (attr->underline) - draw_string(fontref, foreground_pos, "_", HORIZONTAL_ALIGNMENT_LEFT, -1, 16, fgcolor); // FIXME -} - -std::pair Terminal::get_cell_colors(const tsm_screen_attr *attr) { - Color fgcol, bgcol; - float fr = 0, fg = 0, fb = 0, br = 1, bg = 1, bb = 1; - - /* Get foreground color */ - - if (attr->fccode && palette.count(attr->fccode)) { - fgcol = palette[attr->fccode]; - } else { - fr = (float)attr->fr / 255.0; - fg = (float)attr->fg / 255.0; - fb = (float)attr->fb / 255.0; - fgcol = Color(fr, fg, fb); - - if (attr->fccode != -1) { - palette.insert(std::pair(attr->fccode, Color(fr, fg, fb))); - } - } - - /* Get background color */ - - if (attr->bccode && palette.count(attr->bccode)) { - bgcol = palette[attr->bccode]; - } else { - br = (float)attr->br / 255.0; - bg = (float)attr->bg / 255.0; - bb = (float)attr->bb / 255.0; - bgcol = Color(br, bg, bb); - - if (attr->bccode != -1) { - palette.insert(std::pair(attr->bccode, Color(br, bg, bb))); - } - } - - if (attr->inverse) - std::swap(bgcol, fgcol); - - return std::make_pair(bgcol, fgcol); -} - -void Terminal::update_size() { - // Recalculates the cell_size and number of cols/rows based on font size and - // the Control's rect_size. - - Ref fontref; - if (fontmap.count("regular")) - fontref = fontmap["regular"]; - else if (has_theme_font("regular", "Terminal")) - fontref = get_theme_font("regular", "Terminal"); - else - fontref = get_theme_font(""); - - cell_size = fontref->get_string_size("W"); - - rows = std::max(1, (int)floor(get_rect().size.y / cell_size.y)); - cols = std::max(1, (int)floor(get_rect().size.x / cell_size.x)); - - tsm_screen_resize(screen, cols, rows); - - emit_signal("size_changed", Vector2(cols, rows)); -} - -void Terminal::write(PackedByteArray data) { - tsm_vte_input(vte, (char *)data.ptr(), data.size()); -} - -void Terminal::sb_up(int num) { - tsm_screen_sb_up(screen, num); - queue_redraw(); -} - -void Terminal::sb_down(int num) { - tsm_screen_sb_down(screen, num); - queue_redraw(); -} - -void Terminal::sb_reset() { - tsm_screen_sb_reset(screen); - queue_redraw(); -} - -void Terminal::clear_sb() { +void Terminal::clear() { + Vector2 initial_size = get_size(); + set_size(Vector2(initial_size.x, cell_size.y)); tsm_screen_clear_sb(screen); - queue_redraw(); + set_size(initial_size); + back_buffer->queue_redraw(); } -void Terminal::start_selection(Vector2 position) { - tsm_screen_selection_start(screen, position.x, position.y); - queue_redraw(); -} - -void Terminal::select_to_pointer(Vector2 position) { - tsm_screen_selection_target(screen, position.x, position.y); - queue_redraw(); -} - -void Terminal::reset_selection() { - tsm_screen_selection_reset(screen); - queue_redraw(); +String Terminal::copy_all() { + char *out = nullptr; + int len = tsm_screen_copy_all(screen, &out); + String result = String(out); + std::free(out); + return result; } String Terminal::copy_selection() { @@ -646,10 +95,559 @@ String Terminal::copy_selection() { return result; } -String Terminal::copy_all() { - char *out = nullptr; - int len = tsm_screen_copy_all(screen, &out); - String result = String(out); - std::free(out); - return result; +int Terminal::get_cols() { return cols; } +int Terminal::get_rows() { return rows; } + +void Terminal::write(Variant data) { + switch (data.get_type()) { + case Variant::PACKED_BYTE_ARRAY: + break; + case Variant::STRING: + data = ((String)data).to_utf8_buffer(); + break; + default: + ERR_PRINT("Expected data to be a String or PackedByteArray."); + } + + write_buffer.push_back(data); + + queue_redraw(); +} + +void Terminal::_bind_methods() { + // Properties. + ClassDB::bind_method(D_METHOD("set_copy_on_selection", "value"), + &Terminal::set_copy_on_selection); + ClassDB::bind_method(D_METHOD("get_copy_on_selection"), + &Terminal::get_copy_on_selection); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "copy_on_selection"), + "set_copy_on_selection", "get_copy_on_selection"); + ClassDB::bind_method(D_METHOD("set_update_mode", "value"), + &Terminal::set_update_mode); + ClassDB::bind_method(D_METHOD("get_update_mode"), &Terminal::get_update_mode); + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode"), "set_update_mode", + "get_update_mode"); + + ADD_GROUP("Bell", "bell_"); + ClassDB::bind_method(D_METHOD("set_bell_cooldown", "value"), + &Terminal::set_bell_cooldown); + ClassDB::bind_method(D_METHOD("get_bell_cooldown"), + &Terminal::get_bell_cooldown); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bell_cooldown"), + "set_bell_cooldown", "get_bell_cooldown"); + ClassDB::bind_method(D_METHOD("set_bell_muted", "value"), + &Terminal::set_bell_muted); + ClassDB::bind_method(D_METHOD("get_bell_muted"), &Terminal::get_bell_muted); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bell_muted"), "set_bell_muted", + "get_bell_muted"); + + ADD_GROUP("Blink", "blink_"); + ClassDB::bind_method(D_METHOD("set_blink_enabled", "value"), + &Terminal::set_blink_enabled); + ClassDB::bind_method(D_METHOD("get_blink_enabled"), + &Terminal::get_blink_enabled); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blink_enabled"), + "set_blink_enabled", "get_blink_enabled"); + ClassDB::bind_method(D_METHOD("get_blink_time_off"), + &Terminal::get_blink_time_off); + ClassDB::bind_method(D_METHOD("set_blink_time_off", "value"), + &Terminal::set_blink_time_off); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "blink_time_off"), + "set_blink_time_off", "get_blink_time_off"); + ClassDB::bind_method(D_METHOD("set_blink_time_on", "value"), + &Terminal::set_blink_time_on); + ClassDB::bind_method(D_METHOD("get_blink_time_on"), + &Terminal::get_blink_time_on); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "blink_time_on"), + "set_blink_time_on", "get_blink_time_on"); + + // Methods. + ClassDB::bind_method(D_METHOD("clear"), &Terminal::clear); + ClassDB::bind_method(D_METHOD("copy_all"), &Terminal::copy_all); + ClassDB::bind_method(D_METHOD("copy_selection"), &Terminal::copy_selection); + ClassDB::bind_method(D_METHOD("get_cols"), &Terminal::get_cols); + ClassDB::bind_method(D_METHOD("get_rows"), &Terminal::get_rows); + ClassDB::bind_method(D_METHOD("write", "data"), &Terminal::write); + + // Signals. + ADD_SIGNAL(MethodInfo("bell")); + ADD_SIGNAL(MethodInfo("data_sent", + PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"))); + ADD_SIGNAL(MethodInfo("key_pressed", + PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data"), + PropertyInfo(Variant::OBJECT, "event"))); + ADD_SIGNAL( + MethodInfo("size_changed", PropertyInfo(Variant::VECTOR2, "new_size"))); + + // Enumerations. + BIND_ENUM_CONSTANT(UPDATE_MODE_DISABLED); + BIND_ENUM_CONSTANT(UPDATE_MODE_AUTO); + BIND_ENUM_CONSTANT(UPDATE_MODE_ALL); + BIND_ENUM_CONSTANT(UPDATE_MODE_ALL_NEXT_FRAME); + + // Private methods (must be exposed as they are connected to signals). + ClassDB::bind_method(D_METHOD("_flush"), &Terminal::_flush); + ClassDB::bind_method(D_METHOD("_on_back_buffer_draw"), + &Terminal::_on_back_buffer_draw); + ClassDB::bind_method(D_METHOD("_on_selection_held"), + &Terminal::_on_selection_held); + ClassDB::bind_method(D_METHOD("_toggle_blink"), &Terminal::_toggle_blink); +} + +void Terminal::_write_cb(tsm_vte *vte, const char *u8, size_t len, void *data) { + Terminal *term = static_cast(data); + + PackedByteArray bytes; + bytes.resize(len); + { memcpy(bytes.ptrw(), u8, len); } + + if (len > 0) { + if (term->last_input_event_key.is_valid()) { + // The callback was fired from a key press event so emit the "key_pressed" + // signal. + term->emit_signal("key_pressed", bytes.get_string_from_utf8(), + term->last_input_event_key); + term->last_input_event_key.unref(); + } + + term->emit_signal("data_sent", bytes); + } +} + +int Terminal::_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) { + Terminal *term = static_cast(data); + if (term->update_mode == Terminal::UpdateMode::AUTO && age != 0 && + age <= term->framebuffer_age) { + return 0; + } + + if (width < 1) { // No foreground or background to draw. + return 0; + } + + ColorPair color_pair = term->_get_cell_colors(attr); + term->_draw_background(row, col, color_pair.first, width); + + if (len < 1) // No foreground to draw. + return 0; + + size_t ulen = 0; + char buf[5] = {0}; + + char *utf8 = tsm_ucs4_to_utf8_alloc(ch, len, &ulen); + memcpy(buf, utf8, ulen); + term->_draw_foreground(row, col, buf, attr, color_pair.second); + + return 0; +} + +Terminal::Terminal() { + // Ensure we write to terminal before the frame is drawn. Otherwise, the + // terminal state may be updated but not drawn until it is updated again, + // which may not happen for some time. + RenderingServer::get_singleton()->connect("frame_pre_draw", + Callable(this, "_flush")); + + // Override default focus mode. + set_focus_mode(FOCUS_ALL); + + // Name our nodes for easier debugging. + back_buffer->set_name("BackBuffer"); + sub_viewport->set_name("SubViewport"); + front_buffer->set_name("FrontBuffer"); + + // Ensure buffers always have correct size. + back_buffer->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT); + front_buffer->set_anchors_preset(LayoutPreset::PRESET_FULL_RECT); + + // Setup back buffer. + back_buffer->connect("draw", Callable(this, "_on_back_buffer_draw")); + + // Setup sub viewport. + sub_viewport->set_handle_input_locally(false); + sub_viewport->set_transparent_background(true); + sub_viewport->set_snap_controls_to_pixels(false); + sub_viewport->set_update_mode(SubViewport::UPDATE_WHEN_PARENT_VISIBLE); + sub_viewport->set_clear_mode(SubViewport::CLEAR_MODE_NEVER); + sub_viewport->add_child(back_buffer); + add_child(sub_viewport); + + // Setup bell timer. + bell_timer->set_name("BellTimer"); + bell_timer->set_one_shot(true); + add_child(bell_timer); + + // Setup blink timer. + blink_timer->set_name("BlinkTimer"); + blink_timer->set_one_shot(true); + blink_timer->connect("timeout", Callable(this, "_toggle_blink")); + add_child(blink_timer); + + // Setup selection timer. + selection_timer->set_name("SelectionTimer"); + selection_timer->set_wait_time(0.05); + selection_timer->connect("timeout", Callable(this, "_on_selection_held")); + add_child(selection_timer); + + // Setup front buffer. + front_buffer->set_texture(sub_viewport->get_texture()); + add_child(front_buffer); + + framebuffer_age = 0; + update_mode = UpdateMode::AUTO; + + if (tsm_screen_new(&screen, NULL, NULL)) { + ERR_PRINT("Error creating new tsm screen."); + } + tsm_screen_set_max_sb(screen, 1000); + + if (tsm_vte_new(&vte, screen, &Terminal::_write_cb, this, NULL, NULL)) { + ERR_PRINT("Error creating new tsm vte."); + } + + tsm_vte_set_bell_cb(vte, &Terminal::_bell_cb, this); + + _update_theme_item_cache(); +} + +Terminal::~Terminal() { + back_buffer->queue_free(); + sub_viewport->queue_free(); + front_buffer->queue_free(); +} + +void Terminal::_refresh() { + back_buffer->queue_redraw(); + front_buffer->queue_redraw(); + + if (update_mode == UpdateMode::AUTO) + update_mode = UpdateMode::ALL_NEXT_FRAME; +} + +void Terminal::_notification(int what) { + switch (what) { + case NOTIFICATION_RESIZED: + _recalculate_size(); + sub_viewport->set_size(get_size()); + _refresh(); + break; + case NOTIFICATION_THEME_CHANGED: + _update_theme_item_cache(); + _refresh(); + break; + } +} + +#include + +void Terminal::_gui_input(Ref event) { + _handle_key_input(event); + _handle_selection(event); + _handle_mouse_wheel(event); +} +void Terminal::_draw_background(int row, int col, Color bgcolor, + int width = 1) { + /* Draw the background */ + Vector2 background_pos = Vector2(col * cell_size.x, row * cell_size.y); + Rect2 background_rect = Rect2(background_pos, cell_size * Vector2(width, 1)); + back_buffer->draw_rect(background_rect, bgcolor); +} + +void Terminal::_draw_foreground(int row, int col, char *ch, + const tsm_screen_attr *attr, Color fgcolor) { + Ref font; + + if (attr->bold && attr->italic) { + font = theme_cache.fonts["bold_italics_font"]; + } else if (attr->bold) { + font = theme_cache.fonts["bold_font"]; + } else if (attr->italic) { + font = theme_cache.fonts["italics_font"]; + } else { + font = theme_cache.fonts["normal_font"]; + } + + if (attr->blink && blink_enabled) { + if (blink_timer->is_stopped()) + blink_timer->start(blink_on ? blink_time_on : blink_time_off); + + if (!blink_on) + return; + } + + int font_height = font->get_height(theme_cache.font_size); + Vector2 foreground_pos = + Vector2(col * cell_size.x, row * cell_size.y + font_height / 1.25); + back_buffer->draw_string(font, foreground_pos, ch, HORIZONTAL_ALIGNMENT_LEFT, + -1, theme_cache.font_size, fgcolor); + + if (attr->underline) + back_buffer->draw_string(font, foreground_pos, "_", + HORIZONTAL_ALIGNMENT_LEFT, -1, + theme_cache.font_size, fgcolor); +} + +Terminal::ColorPair Terminal::_get_cell_colors(const tsm_screen_attr *attr) { + Color fgcol, bgcol; + int8_t fccode = attr->fccode; + int8_t bccode = attr->bccode; + + // Get foreground color. + if (fccode && palette.count(fccode)) { + fgcol = palette[fccode]; + } else { + fgcol = Color(attr->fr / 255.0f, attr->fg / 255.0f, attr->fb / 255.0f); + + if (fccode != -1) + palette.insert({fccode, fgcol}); + } + + // Get background color. + if (bccode && palette.count(bccode)) { + bgcol = palette[bccode]; + } else { + bgcol = Color(attr->br / 255.0f, attr->bg / 255.0f, attr->bb / 255.0f); + + if (bccode != -1) + palette.insert({bccode, bgcol}); + } + + if (attr->inverse) + std::swap(bgcol, fgcol); + + return std::make_pair(bgcol, fgcol); +} + +void Terminal::_update_theme_item_cache() { + // Fonts. + for (std::map::const_iterator iter = + Terminal::FONTS.begin(); + iter != Terminal::FONTS.end(); ++iter) { + String name = iter->first; + + Ref font = has_theme_font_override(name) ? get_theme_font(name) + : has_theme_font(name, "Terminal") + ? get_theme_font(name, "Terminal") + : ThemeDB::get_singleton()->get_fallback_font(); + + theme_cache.fonts[name] = font; + } + + // Font size. + theme_cache.font_size = + has_theme_font_size_override("font_size") + ? get_theme_font_size("font_size") + : has_theme_font_size("font_size", "Terminal") + ? get_theme_font_size("font_size", "Terminal") + : ThemeDB::get_singleton()->get_fallback_font_size(); + + // Colors. + uint8_t custom_palette[TSM_COLOR_NUM][3]; + + for (ColorMap::const_iterator iter = Terminal::COLORS.begin(); + iter != Terminal::COLORS.end(); ++iter) { + String name = iter->first; + + Color color = has_theme_color_override(name) ? get_theme_color(name) + : has_theme_color(name, "Terminal") + ? get_theme_color(name, "Terminal") + : color = Color::html(iter->second.default_color); + + theme_cache.colors[name] = color; + palette[iter->second.tsm_color] = color; + custom_palette[iter->second.tsm_color][0] = color.get_r8(); + custom_palette[iter->second.tsm_color][1] = color.get_g8(); + custom_palette[iter->second.tsm_color][2] = color.get_b8(); + } + + if (tsm_vte_set_custom_palette(vte, custom_palette)) + ERR_PRINT("Error setting custom palette."); + if (tsm_vte_set_palette(vte, "custom")) + ERR_PRINT("Error setting palette to custom palette."); + + _recalculate_size(); +} + +void Terminal::_flush() { + if (write_buffer.is_empty()) + return; + + for (int i = 0; i < write_buffer.size(); i++) { + PackedByteArray data = static_cast(write_buffer[i]); + tsm_vte_input(vte, (char *)data.ptr(), data.size()); + } + + write_buffer.clear(); + + back_buffer->queue_redraw(); +} + +void Terminal::_on_back_buffer_draw() { + if (update_mode == UpdateMode::DISABLED) { + return; + } + + if ((update_mode > UpdateMode::AUTO) || framebuffer_age == 0) { + Color background_color = palette[TSM_COLOR_BACKGROUND]; + back_buffer->draw_rect(back_buffer->get_rect(), background_color); + } + + int prev_framebuffer_age = framebuffer_age; + framebuffer_age = tsm_screen_draw(screen, &Terminal::_text_draw_cb, this); + + if (update_mode == UpdateMode::ALL_NEXT_FRAME && prev_framebuffer_age != 0) + update_mode = UpdateMode::AUTO; +} + +void Terminal::_toggle_blink() { + if (blink_enabled) { + blink_on = !blink_on; + _refresh(); + } +} + +void Terminal::_on_selection_held() { + if (!(Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) || + selection_mode == SelectionMode::NONE) { + if (copy_on_selection) + DisplayServer::get_singleton()->clipboard_set_primary(copy_selection()); + selection_timer->stop(); + return; + } + + Vector2 target = get_local_mouse_position() / cell_size; + tsm_screen_selection_target(screen, target.x, target.y); + back_buffer->queue_redraw(); + selection_timer->start(); +} + +void Terminal::_bell_cb(tsm_vte *vte, void *data) { + Terminal *term = static_cast(data); + + if (!term->bell_muted && term->bell_cooldown == 0 || + term->bell_timer->get_time_left() == 0) { + term->emit_signal("bell"); + if (term->bell_cooldown > 0) + term->bell_timer->start(term->bell_cooldown); + } +} + +void Terminal::_handle_key_input(Ref event) { + if (!event.is_valid() || !event->is_pressed()) + return; + + const Key keycode = event->get_keycode(); + char32_t unicode = event->get_unicode(); + uint32_t ascii = unicode <= 127 ? unicode : 0; + + unsigned int mods = 0; + if (event->is_alt_pressed()) + mods |= TSM_ALT_MASK; + if (event->is_ctrl_pressed()) + mods |= TSM_CONTROL_MASK; + if (event->is_shift_pressed()) + mods |= TSM_SHIFT_MASK; + + std::pair key = {keycode, unicode}; + uint32_t keysym = + (KEY_MAP.count(key) > 0) ? KEY_MAP.at(key) : XKB_KEY_NoSymbol; + + last_input_event_key = event; + tsm_vte_handle_keyboard(vte, keysym, ascii, mods, + unicode ? unicode : TSM_VTE_INVALID); + + // Return to the bottom of the scrollback buffer if we scrolled up. Ignore + // modifier keys pressed in isolation or if Ctrl+Shift modifier keys are + // pressed. + std::set mod_keys = {KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META}; + if (mod_keys.find(keycode) == mod_keys.end() && + !(event->is_ctrl_pressed() && event->is_shift_pressed())) { + tsm_screen_sb_reset(screen); + back_buffer->queue_redraw(); + } + + // Prevent focus changing to other inputs when pressing Tab or Arrow keys. + std::set tab_arrow_keys = {KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, + KEY_TAB}; + if (tab_arrow_keys.find(keycode) != tab_arrow_keys.end()) + accept_event(); +} + +void Terminal::_handle_mouse_wheel(Ref event) { + if (!event.is_valid() || !event->is_pressed()) + return; + + void (*scroll_func)(tsm_screen *, unsigned int) = nullptr; + + switch (event->get_button_index()) { + case MOUSE_BUTTON_WHEEL_UP: + scroll_func = &tsm_screen_sb_up; + break; + case MOUSE_BUTTON_WHEEL_DOWN: + scroll_func = &tsm_screen_sb_down; + break; + }; + + if (scroll_func != nullptr) { + // Scroll 5 times as fast as normal if alt is pressed (like TextEdit). + // Otherwise, just scroll 3 lines. + int speed = event->is_alt_pressed() ? 15 : 3; + double factor = event->get_factor(); + (*scroll_func)(screen, speed * factor); + back_buffer->queue_redraw(); + } +} + +void Terminal::_handle_selection(Ref event) { + if (!event.is_valid()) + return; + + Ref mb = event; + if (mb.is_valid()) { + if (!mb->is_pressed() || !mb->get_button_index() == MOUSE_BUTTON_LEFT) + return; + + if (selecting) { + selecting = false; + selection_mode = SelectionMode::NONE; + tsm_screen_selection_reset(screen); + back_buffer->queue_redraw(); + } + + selecting = false; + selection_mode = SelectionMode::POINTER; + + return; + } + + Ref mm = event; + if (mm.is_valid()) { + if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) && + selection_mode != SelectionMode::NONE && !selecting) { + selecting = true; + Vector2 start = event->get_position() / cell_size; + tsm_screen_selection_start(screen, start.x, start.y); + back_buffer->queue_redraw(); + selection_timer->start(); + } + return; + } +} + +void Terminal::_recalculate_size() { + Vector2 size = get_size(); + + cell_size = theme_cache.fonts["normal_font"]->get_string_size( + "W", HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size); + + rows = std::max(1, (int)floor(size.y / cell_size.y)); + cols = std::max(1, (int)floor(size.x / cell_size.x)); + + tsm_screen_resize(screen, cols, rows); + sub_viewport->set_size(size); + + emit_signal("size_changed", Vector2(cols, rows)); } diff --git a/addons/godot_xterm/native/src/terminal.h b/addons/godot_xterm/native/src/terminal.h index 0db6b38..c42a266 100644 --- a/addons/godot_xterm/native/src/terminal.h +++ b/addons/godot_xterm/native/src/terminal.h @@ -1,93 +1,165 @@ -// SPDX-FileCopyrightText: 2021-2022 Leroy Hopson +// SPDX-FileCopyrightText: 2021-2023 Leroy Hopson // SPDX-License-Identifier: MIT -#ifndef TERMINAL_H -#define TERMINAL_H +#ifndef GODOT_XTERM_TERMINAL_H +#define GODOT_XTERM_TERMINAL_H #include #include +#include #include +#include +#include +#include +#include +#include #include #include #include #include -namespace godot { +using namespace godot; class Terminal : public Control { GDCLASS(Terminal, Control) -public: - Ref 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, int> _key_list; - static void _populate_key_list(); - static uint32_t mapkey(std::pair key); - - std::map palette = {}; - std::map> fontmap = {}; - - void update_size(); - void update_theme(); - -public: - std::pair 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 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> fonts = std::map>{}; + std::map colors = std::map{}; + } theme_cache; + + typedef std::map ColorMap; + typedef std::pair ColorPair; + typedef std::map FontMap; + typedef std::map, 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 palette = {}; tsm_age_t framebuffer_age; -}; -} // namespace godot -#endif // TERMINAL_H + Ref 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 event); + void _handle_mouse_wheel(Ref event); + void _handle_selection(Ref event); + void _recalculate_size(); + void _refresh(); + void _update_theme_item_cache(); +}; + +VARIANT_ENUM_CAST(Terminal, UpdateMode); + +#endif // GODOT_XTERM_TERMINAL_H diff --git a/addons/godot_xterm/nodes/pty/libuv_utils.gd b/addons/godot_xterm/nodes/pty/libuv_utils.gd deleted file mode 100644 index 8a818ea..0000000 --- a/addons/godot_xterm/nodes/pty/libuv_utils.gd +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2021, Leroy Hopson (MIT License) - -@tool -extends Object -# Wrapper around libuv utility functions. -# GDNative does not currently support registering static functions so we fake it. -# Only the static functions of this class should be called. - - -static func get_os_environ() -> Dictionary: - # 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 - # specific code. Easier to use libuv's utility function. - return LibuvUtils.get_os_environ() - - -static func get_cwd() -> String: - # Use uv_cwd() rather than Directory.get_current_dir() because the latter - # defaults to res:// even if starting godot from a different directory. - return LibuvUtils.new().get_cwd() - - -static func get_windows_build_number() -> int: - assert(OS.get_name() == "Windows") #,"This function is only supported checked Windows.") - var release: String = LibuvUtils.new().get_os_release() - assert(false) #,"Not implemented.") - return 0 - - -static func kill(pid: int, signum: int): - if pid > 1: - return LibuvUtils.new().kill(pid, signum) - - -static func new(): - assert(false) #,"Abstract sealed (i.e. static) class should not be instantiated.") diff --git a/addons/godot_xterm/nodes/pty/unix/pty_unix.gd b/addons/godot_xterm/nodes/pty/unix/pty_unix.gd index 44ed973..7ebdf45 100644 --- a/addons/godot_xterm/nodes/pty/unix/pty_unix.gd +++ b/addons/godot_xterm/nodes/pty/unix/pty_unix.gd @@ -64,7 +64,10 @@ var _exit_cb: Callable # Writes data to the socket. # data: The data to write. func write(data) -> void: - assert(data is PackedByteArray or data is String, "Invalid type for argument 'data'. Should be of type PackedByteArray or String") + var correct_type: bool = data is PackedByteArray or data is String + var err_message := "Invalid type for argument 'data'. Should be of type PackedByteArray or String" + assert(correct_type, err_message) + if _pipe: _pipe.write(data if data is PackedByteArray else data.to_utf8_buffer()) @@ -88,7 +91,7 @@ func _parse_env(env: Dictionary = {}) -> PackedStringArray: for key in keys: var value = env[key] var valid = key is String and value is String - assert(valid) #,"Env key/value pairs must be of type String/String.") + assert(valid) #,"Env key/value pairs must be of type String/String.") if not valid: push_warning("Skipping invalid env key/value pair.") @@ -129,9 +132,23 @@ func fork( _exit_cb = Callable(self, "on_exit") # Actual fork. - var result = PTYUnix.new().fork( - # 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 + var result = ( + PTYUnix + . new() + . fork( + # 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: @@ -146,10 +163,10 @@ func fork( _pid = result[1].pid _pipe = Pipe.new() - _pipe.open(_fd, true) # FIXME: _pipe.open(_fd) should be sufficient but requires two args. + _pipe.open(_fd, false) # 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")) return OK @@ -159,7 +176,7 @@ func open(cols: int = DEFAULT_COLS, rows: int = DEFAULT_ROWS) -> Array: func _exit_tree(): - _exit_cb = null + _exit_cb = Callable() if _pid > 1: LibuvUtils.kill(_pid, IPCSignal.SIGHUP) if _pipe: diff --git a/addons/godot_xterm/nodes/terminal/viewport.gd b/addons/godot_xterm/nodes/terminal/viewport.gd deleted file mode 100644 index 74dffa3..0000000 --- a/addons/godot_xterm/nodes/terminal/viewport.gd +++ /dev/null @@ -1,2 +0,0 @@ -@tool -extends SubViewport diff --git a/addons/godot_xterm/nodes/terminal/viewport.tscn b/addons/godot_xterm/nodes/terminal/viewport.tscn deleted file mode 100644 index 669241a..0000000 --- a/addons/godot_xterm/nodes/terminal/viewport.tscn +++ /dev/null @@ -1,23 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://c62updkby54f3"] - -[sub_resource type="GDScript" id="GDScript_d8lvm"] -script/source = "@tool -extends SubViewport -" - -[node name="SubViewport" type="SubViewport"] -transparent_bg = true -handle_input_locally = false -gui_snap_controls_to_pixels = false -size = Vector2i(2, 2) -render_target_clear_mode = 1 -script = SubResource("GDScript_d8lvm") - -[node name="Terminal" type="Terminal" parent="."] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_right = -2.0 -offset_bottom = -2.0 -grow_horizontal = 2 -grow_vertical = 2 diff --git a/addons/godot_xterm/plugin.gd b/addons/godot_xterm/plugin.gd index 916476d..a8034a6 100644 --- a/addons/godot_xterm/plugin.gd +++ b/addons/godot_xterm/plugin.gd @@ -1,7 +1,7 @@ @tool extends EditorPlugin -var pty_supported := OS.get_name() in ["X11", "Server", "OSX"] +var pty_supported := OS.get_name() in ["Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS"] var asciicast_import_plugin var xrdb_import_plugin var terminal_panel: Control @@ -17,18 +17,16 @@ func _enter_tree(): var asciicast_script = preload("./resources/asciicast.gd") add_custom_type("Asciicast", "Animation", asciicast_script, null) - var terminal_script = preload("./terminal.gd") var terminal_icon = load( "%s/nodes/terminal/terminal_icon.svg" % get_script().resource_path.get_base_dir() ) - add_custom_type("Terminal", "Control", terminal_script, terminal_icon) if pty_supported: var base_dir = get_script().resource_path.get_base_dir() var pty_icon = load("%s/nodes/pty/pty_icon.svg" % base_dir) var pty_script match OS.get_name(): - "X11", "Server", "OSX": + "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS": pty_script = load("%s/pty.gd" % base_dir) add_custom_type("PTY", "Node", pty_script, pty_icon) terminal_panel = preload("./editor_plugins/terminal/terminal_panel.tscn").instantiate() @@ -45,7 +43,6 @@ func _exit_tree(): xrdb_import_plugin = null remove_custom_type("Asciicast") - remove_custom_type("Terminal") if pty_supported: remove_custom_type("PTY") diff --git a/addons/godot_xterm/pty.gd b/addons/godot_xterm/pty.gd index 5ffbfe3..f9ff5b3 100644 --- a/addons/godot_xterm/pty.gd +++ b/addons/godot_xterm/pty.gd @@ -6,10 +6,9 @@ @tool extends Node -const _LibuvUtils := preload("./nodes/pty/libuv_utils.gd") +var _LibuvUtils := LibuvUtils const _PTYNative := preload("./nodes/pty/pty_native.gd") const _PTYUnix := preload("./nodes/pty/unix/pty_unix.gd") -const _Terminal := preload("./terminal.gd") const DEFAULT_NAME := "xterm-256color" const DEFAULT_COLS := 80 @@ -23,30 +22,26 @@ const IPCSignal = _PTYUnix.IPCSignal signal data_received(data) signal exited(exit_code, signum) -@export var terminal_path: NodePath = NodePath() : +@export var terminal_path: NodePath = NodePath(): get: return terminal_path set(value): terminal_path = value _set_terminal(get_node_or_null(terminal_path)) -var _terminal: _Terminal = null : - get: - return _terminal # TODOConverter40 Non existent get function - set(mod_value): - mod_value # TODOConverter40 Copy here content of _set_terminal +var _terminal # The column size in characters. -@export var cols: int = DEFAULT_COLS : +@export var cols: int = DEFAULT_COLS: get: - return cols # TODOConverter40 Copy here content of get_cols + return cols # TODOConverter40 Copy here content of get_cols set(mod_value): mod_value # TODOConverter40 Copy here content of set_cols # The row size in characters. -@export var rows: int = DEFAULT_ROWS : +@export var rows: int = DEFAULT_ROWS: get: - return rows # TODOConverter40 Copy here content of get_rows + return rows # TODOConverter40 Copy here content of get_rows set(mod_value): mod_value # TODOConverter40 Copy here content of set_rows @@ -66,13 +61,13 @@ var _pty_native: _PTYNative func _init(): var os_name := OS.get_name() match os_name: - "X11", "Server", "OSX": + "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS": _pty_native = _PTYUnix.new() _: - push_error("PTY is not support on current platform (%s)." % os_name) + push_error("PTY is not supported on the current platform (%s)." % os_name) - _pty_native.connect("data_received",Callable(self,"_on_pty_native_data_received")) - _pty_native.connect("exited",Callable(self,"_on_pty_native_exited")) + _pty_native.connect("data_received", Callable(self, "_on_pty_native_data_received")) + _pty_native.connect("exited", Callable(self, "_on_pty_native_exited")) add_child(_pty_native) @@ -96,31 +91,31 @@ func set_rows(value: int): func get_rows() -> int: return _rows - -func _set_terminal(value: _Terminal): + +func _set_terminal(value): if _terminal == value: return # Disconect the current terminal, if any. - if _terminal: - disconnect("data_received",Callable(_terminal,"write")) - _terminal.disconnect("data_sent",Callable(self,"write")) - _terminal.disconnect("size_changed",Callable(self,"resizev")) + if _terminal != null: + disconnect("data_received", Callable(_terminal, "write")) + _terminal.disconnect("data_sent", Callable(self, "write")) + _terminal.disconnect("size_changed", Callable(self, "resizev")) _terminal = value - if not _terminal: + if _terminal == null: return # Connect the new terminal. resize(_terminal.get_cols(), _terminal.get_rows()) - if not _terminal.is_connected("size_changed",Callable(self,"resizev")): - _terminal.connect("size_changed",Callable(self,"resizev")) - if not _terminal.is_connected("data_sent",Callable(self,"write")): - _terminal.connect("data_sent",Callable(self,"write")) - if not is_connected("data_received",Callable(_terminal,"write")): - connect("data_received",Callable(_terminal,"write")) + if not _terminal.is_connected("size_changed", Callable(self, "resizev")): + _terminal.connect("size_changed", Callable(self, "resizev")) + if not _terminal.is_connected("data_sent", Callable(self, "write")): + _terminal.connect("data_sent", Callable(self, "write")) + if not is_connected("data_received", Callable(_terminal, "write")): + connect("data_received", Callable(_terminal, "write")) # Writes data to the socket. @@ -159,7 +154,7 @@ func _notification(what: int): match what: NOTIFICATION_PARENTED: var parent = get_parent() - if parent is _Terminal: + if parent is Terminal: self.terminal_path = get_path_to(parent) diff --git a/addons/godot_xterm/terminal.gd b/addons/godot_xterm/terminal.gd deleted file mode 100644 index e896274..0000000 --- a/addons/godot_xterm/terminal.gd +++ /dev/null @@ -1,291 +0,0 @@ -# Copyright (c) 2021, Leroy Hopson (MIT License). -# -# This file contains snippets of code derived from Godot's TextEdit node. -# These snippets are copyright of their authors and released under the MIT license: -# - Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur (MIT License). -# - Copyright (c) 2014-2021 Godot Engine contributors (MIT License). -@tool -extends Control - -signal data_sent(data) -signal key_pressed(data, event) -signal size_changed(new_size) -signal bell - -enum UpdateMode { - DISABLED, - AUTO, - ALL, - ALL_NEXT_FRAME, -} - -enum SelectionMode { - NONE, - POINTER, -} - -@export var update_mode: UpdateMode = UpdateMode.AUTO : - get: - return update_mode # TODOConverter40 Non existent get function - set(p_update_mode): - set_update_mode(p_update_mode) - -# If true, text in the terminal will be copied to the clipboard when selected. -@export var copy_on_selection: bool - -# Bell -# If muted, the "bell" signal will not be emitted when the bell "\u0007" character -# is written to the terminal. -@export var bell_muted := false -# Amount of time in seconds that must pass before emitting a new "bell" signal. -# This can be useful in cases where the bell character is being written too -# frequently such as `while true; do echo -e "\a"; done`. -@export var bell_cooldown: float = 0.1 - -@export var blink_on_time: float = 0.6 -@export var blink_off_time: float = 0.3 - -var _cols := 2 -var _rows := 2 - -var _default_theme: Theme = preload("./themes/default.tres") -var _viewport: SubViewport = preload("./nodes/terminal/viewport.tscn").instantiate() -var _native_terminal: Control = _viewport.get_node("Terminal") -var _screen := TextureRect.new() - -var _bell_timer := Timer.new() - -var _selecting := false -var _selecting_mode: int = SelectionMode.NONE -var _selection_timer := Timer.new() - -var _buffer := [] - - -func set_update_mode(value): - update_mode = value - _native_terminal.update_mode = value - - -func get_cols() -> int: - return _cols - - -func get_rows() -> int: - return _rows - - -func write(data) -> void: - # "Invalid type for argument 'data'. Should be of type PackedByteArray or String." - assert(data is PackedByteArray or data is String) - - # Will be cleared when _flush() is called after RenderingServer emits the "frame_pre_draw" signal. - _buffer.push_back(data) - - # Ensure redraw is requested if terminal is visible. - if visible: - queue_redraw() - - -func _flush(): - for data in _buffer: - _native_terminal.write(data if data is PackedByteArray else data.to_utf8_buffer()) - _native_terminal.queue_redraw() - _buffer.clear() - - -func clear() -> void: - var initial_size = _native_terminal.size - _native_terminal.size.y = _native_terminal.cell_size.y - _native_terminal.clear_sb() - _native_terminal.size = initial_size - - -func copy_selection() -> String: - return _native_terminal.copy_selection() - - -func copy_all() -> String: - return _native_terminal.copy_all() - - -func _ready(): - _update_theme() - - _native_terminal.update_mode = update_mode - _native_terminal.connect("data_sent",Callable(self,"_on_data_sent")) - _native_terminal.connect("key_pressed",Callable(self,"_on_key_pressed")) - _native_terminal.connect("size_changed",Callable(self,"_on_size_changed")) - - _viewport.size = size - _viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS - - _screen.set_anchors_preset(PRESET_VCENTER_WIDE) - _screen.texture = _viewport.get_texture() - - _native_terminal.connect("bell",Callable(self,"_on_bell")) - _bell_timer.one_shot = true - add_child(_bell_timer) - - _selection_timer.wait_time = 0.05 - _selection_timer.connect("timeout",Callable(self,"_on_selection_held")) - - add_child(_viewport) - add_child(_screen) - add_child(_selection_timer) - - _refresh() - - # Ensure the terminal state machine's framebuffer is up to date before - # we make all the draw_* calls caused by writing. We need to use signals - # here rather than yield otherwise we will sometimes get a "Resumed - # function after yield but class instance is gone" error. - RenderingServer.connect("frame_pre_draw",Callable(self,"_flush")) - - -func _update_theme(): - # Themes are not propagated through the SubViewport, so in order for theme - # inheritance to work we can pass through the theme variables manually. - for color in _default_theme.get_color_list("Terminal"): - var c: Color - if has_theme_color(color, "Terminal"): - c = get_theme_color(color, "Terminal") - else: - c = _default_theme.get_color(color, "Terminal") - _native_terminal.add_theme_color_override(color, c) - for font in _default_theme.get_font_list("Terminal"): - var f: Font - if has_theme_font(font, "Terminal"): - f = get_theme_font(font, "Terminal") - elif has_theme_font("regular", "Terminal"): - f = get_theme_font("regular", "Terminal") - else: - if _default_theme.has_theme_font(font, "Terminal"): - f = _default_theme.get_font(font, "Terminal") - else: - f = _default_theme.get_font(font, "regular") - _native_terminal.add_theme_font_override(font, f) - _native_terminal._update_theme() - _native_terminal._update_size() - - -func _refresh(): - _screen.queue_redraw() - if update_mode == UpdateMode.AUTO: - _native_terminal.update_mode = UpdateMode.ALL_NEXT_FRAME - - -func _gui_input(event): - _native_terminal.__gui_input(event) # FIXME: use _gui_input rather than __gui_input. - - if event is InputEventKey and event.pressed: - # Return to bottom of scrollback buffer if we scrolled up. Ignore modifier - # keys pressed in isolation or if Ctrl+Shift modifier keys are pressed. - if ( - not event.keycode in [KEY_ALT, KEY_SHIFT, KEY_CTRL, KEY_META, KEY_MASK_CMD_OR_CTRL] - and not (event.ctrl_pressed and event.shift_pressed) - ): - _native_terminal.sb_reset() - - # Prevent focus changing to other inputs when pressing Tab or Arrow keys. - if event.keycode in [KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_TAB]: - accept_event() - - # FIXME - #_handle_mouse_wheel(event) - #_handle_selection(event) - - -func _handle_mouse_wheel(event: InputEventMouseButton): - if not event or not event.is_pressed(): - return - - if event.button_index == MOUSE_BUTTON_WHEEL_UP: - if event.alt: - # Scroll 5 times as fast as normal (like TextEdit). - _native_terminal.sb_up(15 * event.factor) - else: - # Scroll 3 lines. - _native_terminal.sb_up(3 * event.factor) - - if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: - if event.alt: - # Scroll 5 times as fast as normal (like TextEdit). - _native_terminal.sb_down(15 * event.factor) - else: - # Scroll 3 lines. - _native_terminal.sb_down(3 * event.factor) - - -func _handle_selection(event: InputEventMouse): - if event is InputEventMouseButton: - if not event or not event.is_pressed() or not event.button_index == MOUSE_BUTTON_LEFT: - return - - if _selecting: - _selecting = false - _selecting_mode = SelectionMode.NONE - _native_terminal.reset_selection() - - # Single-click select pointer. - _selecting = false - _selecting_mode = SelectionMode.POINTER - - elif event is InputEventMouseMotion: - if ( - event.button_mask & MOUSE_BUTTON_MASK_LEFT - and _selecting_mode != SelectionMode.NONE - and not _selecting - ): - _selecting = true - _native_terminal.start_selection(_mouse_to_cell(event.position)) - _selection_timer.start() - - -func _on_selection_held() -> void: - if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) or _selecting_mode == SelectionMode.NONE: - if copy_on_selection: - var selection = _native_terminal.copy_selection() - # TODO:godot4 - # OS.set_clipboard(selection) - _selection_timer.stop() - return - - var position: Vector2 = _mouse_to_cell(get_local_mouse_position()) - _native_terminal.select_to_pointer(position) - _selection_timer.start() - - -func _notification(what: int) -> void: - match what: - NOTIFICATION_RESIZED: - _viewport.size = size - _refresh() - NOTIFICATION_THEME_CHANGED: - _update_theme() - _refresh() - - -func _on_data_sent(data: PackedByteArray): - emit_signal("data_sent", data) - - -func _on_key_pressed(data: PackedByteArray, event: InputEventKey): - emit_signal("key_pressed", data.get_string_from_utf8(), event) - - -func _on_size_changed(new_size: Vector2): - _cols = new_size.x - _rows = new_size.y - emit_signal("size_changed", new_size) - - -func _on_bell(): - if not bell_muted and (bell_cooldown == 0 or _bell_timer.time_left == 0): - emit_signal("bell") - if bell_cooldown > 0: - _bell_timer.start(bell_cooldown) - - -func _mouse_to_cell(pos: Vector2) -> Vector2: - return Vector2(pos / _native_terminal.cell_size) diff --git a/addons/godot_xterm/themes/fonts/regular.tres b/addons/godot_xterm/themes/fonts/regular.tres index 6c91b12..d167d8a 100644 --- a/addons/godot_xterm/themes/fonts/regular.tres +++ b/addons/godot_xterm/themes/fonts/regular.tres @@ -1,8 +1,23 @@ -[gd_resource type="FontFile" load_steps=2 format=2] +[gd_resource type="FontFile" load_steps=2 format=3 uid="uid://dhmyegcx24dpo"] -[ext_resource path="res://addons/godot_xterm/themes/fonts/hack/hack_regular-3.003.ttf" type="FontFile" id=4] +[ext_resource type="FontFile" uid="uid://edo5q2qxos3u" path="res://addons/godot_xterm/themes/fonts/hack/hack_regular-3.003.ttf" id="4"] [resource] -size = 14 -extra_spacing_bottom = 1 -font_data = ExtResource( 4 ) +fallbacks = [ExtResource("4")] +face_index = null +embolden = null +transform = null +cache/0/16/0/ascent = 0.0 +cache/0/16/0/descent = 0.0 +cache/0/16/0/underline_position = 0.0 +cache/0/16/0/underline_thickness = 0.0 +cache/0/16/0/scale = 1.0 +cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/16/0/kerning_overrides/14/0 = Vector2(0, 0) +cache/0/14/0/ascent = 0.0 +cache/0/14/0/descent = 0.0 +cache/0/14/0/underline_position = 0.0 +cache/0/14/0/underline_thickness = 0.0 +cache/0/14/0/scale = 1.0 +cache/0/14/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/14/0/kerning_overrides/14/0 = Vector2(0, 0) diff --git a/examples/asciicast/asciicast.tscn b/examples/asciicast/asciicast.tscn index 8d22976..be321f7 100644 --- a/examples/asciicast/asciicast.tscn +++ b/examples/asciicast/asciicast.tscn @@ -1,19 +1,26 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=4 format=3 uid="uid://did1ipako11pd"] -[ext_resource path="res://addons/godot_xterm/terminal.gd" type="Script" id=1] -[ext_resource path="res://examples/asciicast/example.cast" type="Animation" id=6] +[ext_resource type="Theme" uid="uid://b7vd50tw2g1nl" path="res://themes/default.tres" id="1_nsh8i"] +[ext_resource type="Animation" uid="uid://dcgqvylq0648u" path="res://examples/asciicast/example.cast" id="2_3048a"] -[node name="Terminal" type="Control"] +[sub_resource type="AnimationLibrary" id="AnimationLibrary_02l7k"] +_data = { +"example": ExtResource("2_3048a") +} + +[node name="Terminal" type="Terminal"] +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 focus_mode = 1 -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} +theme = ExtResource("1_nsh8i") [node name="AnimationPlayer" type="AnimationPlayer" parent="."] autoplay = "example" playback_speed = 2.0 method_call_mode = 1 -anims/example = ExtResource( 6 ) +libraries = { +"": SubResource("AnimationLibrary_02l7k") +} diff --git a/examples/menu/menu.gd b/examples/menu/menu.gd index 2827257..4c587bd 100644 --- a/examples/menu/menu.gd +++ b/examples/menu/menu.gd @@ -42,11 +42,11 @@ var offset: int func _ready(): - if not $Terminal.is_connected("key_pressed",Callable(self,"_on_Terminal_key_pressed")): + if not $Terminal.is_connected("key_pressed", Callable(self, "_on_Terminal_key_pressed")): # warning-ignore:return_value_discarded - $Terminal.connect("key_pressed",Callable(self,"_on_Terminal_key_pressed")) + $Terminal.connect("key_pressed", Callable(self, "_on_Terminal_key_pressed")) # warning-ignore:return_value_discarded - $Terminal.connect("size_changed",Callable(self,"draw_all")) + $Terminal.connect("size_changed", Callable(self, "draw_all")) $Terminal.grab_focus() draw_all() @@ -134,7 +134,10 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void: "Asciicast": var scene = item.scene.instantiate() var animation_player: AnimationPlayer = scene.get_node("AnimationPlayer") - scene.connect("key_pressed",Callable(self,"_on_Asciicast_key_pressed").bind(animation_player)) + scene.connect( + "key_pressed", + Callable(self, "_on_Asciicast_key_pressed").bind(animation_player) + ) add_child(scene) scene.grab_focus() await animation_player.animation_finished @@ -163,7 +166,7 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void: pass # FIXME #if OS.has_feature("JavaScript"): - #JavaScript.eval("window.history.back() || window.close()") + #JavaScript.eval("window.history.back() || window.close()") #get_tree().quit() diff --git a/examples/menu/menu.tscn b/examples/menu/menu.tscn index 4576d86..776b625 100644 --- a/examples/menu/menu.tscn +++ b/examples/menu/menu.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://brjrtf5fpptw8"] -[ext_resource type="Script" path="res://addons/godot_xterm/terminal.gd" id="1"] [ext_resource type="Script" path="res://examples/menu/menu.gd" id="2"] +[ext_resource type="Theme" uid="uid://b7vd50tw2g1nl" path="res://themes/default.tres" id="2_o1653"] [node name="Menu" type="Control"] layout_mode = 3 @@ -12,13 +12,11 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("2") -[node name="Terminal" type="Control" parent="."] +[node name="Terminal" type="Terminal" parent="."] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -focus_mode = 1 -script = ExtResource("1") -copy_on_selection = false \ No newline at end of file +theme = ExtResource("2_o1653") diff --git a/examples/retro_term/retro_term.tscn b/examples/retro_term/retro_term.tscn index bbc6710..0110ebf 100644 --- a/examples/retro_term/retro_term.tscn +++ b/examples/retro_term/retro_term.tscn @@ -1,9 +1,9 @@ -[gd_scene load_steps=6 format=2] +[gd_scene load_steps=6 format=3 uid="uid://ddebdj001o1m5"] -[ext_resource path="res://examples/menu/menu.tscn" type="PackedScene" id=1] -[ext_resource path="res://themes/retro_green.tres" type="Theme" id=2] +[ext_resource type="PackedScene" uid="uid://brjrtf5fpptw8" path="res://examples/menu/menu.tscn" id="1"] +[ext_resource type="Theme" uid="uid://blh56m1gdieyu" path="res://themes/retro_green.tres" id="2"] -[sub_resource type="Shader" id=1] +[sub_resource type="Shader" id="1"] code = "/* Shader from Godot Shaders - the free shader library. godotshaders.com/shader/VHS-and-CRT-monitor-effect @@ -234,31 +234,31 @@ void fragment() COLOR = text; }" -[sub_resource type="ShaderMaterial" id=2] -shader = SubResource( 1 ) -shader_param/overlay = true -shader_param/scanlines_opacity = 0.4 -shader_param/scanlines_width = 0.25 -shader_param/grille_opacity = 0.3 -shader_param/resolution = Vector2( 768, 240 ) -shader_param/pixelate = false -shader_param/roll = true -shader_param/roll_speed = 8.0 -shader_param/roll_size = 15.0 -shader_param/roll_variation = 1.8 -shader_param/distort_intensity = 0.05 -shader_param/noise_opacity = 0.4 -shader_param/noise_speed = 5.0 -shader_param/static_noise_intensity = 0.06 -shader_param/aberration = 0.0 -shader_param/brightness = 2.5 -shader_param/discolor = true -shader_param/warp_amount = 1.0 -shader_param/clip_warp = false -shader_param/vignette_intensity = 0.4 -shader_param/vignette_opacity = 0.5 +[sub_resource type="ShaderMaterial" id="2"] +shader = SubResource("1") +shader_parameter/overlay = true +shader_parameter/scanlines_opacity = 0.4 +shader_parameter/scanlines_width = 0.25 +shader_parameter/grille_opacity = 0.3 +shader_parameter/resolution = Vector2(768, 240) +shader_parameter/pixelate = false +shader_parameter/roll = true +shader_parameter/roll_speed = 8.0 +shader_parameter/roll_size = 15.0 +shader_parameter/roll_variation = 1.8 +shader_parameter/distort_intensity = 0.05 +shader_parameter/noise_opacity = 0.4 +shader_parameter/noise_speed = 5.0 +shader_parameter/static_noise_intensity = 0.06 +shader_parameter/aberration = 0.0 +shader_parameter/brightness = 2.5 +shader_parameter/discolor = true +shader_parameter/warp_amount = 1.0 +shader_parameter/clip_warp = false +shader_parameter/vignette_intensity = 0.4 +shader_parameter/vignette_opacity = 0.5 -[sub_resource type="Environment" id=3] +[sub_resource type="Environment" id="3"] background_mode = 4 glow_enabled = true glow_intensity = 1.0 @@ -266,36 +266,39 @@ glow_strength = 1.15 glow_blend_mode = 0 [node name="RetroTerm" type="Control"] +layout_mode = 3 +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -theme = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("2") [node name="ColorRect" type="ColorRect" parent="."] show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -color = Color( 0.156863, 0.156863, 0.156863, 1 ) -__meta__ = { -"_edit_use_anchors_": false -} +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.156863, 0.156863, 0.156863, 1) -[node name="Menu" parent="." instance=ExtResource( 1 )] +[node name="Menu" parent="." instance=ExtResource("1")] +layout_mode = 1 offset_left = 30.0 offset_top = 30.0 [node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="ColorRect" type="ColorRect" parent="CanvasLayer"] -modulate = Color( 0, 1, 0.4, 1 ) -material = SubResource( 2 ) +modulate = Color(0, 1, 0.4, 1) +material = SubResource("2") +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -__meta__ = { -"_edit_use_anchors_": false -} +grow_horizontal = 2 +grow_vertical = 2 [node name="WorldEnvironment" type="WorldEnvironment" parent="."] -environment = SubResource( 3 ) +environment = SubResource("3") diff --git a/examples/socat_terminal/socat_terminal.gd b/examples/socat_terminal/socat_terminal.gd index c4eaca1..e00d233 100644 --- a/examples/socat_terminal/socat_terminal.gd +++ b/examples/socat_terminal/socat_terminal.gd @@ -1,9 +1,9 @@ -extends "res://addons/godot_xterm/terminal.gd" +extends Terminal -@export var exec_path: String := "bash" -@export var socat_path: String := "socat" # E.g. /usr/bin/socat -@export var port: int := 2023 -@export var verbose: bool := false +@export var exec_path := "bash" +@export var socat_path := "socat" # E.g. /usr/bin/socat +@export var port := 2023 +@export var verbose := false var _timeout = 30 var _pid: int @@ -15,7 +15,7 @@ func _ready(): args.append("tcp-l:%d,reuseaddr,fork" % port) args.append("exec:%s,pty,setsid,setpgid,stderr,ctty" % exec_path) - _pid = OS.execute(socat_path, args, false) + _pid = OS.create_process(socat_path, args) func _process(delta): @@ -25,7 +25,7 @@ func _process(delta): if _timeout < 1: _error("Timeout: could not connect to socat") - if not _stream.is_connected_to_host(): + if _stream.get_connected_host().is_empty(): if _stream.connect_to_host("127.0.0.1", port) != OK: _error("Could not connect to socat") diff --git a/examples/socat_terminal/socat_terminal.tscn b/examples/socat_terminal/socat_terminal.tscn index 2db3443..904b453 100644 --- a/examples/socat_terminal/socat_terminal.tscn +++ b/examples/socat_terminal/socat_terminal.tscn @@ -1,14 +1,11 @@ -[gd_scene load_steps=2 format=2] +[gd_scene load_steps=2 format=3 uid="uid://bc5o6m6ty0ejn"] -[ext_resource path="res://examples/socat_terminal/socat_terminal.gd" type="Script" id=1] +[ext_resource type="Script" path="res://examples/socat_terminal/socat_terminal.gd" id="1"] -[node name="Terminal" type="Control"] +[node name="Terminal" type="Terminal"] +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -focus_mode = 2 -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[connection signal="data_sent" from="." to="." method="_on_Terminal_data_sent"] +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1") diff --git a/examples/terminal/terminal.gd b/examples/terminal/terminal.gd index 74cc877..bfc118f 100644 --- a/examples/terminal/terminal.gd +++ b/examples/terminal/terminal.gd @@ -1,4 +1,4 @@ -extends "res://addons/godot_xterm/terminal.gd" +extends Terminal @onready var pty = $PTY diff --git a/examples/terminal/terminal.tscn b/examples/terminal/terminal.tscn index 0a67e70..2b52368 100644 --- a/examples/terminal/terminal.tscn +++ b/examples/terminal/terminal.tscn @@ -1,28 +1,24 @@ -[gd_scene load_steps=4 format=2] +[gd_scene load_steps=5 format=3 uid="uid://cysad55lwtnc6"] -[ext_resource path="res://themes/audio/bell.wav" type="AudioStream" id=1] -[ext_resource path="res://addons/godot_xterm/pty.gd" type="Script" id=2] -[ext_resource path="res://examples/terminal/terminal.gd" type="Script" id=3] +[ext_resource type="AudioStream" uid="uid://n0hqjfxltbm0" path="res://themes/audio/bell.wav" id="1"] +[ext_resource type="Theme" uid="uid://0gk8swmcldbg" path="res://addons/godot_xterm/themes/default.tres" id="1_uci3c"] +[ext_resource type="Script" path="res://addons/godot_xterm/pty.gd" id="2"] +[ext_resource type="Script" path="res://examples/terminal/terminal.gd" id="3"] -[node name="Terminal" type="Control"] +[node name="Terminal" type="Terminal"] +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -focus_mode = 2 -script = ExtResource( 3 ) -__meta__ = { -"_edit_use_anchors_": false -} -copy_on_selection = true +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_uci3c") +script = ExtResource("3") [node name="PTY" type="Node" parent="."] -script = ExtResource( 2 ) +script = ExtResource("2") terminal_path = NodePath("..") -env = { -"COLORTERM": "truecolor", -"TERM": "xterm-256color" -} [node name="Bell" type="AudioStreamPlayer" parent="."] -stream = ExtResource( 1 ) +stream = ExtResource("1") [connection signal="bell" from="." to="Bell" method="play"] diff --git a/project.godot b/project.godot index f4531df..05aae4b 100644 --- a/project.godot +++ b/project.godot @@ -41,6 +41,10 @@ config/icon="res://docs/media/icon.png" window/vsync/use_vsync=false +[editor_plugins] + +enabled=PackedStringArray("res://addons/godot_xterm/plugin.cfg") + [rendering] quality/driver/driver_name="GLES2" diff --git a/themes/retro_green.tres b/themes/retro_green.tres index 1b29658..554c8fc 100644 --- a/themes/retro_green.tres +++ b/themes/retro_green.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://addons/godot_xterm/resources/xrdb_theme.gd" id="1"] [ext_resource type="FontFile" uid="uid://edo5q2qxos3u" path="res://addons/godot_xterm/themes/fonts/hack/hack_regular-3.003.ttf" id="2"] -[sub_resource type="FontFile" id="1"] +[sub_resource type="FontFile" id="FontFile_tdf0u"] fallbacks = [ExtResource("2")] face_index = null embolden = null @@ -39,5 +39,5 @@ Terminal/font_sizes/font_size = 14 Terminal/fonts/bold_font = null Terminal/fonts/bold_italics_font = null Terminal/fonts/italics_font = null -Terminal/fonts/normal_font = SubResource("1") +Terminal/fonts/normal_font = SubResource("FontFile_tdf0u") script = ExtResource("1")