Refactor file structure

Former-commit-id: 3eecf504cf
This commit is contained in:
Leroy Hopson 2020-09-17 16:23:01 +07:00
parent a022104230
commit ee6d7cb0fa
33 changed files with 89 additions and 50 deletions

View file

@ -0,0 +1,21 @@
#include "terminal.h"
#include "pseudoterminal.h"
extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o)
{
godot::Godot::gdnative_init(o);
}
extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o)
{
godot::Godot::gdnative_terminate(o);
}
extern "C" void GDN_EXPORT godot_nativescript_init(void *handle)
{
godot::Godot::nativescript_init(handle);
godot::register_class<godot::Terminal>();
godot::register_class<godot::Pseudoterminal>();
}

View file

@ -0,0 +1,156 @@
#include "pseudoterminal.h"
#include <pty.h>
#include <unistd.h>
#include <termios.h>
using namespace godot;
void Pseudoterminal::_register_methods()
{
register_method("_init", &Pseudoterminal::_init);
register_method("_ready", &Pseudoterminal::_ready);
register_method("put_data", &Pseudoterminal::put_data);
register_signal<Pseudoterminal>((char *)"data_received", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
}
Pseudoterminal::Pseudoterminal()
{
}
Pseudoterminal::~Pseudoterminal()
{
}
void Pseudoterminal::_init()
{
pty_thread = std::thread(&Pseudoterminal::process_pty, this);
bytes_to_write = 0;
}
void Pseudoterminal::process_pty()
{
int fd;
char *name;
should_process_pty = true;
struct termios termios = {};
termios.c_iflag = IGNPAR | ICRNL;
termios.c_oflag = 0;
termios.c_cflag = B38400 | CRTSCTS | CS8 | CLOCAL | CREAD;
termios.c_lflag = ICANON;
termios.c_cc[VMIN] = 1;
termios.c_cc[VTIME] = 0;
pid_t pty_pid = forkpty(&fd, NULL, NULL, NULL);
if (pty_pid == -1)
{
ERR_PRINT(String("Error forking pty: {0}").format(Array::make(strerror(errno))));
should_process_pty = false;
return;
}
else if (pty_pid == 0)
{
/* Child */
char termenv[11] = {"TERM=xterm"};
putenv(termenv);
char colortermenv[20] = {"COLORTERM=truecolor"};
putenv(colortermenv);
char *shell = getenv("SHELL");
execvp(shell, NULL);
}
else
{
/* Parent */
while (1)
{
int ready = -1;
fd_set read_fds;
fd_set write_fds;
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
FD_SET(fd, &write_fds);
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
ready = select(fd + 1, &read_fds, &write_fds, NULL, &timeout);
if (ready > 0)
{
if (FD_ISSET(fd, &write_fds))
{
std::lock_guard<std::mutex> guard(write_buffer_mutex);
if (bytes_to_write > 0)
{
write(fd, write_buffer, bytes_to_write);
Godot::print(String("wrote {0} bytes").format(Array::make(bytes_to_write)));
bytes_to_write = 0;
}
}
if (FD_ISSET(fd, &read_fds))
{
std::lock_guard<std::mutex> guard(read_buffer_mutex);
int ret;
int bytes_read = 0;
bytes_read = read(fd, read_buffer, MAX_READ_BUFFER_LENGTH);
// TODO: handle error (-1)
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)));
}
}
}
}
}
}
void Pseudoterminal::_ready()
{
}
void Pseudoterminal::put_data(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);
}

View file

@ -0,0 +1,47 @@
#ifndef PSEUDOTERMINAL_H
#define PSEUDOTERMINAL_H
#include <Godot.hpp>
#include <Node.hpp>
#include <thread>
#include <mutex>
namespace godot
{
class Pseudoterminal : public Node
{
GODOT_CLASS(Pseudoterminal, Node)
public:
static const int MAX_READ_BUFFER_LENGTH = 1024;
static const int MAX_WRITE_BUFFER_LENGTH = 1024;
private:
std::thread pty_thread;
bool should_process_pty;
char write_buffer[MAX_WRITE_BUFFER_LENGTH];
int bytes_to_write;
std::mutex write_buffer_mutex;
char read_buffer[MAX_READ_BUFFER_LENGTH];
int bytes_to_read;
std::mutex read_buffer_mutex;
void process_pty();
public:
static void _register_methods();
Pseudoterminal();
~Pseudoterminal();
void _init();
void _ready();
void put_data(PoolByteArray data);
};
} // namespace godot
#endif // PSEUDOTERMINAL_H

