mirror of
https://github.com/lihop/godot-xterm.git
synced 2024-11-22 01:30:25 +01:00
Add asciicast importer
This commit is contained in:
parent
55544de93e
commit
ffa8561865
11 changed files with 199 additions and 77 deletions
|
@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
## [Unreleased]
|
||||
### Added
|
||||
- Changelog.
|
||||
- Asciicast importer plugin. Enables the import of .cast ([asciicast files v2](https://github.com/asciinema/asciinema/blob/master/doc/asciicast-v2.md)) that can be made using the [asciinema](https://asciinema.org/) terminal session recorder. See the [asciicast scene](/examples/asciicast) for example usage.
|
||||
|
||||
### Changed
|
||||
- Implementation of Terminal node from GDScript to GDNative using [Aetf's patched version of libtsm](https://github.com/Aetf/libtsm).
|
||||
|
|
|
@ -18,6 +18,13 @@ Click the thumbnail to watch a demo video on youtube:
|
|||
|
||||
[![Demo video thumbnail](https://img.youtube.com/vi/_Tt4eQEBybo/0.jpg)](https://www.youtube.com/watch?v=_Tt4eQEBybo)
|
||||
|
||||
## Usage
|
||||
|
||||
### Asciicast (.cast) file importer
|
||||
[Asciinema](https://asciinema.org) recordings saved with the `.cast` extension will be automatically imported as animations. They can then be added to AnimationPlayer which is a child of the Terminal node. Playing the animation will play the terminal session recording in the parent Terminal.
|
||||
|
||||
For an example, see the scene in [/examples/asciicast](/examples/asciicast).
|
||||
|
||||
## License
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license.
|
||||
|
|
69
addons/godot_xterm/import_plugins/asciicast_import_plugin.gd
Normal file
69
addons/godot_xterm/import_plugins/asciicast_import_plugin.gd
Normal file
|
@ -0,0 +1,69 @@
|
|||
tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
const Asciicast = preload("res://addons/godot_xterm/resources/asciicast.gd")
|
||||
|
||||
|
||||
func get_importer_name():
|
||||
return "godot_xterm"
|
||||
|
||||
|
||||
func get_visible_name():
|
||||
return "asciicast"
|
||||
|
||||
|
||||
func get_recognized_extensions():
|
||||
return ["cast"]
|
||||
|
||||
|
||||
func get_save_extension():
|
||||
return "res"
|
||||
|
||||
|
||||
func get_resource_type():
|
||||
return "Animation"
|
||||
|
||||
|
||||
func get_import_options(preset):
|
||||
return []
|
||||
|
||||
|
||||
func get_preset_count():
|
||||
return 0
|
||||
|
||||
|
||||
func import(source_file, save_path, options, r_platform_variant, r_gen_files):
|
||||
var file = File.new()
|
||||
var err = file.open(source_file, File.READ)
|
||||
if err != OK:
|
||||
return err
|
||||
|
||||
var header = file.get_line()
|
||||
|
||||
var asciicast = Asciicast.new()
|
||||
|
||||
asciicast.add_track(Animation.TYPE_METHOD, 0)
|
||||
asciicast.track_set_path(0, ".")
|
||||
|
||||
var time: float
|
||||
|
||||
while not file.eof_reached():
|
||||
var line = file.get_line()
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
var p = JSON.parse(line)
|
||||
if typeof(p.result) != TYPE_ARRAY:
|
||||
continue
|
||||
|
||||
time = p.result[0]
|
||||
var event_type: String = p.result[1]
|
||||
var event_data: PoolByteArray = p.result[2].to_utf8()
|
||||
|
||||
if event_type == "o":
|
||||
asciicast.track_insert_key(0, time, {"method": "write",
|
||||
"args": [event_data]})
|
||||
|
||||
asciicast.length = time
|
||||
|
||||
return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], asciicast)
|
|
@ -1,16 +0,0 @@
|
|||
extends Node
|
||||
|
||||
|
||||
# Declare member variables here. Examples:
|
||||
# var a = 2
|
||||
# var b = "text"
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
#func _process(delta):
|
||||
# pass
|
|
@ -2,7 +2,16 @@ tool
|
|||
extends EditorPlugin
|
||||
|
||||
|
||||
var asciicast_import_plugin
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
asciicast_import_plugin = preload("res://addons/godot_xterm/import_plugins/asciicast_import_plugin.gd").new()
|
||||
add_import_plugin(asciicast_import_plugin)
|
||||
|
||||
var asciicast_script = preload("res://addons/godot_xterm/resources/asciicast.gd")
|
||||
add_custom_type("Asciicast", "Animation", asciicast_script, null)
|
||||
|
||||
var terminal_script = preload("res://addons/godot_xterm/nodes/terminal/terminal.gdns")
|
||||
var terminal_icon = preload("res://addons/godot_xterm/nodes/terminal/terminal_icon.svg")
|
||||
add_custom_type("Terminal", "Control", terminal_script, terminal_icon)
|
||||
|
@ -13,5 +22,9 @@ func _enter_tree():
|
|||
|
||||
|
||||
func _exit_tree():
|
||||
remove_import_plugin(asciicast_import_plugin)
|
||||
asciicast_import_plugin = null
|
||||
|
||||
remove_custom_type("Asciicast")
|
||||
remove_custom_type("Terminal")
|
||||
remove_custom_type("Psuedoterminal")
|
||||
|
|
19
addons/godot_xterm/resources/asciicast.gd
Normal file
19
addons/godot_xterm/resources/asciicast.gd
Normal file
|
@ -0,0 +1,19 @@
|
|||
extends Animation
|
||||
|
||||
|
||||
signal data_written(data)
|
||||
signal data_read(data)
|
||||
|
||||
export(int) var version: int = 2
|
||||
# Initial terminal width (number of columns).
|
||||
export(int) var width: int
|
||||
# Initial terminal height (number of rows).
|
||||
export(int) var height: int
|
||||
|
||||
|
||||
func get_class() -> String:
|
||||
return "Asciicast"
|
||||
|
||||
|
||||
func is_class(name) -> bool:
|
||||
return name == get_class() or .is_class(name)
|
53
examples/asciicast/asciicast.gd
Normal file
53
examples/asciicast/asciicast.gd
Normal file
|
@ -0,0 +1,53 @@
|
|||
extends Container
|
||||
# This Container ensures that the terminal always fills
|
||||
# the window and/or screen. It also connects the terminal
|
||||
# to the input/output of the Psuedoterminal.
|
||||
|
||||
const ESCAPE = 27
|
||||
const BACKSPACE = 8
|
||||
const BEEP = 7
|
||||
const SPACE = 32
|
||||
const LEFT_BRACKET = 91
|
||||
const ENTER = 10
|
||||
const BACKSPACE_ALT = 127
|
||||
|
||||
onready var viewport = get_viewport()
|
||||
|
||||
func _ready():
|
||||
viewport.connect("size_changed", self, "_resize")
|
||||
_resize()
|
||||
$Terminal/AnimationPlayer.play("a")
|
||||
|
||||
|
||||
func _input(event):
|
||||
#return
|
||||
if event is InputEventKey and event.pressed:
|
||||
var data = PoolByteArray([])
|
||||
accept_event()
|
||||
|
||||
# TODO: Handle more of these.
|
||||
if (event.control and event.scancode == KEY_C):
|
||||
data.append(3)
|
||||
elif event.unicode:
|
||||
data.append(event.unicode)
|
||||
elif event.scancode == KEY_ENTER:
|
||||
data.append(ENTER)
|
||||
elif event.scancode == KEY_BACKSPACE:
|
||||
data.append(BACKSPACE_ALT)
|
||||
elif event.scancode == KEY_ESCAPE:
|
||||
data.append(27)
|
||||
elif event.scancode == KEY_TAB:
|
||||
data.append(9)
|
||||
elif OS.get_scancode_string(event.scancode) == "Shift":
|
||||
pass
|
||||
elif OS.get_scancode_string(event.scancode) == "Control":
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
#push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
|
||||
#emit_signal("output", data)
|
||||
|
||||
|
||||
func _resize():
|
||||
rect_size = viewport.size
|
||||
$Terminal.rect_size = rect_size
|
24
examples/asciicast/asciicast.tscn
Normal file
24
examples/asciicast/asciicast.tscn
Normal file
|
@ -0,0 +1,24 @@
|
|||
[gd_scene load_steps=5 format=2]
|
||||
|
||||
[ext_resource path="res://examples/asciicast/asciicast.gd" type="Script" id=1]
|
||||
[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=2]
|
||||
[ext_resource path="res://addons/godot_xterm/nodes/terminal/terminal.gdns" type="Script" id=4]
|
||||
[ext_resource path="res://examples/asciicast/example.cast" type="Animation" id=6]
|
||||
|
||||
[node name="Container" type="Container"]
|
||||
margin_right = 40.0
|
||||
margin_bottom = 40.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="Terminal" type="Control" parent="."]
|
||||
margin_right = 40.0
|
||||
margin_bottom = 40.0
|
||||
theme = ExtResource( 2 )
|
||||
script = ExtResource( 4 )
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Terminal"]
|
||||
method_call_mode = 1
|
||||
anims/a = ExtResource( 6 )
|
|
@ -1,61 +0,0 @@
|
|||
{"version": 2, "width": 86, "height": 29, "timestamp": 1589772748, "env": {"SHELL": "/run/current-system/sw/bin/bash", "TERM": "xterm"}}
|
||||
[0.082961, "o", "> "]
|
||||
[0.798002, "o", "e"]
|
||||
[0.893414, "o", "c"]
|
||||
[0.956255, "o", "h"]
|
||||
[1.008677, "o", "o"]
|
||||
[1.089472, "o", " "]
|
||||
[1.189602, "o", "h"]
|
||||
[1.266892, "o", "e"]
|
||||
[1.347483, "o", "l"]
|
||||
[1.46568, "o", "l"]
|
||||
[1.541039, "o", "o"]
|
||||
[1.726772, "o", "\r\n"]
|
||||
[1.727475, "o", "hello\r\n> "]
|
||||
[2.060109, "o", "#"]
|
||||
[2.179668, "o", " "]
|
||||
[2.471941, "o", "T"]
|
||||
[2.652735, "o", "h"]
|
||||
[2.746515, "o", "i"]
|
||||
[2.810578, "o", "s"]
|
||||
[2.921342, "o", " "]
|
||||
[2.98886, "o", "i"]
|
||||
[3.069095, "o", "s"]
|
||||
[3.31728, "o", " "]
|
||||
[3.399615, "o", "a"]
|
||||
[3.513605, "o", " "]
|
||||
[3.72609, "o", "d"]
|
||||
[3.811197, "o", "e"]
|
||||
[3.94649, "o", "m"]
|
||||
[4.047162, "o", "o"]
|
||||
[4.225042, "o", "\r\n"]
|
||||
[4.225402, "o", "> "]
|
||||
[4.935288, "o", "t"]
|
||||
[5.163552, "o", "o"]
|
||||
[5.323205, "o", "i"]
|
||||
[5.46746, "o", "l"]
|
||||
[5.561098, "o", "et "]
|
||||
[6.064937, "o", "-"]
|
||||
[6.41563, "o", "-"]
|
||||
[6.60443, "o", "g"]
|
||||
[6.666621, "o", "a"]
|
||||
[6.768317, "o", "y"]
|
||||
[6.848917, "o", " "]
|
||||
[7.076406, "o", "H"]
|
||||
[7.250067, "o", "E"]
|
||||
[7.410878, "o", "L"]
|
||||
[7.537016, "o", "L"]
|
||||
[7.604155, "o", "O"]
|
||||
[7.888992, "o", " "]
|
||||
[8.193437, "o", "W"]
|
||||
[8.365871, "o", "O"]
|
||||
[8.454678, "o", "R"]
|
||||
[8.525163, "o", "L"]
|
||||
[8.60286, "o", "D"]
|
||||
[8.873053, "o", "!"]
|
||||
[9.216434, "o", "\r\n"]
|
||||
[9.251462, "o", " \r\n \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;33;93mm\u001b[0m \u001b[0;1;35;95mm\u001b[0m \u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;33;93mm\u001b[0;1;32;92mmm\u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;32;92mm\u001b[0m \u001b[0;1;35;95mm\u001b[0;1;31;91mmm\u001b[0;1;33;93mm\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;36;96mm\u001b[0;1;34;94m\"\u001b[0m \u001b[0;1;35;95m\"\u001b[0;1;31;91mm\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92mm\"\u001b[0m \u001b[0;1;34;94m\"m\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m\"\u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;32;92m\"\u001b[0;1;36;96mm\u001b[0m\r\n \u001b[0;1;32;92m#\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b"]
|
||||
[9.251901, "o", "[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#\"\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m\r\n \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m##\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m\"\u001b[0;1;34;94mm\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m\r\n \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#m\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93mmm\u001b[0;1;32;92mm\u001b[0m \u001b[0;1;36;96m#m\u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0m \u001b[0;1;33;93m#m\u001b[0;1;32;92mm#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1"]
|
||||
[9.251944, "o", ";32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0;1;34;94mmm\u001b[0;1;35;95mm\"\u001b[0m \r\n \r\n \r\n \r\n \u001b[0;1;36;96mm\u001b[0m \r\n \u001b[0;1;34;94m#\u001b[0m \r\n \u001b[0;1;35;95m#\u001b[0m \r\n \u001b[0;1;31;91m\"\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \r\n \r\n \r\n"]
|
||||
[9.252259, "o", "> "]
|
||||
[12.56287, "o", "exit\r\n"]
|
13
examples/asciicast/example.cast.import
Normal file
13
examples/asciicast/example.cast.import
Normal file
|
@ -0,0 +1,13 @@
|
|||
[remap]
|
||||
|
||||
importer="godot_xterm"
|
||||
type="Animation"
|
||||
path="res://.import/example.cast-9299cc6d12357f676344c3d48e3179e0.res"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://examples/asciicast/example.cast"
|
||||
dest_files=[ "res://.import/example.cast-9299cc6d12357f676344c3d48e3179e0.res" ]
|
||||
|
||||
[params]
|
||||
|
Loading…
Reference in a new issue