mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-04 12:14:24 +02:00
parent
a022104230
commit
ee6d7cb0fa
33 changed files with 89 additions and 50 deletions
118
addons/godot_xterm/native/SConstruct
Normal file
118
addons/godot_xterm/native/SConstruct
Normal file
|
@ -0,0 +1,118 @@
|
|||
#!python
|
||||
import os, subprocess
|
||||
|
||||
opts = Variables([], ARGUMENTS)
|
||||
|
||||
# Gets the standard flags CC, CCX, etc.
|
||||
env = DefaultEnvironment()
|
||||
|
||||
# Add PATH to environment so scons can find commands such as g++, etc.
|
||||
env.AppendENVPath('PATH', os.getenv('PATH'))
|
||||
|
||||
# Define our options
|
||||
opts.Add(EnumVariable('target', "Compilation target", 'debug', ['d', 'debug', 'r', 'release']))
|
||||
opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'x11', 'linux', 'osx']))
|
||||
opts.Add(EnumVariable('p', "Compilation target, alias for 'platform'", '', ['', 'windows', 'x11', 'linux', 'osx']))
|
||||
opts.Add(BoolVariable('use_llvm', "Use the LLVM / Clang compiler", 'no'))
|
||||
opts.Add(PathVariable('target_path', 'The path where the lib is installed.', 'bin/'))
|
||||
opts.Add(PathVariable('target_name', 'The library name.', 'libgodotxtermnative', PathVariable.PathAccept))
|
||||
|
||||
# Local dependency paths, adapt them to your setup
|
||||
godot_headers_path = "modules/godot-cpp/godot_headers/"
|
||||
cpp_bindings_path = "modules/godot-cpp/"
|
||||
cpp_library = "libgodot-cpp"
|
||||
libtsm_path = "modules/libtsm/"
|
||||
|
||||
# only support 64 at this time..
|
||||
bits = 64
|
||||
|
||||
# Updates the environment with the option variables.
|
||||
opts.Update(env)
|
||||
|
||||
# Process some arguments
|
||||
if env['use_llvm']:
|
||||
env['CC'] = 'clang'
|
||||
env['CXX'] = 'clang++'
|
||||
else:
|
||||
env['CC'] = 'gcc'
|
||||
env['CXX'] = 'g++'
|
||||
|
||||
if env['p'] != '':
|
||||
env['platform'] = env['p']
|
||||
|
||||
if env['platform'] == '':
|
||||
print("No valid target platform selected.")
|
||||
quit();
|
||||
|
||||
# For the reference:
|
||||
# - CCFLAGS are compilation flags shared between C and C++
|
||||
# - CFLAGS are for C-specific compilation flags
|
||||
# - CXXFLAGS are for C++-specific compilation flags
|
||||
# - CPPFLAGS are for pre-processor flags
|
||||
# - CPPDEFINES are for pre-processor defines
|
||||
# - LINKFLAGS are for linking flags
|
||||
|
||||
# Check our platform specifics
|
||||
if env['platform'] == "osx":
|
||||
env['target_path'] += 'osx/'
|
||||
cpp_library += '.osx'
|
||||
env.Append(CCFLAGS=['-arch', 'x86_64'])
|
||||
env.Append(CXXFLAGS=['-std=c++17'])
|
||||
env.Append(LINKFLAGS=['-arch', 'x86_64'])
|
||||
if env['target'] in ('debug', 'd'):
|
||||
env.Append(CCFLAGS=['-g', '-O2'])
|
||||
else:
|
||||
env.Append(CCFLAGS=['-g', '-O3'])
|
||||
|
||||
elif env['platform'] in ('x11', 'linux'):
|
||||
env['target_path'] += 'x11/'
|
||||
cpp_library += '.linux'
|
||||
env.Append(CCFLAGS=['-fPIC', '-shared'])
|
||||
env.Append(CXXFLAGS=['-std=c++17', '-shared', '-pthread'])
|
||||
if env['target'] in ('debug', 'd'):
|
||||
env.Append(CCFLAGS=['-g3', '-Og'])
|
||||
else:
|
||||
env.Append(CCFLAGS=['-g', '-O3'])
|
||||
|
||||
elif env['platform'] == "windows":
|
||||
env['target_path'] += 'win64/'
|
||||
cpp_library += '.windows'
|
||||
# This makes sure to keep the session environment variables on windows,
|
||||
# that way you can run scons in a vs 2017 prompt and it will find all the required tools
|
||||
env.Append(ENV=os.environ)
|
||||
|
||||
env.Append(CPPDEFINES=['WIN32', '_WIN32', '_WINDOWS', '_CRT_SECURE_NO_WARNINGS'])
|
||||
env.Append(CCFLAGS=['-W3', '-GR'])
|
||||
if env['target'] in ('debug', 'd'):
|
||||
env.Append(CPPDEFINES=['_DEBUG'])
|
||||
env.Append(CCFLAGS=['-EHsc', '-MDd', '-ZI'])
|
||||
env.Append(LINKFLAGS=['-DEBUG'])
|
||||
else:
|
||||
env.Append(CPPDEFINES=['NDEBUG'])
|
||||
env.Append(CCFLAGS=['-O2', '-EHsc', '-MD'])
|
||||
|
||||
if env['target'] in ('debug', 'd'):
|
||||
cpp_library += '.debug'
|
||||
else:
|
||||
cpp_library += '.release'
|
||||
|
||||
cpp_library += '.' + str(bits)
|
||||
|
||||
# make sure our binding library is properly includes
|
||||
env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/', libtsm_path + 'src/tsm'])
|
||||
env.Append(LIBPATH=[cpp_bindings_path + 'bin/', libtsm_path + 'build/src/tsm'])
|
||||
env.Append(LIBS=[cpp_library, 'tsm', 'util']) # Note util used by pseudoterminal, tsm used by terminal.
|
||||
|
||||
# tweak this if you want to use different folders, or more folders, to store your source code in.
|
||||
env.Append(CPPPATH=['src/'])
|
||||
sources = []
|
||||
sources.append(Glob('src/*.c'))
|
||||
sources.append(Glob('src/*.cpp'))
|
||||
|
||||
|
||||
library = env.SharedLibrary(target=env['target_path'] + env['target_name'] , source=sources)
|
||||
|
||||
Default(library)
|
||||
|
||||
# Generates help for the -h scons option.
|
||||
Help(opts.GenerateHelpText(env))
|
2
addons/godot_xterm/native/bin/.gitignore
vendored
Normal file
2
addons/godot_xterm/native/bin/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
28
addons/godot_xterm/native/build.sh
Executable file
28
addons/godot_xterm/native/build.sh
Executable file
|
@ -0,0 +1,28 @@
|
|||
#! /usr/bin/env nix-shell
|
||||
#! nix-shell -i bash --pure -p binutils.bintools cmake scons
|
||||
set -e
|
||||
|
||||
# Make sure we are in the addons/godot_xterm directory
|
||||
cd ${BASH_SOURCE%/*}
|
||||
|
||||
# Initialize godot-cpp
|
||||
if [ ! -d "modules/godot-cpp/bin" ]
|
||||
then
|
||||
cd modules/godot-cpp
|
||||
scons platform=linux generate_bindings=yes -j12
|
||||
cd ../..
|
||||
fi
|
||||
|
||||
# Build libtsm
|
||||
if [ ! -f "modules/libtsm/build/src/tsm/libtsm.a" ]
|
||||
then
|
||||
cd modules/libtsm
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake -DBUILD_SHARED_LIBS=n ..
|
||||
make
|
||||
cd ../../..
|
||||
fi
|
||||
|
||||
# Build godotxtermnative
|
||||
scons platform=linux
|
16
addons/godot_xterm/native/godotxtermnative.gdnlib
Normal file
16
addons/godot_xterm/native/godotxtermnative.gdnlib
Normal file
|
@ -0,0 +1,16 @@
|
|||
[general]
|
||||
|
||||
singleton=false
|
||||
load_once=true
|
||||
symbol_prefix="godot_"
|
||||
reloadable=true
|
||||
|
||||
[entry]
|
||||
|
||||
X11.64="res://addons/godot_xterm/native/bin/x11/libgodotxtermnative.so"
|
||||
Server.64="res://addons/godot_xterm/native/bin/x11/libgodotxtermnative.so"
|
||||
|
||||
[dependencies]
|
||||
|
||||
X11.64=[ ]
|
||||
Server.64=[ ]
|
0
addons/godot_xterm/native/modules/.gdignore
Normal file
0
addons/godot_xterm/native/modules/.gdignore
Normal file
1
addons/godot_xterm/native/modules/godot-cpp
Submodule
1
addons/godot_xterm/native/modules/godot-cpp
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 20d57e6cf5d0353de0905f4edd3697b6de32bc74
|
1
addons/godot_xterm/native/modules/libtsm
Submodule
1
addons/godot_xterm/native/modules/libtsm
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f70e37982f382b03c6939dac3d5f814450bda253
|
21
addons/godot_xterm/native/src/libgodotxtermnative.cpp
Normal file
21
addons/godot_xterm/native/src/libgodotxtermnative.cpp
Normal 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>();
|
||||
}
|
156
addons/godot_xterm/native/src/pseudoterminal.cpp
Normal file
156
addons/godot_xterm/native/src/pseudoterminal.cpp
Normal 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);
|
||||
}
|
47
addons/godot_xterm/native/src/pseudoterminal.h
Normal file
47
addons/godot_xterm/native/src/pseudoterminal.h
Normal 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
|
362
addons/godot_xterm/native/src/terminal.cpp
Normal file
362
addons/godot_xterm/native/src/terminal.cpp
Normal 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);
|
||||
}
|
73
addons/godot_xterm/native/src/terminal.h
Normal file
73
addons/godot_xterm/native/src/terminal.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue