Further progress towards Godot 4.0 support

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

View file

@ -1,5 +1,5 @@
@tool @tool
extends "../../terminal.gd" extends Terminal
signal exited(exit_code, signum) signal exited(exit_code, signum)
@ -14,46 +14,43 @@ func _set_terminal_colors(color_map: Dictionary) -> void:
for key in color_map.keys(): for key in color_map.keys():
var val: String = color_map[key] var val: String = color_map[key]
var color: Color = editor_settings.get_setting("text_editor/highlighting/%s" % val) 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(): func _ready():
if not editor_settings: if not editor_settings:
return return
theme = Theme.new()
# Get colors from TextEdit theme. Created using the default (Adaptive) theme # Get colors from TextEdit theme. Created using the default (Adaptive) theme
# for reference, but will probably cause strange results if using another 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. # better to use a dedicated terminal theme, rather than relying on this.
_set_terminal_colors( _set_terminal_colors(
{ {
"black": "caret_background_color", "ansi_0_color": "completion_background_color",
"red": "keyword_color", "ansi_1_color": "keyword_color",
"green": "gdscript/node_path_color", "ansi_2_color": "gdscript/node_path_color",
"yellow": "string_color", "ansi_3_color": "string_color",
"blue": "function_color", "ansi_4_color": "function_color",
"magenta": "symbol_color", "ansi_5_color": "symbol_color",
"cyan": "gdscript/function_definition_color", "ansi_6_color": "gdscript/function_definition_color",
"white": "text_color", "ansi_7_color": "text_color",
"bright_black": "comment_color", "ansi_8_color": "comment_color",
"bright_red": "breakpoint_color", "ansi_9_color": "breakpoint_color",
"bright_green": "base_type_color", "ansi_10_color": "base_type_color",
"bright_yellow": "search_result_color", "ansi_11_color": "search_result_color",
"bright_blue": "member_variable_color", "ansi_12_color": "member_variable_color",
"bright_magenta": "code_folding_color", "ansi_13_color": "code_folding_color",
"bright_cyan": "user_type_color", "ansi_14_color": "user_type_color",
"bright_white": "text_selected_color", "ansi_15_color": "text_selected_color",
"background": "background_color", "background_color": "background_color",
"foreground": "caret_color", "foreground_color": "caret_color",
} }
) )
_native_terminal._update_theme()
func _input(event): func _input(event):
if has_focus() and event is InputEventKey and event.is_pressed(): 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. # Handled by switch tabs shortcut.
return return

View file

