mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-05 04:34:23 +02:00
parent
a55a05d3a4
commit
9bd17ec8dc
31 changed files with 5058 additions and 1031 deletions
|
@ -45,7 +45,13 @@ func import(source_file, save_path, options, r_platform_variant, r_gen_files):
|
|||
asciicast.add_track(Animation.TYPE_METHOD, 0)
|
||||
asciicast.track_set_path(0, ".")
|
||||
|
||||
var time: float
|
||||
var frame = {
|
||||
"time": 0.0,
|
||||
"data": {
|
||||
"method": "write",
|
||||
"args": [PoolByteArray()]
|
||||
}
|
||||
}
|
||||
|
||||
while not file.eof_reached():
|
||||
var line = file.get_line()
|
||||
|
@ -56,14 +62,25 @@ func import(source_file, save_path, options, r_platform_variant, r_gen_files):
|
|||
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()
|
||||
|
||||
# Asciicast recordings have a resolution of 0.000001, however animation
|
||||
# track keys only have a resolution of 0.01, therefore we must combine
|
||||
# events that would occur in the same keyframe, otherwise only the last
|
||||
# event is inserted and the previous events are overwritten.
|
||||
var time = stepify(p.result[0], 0.01)
|
||||
|
||||
if event_type == "o":
|
||||
asciicast.track_insert_key(0, time, {"method": "write",
|
||||
"args": [event_data]})
|
||||
if time == frame.time:
|
||||
asciicast.track_remove_key_at_position(0, time)
|
||||
frame.data.args[0] = frame.data.args[0] + event_data
|
||||
else:
|
||||
frame.time = time
|
||||
frame.data.args = [event_data]
|
||||
|
||||
asciicast.track_insert_key(0, frame.time, frame.data)
|
||||
|
||||
asciicast.length = time
|
||||
asciicast.length = frame.time
|
||||
|
||||
return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], asciicast)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "pseudoterminal.h"
|
||||
#include <pty.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <termios.h>
|
||||
|
||||
using namespace godot;
|
||||
|
@ -11,9 +12,11 @@ void Pseudoterminal::_register_methods()
|
|||
register_method("_init", &Pseudoterminal::_init);
|
||||
register_method("_ready", &Pseudoterminal::_ready);
|
||||
|
||||
register_method("put_data", &Pseudoterminal::put_data);
|
||||
register_method("write", &Pseudoterminal::write);
|
||||
register_method("resize", &Pseudoterminal::resize);
|
||||
|
||||
register_signal<Pseudoterminal>((char *)"data_received", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Pseudoterminal>((char *)"data_sent", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Pseudoterminal>((char *)"exited", "status", GODOT_VARIANT_TYPE_INT);
|
||||
}
|
||||
|
||||
Pseudoterminal::Pseudoterminal()
|
||||
|
@ -22,18 +25,20 @@ Pseudoterminal::Pseudoterminal()
|
|||
|
||||
Pseudoterminal::~Pseudoterminal()
|
||||
{
|
||||
pty_thread.join();
|
||||
}
|
||||
|
||||
void Pseudoterminal::_init()
|
||||
{
|
||||
pty_thread = std::thread(&Pseudoterminal::process_pty, this);
|
||||
bytes_to_write = 0;
|
||||
pty_thread = std::thread(&Pseudoterminal::process_pty, this);
|
||||
}
|
||||
|
||||
void Pseudoterminal::process_pty()
|
||||
{
|
||||
int fd;
|
||||
char *name;
|
||||
int status;
|
||||
|
||||
should_process_pty = true;
|
||||
|
||||
|
@ -68,9 +73,30 @@ void Pseudoterminal::process_pty()
|
|||
}
|
||||
else
|
||||
{
|
||||
Vector2 zero = Vector2(0, 0);
|
||||
|
||||
/* Parent */
|
||||
while (1)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(size_mutex);
|
||||
if (size != zero)
|
||||
{
|
||||
struct winsize ws;
|
||||
memset(&ws, 0, sizeof(ws));
|
||||
ws.ws_col = size.x;
|
||||
ws.ws_row = size.y;
|
||||
|
||||
ioctl(fd, TIOCSWINSZ, &ws);
|
||||
}
|
||||
}
|
||||
|
||||
if (waitpid(pty_pid, &status, WNOHANG))
|
||||
{
|
||||
emit_signal("exited", status);
|
||||
return;
|
||||
}
|
||||
|
||||
int ready = -1;
|
||||
fd_set read_fds;
|
||||
fd_set write_fds;
|
||||
|
@ -93,10 +119,7 @@ void Pseudoterminal::process_pty()
|
|||
|
||||
if (bytes_to_write > 0)
|
||||
{
|
||||
write(fd, write_buffer, bytes_to_write);
|
||||
|
||||
Godot::print(String("wrote {0} bytes").format(Array::make(bytes_to_write)));
|
||||
|
||||
::write(fd, write_buffer, bytes_to_write);
|
||||
bytes_to_write = 0;
|
||||
}
|
||||
}
|
||||
|
@ -114,30 +137,11 @@ void Pseudoterminal::process_pty()
|
|||
if (bytes_read <= 0)
|
||||
continue;
|
||||
|
||||
//while (1)
|
||||
//{
|
||||
// ret = read(fd, read_buffer, 1);
|
||||
|
||||
// if (ret == -1 || ret == 0)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// bytes_read += ret;
|
||||
// }
|
||||
//}
|
||||
|
||||
PoolByteArray data = PoolByteArray();
|
||||
data.resize(bytes_read);
|
||||
memcpy(data.write().ptr(), read_buffer, bytes_read);
|
||||
|
||||
emit_signal("data_received", PoolByteArray(data));
|
||||
|
||||
if (bytes_read > 0)
|
||||
{
|
||||
//Godot::print(String("read {0} bytes").format(Array::make(bytes_read)));
|
||||
}
|
||||
emit_signal("data_sent", PoolByteArray(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,9 +152,15 @@ void Pseudoterminal::_ready()
|
|||
{
|
||||
}
|
||||
|
||||
void Pseudoterminal::put_data(PoolByteArray data)
|
||||
void Pseudoterminal::write(PoolByteArray data)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(write_buffer_mutex);
|
||||
bytes_to_write = data.size();
|
||||
memcpy(write_buffer, data.read().ptr(), bytes_to_write);
|
||||
}
|
||||
|
||||
void Pseudoterminal::resize(Vector2 new_size)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(size_mutex);
|
||||
size = new_size;
|
||||
}
|
|
@ -29,6 +29,9 @@ namespace godot
|
|||
int bytes_to_read;
|
||||
std::mutex read_buffer_mutex;
|
||||
|
||||
Vector2 size;
|
||||
std::mutex size_mutex;
|
||||
|
||||
void process_pty();
|
||||
|
||||
public:
|
||||
|
@ -40,7 +43,8 @@ namespace godot
|
|||
void _init();
|
||||
void _ready();
|
||||
|
||||
void put_data(PoolByteArray data);
|
||||
void write(PoolByteArray data);
|
||||
void resize(Vector2 size);
|
||||
};
|
||||
} // namespace godot
|
||||
|
||||
|
|
|
@ -215,7 +215,17 @@ static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data
|
|||
for (int i = 0; i < len; i++)
|
||||
bytes.append(u8[i]);
|
||||
|
||||
term->emit_signal("data_read", bytes);
|
||||
if (len > 0)
|
||||
{
|
||||
if (term->input_event_key.is_valid())
|
||||
{
|
||||
// The callback was fired from a key press event so emit the "key_pressed" signal.
|
||||
term->emit_signal("key_pressed", String(u8), term->input_event_key);
|
||||
term->input_event_key.unref();
|
||||
}
|
||||
|
||||
term->emit_signal("data_sent", bytes);
|
||||
}
|
||||
}
|
||||
|
||||
static int text_draw_cb(struct tsm_screen *con,
|
||||
|
@ -258,7 +268,6 @@ static int text_draw_cb(struct tsm_screen *con,
|
|||
|
||||
void Terminal::_register_methods()
|
||||
{
|
||||
|
||||
register_method("_init", &Terminal::_init);
|
||||
register_method("_ready", &Terminal::_ready);
|
||||
register_method("_gui_input", &Terminal::_gui_input);
|
||||
|
@ -267,10 +276,12 @@ void Terminal::_register_methods()
|
|||
register_method("write", &Terminal::write);
|
||||
register_method("update_size", &Terminal::update_size);
|
||||
|
||||
//register_property<Terminal, int>("rows", &Terminal::rows, 24);
|
||||
//register_property<Terminal, int>("cols", &Terminal::cols, 80);
|
||||
register_property<Terminal, int>("rows", &Terminal::rows, 24);
|
||||
register_property<Terminal, int>("cols", &Terminal::cols, 80);
|
||||
|
||||
register_signal<Terminal>("data_read", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Terminal>("data_sent", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
|
||||
register_signal<Terminal>("key_pressed", "data", GODOT_VARIANT_TYPE_STRING, "event", GODOT_VARIANT_TYPE_OBJECT);
|
||||
register_signal<Terminal>("size_changed", "new_size", GODOT_VARIANT_TYPE_VECTOR2);
|
||||
}
|
||||
|
||||
Terminal::Terminal()
|
||||
|
@ -349,6 +360,7 @@ void Terminal::_gui_input(Variant event)
|
|||
auto iter = keymap.find({unicode, scancode});
|
||||
uint32_t keysym = (iter != keymap.end() ? iter->second : XKB_KEY_NoSymbol);
|
||||
|
||||
input_event_key = k;
|
||||
tsm_vte_handle_keyboard(vte, keysym, ascii, mods, unicode ? unicode : TSM_VTE_INVALID);
|
||||
}
|
||||
}
|
||||
|
@ -425,6 +437,9 @@ void Terminal::draw_foreground(int row, int col, Color fgcolor)
|
|||
|
||||
struct cell cell = cells[row][col];
|
||||
|
||||
if (cell.ch == nullptr)
|
||||
return; // No foreground to draw
|
||||
|
||||
/* Set the font */
|
||||
|
||||
Ref<Font> fontref = get_font("");
|
||||
|
@ -448,9 +463,6 @@ void Terminal::draw_foreground(int row, int col, Color fgcolor)
|
|||
|
||||
/* Draw the foreground */
|
||||
|
||||
if (cell.ch == nullptr)
|
||||
return; // No foreground to draw
|
||||
|
||||
if (cell.attr.blink)
|
||||
; // TODO: Handle blink
|
||||
|
||||
|
@ -523,7 +535,7 @@ void Terminal::update_size()
|
|||
rows = std::max(2, (int)floor(get_rect().size.y / cell_size.y));
|
||||
cols = std::max(1, (int)floor(get_rect().size.x / cell_size.x));
|
||||
|
||||
Godot::print(String("resized_rows: {0}, resized_cols: {1}").format(Array::make(rows, cols)));
|
||||
emit_signal("size_changed", Vector2(cols, rows));
|
||||
|
||||
Cells new_cells = {};
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace godot
|
|||
|
||||
Cells cells;
|
||||
|
||||
Ref<InputEventKey> input_event_key;
|
||||
|
||||
protected:
|
||||
tsm_screen *screen;
|
||||
tsm_vte *vte;
|
||||
|
|
131
addons/godot_xterm/nodes/terminal/README.md
Normal file
131
addons/godot_xterm/nodes/terminal/README.md
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Terminal
|
||||
|
||||
**Inherits:** [Control] < [CanvasItem] < [Node] < [Object]
|
||||
|
||||
|
||||
Terminal emulator.
|
||||
|
||||
**IMPORTANT:**
|
||||
|
||||
<img align="right" src="./docs/important_properties.png"/>
|
||||
|
||||
- If you are not seeing anything in the terminal check that a theme has been set. If there is no theme, everything will be drawn in black by default.
|
||||
- If the terminal isn't responding to keyboard or mouse input check that `focus_mode` is set to `All`, otherwise `_gui_input()` won't be called so no input will be processed.
|
||||
|
||||
|
||||
## Description
|
||||
|
||||

|
||||
|
||||
### (1) User Input
|
||||
|
||||
The users enters some data into the terminal, typically by typing something on the keyboard or clicking (and possibly dragging) somewhere on the screen.
|
||||
This corresponds to the `_gui_input()` method which is implemented in [terminal.cpp](../native/src/terminal.cpp).
|
||||
|
||||
### (2) Terminal Output
|
||||
|
||||
The user input from (1) is processed by the terminal and converted.
|
||||
For example, if the user were to press the down key, the terminal would
|
||||
and the `data_sent` signal would be emitted with the value `"\u001b[A"`.
|
||||
For a full list of escape sequences ["XTerm Control Sequences"](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html).
|
||||
|
||||
### (3) Terminal Input
|
||||
|
||||
In the other direction, characters can be sent to the terminal. This corresponds to the `write()` method.
|
||||
|
||||
### (4) Draw
|
||||
|
||||
The input from (3) is then intepreted by the terminal and drawn to the screen.
|
||||
For example if the string "\u001b[39;m" was written to the terminal, then it would draw some colorful output to the screen.
|
||||
|
||||
## Properties
|
||||
|
||||
| Type | Name | Default |
|
||||
|-------|------|---------|
|
||||
| [int] | rows | 24 |
|
||||
| [int] | cols | 80 |
|
||||
|
||||
## Methods
|
||||
|
||||
## Theme Properties
|
||||
|
||||
| Type | Name | Default |
|
||||
|---------|-------------------------------|------------------------------|
|
||||
| [Color] | Terminal/colors/Background | Color(0.0, 0.0, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Black | Color(0.0, 0.0, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Blue | Color(0.0, 0.0, 0.5, 1.0) |
|
||||
| [Color] | Terminal/colors/Cyan | Color(0.0, 0.5, 0.5, 1.0) |
|
||||
| [Color] | Terminal/colors/Dark Grey | Color(0.5, 0.5, 0.5, 1.0) |
|
||||
| [Color] | Terminal/colors/Foreground | Color(1.0, 1.0, 1.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Green | Color(0.0, 0.5, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Blue | Color(0.0, 0.0, 1.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Cyan | Color(0.0, 1.0, 1.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Green | Color(0.0, 1.0, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Grey | Color(0.75, 0.75, 0.75, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Magenta | Color(1.0, 0.0, 1.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Red | Color(1.0, 0.0, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Light Yellow | Color(1.0, 1.0, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Magenta | Color(0.5, 0.0, 0.5, 1.0) |
|
||||
| [Color] | Terminal/colors/Red | Color(0.5, 0.0, 0.0, 1.0) |
|
||||
| [Color] | Terminal/colors/White | Color(1.0, 1.0, 1.0, 1.0) |
|
||||
| [Color] | Terminal/colors/Yellow | Color(0.5, 0.5, 0.0, 1.0) |
|
||||
| [Font] | Terminal/fonts/Bold | |
|
||||
| [Font] | Terminal/fonts/Bold Italic | |
|
||||
| [Font] | Terminal/fonts/Italic | |
|
||||
| [Font] | Terminal/fonts/Regular | |
|
||||
|
||||
## Signals
|
||||
|
||||
- **data_sent** **(** PoolByteArray data **)**
|
||||
|
||||
Emitted when some data comes out of the terminal.
|
||||
This typically occurs when the user interacts with the terminal by typing on the keyboard or clicking somewhere.
|
||||
It might also occur.
|
||||
Depending on several terminal settings can be interpreted differently, depending on modifier keys and the terminals settings/state.
|
||||
In a typical setup, this data would be forwarded to the linux operating system.
|
||||
|
||||
---
|
||||
|
||||
- **key_pressed** **(** int row **)**
|
||||
|
||||
Emitted when a key is pressed.
|
||||
|
||||
## Property Descriptions
|
||||
|
||||
- [int] **rows**
|
||||
|
||||
| | |
|
||||
|-----------|-----------------|
|
||||
| *Default* | 24 |
|
||||
| *Setter* | set_rows(value) |
|
||||
| *Getter* | get_rows(value) |
|
||||
|
||||
The number of rows in the terminal's rect.
|
||||
When using a monospace font, this is typically the number of characters that can fit from one side to another.
|
||||
Therefore it is affected by the things thing.
|
||||
|
||||
---
|
||||
|
||||
- [int] **cols**
|
||||
The number of columns in the terminal's rect.
|
||||
|
||||
## Method Descriptions
|
||||
|
||||
- void **write** **(** String|PoolByteArray data **)**
|
||||
|
||||
Writes data to the terminal emulator. Accepts either a String or PoolByteArray.
|
||||
|
||||
Example:
|
||||
```gdscript
|
||||
$Terminal.write("Hello World")
|
||||
$Terminal.write("Hello World".to_utf8())
|
||||
$Terminal.write(PoolByteArray([0x1b, 0x9e])
|
||||
```
|
||||
|
||||
[Control]: https://docs.godotengine.org/en/stable/classes/class_control.html#class-control
|
||||
[CanvasItem]: https://docs.godotengine.org/en/stable/classes/class_canvasitem.html#class-canvasitem
|
||||
[Node]: https://docs.godotengine.org/en/stable/classes/class_node.html#class-node
|
||||
[Object]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object
|
||||
[Color]: https://docs.godotengine.org/en/stable/classes/class_color.html#class-color
|
||||
[Font]: https://docs.godotengine.org/en/stable/classes/class_font.html#class-font
|
||||
[int]: https://docs.godotengine.org/en/stable/classes/class_int.html#class-int
|
0
addons/godot_xterm/nodes/terminal/docs/.gdignore
Normal file
0
addons/godot_xterm/nodes/terminal/docs/.gdignore
Normal file
3777
addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg
Normal file
3777
addons/godot_xterm/nodes/terminal/docs/flow_diagram.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 360 KiB |
BIN
addons/godot_xterm/nodes/terminal/docs/important_properties.png
Normal file
BIN
addons/godot_xterm/nodes/terminal/docs/important_properties.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -17,3 +17,7 @@ func get_class() -> String:
|
|||
|
||||
func is_class(name) -> bool:
|
||||
return name == get_class() or .is_class(name)
|
||||
|
||||
|
||||
func _init():
|
||||
step = 0.01 # Parent override.
|
||||
|
|
66
addons/godot_xterm/util/tput.gd
Normal file
66
addons/godot_xterm/util/tput.gd
Normal file
|
@ -0,0 +1,66 @@
|
|||
extends Reference
|
||||
class_name TPut
|
||||
|
||||
# Control Sequence Introducer
|
||||
const CSI = "\u001b["
|
||||
|
||||
const CURSOR_UP = "\u001b[A"
|
||||
const CURSOR_DOWN = "\u001b[B"
|
||||
const CURSOR_RIGHT = "\u001b[C"
|
||||
const CURSOR_LEFT = "\u001b[D"
|
||||
|
||||
const DEFAULT_FOREGROUND_COLOR = "\u001b[0m"
|
||||
|
||||
|
||||
var terminal
|
||||
|
||||
|
||||
func _init(p_terminal: Control) -> void:
|
||||
if p_terminal:
|
||||
terminal = p_terminal
|
||||
|
||||
|
||||
func write_string(string: String, color: Color = Color.white) -> void:
|
||||
if color:
|
||||
var fg = "\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
|
||||
terminal.write(fg.to_utf8())
|
||||
|
||||
terminal.write(string.to_utf8())
|
||||
|
||||
# Reset color back to default.
|
||||
terminal.write("\u001b[0m".to_utf8())
|
||||
|
||||
# tput_* functions based on the tput command.
|
||||
# See: https://man7.org/linux/man-pages/man1/tput.1.html for more info.
|
||||
|
||||
|
||||
# Hide the cursor.
|
||||
func civis():
|
||||
terminal.write("%s?25l" % CSI)
|
||||
|
||||
|
||||
# Position the cursor at the given row and col.
|
||||
func cup(row: int = 0, col: int = 0) -> void:
|
||||
terminal.write("\u001b[%d;%dH" % [row, col])
|
||||
|
||||
|
||||
func setaf(color: Color) -> void:
|
||||
var fg = "\u001b[38;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
|
||||
terminal.write(fg)
|
||||
|
||||
|
||||
func setab(color: Color) -> void:
|
||||
var bg = "\u001b[48;2;%d;%d;%dm" % [color.r8, color.g8, color.b8]
|
||||
terminal.write(bg)
|
||||
|
||||
|
||||
func rev() -> void:
|
||||
terminal.write("\u001b[7m")
|
||||
|
||||
|
||||
func sgr0() -> void:
|
||||
terminal.write("\u001b[0m")
|
||||
|
||||
|
||||
func reset() -> void:
|
||||
terminal.write("\u001bc")
|
Loading…
Add table
Add a link
Reference in a new issue