View file

@ -0,0 +1,362 @@
#include "terminal.h"
#include <algorithm>
#include <Theme.hpp>
using namespace godot;
// Use xterm default for default color palette.
const uint8_t Terminal::default_color_palette[TSM_COLOR_NUM][3] = {
[TSM_COLOR_BLACK] = {0x00, 0x00, 0x00},
[TSM_COLOR_RED] = {0x80, 0x00, 0x00},
[TSM_COLOR_GREEN] = {0x00, 0x80, 0x00},
[TSM_COLOR_YELLOW] = {0x80, 0x80, 0x00},
[TSM_COLOR_BLUE] = {0x00, 0x00, 0x80},
[TSM_COLOR_MAGENTA] = {0x80, 0x00, 0x80},
[TSM_COLOR_CYAN] = {0x00, 0x80, 0x80},
[TSM_COLOR_LIGHT_GREY] = {0xc0, 0xc0, 0xc0},
[TSM_COLOR_DARK_GREY] = {0x80, 0x80, 0x80},
[TSM_COLOR_LIGHT_RED] = {0xff, 0x00, 0x00},
[TSM_COLOR_LIGHT_GREEN] = {0x00, 0xff, 0x00},
[TSM_COLOR_LIGHT_YELLOW] = {0xff, 0xff, 0x00},
[TSM_COLOR_LIGHT_BLUE] = {0x00, 0x00, 0xff},
[TSM_COLOR_LIGHT_MAGENTA] = {0xff, 0x00, 0xff},
[TSM_COLOR_LIGHT_CYAN] = {0x00, 0xff, 0xff},
[TSM_COLOR_WHITE] = {0xff, 0xff, 0xff},
[TSM_COLOR_FOREGROUND] = {0xff, 0xff, 0xff},
[TSM_COLOR_BACKGROUND] = {0x00, 0x00, 0x00},
};
static struct
{
Color col;
bool is_set;
} colours[16];
static void term_output(const char *s, size_t len, void *user)
{
}
static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data)
{
Terminal *term = static_cast<Terminal *>(data);
}
static int text_draw_cb(struct tsm_screen *con,
uint64_t id,
const uint32_t *ch,
size_t len,
unsigned int width,
unsigned int posx,
unsigned int posy,
const struct tsm_screen_attr *attr,
tsm_age_t age,
void *data)
{
Terminal *terminal = static_cast<Terminal *>(data);
if (age <= terminal->framebuffer_age)
return 0;
size_t ulen;
char buf[5] = {0};
if (len > 0)
{
char *utf8 = tsm_ucs4_to_utf8_alloc(ch, len, &ulen);
memcpy(terminal->cells[posy][posx].ch, utf8, ulen);
}
else
{
terminal->cells[posy][posx] = {};
}
memcpy(&terminal->cells[posy][posx].attr, attr, sizeof(tsm_screen_attr));
if (!terminal->sleep)
terminal->update();
return 0;
}
void Terminal::_register_methods()
{
register_method("_init", &Terminal::_init);
register_method("_ready", &Terminal::_ready);
register_method("_input", &Terminal::_input);
register_method("_draw", &Terminal::_draw);
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);
}
Terminal::Terminal()
{
}
Terminal::~Terminal()
{
}
void Terminal::_init()
{
sleep = true;
if (tsm_screen_new(&screen, NULL, NULL))
{
ERR_PRINT("Error creating new tsm screen");
}
tsm_screen_set_max_sb(screen, 1000); // TODO: Use config var for scrollback size.
if (tsm_vte_new(&vte, screen, write_cb, this, NULL, NULL))
{
ERR_PRINT("Error creating new tsm vte");
}
update_color_palette();
if (tsm_vte_set_custom_palette(vte, color_palette))
{
ERR_PRINT("Error setting custom palette");
}
if (tsm_vte_set_palette(vte, "custom"))
{
ERR_PRINT("Error setting palette");
}
update_size();
}
void Terminal::_ready()
{
connect("resized", this, "update_size");
}
void Terminal::_notification(int what)
{
switch (what)
{
case NOTIFICATION_RESIZED:
Godot::print("resized!");
update_size();
break;
}
}
void Terminal::_input(Variant event)
{
}
void Terminal::_draw()
{
if (sleep)
return;
/* Draw the background */
draw_rect(get_rect(), get_color("Background", "Terminal"));
/* Draw the cell backgrounds */
// Draw the background first so subsequent rows don't overlap
// foreground characters such as y that may extend below the baseline.
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
draw_background(row, col, get_cell_colors(row, col).first);
}
}
/* Draw the cell foregrounds */
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
draw_foreground(row, col, get_cell_colors(row, col).second);
}
}
}
void Terminal::update_color_palette()
{
// Start with a copy of the default color palette
memcpy(color_palette, Terminal::default_color_palette, sizeof(Terminal::default_color_palette));
/* Generate color palette based on theme */
// Converts a color from the Control's theme to one that can
// be used in a tsm color palette.
auto set_pallete_color = [this](tsm_vte_color color, String theme_color) -> void {
Color c = get_color(theme_color, "Terminal");
uint32_t argb32 = c.to_ARGB32();
color_palette[color][0] = (argb32 >> (8 * 0)) & 0xff;
color_palette[color][1] = (argb32 >> (8 * 1)) & 0xff;
color_palette[color][2] = (argb32 >> (8 * 2)) & 0xff;
};
set_pallete_color(TSM_COLOR_BLACK, "Black");
set_pallete_color(TSM_COLOR_RED, "Red");
set_pallete_color(TSM_COLOR_GREEN, "Green");
set_pallete_color(TSM_COLOR_YELLOW, "Yellow");
set_pallete_color(TSM_COLOR_BLUE, "Blue");
set_pallete_color(TSM_COLOR_MAGENTA, "Magenta");
set_pallete_color(TSM_COLOR_CYAN, "Cyan");
set_pallete_color(TSM_COLOR_LIGHT_GREY, "Light Grey");
set_pallete_color(TSM_COLOR_DARK_GREY, "Dark Grey");
set_pallete_color(TSM_COLOR_LIGHT_RED, "Light Red");
set_pallete_color(TSM_COLOR_LIGHT_GREEN, "Light Green");
set_pallete_color(TSM_COLOR_LIGHT_YELLOW, "Light Yellow");
set_pallete_color(TSM_COLOR_LIGHT_BLUE, "Light Blue");
set_pallete_color(TSM_COLOR_LIGHT_MAGENTA, "Light Magenta");
set_pallete_color(TSM_COLOR_WHITE, "White");
set_pallete_color(TSM_COLOR_BACKGROUND, "Background");
set_pallete_color(TSM_COLOR_FOREGROUND, "Foreground");
}
void Terminal::draw_background(int row, int col, Color bgcolor)
{
/* Draw the background */
Vector2 background_pos = Vector2(col * cell_size.x, row * cell_size.y);
Rect2 background_rect = Rect2(background_pos, cell_size);
draw_rect(background_rect, bgcolor);
}
void Terminal::draw_foreground(int row, int col, Color fgcolor)
{
struct cell cell = cells[row][col];
/* Set the font */
Ref<Font> fontref = get_font("");
if (cell.attr.bold && cell.attr.italic)
{
fontref = get_font("Bold Italic", "Terminal");
}
else if (cell.attr.bold)
{
fontref = get_font("Bold", "Terminal");
}
else if (cell.attr.italic)
{
fontref = get_font("Italic", "Terminal");
}
else
{
fontref = get_font("Regular", "Terminal");
}
/* Draw the foreground */
if (cell.ch == nullptr)
return; // No foreground to draw
if (cell.attr.blink)
; // TODO: Handle blink
int font_height = fontref.ptr()->get_height();
Vector2 foreground_pos = Vector2(col * cell_size.x, row * cell_size.y + font_height);
draw_string(fontref, foreground_pos, cell.ch, fgcolor);
if (cell.attr.underline)
draw_string(fontref, foreground_pos, "_", fgcolor);
}
std::pair<Color, Color> Terminal::get_cell_colors(int row, int col)
{
struct cell cell = cells[row][col];
Color fgcol, bgcol;
float fr = 1, fg = 1, fb = 1, br = 0, bg = 0, bb = 0;
Ref<Font> fontref = get_font("");
/* Get foreground color */
if (cell.attr.fccode && palette.count(cell.attr.fccode))
{
fgcol = palette[cell.attr.fccode];
}
else
{
fr = (float)cell.attr.fr / 255.0;
fg = (float)cell.attr.fg / 255.0;
fb = (float)cell.attr.fb / 255.0;
fgcol = Color(fr, fg, fb);
if (cell.attr.fccode)
{
palette.insert(std::pair<int, Color>(cell.attr.fccode, Color(fr, fg, fb)));
}
}
/* Get background color */
if (cell.attr.bccode && palette.count(cell.attr.bccode))
{
bgcol = palette[cell.attr.bccode];
}
else
{
br = (float)cell.attr.br / 255.0;
bg = (float)cell.attr.bg / 255.0;
bb = (float)cell.attr.bb / 255.0;
bgcol = Color(br, bg, bb);
if (cell.attr.bccode)
{
palette.insert(std::pair<int, Color>(cell.attr.bccode, Color(br, bg, bb)));
}
}
if (cell.attr.inverse)
std::swap(bgcol, fgcol);
return std::make_pair(bgcol, fgcol);
}
// Recalculates the cell_size and number of cols/rows based on font size and the Control's rect_size
void Terminal::update_size()
{
sleep = true;
cell_size = get_font("Regular", "Terminal").ptr()->get_string_size("W");
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)));
cells = {};
for (int x = 0; x < rows; x++)
{
Row row(cols);
for (int y = 0; y < cols; y++)
{
row[y] = empty_cell;
}
cells.push_back(row);
}
tsm_screen_resize(screen, cols, rows);
sleep = false;
update();
}
void Terminal::write(PoolByteArray data)
{
tsm_vte_input(vte, (char *)data.read().ptr(), data.size());
framebuffer_age = tsm_screen_draw(screen, text_draw_cb, this);
}