@ -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 type="Script" path="res://addons/godot_xterm/editor_plugins/terminal/editor_terminal.gd" id="1"]
[ext_resource path="res://addons/godot_xterm/pty.gd" type="Script" id=2] [ext_resource type="Script" path="res://addons/godot_xterm/pty.gd" id="2"]
[ext_resource path="res://addons/godot_xterm/themes/default.tres" type="Theme" id=3] [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_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
focus_mode = 1 grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4 size_flags_horizontal = 4
size_flags_vertical = 4 size_flags_vertical = 4
theme = ExtResource( 3 ) focus_mode = 1
script = ExtResource( 1 ) theme = ExtResource("3")
script = ExtResource("1")
[node name="PTY" type="Node" parent="."] [node name="PTY" type="Node" parent="."]
script = ExtResource( 2 ) script = ExtResource("2")
terminal_path = NodePath("..") terminal_path = NodePath("..")
env = {
"COLORTERM": "truecolor",
"TERM": "xterm-256color"
}
[node name="Bell" type="AudioStreamPlayer" parent="."] [node name="Bell" type="AudioStreamPlayer" parent="."]
[connection signal="bell" from="." to="Bell" method="play"]
[connection signal="exited" from="PTY" to="." method="_on_PTY_exited"] [connection signal="exited" from="PTY" to="." method="_on_PTY_exited"]

View file

@ -28,9 +28,9 @@ var editor_plugin: EditorPlugin
var editor_interface: EditorInterface var editor_interface: EditorInterface
@onready var editor_settings: EditorSettings = editor_interface.get_editor_settings() @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 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 tab_container: TabContainer = $VBoxContainer/TabContainer
@onready var terminal_popup_menu: PopupMenu = $VBoxContainer/TerminalPopupMenu @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: Label = $SizeLabel
@onready var size_label_timer: Timer = $SizeLabel/SizeLabelTimer @onready var size_label_timer: Timer = $SizeLabel/SizeLabelTimer
@onready var ready := true @onready var is_ready := true
var _theme := Theme.new() var _theme := Theme.new()
var _settings: TerminalSettings var _settings: TerminalSettings
@ -47,7 +47,9 @@ var _tab_container_min_size
func _ready(): 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() _update_settings()
@ -55,18 +57,6 @@ func _load_or_create_settings() -> void:
# Use only default settings for now, until settings are properly defined # Use only default settings for now, until settings are properly defined
# and documented. # and documented.
_settings = TerminalSettings.new() _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: func _update_settings() -> void:
@ -76,36 +66,37 @@ func _update_settings() -> void:
if editor_interface.has_method("get_editor_scale"): if editor_interface.has_method("get_editor_scale"):
editor_scale = editor_interface.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 size.y = 415
tabs.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ALWAYS tabs.tab_close_display_policy = TabBar.CLOSE_BUTTON_SHOW_ALWAYS
# Update shortcuts. #FIXME
if _settings.new_terminal_shortcut: # # Update shortcuts.
terminal_popup_menu.set_item_shortcut( # if _settings.new_terminal_shortcut:
TerminalPopupMenuOptions.NEW_TERMINAL, _settings.new_terminal_shortcut, true # 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( # if _settings.kill_terminal_shortcut:
TerminalPopupMenuOptions.KILL_TERMINAL, _settings.kill_terminal_shortcut, false # terminal_popup_menu.set_item_shortcut(
) # TerminalPopupMenuOptions.KILL_TERMINAL, _settings.kill_terminal_shortcut, false
if _settings.copy_shortcut: # )
terminal_popup_menu.set_item_shortcut( # if _settings.copy_shortcut:
TerminalPopupMenuOptions.COPY, _settings.copy_shortcut, false # terminal_popup_menu.set_item_shortcut(
) # TerminalPopupMenuOptions.COPY, _settings.copy_shortcut, false
if _settings.paste_shortcut: # )
terminal_popup_menu.set_item_shortcut( # if _settings.paste_shortcut:
TerminalPopupMenuOptions.PASTE, _settings.paste_shortcut, false # terminal_popup_menu.set_item_shortcut(
) # TerminalPopupMenuOptions.PASTE, _settings.paste_shortcut, false
# )
_update_terminal_tabs() _update_terminal_tabs()
func _update_terminal_tabs(): func _update_terminal_tabs():
# Wait a couple of frames to allow everything to resize before updating. # Wait a couple of frames to allow everything to resize before updating.
await get_tree().idle_frame await get_tree().process_frame
await get_tree().idle_frame await get_tree().process_frame
if tabs.get_offset_buttons_visible(): if tabs.get_offset_buttons_visible():
# Move add button to fixed position on the tabbar. # Move add button to fixed position on the tabbar.
@ -116,7 +107,7 @@ func _update_terminal_tabs():
tabbar_container.move_child(add_button, 0) tabbar_container.move_child(add_button, 0)
else: else:
# Move add button after last tab. # 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) tabbar_container.remove_child(add_button)
tabs.add_child(add_button) tabs.add_child(add_button)
var last_tab := Rect2() var last_tab := Rect2()
@ -125,6 +116,9 @@ func _update_terminal_tabs():
add_button.position = Vector2( add_button.position = Vector2(
last_tab.position.x + last_tab.size.x + 3, last_tab.position.y 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. # Make sure we still own the button, so it gets saved with our scene.
add_button.owner = self add_button.owner = self
@ -135,9 +129,9 @@ func _on_AddButton_pressed():
var terminal := EditorTerminal.instantiate() var terminal := EditorTerminal.instantiate()
tabs.add_tab(shell.get_file()) tabs.add_tab(shell.get_file())
terminal.editor_settings = editor_settings terminal.editor_settings = editor_settings
terminal.set_anchors_preset(PRESET_WIDE) terminal.set_anchors_preset(PRESET_BOTTOM_WIDE)
terminal.connect("gui_input",Callable(self,"_on_TabContainer_gui_input")) terminal.connect("gui_input", Callable(self, "_on_TabContainer_gui_input"))
terminal.connect("exited",Callable(self,"_on_Terminal_exited").bind(terminal)) terminal.connect("exited", Callable(self, "_on_Terminal_exited").bind(terminal))
tab_container.add_child(terminal) tab_container.add_child(terminal)
terminal.pty.fork(shell) terminal.pty.fork(shell)
terminal.grab_focus() terminal.grab_focus()
@ -163,7 +157,7 @@ func _on_Tabs_tab_close(tab_index):
func _notification(what): func _notification(what):
if not ready: if not is_ready:
return return
match what: match what:
@ -181,46 +175,53 @@ func _input(event: InputEvent) -> void:
return return
# Global shortcut to open new terminal and make terminal panel visible. # Global shortcut to open new terminal and make terminal panel visible.
if _settings.new_terminal_shortcut and _settings.new_terminal_shortcut.shortcut: # FIXME
if event.is_match(_settings.new_terminal_shortcut.shortcut): # if _settings.new_terminal_shortcut and _settings.new_terminal_shortcut.shortcut:
get_tree().set_input_as_handled() # if event.is_match(_settings.new_terminal_shortcut.shortcut):
editor_plugin.make_bottom_panel_item_visible(self) # get_tree().set_input_as_handled()
_on_AddButton_pressed() # editor_plugin.make_bottom_panel_item_visible(self)
# _on_AddButton_pressed()
# Non-global shortcuts, only applied if terminal is active and focused. # Non-global shortcuts, only applied if terminal is active and focused.
if ( if (
tabs.get_tab_count() > 0 and tab_container.get_child(tabs.current_tab).has_focus() tabs.get_tab_count() > 0 and tab_container.get_child(tabs.current_tab).has_focus()
or terminal_popup_menu.has_focus() or terminal_popup_menu.has_focus()
): ):
pass
# Kill terminal. # Kill terminal.
if _settings.kill_terminal_shortcut and _settings.kill_terminal_shortcut.shortcut: # FIXME shortcut
if event.is_match(_settings.kill_terminal_shortcut.shortcut):
get_tree().set_input_as_handled()
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.KILL_TERMINAL)
# 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.kill_terminal_shortcut and _settings.kill_terminal_shortcut.shortcut:
if _settings.paste_shortcut and _settings.paste_shortcut.shortcut: # if event.is_match(_settings.kill_terminal_shortcut.shortcut):
if event.is_match(_settings.paste_shortcut.shortcut): # get_tree().set_input_as_handled()
get_tree().set_input_as_handled() # _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.KILL_TERMINAL)
_on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.PASTE)
# Next tab. # Copy.
if _settings.next_tab_shortcut and _settings.next_tab_shortcut.shortcut: #FIXME
if event.is_match(_settings.next_tab_shortcut.shortcut): # if _settings.copy_shortcut and _settings.copy_shortcut.shortcut:
get_tree().set_input_as_handled() # if event.is_match(_settings.copy_shortcut.shortcut):
tabs.current_tab = min(tabs.current_tab + 1, tabs.get_tab_count() - 1) # get_tree().set_input_as_handled()
# _on_TerminalPopupMenu_id_pressed(TerminalPopupMenuOptions.COPY)
# Previous tab. # Paste.
if _settings.previous_tab_shortcut and _settings.previous_tab_shortcut.shortcut: #FIXME
if event.is_match(_settings.previous_tab_shortcut.shortcut): # if _settings.paste_shortcut and _settings.paste_shortcut.shortcut:
get_tree().set_input_as_handled() # if event.is_match(_settings.paste_shortcut.shortcut):
tabs.current_tab = max(tabs.current_tab - 1, 0) # 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): 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) var terminal = tab_container.get_child(tab_container.current_tab)
match id: match id:
TerminalPopupMenuOptions.COPY: TerminalPopupMenuOptions.COPY:
OS.clipboard = terminal.copy_selection() DisplayServer.clipboard_set(terminal.copy_selection())
TerminalPopupMenuOptions.PASTE: TerminalPopupMenuOptions.PASTE:
for i in OS.clipboard.length(): for i in DisplayServer.clipboard_get().length():
var event = InputEventKey.new() var event = InputEventKey.new()
event.unicode = ord(OS.clipboard[i]) event.unicode = DisplayServer.clipboard_get().unicode_at(i)
event.button_pressed = true event.button_pressed = true
terminal._gui_input(event) terminal._gui_input(event)
TerminalPopupMenuOptions.COPY_ALL: TerminalPopupMenuOptions.COPY_ALL:
OS.clipboard = terminal.copy_all() DisplayServer.clipboard_set(terminal.copy_all())
TerminalPopupMenuOptions.CLEAR: TerminalPopupMenuOptions.CLEAR:
terminal.clear() terminal.clear()
TerminalPopupMenuOptions.KILL_TERMINAL: TerminalPopupMenuOptions.KILL_TERMINAL:

View file

@ -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 = {
"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 ), "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": "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 ),
"format": "RGBA8", "format": "RGBA8",
"height": 16, "height": 16,
"mipmaps": false, "mipmaps": false,
"width": 16 "width": 16
} }
[sub_resource type="ImageTexture" id=5] [sub_resource type="ImageTexture" id="ImageTexture_e8boo"]
flags = 0 image = SubResource("Image_x7bb3")
flags = 0
image = SubResource( 7 )
size = Vector2( 16, 16 )
[node name="Panel" type="Panel"] [node name="Panel" type="Panel"]
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
offset_top = -3.0 offset_bottom = 3.0
minimum_size = Vector2( 0, 34 ) grow_horizontal = 2
custom_styles/panel = SubResource( 3 ) grow_vertical = 2
script = ExtResource( 1 ) script = ExtResource("1")
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."] [node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
minimum_size = Vector2( 0, 24 ) grow_horizontal = 2
custom_constants/separation = 0 grow_vertical = 2
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TabbarContainer" type="HBoxContainer" parent="VBoxContainer"] [node name="TabbarContainer" type="HBoxContainer" parent="VBoxContainer"]
offset_right = 1024.0 layout_mode = 2
offset_bottom = 24.0
[node name="TabBar" type="TabBar" parent="VBoxContainer/TabbarContainer"] [node name="Tabs" type="TabBar" parent="VBoxContainer/TabbarContainer"]
offset_right = 1024.0 layout_mode = 2
offset_bottom = 24.0 clip_tabs = false
size_flags_horizontal = 3
tab_alignment = 0
tab_close_display_policy = 1
drag_to_rearrange_enabled = true drag_to_rearrange_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AddButton" type="Button" parent="VBoxContainer/TabbarContainer/TabBar"] [node name="AddButton" type="Button" parent="VBoxContainer/TabbarContainer"]
offset_left = 3.0 layout_mode = 2
offset_right = 31.0
offset_bottom = 24.0
tooltip_text = "Add a new scene." tooltip_text = "Add a new scene."
icon = SubResource( 5 ) icon = SubResource("ImageTexture_e8boo")
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PopupMenu" type="PopupMenu" parent="VBoxContainer/TabbarContainer"] [node name="PopupMenu" type="PopupMenu" parent="VBoxContainer/TabbarContainer"]
offset_left = 906.0 item_count = 4
offset_right = 1024.0 item_0/text = "Kill"
offset_bottom = 88.0 item_0/id = 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_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"] [node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
offset_top = 24.0
offset_right = 1024.0
offset_bottom = 603.0
clip_contents = true clip_contents = true
layout_mode = 2
size_flags_vertical = 3 size_flags_vertical = 3
custom_constants/top_margin = 0
custom_constants/side_margin = 0
custom_styles/panel = SubResource( 3 )
tabs_visible = false tabs_visible = false
[node name="TerminalPopupMenu" type="PopupMenu" parent="VBoxContainer"] [node name="TerminalPopupMenu" type="PopupMenu" parent="VBoxContainer"]
offset_right = 193.0 size = Vector2i(136, 178)
offset_bottom = 160.0 item_count = 8
size_flags_horizontal = 0 item_0/text = "New Terminal"
size_flags_vertical = 0 item_0/id = 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 ] item_1/text = ""
__meta__ = { item_1/id = 1
"_edit_use_anchors_": false 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="."] [node name="SizeLabel" type="Label" parent="."]
visible = false visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
@ -122,15 +96,15 @@ offset_left = -52.0
offset_top = -15.5 offset_top = -15.5
offset_right = 52.0 offset_right = 52.0
offset_bottom = 15.5 offset_bottom = 15.5
grow_horizontal = 2
grow_vertical = 2
text = "Size: %d rows; %d cols text = "Size: %d rows; %d cols
(%d x %d)" (%d x %d)"
align = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Panel" type="Panel" parent="SizeLabel"] [node name="Panel" type="Panel" parent="SizeLabel"]
show_behind_parent = true show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
offset_left = -5.0 offset_left = -5.0
@ -139,17 +113,13 @@ offset_right = 5.0
offset_bottom = 5.0 offset_bottom = 5.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
__meta__ = {
"_edit_use_anchors_": false
}
[node name="SizeLabelTimer" type="Timer" parent="SizeLabel"] [node name="SizeLabelTimer" type="Timer" parent="SizeLabel"]
[connection signal="resized" from="." to="." method="_on_Panel_resized"] [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/Tabs" to="." method="_on_Tabs_tab_changed"]
[connection signal="tab_changed" from="VBoxContainer/TabbarContainer/TabBar" to="." method="_on_Tabs_tab_changed"] [connection signal="tab_close_pressed" from="VBoxContainer/TabbarContainer/Tabs" to="." method="_on_Tabs_tab_close"]
[connection signal="tab_closed" from="VBoxContainer/TabbarContainer/TabBar" to="." method="_on_Tabs_tab_close"] [connection signal="pressed" from="VBoxContainer/TabbarContainer/AddButton" to="." method="_on_AddButton_pressed"]
[connection signal="pressed" from="VBoxContainer/TabbarContainer/TabBar/AddButton" to="." method="_on_AddButton_pressed"]
[connection signal="gui_input" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_gui_input"] [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="id_pressed" from="VBoxContainer/TerminalPopupMenu" to="." method="_on_TerminalPopupMenu_id_pressed"]
[connection signal="timeout" from="SizeLabel/SizeLabelTimer" to="." method="_on_SizeLabelTimer_timeout"] [connection signal="timeout" from="SizeLabel/SizeLabelTimer" to="." method="_on_SizeLabelTimer_timeout"]

View file

@ -39,8 +39,9 @@ func _import(source_file, save_path, options, r_platform_variant, r_gen_files):
return err return err
var theme: Theme = XrdbTheme.new() var theme: Theme = XrdbTheme.new()
theme.set_font("regular", "Terminal", preload("../themes/fonts/regular.tres")) theme.set_font_size("font_size", "Terminal", 14)
for font in ["bold", "italic", "bold_italic"]: 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) theme.set_font(font, "Terminal", null)
var word_regex = RegEx.new() var word_regex = RegEx.new()
@ -77,37 +78,37 @@ func _import(source_file, save_path, options, r_platform_variant, r_gen_files):
match name: match name:
"color0", "ansi_0_color": "color0", "ansi_0_color":
theme.set_color("black", "Terminal", color) theme.set_color("ansi_0_color", "Terminal", color)
"color1", "ansi_1_color": "color1", "ansi_1_color":
theme.set_color("red", "Terminal", color) theme.set_color("ansi_1_color", "Terminal", color)
"color2", "ansi_2_color": "color2", "ansi_2_color":
theme.set_color("green", "Terminal", color) theme.set_color("ansi_2_color", "Terminal", color)
"color3", "ansi_3_color": "color3", "ansi_3_color":
theme.set_color("yellow", "Terminal", color) theme.set_color("ansi_3_color", "Terminal", color)
"color4", "ansi_4_color": "color4", "ansi_4_color":
theme.set_color("blue", "Terminal", color) theme.set_color("ansi_4_color", "Terminal", color)
"color5", "ansi_5_color": "color5", "ansi_5_color":
theme.set_color("magenta", "Terminal", color) theme.set_color("ansi_5_color", "Terminal", color)
"color6", "ansi_6_color": "color6", "ansi_6_color":
theme.set_color("cyan", "Terminal", color) theme.set_color("ansi_6_color", "Terminal", color)
"color7", "ansi_7_color": "color7", "ansi_7_color":
theme.set_color("white", "Terminal", color) theme.set_color("ansi_7_color", "Terminal", color)
"color8", "ansi_8_color": "color8", "ansi_8_color":
theme.set_color("bright_black", "Terminal", color) theme.set_color("ansi_8_color", "Terminal", color)
"color9", "ansi_9_color": "color9", "ansi_9_color":
theme.set_color("bright_red", "Terminal", color) theme.set_color("ansi_9_color", "Terminal", color)
"color10", "ansi_10_color": "color10", "ansi_10_color":
theme.set_color("bright_green", "Terminal", color) theme.set_color("ansi_10_color", "Terminal", color)
"color11", "ansi_11_color": "color11", "ansi_11_color":
theme.set_color("bright_yellow", "Terminal", color) theme.set_color("ansi_11_color", "Terminal", color)
"color12", "ansi_12_color": "color12", "ansi_12_color":
theme.set_color("bright_blue", "Terminal", color) theme.set_color("ansi_12_color", "Terminal", color)
"color13", "ansi_13_color": "color13", "ansi_13_color":
theme.set_color("bright_magenta", "Terminal", color) theme.set_color("ansi_13_color", "Terminal", color)
"color14", "ansi_14_color": "color14", "ansi_14_color":
theme.set_color("bright_cyan", "Terminal", color) theme.set_color("ansi_14_color", "Terminal", color)
"color15", "ansi_15_color": "color15", "ansi_15_color":
theme.set_color("bright_white", "Terminal", color) theme.set_color("ansi_15_color", "Terminal", color)
"foreground", "foreground_color": "foreground", "foreground_color":
theme.set_color("foreground", "Terminal", color) theme.set_color("foreground", "Terminal", color)
"background", "background_color": "background", "background_color":

View file

@ -277,6 +277,7 @@ env.Append(LIBS=[
sources = [] sources = []
sources.append('src/register_types.cpp') sources.append('src/register_types.cpp')
sources.append('src/constants.cpp')
sources.append('src/terminal.cpp') sources.append('src/terminal.cpp')

View file

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

View file

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

View file

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

View file

@ -1,5 +1,3 @@
// Copyright (c) 2022, Leroy Hopson (MIT License).
#include "register_types.h" #include "register_types.h"
#include <gdextension_interface.h> #include <gdextension_interface.h>
@ -25,9 +23,9 @@ using namespace godot;
void initialize_godot_xterm_module(ModuleInitializationLevel p_level) { void initialize_godot_xterm_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }
ClassDB::register_class<Terminal>(); ClassDB::register_class<Terminal>();
#if !defined(_PTY_DISABLED) #if !defined(_PTY_DISABLED)
ClassDB::register_class<Pipe>(); ClassDB::register_class<Pipe>();
@ -36,25 +34,30 @@ void initialize_godot_xterm_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<PTYUnix>(); ClassDB::register_class<PTYUnix>();
#endif #endif
#if defined(__WIN32) #if defined(__WIN32)
//ClassDB::register_class<ConPTY>(); // ClassDB::register_class<ConPTY>();
#endif #endif
#endif #endif
} }
void uninitialize_godot_xterm_module(ModuleInitializationLevel p_level) { void uninitialize_godot_xterm_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }
} }
extern "C" extern "C"
// Initialization // Initialization
GDExtensionBool GDE_EXPORT godot_xterm_library_init(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { GDExtensionBool GDE_EXPORT
godot::GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization); 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_initializer(initialize_godot_xterm_module);
init_obj.register_terminator(uninitialize_godot_xterm_module); init_obj.register_terminator(uninitialize_godot_xterm_module);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); init_obj.set_minimum_library_initialization_level(
MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init(); return init_obj.init();
} }

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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.")

