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

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

View file

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

View file

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

View file

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

View file

@ -277,6 +277,7 @@ env.Append(LIBS=[
sources = []
sources.append('src/register_types.cpp')
sources.append('src/constants.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
#include "libuv_utils.h"
@ -8,11 +8,15 @@
using namespace godot;
void LibuvUtils::_bind_methods() {
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_os_environ"), &LibuvUtils::get_os_environ);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_os_release"), &LibuvUtils::get_os_release);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_cwd"), &LibuvUtils::get_cwd);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_os_environ"),
&LibuvUtils::get_os_environ);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_os_release"),
&LibuvUtils::get_os_release);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("get_cwd"),
&LibuvUtils::get_cwd);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("kill", "pid", "signum"), &LibuvUtils::kill);
ClassDB::bind_static_method("LibuvUtils", D_METHOD("kill", "pid", "signum"),
&LibuvUtils::kill);
}
LibuvUtils::LibuvUtils() {}

View file

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

View file

@ -1,5 +1,3 @@
// Copyright (c) 2022, Leroy Hopson (MIT License).
#include "register_types.h"
#include <gdextension_interface.h>
@ -49,12 +47,17 @@ void uninitialize_godot_xterm_module(ModuleInitializationLevel p_level) {
extern "C"
// Initialization
GDExtensionBool GDE_EXPORT godot_xterm_library_init(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization);
GDExtensionBool GDE_EXPORT
godot_xterm_library_init(const GDExtensionInterface *p_interface,
GDExtensionClassLibraryPtr p_library,
GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_interface, p_library,
r_initialization);
init_obj.register_initializer(initialize_godot_xterm_module);
init_obj.register_terminator(uninitialize_godot_xterm_module);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
init_obj.set_minimum_library_initialization_level(
MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}

File diff suppressed because it is too large Load diff

View file

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

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.
# data: The data to write.
func write(data) -> void:
assert(data is PackedByteArray or data is String, "Invalid type for argument 'data'. Should be of type PackedByteArray or String")
var correct_type: bool = data is PackedByteArray or data is String
var err_message := "Invalid type for argument 'data'. Should be of type PackedByteArray or String"
assert(correct_type, err_message)
if _pipe:
_pipe.write(data if data is PackedByteArray else data.to_utf8_buffer())
@ -129,9 +132,23 @@ func fork(
_exit_cb = Callable(self, "on_exit")
# Actual fork.
var result = PTYUnix.new().fork(
var result = (
PTYUnix
. new()
. fork(
# VERY IMPORTANT: The second argument must be 0, otherwise will get an ENOTSOCK error after connecting our pipe to the fd.
file, 0, args, parsed_env, cwd, cols, rows, uid, gid, utf8, _exit_cb
file,
0,
args,
parsed_env,
cwd,
cols,
rows,
uid,
gid,
utf8,
_exit_cb
)
)
if result[0] != OK:
@ -146,7 +163,7 @@ func fork(
_pid = result[1].pid
_pipe = Pipe.new()
_pipe.open(_fd, true) # FIXME: _pipe.open(_fd) should be sufficient but requires two args.
_pipe.open(_fd, false)
# Must connect to signal AFTER opening, otherwise we will get error ENOTSOCK.
_pipe.connect("data_received", Callable(self, "_on_pipe_data_received"))
@ -159,7 +176,7 @@ func open(cols: int = DEFAULT_COLS, rows: int = DEFAULT_ROWS) -> Array:
func _exit_tree():
_exit_cb = null
_exit_cb = Callable()
if _pid > 1:
LibuvUtils.kill(_pid, IPCSignal.SIGHUP)
if _pipe:

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

View file

@ -6,10 +6,9 @@
@tool
extends Node
const _LibuvUtils := preload("./nodes/pty/libuv_utils.gd")
var _LibuvUtils := LibuvUtils
const _PTYNative := preload("./nodes/pty/pty_native.gd")
const _PTYUnix := preload("./nodes/pty/unix/pty_unix.gd")
const _Terminal := preload("./terminal.gd")
const DEFAULT_NAME := "xterm-256color"
const DEFAULT_COLS := 80
@ -30,11 +29,7 @@ signal exited(exit_code, signum)
terminal_path = value
_set_terminal(get_node_or_null(terminal_path))
var _terminal: _Terminal = null :
get:
return _terminal # TODOConverter40 Non existent get function
set(mod_value):
mod_value # TODOConverter40 Copy here content of _set_terminal
var _terminal
# The column size in characters.
@export var cols: int = DEFAULT_COLS:
@ -66,10 +61,10 @@ var _pty_native: _PTYNative
func _init():
var os_name := OS.get_name()
match os_name:
"X11", "Server", "OSX":
"Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD", "macOS":
_pty_native = _PTYUnix.new()
_:
push_error("PTY is not support on current platform (%s)." % os_name)
push_error("PTY is not supported on the current platform (%s)." % os_name)
_pty_native.connect("data_received", Callable(self, "_on_pty_native_data_received"))
_pty_native.connect("exited", Callable(self, "_on_pty_native_exited"))
@ -98,19 +93,19 @@ func get_rows() -> int:
return _rows
func _set_terminal(value: _Terminal):
func _set_terminal(value):
if _terminal == value:
return
# Disconect the current terminal, if any.
if _terminal:
if _terminal != null:
disconnect("data_received", Callable(_terminal, "write"))
_terminal.disconnect("data_sent", Callable(self, "write"))
_terminal.disconnect("size_changed", Callable(self, "resizev"))
_terminal = value
if not _terminal:
if _terminal == null:
return
# Connect the new terminal.
@ -159,7 +154,7 @@ func _notification(what: int):
match what:
NOTIFICATION_PARENTED:
var parent = get_parent()
if parent is _Terminal:
if parent is Terminal:
self.terminal_path = get_path_to(parent)

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]
size = 14
extra_spacing_bottom = 1
font_data = ExtResource( 4 )
fallbacks = [ExtResource("4")]
face_index = null
embolden = null
transform = null
cache/0/16/0/ascent = 0.0
cache/0/16/0/descent = 0.0
cache/0/16/0/underline_position = 0.0
cache/0/16/0/underline_thickness = 0.0
cache/0/16/0/scale = 1.0
cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/16/0/kerning_overrides/14/0 = Vector2(0, 0)
cache/0/14/0/ascent = 0.0
cache/0/14/0/descent = 0.0
cache/0/14/0/underline_position = 0.0
cache/0/14/0/underline_thickness = 0.0
cache/0/14/0/scale = 1.0
cache/0/14/0/kerning_overrides/16/0 = Vector2(0, 0)
cache/0/14/0/kerning_overrides/14/0 = Vector2(0, 0)

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

View file

@ -134,7 +134,10 @@ func _on_Terminal_key_pressed(data: String, event: InputEventKey) -> void:
"Asciicast":
var scene = item.scene.instantiate()
var animation_player: AnimationPlayer = scene.get_node("AnimationPlayer")
scene.connect("key_pressed",Callable(self,"_on_Asciicast_key_pressed").bind(animation_player))
scene.connect(
"key_pressed",
Callable(self, "_on_Asciicast_key_pressed").bind(animation_player)
)
add_child(scene)
scene.grab_focus()
await animation_player.animation_finished

View file

@ -1,7 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://brjrtf5fpptw8"]
[ext_resource type="Script" path="res://addons/godot_xterm/terminal.gd" id="1"]
[ext_resource type="Script" path="res://examples/menu/menu.gd" id="2"]
[ext_resource type="Theme" uid="uid://b7vd50tw2g1nl" path="res://themes/default.tres" id="2_o1653"]
[node name="Menu" type="Control"]
layout_mode = 3
@ -12,13 +12,11 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2")
[node name="Terminal" type="Control" parent="."]
[node name="Terminal" type="Terminal" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
focus_mode = 1
script = ExtResource("1")
copy_on_selection = false
theme = ExtResource("2_o1653")

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

View file

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

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://addons/godot_xterm/resources/xrdb_theme.gd" id="1"]
[ext_resource type="FontFile" uid="uid://edo5q2qxos3u" path="res://addons/godot_xterm/themes/fonts/hack/hack_regular-3.003.ttf" id="2"]
[sub_resource type="FontFile" id="1"]
[sub_resource type="FontFile" id="FontFile_tdf0u"]
fallbacks = [ExtResource("2")]
face_index = null
embolden = null
@ -39,5 +39,5 @@ Terminal/font_sizes/font_size = 14
Terminal/fonts/bold_font = null
Terminal/fonts/bold_italics_font = null
Terminal/fonts/italics_font = null
Terminal/fonts/normal_font = SubResource("1")
Terminal/fonts/normal_font = SubResource("FontFile_tdf0u")
script = ExtResource("1")