Multiple changes

Former-commit-id: db8e674358
This commit is contained in:
Leroy Hopson 2020-09-29 16:16:59 +07:00
parent a55a05d3a4
commit 9bd17ec8dc
31 changed files with 5058 additions and 1031 deletions

View file

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

View file

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

View file

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

View file

@ -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 = {};

View file

@ -28,6 +28,8 @@ namespace godot
Cells cells;
Ref<InputEventKey> input_event_key;
protected:
tsm_screen *screen;
tsm_vte *vte;

View 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
![Flow Diagram](./docs/flow_diagram.svg)
### (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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

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

View 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")