View file

@ -64,7 +64,10 @@ var _exit_cb: Callable
# Writes data to the socket. # Writes data to the socket.
# data: The data to write. # data: The data to write.
func write(data) -> void: func write(data) -> void:
assert(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: if _pipe:
_pipe.write(data if data is PackedByteArray else data.to_utf8_buffer()) _pipe.write(data if data is PackedByteArray else data.to_utf8_buffer())
@ -88,7 +91,7 @@ func _parse_env(env: Dictionary = {}) -> PackedStringArray:
for key in keys: for key in keys:
var value = env[key] var value = env[key]
var valid = key is String and value is String 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: if not valid:
push_warning("Skipping invalid env key/value pair.") push_warning("Skipping invalid env key/value pair.")
@ -129,9 +132,23 @@ func fork(
_exit_cb = Callable(self, "on_exit") _exit_cb = Callable(self, "on_exit")
# Actual fork. # Actual fork.
var result = PTYUnix.new().fork( var result = (
# VERY IMPORTANT: The second argument must be 0, otherwise will get an ENOTSOCK error after connecting our pipe to the fd. PTYUnix
file, 0, args, parsed_env, cwd, cols, rows, uid, gid, utf8, _exit_cb . 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: if result[0] != OK:
@ -146,10 +163,10 @@ func fork(
_pid = result[1].pid _pid = result[1].pid
_pipe = Pipe.new() _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. # 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 return OK
@ -159,7 +176,7 @@ func open(cols: int = DEFAULT_COLS, rows: int = DEFAULT_ROWS) -> Array:
func _exit_tree(): func _exit_tree():
_exit_cb = null _exit_cb = Callable()
if _pid > 1: if _pid > 1:
LibuvUtils.kill(_pid, IPCSignal.SIGHUP) LibuvUtils.kill(_pid, IPCSignal.SIGHUP)
if _pipe: if _pipe:

View file

@ -1,2 +0,0 @@
@tool
extends SubViewport

View file

@ -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

View file

@ -1,7 +1,7 @@
@tool @tool
extends EditorPlugin 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 asciicast_import_plugin
var xrdb_import_plugin var xrdb_import_plugin
var terminal_panel: Control var terminal_panel: Control
@ -17,18 +17,16 @@ func _enter_tree():
var asciicast_script = preload("./resources/asciicast.gd") var asciicast_script = preload("./resources/asciicast.gd")
add_custom_type("Asciicast", "Animation", asciicast_script, null) add_custom_type("Asciicast", "Animation", asciicast_script, null)
var terminal_script = preload("./terminal.gd")
var terminal_icon = load( var terminal_icon = load(
"%s/nodes/terminal/terminal_icon.svg" % get_script().resource_path.get_base_dir() "%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: if pty_supported:
var base_dir = get_script().resource_path.get_base_dir() var base_dir = get_script().resource_path.get_base_dir()
var pty_icon = load("%s/nodes/pty/pty_icon.svg" % base_dir) var pty_icon = load("%s/nodes/pty/pty_icon.svg" % base_dir)
var pty_script var pty_script
match OS.get_name(): match OS.get_name():
"X11", "Server", "OSX": "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS":
pty_script = load("%s/pty.gd" % base_dir) pty_script = load("%s/pty.gd" % base_dir)
add_custom_type("PTY", "Node", pty_script, pty_icon) add_custom_type("PTY", "Node", pty_script, pty_icon)
terminal_panel = preload("./editor_plugins/terminal/terminal_panel.tscn").instantiate() terminal_panel = preload("./editor_plugins/terminal/terminal_panel.tscn").instantiate()
@ -45,7 +43,6 @@ func _exit_tree():
xrdb_import_plugin = null xrdb_import_plugin = null
remove_custom_type("Asciicast") remove_custom_type("Asciicast")
remove_custom_type("Terminal")
if pty_supported: if pty_supported:
remove_custom_type("PTY") remove_custom_type("PTY")

View file

@ -6,10 +6,9 @@
@tool @tool
extends Node extends Node
const _LibuvUtils := preload("./nodes/pty/libuv_utils.gd") var _LibuvUtils := LibuvUtils
const _PTYNative := preload("./nodes/pty/pty_native.gd") const _PTYNative := preload("./nodes/pty/pty_native.gd")
const _PTYUnix := preload("./nodes/pty/unix/pty_unix.gd") const _PTYUnix := preload("./nodes/pty/unix/pty_unix.gd")
const _Terminal := preload("./terminal.gd")
const DEFAULT_NAME := "xterm-256color" const DEFAULT_NAME := "xterm-256color"
const DEFAULT_COLS := 80 const DEFAULT_COLS := 80
@ -23,30 +22,26 @@ const IPCSignal = _PTYUnix.IPCSignal
signal data_received(data) signal data_received(data)
signal exited(exit_code, signum) signal exited(exit_code, signum)
@export var terminal_path: NodePath = NodePath() : @export var terminal_path: NodePath = NodePath():
get: get:
return terminal_path return terminal_path
set(value): set(value):
terminal_path = value terminal_path = value
_set_terminal(get_node_or_null(terminal_path)) _set_terminal(get_node_or_null(terminal_path))
var _terminal: _Terminal = null : var _terminal
get:
return _terminal # TODOConverter40 Non existent get function
set(mod_value):
mod_value # TODOConverter40 Copy here content of _set_terminal
# The column size in characters. # The column size in characters.
@export var cols: int = DEFAULT_COLS : @export var cols: int = DEFAULT_COLS:
get: get:
return cols # TODOConverter40 Copy here content of get_cols return cols # TODOConverter40 Copy here content of get_cols
set(mod_value): set(mod_value):
mod_value # TODOConverter40 Copy here content of set_cols mod_value # TODOConverter40 Copy here content of set_cols
# The row size in characters. # The row size in characters.
@export var rows: int = DEFAULT_ROWS : @export var rows: int = DEFAULT_ROWS:
get: get:
return rows # TODOConverter40 Copy here content of get_rows return rows # TODOConverter40 Copy here content of get_rows
set(mod_value): set(mod_value):
mod_value # TODOConverter40 Copy here content of set_rows mod_value # TODOConverter40 Copy here content of set_rows
@ -66,13 +61,13 @@ var _pty_native: _PTYNative
func _init(): func _init():
var os_name := OS.get_name() var os_name := OS.get_name()
match os_name: match os_name:
"X11", "Server", "OSX": "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS":
_pty_native = _PTYUnix.new() _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("data_received", Callable(self, "_on_pty_native_data_received"))
_pty_native.connect("exited",Callable(self,"_on_pty_native_exited")) _pty_native.connect("exited", Callable(self, "_on_pty_native_exited"))
add_child(_pty_native) add_child(_pty_native)
@ -96,31 +91,31 @@ func set_rows(value: int):
func get_rows() -> int: func get_rows() -> int:
return _rows return _rows
func _set_terminal(value: _Terminal):
func _set_terminal(value):
if _terminal == value: if _terminal == value:
return return
# Disconect the current terminal, if any. # Disconect the current terminal, if any.
if _terminal: if _terminal != null:
disconnect("data_received",Callable(_terminal,"write")) disconnect("data_received", Callable(_terminal, "write"))
_terminal.disconnect("data_sent",Callable(self,"write")) _terminal.disconnect("data_sent", Callable(self, "write"))
_terminal.disconnect("size_changed",Callable(self,"resizev")) _terminal.disconnect("size_changed", Callable(self, "resizev"))
_terminal = value _terminal = value
if not _terminal: if _terminal == null:
return return
# Connect the new terminal. # Connect the new terminal.
resize(_terminal.get_cols(), _terminal.get_rows()) resize(_terminal.get_cols(), _terminal.get_rows())
if not _terminal.is_connected("size_changed",Callable(self,"resizev")): if not _terminal.is_connected("size_changed", Callable(self, "resizev")):
_terminal.connect("size_changed",Callable(self,"resizev")) _terminal.connect("size_changed", Callable(self, "resizev"))
if not _terminal.is_connected("data_sent",Callable(self,"write")): if not _terminal.is_connected("data_sent", Callable(self, "write")):
_terminal.connect("data_sent",Callable(self,"write")) _terminal.connect("data_sent", Callable(self, "write"))
if not is_connected("data_received",Callable(_terminal,"write")): if not is_connected("data_received", Callable(_terminal, "write")):
connect("data_received",Callable(_terminal,"write")) connect("data_received", Callable(_terminal, "write"))
# Writes data to the socket. # Writes data to the socket.
@ -159,7 +154,7 @@ func _notification(what: int):
match what: match what:
NOTIFICATION_PARENTED: NOTIFICATION_PARENTED:
var parent = get_parent() var parent = get_parent()
if parent is _Terminal: if parent is Terminal:
self.terminal_path = get_path_to(parent) self.terminal_path = get_path_to(parent)

View file

@ -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)

View file

@ -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] [resource]
size = 14 fallbacks = [ExtResource("4")]
extra_spacing_bottom = 1 face_index = null
font_data = ExtResource( 4 ) 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)

View file

@ -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 type="Theme" uid="uid://b7vd50tw2g1nl" path="res://themes/default.tres" id="1_nsh8i"]
[ext_resource path="res://examples/asciicast/example.cast" type="Animation" id=6] [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_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
focus_mode = 1 focus_mode = 1
script = ExtResource( 1 ) theme = ExtResource("1_nsh8i")
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AnimationPlayer" type="AnimationPlayer" parent="."] [node name="AnimationPlayer" type="AnimationPlayer" parent="."]
autoplay = "example" autoplay = "example"
playback_speed = 2.0 playback_speed = 2.0
method_call_mode = 1 method_call_mode = 1
anims/example = ExtResource( 6 ) libraries = {
"": SubResource("AnimationLibrary_02l7k")
}

View file

@ -42,11 +42,11 @@ var offset: int
func _ready(): 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 # 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 # warning-ignore:return_value_discarded
$Terminal.connect("size_changed",Callable(self,"draw_all")) $Terminal.connect("size_changed", Callable(self, "draw_all"))
$Terminal.grab_focus() $Terminal.grab_focus()
draw_all() draw_all()
@ -134,7 +134,10 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
"Asciicast": "Asciicast":
var scene = item.scene.instantiate() var scene = item.scene.instantiate()
var animation_player: AnimationPlayer = scene.get_node("AnimationPlayer") 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) add_child(scene)
scene.grab_focus() scene.grab_focus()
await animation_player.animation_finished await animation_player.animation_finished
@ -163,7 +166,7 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
pass pass
# FIXME # FIXME
#if OS.has_feature("JavaScript"): #if OS.has_feature("JavaScript"):
#JavaScript.eval("window.history.back() || window.close()") #JavaScript.eval("window.history.back() || window.close()")
#get_tree().quit() #get_tree().quit()

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://brjrtf5fpptw8"] [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="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"] [node name="Menu" type="Control"]
layout_mode = 3 layout_mode = 3
@ -12,13 +12,11 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("2") script = ExtResource("2")
[node name="Terminal" type="Control" parent="."] [node name="Terminal" type="Terminal" parent="."]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
focus_mode = 1 theme = ExtResource("2_o1653")
script = ExtResource("1")
copy_on_selection = false

View file

@ -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 type="PackedScene" uid="uid://brjrtf5fpptw8" path="res://examples/menu/menu.tscn" id="1"]
[ext_resource path="res://themes/retro_green.tres" type="Theme" id=2] [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 = "/* code = "/*
Shader from Godot Shaders - the free shader library. Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/VHS-and-CRT-monitor-effect godotshaders.com/shader/VHS-and-CRT-monitor-effect
@ -234,31 +234,31 @@ void fragment()
COLOR = text; COLOR = text;
}" }"
[sub_resource type="ShaderMaterial" id=2] [sub_resource type="ShaderMaterial" id="2"]
shader = SubResource( 1 ) shader = SubResource("1")
shader_param/overlay = true shader_parameter/overlay = true
shader_param/scanlines_opacity = 0.4 shader_parameter/scanlines_opacity = 0.4
shader_param/scanlines_width = 0.25 shader_parameter/scanlines_width = 0.25
shader_param/grille_opacity = 0.3 shader_parameter/grille_opacity = 0.3
shader_param/resolution = Vector2( 768, 240 ) shader_parameter/resolution = Vector2(768, 240)
shader_param/pixelate = false shader_parameter/pixelate = false
shader_param/roll = true shader_parameter/roll = true
shader_param/roll_speed = 8.0 shader_parameter/roll_speed = 8.0
shader_param/roll_size = 15.0 shader_parameter/roll_size = 15.0
shader_param/roll_variation = 1.8 shader_parameter/roll_variation = 1.8
shader_param/distort_intensity = 0.05 shader_parameter/distort_intensity = 0.05
shader_param/noise_opacity = 0.4 shader_parameter/noise_opacity = 0.4
shader_param/noise_speed = 5.0 shader_parameter/noise_speed = 5.0
shader_param/static_noise_intensity = 0.06 shader_parameter/static_noise_intensity = 0.06
shader_param/aberration = 0.0 shader_parameter/aberration = 0.0
shader_param/brightness = 2.5 shader_parameter/brightness = 2.5
shader_param/discolor = true shader_parameter/discolor = true
shader_param/warp_amount = 1.0 shader_parameter/warp_amount = 1.0
shader_param/clip_warp = false shader_parameter/clip_warp = false
shader_param/vignette_intensity = 0.4 shader_parameter/vignette_intensity = 0.4
shader_param/vignette_opacity = 0.5 shader_parameter/vignette_opacity = 0.5
[sub_resource type="Environment" id=3] [sub_resource type="Environment" id="3"]
background_mode = 4 background_mode = 4
glow_enabled = true glow_enabled = true
glow_intensity = 1.0 glow_intensity = 1.0
@ -266,36 +266,39 @@ glow_strength = 1.15
glow_blend_mode = 0 glow_blend_mode = 0
[node name="RetroTerm" type="Control"] [node name="RetroTerm" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
theme = ExtResource( 2 ) grow_horizontal = 2
__meta__ = { grow_vertical = 2
"_edit_use_anchors_": false theme = ExtResource("2")
}
[node name="ColorRect" type="ColorRect" parent="."] [node name="ColorRect" type="ColorRect" parent="."]
show_behind_parent = true show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
color = Color( 0.156863, 0.156863, 0.156863, 1 ) grow_horizontal = 2
__meta__ = { grow_vertical = 2
"_edit_use_anchors_": false 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_left = 30.0
offset_top = 30.0 offset_top = 30.0
[node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="CanvasLayer" type="CanvasLayer" parent="."]
[node name="ColorRect" type="ColorRect" parent="CanvasLayer"] [node name="ColorRect" type="ColorRect" parent="CanvasLayer"]
modulate = Color( 0, 1, 0.4, 1 ) modulate = Color(0, 1, 0.4, 1)
material = SubResource( 2 ) material = SubResource("2")
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
__meta__ = { grow_horizontal = 2
"_edit_use_anchors_": false grow_vertical = 2
}
[node name="WorldEnvironment" type="WorldEnvironment" parent="."] [node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource( 3 ) environment = SubResource("3")

View file

@ -1,9 +1,9 @@
extends "res://addons/godot_xterm/terminal.gd" extends Terminal
@export var exec_path: String := "bash" @export var exec_path := "bash"
@export var socat_path: String := "socat" # E.g. /usr/bin/socat @export var socat_path := "socat" # E.g. /usr/bin/socat
@export var port: int := 2023 @export var port := 2023
@export var verbose: bool := false @export var verbose := false
var _timeout = 30 var _timeout = 30
var _pid: int var _pid: int
@ -15,7 +15,7 @@ func _ready():
args.append("tcp-l:%d,reuseaddr,fork" % port) args.append("tcp-l:%d,reuseaddr,fork" % port)
args.append("exec:%s,pty,setsid,setpgid,stderr,ctty" % exec_path) 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): func _process(delta):
@ -25,7 +25,7 @@ func _process(delta):
if _timeout < 1: if _timeout < 1:
_error("Timeout: could not connect to socat") _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: if _stream.connect_to_host("127.0.0.1", port) != OK:
_error("Could not connect to socat") _error("Could not connect to socat")

View file

@ -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_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
focus_mode = 2 grow_horizontal = 2
script = ExtResource( 1 ) grow_vertical = 2
__meta__ = { script = ExtResource("1")
"_edit_use_anchors_": false
}
[connection signal="data_sent" from="." to="." method="_on_Terminal_data_sent"]

View file

@ -1,4 +1,4 @@
extends "res://addons/godot_xterm/terminal.gd" extends Terminal
@onready var pty = $PTY @onready var pty = $PTY

View file

@ -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 type="AudioStream" uid="uid://n0hqjfxltbm0" path="res://themes/audio/bell.wav" id="1"]
[ext_resource path="res://addons/godot_xterm/pty.gd" type="Script" id=2] [ext_resource type="Theme" uid="uid://0gk8swmcldbg" path="res://addons/godot_xterm/themes/default.tres" id="1_uci3c"]
[ext_resource path="res://examples/terminal/terminal.gd" type="Script" id=3] [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_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
focus_mode = 2 grow_horizontal = 2
script = ExtResource( 3 ) grow_vertical = 2
__meta__ = { theme = ExtResource("1_uci3c")
"_edit_use_anchors_": false script = ExtResource("3")
}
copy_on_selection = true
[node name="PTY" type="Node" parent="."] [node name="PTY" type="Node" parent="."]
script = ExtResource( 2 ) script = ExtResource("2")
terminal_path = NodePath("..") terminal_path = NodePath("..")
env = {
"COLORTERM": "truecolor",
"TERM": "xterm-256color"
}
[node name="Bell" type="AudioStreamPlayer" parent="."] [node name="Bell" type="AudioStreamPlayer" parent="."]
stream = ExtResource( 1 ) stream = ExtResource("1")
[connection signal="bell" from="." to="Bell" method="play"] [connection signal="bell" from="." to="Bell" method="play"]

View file

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

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://addons/godot_xterm/resources/xrdb_theme.gd" id="1"] [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"] [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")] fallbacks = [ExtResource("2")]
face_index = null face_index = null
embolden = null embolden = null
@ -39,5 +39,5 @@ Terminal/font_sizes/font_size = 14
Terminal/fonts/bold_font = null Terminal/fonts/bold_font = null
Terminal/fonts/bold_italics_font = null Terminal/fonts/bold_italics_font = null
Terminal/fonts/italics_font = null Terminal/fonts/italics_font = null
Terminal/fonts/normal_font = SubResource("1") Terminal/fonts/normal_font = SubResource("FontFile_tdf0u")
script = ExtResource("1") script = ExtResource("1")