View file

@ -0,0 +1,73 @@
#ifndef TERMINAL_H
#define TERMINAL_H
#include <libtsm.h>
#include <Control.hpp>
#include <Font.hpp>
#include <Godot.hpp>
#include <map>
#include <vector>
namespace godot
{
class Terminal : public Control
{
GODOT_CLASS(Terminal, Control)
public:
struct cell
{
char ch[5];
struct tsm_screen_attr attr;
} empty_cell = {ch : {0, 0, 0, 0, 0}, attr : {}};
public:
typedef std::vector<std::vector<struct cell>> Cells;
typedef std::vector<struct cell> Row;
Cells cells;
protected:
tsm_screen *screen;
tsm_vte *vte;
private:
static const uint8_t default_color_palette[TSM_COLOR_NUM][3];
Vector2 cell_size;
std::map<int, Color> palette = {};
void update_size();
void update_color_palette();
std::pair<Color, Color> get_cell_colors(int row, int col);
void draw_background(int row, int col, Color bgcol);
void draw_foreground(int row, int col, Color fgcol);
public:
static void _register_methods();
Terminal();
~Terminal();
void _init();
void _ready();
void _notification(int what);
void _input(Variant event);
void _draw();
void write(PoolByteArray bytes);
int rows;
int cols;
bool sleep;
uint8_t color_palette[TSM_COLOR_NUM][3];
tsm_age_t framebuffer_age;
};
} // namespace godot
#endif // TERMINAL_H