kNCurses/PtyWindow.cpp

367 lines
12 KiB
C++

/**
* @author Christian Burger (christian@krikkel.de)
*/
#include "kNCurses/PtyWindow.hpp"
#include "Debug.hpp"
#include <cstdio>
#include <unistd.h>
#include <sys/select.h>
#include <gsl/gsl>
#include <cctype>
#include <termios.h>
#include <fcntl.h>
#include <exception>
#include <cerrno>
namespace krikkel::NCurses
{
using gsl::narrow;
using std::scoped_lock;
using std::recursive_mutex;
using std::system_error;
using std::system_category;
using std::string;
PtyWindow::PtyWindow(recursive_mutex *ncursesMutex, int lines, int columns, int y, int x)
: Window(lines, columns, y, x), ncursesMutex(ncursesMutex)
{
// to get the original terminal we need to shutdown ncurses for a moment
/// @todo maybe try `reset_prog_mode()` and `reset_shell_mode()` instead
::endwin();
tcgetattr(STDIN_FILENO, &terminalParameters);
::refresh();
winsize windowSize =
{
/*.ws_row =*/ narrow<short unsigned int>(this->height())
, /*.ws_col =*/ narrow<short unsigned int>(this->width())
, /*.y =*/ 0
, /*.x =*/ 0
};
openpty(&fdPtyHost, &fdPtyClient, NULL, &terminalParameters, &windowSize);
pseudoTerminal = vterm_new(windowSize.ws_row, windowSize.ws_col);
vterm_set_utf8(pseudoTerminal, true);
pseudoTerminalScreen = vterm_obtain_screen(pseudoTerminal);
vterm_screen_reset(pseudoTerminalScreen, true);
vterm_screen_set_callbacks(pseudoTerminalScreen, &screenCallbacks, this);
vterm_output_set_callback(pseudoTerminal, &staticHandlerOutput, this);
vterm_screen_enable_altscreen(pseudoTerminalScreen, true);
//raw(); // — cbreak might suffice
//noecho(); // — already set by NCursesWindow
keypad(true); /// — already set by NCursesWindow
//meta(true); // — already set by NCursesWindow
//nodelay(true); // — @todo would need some programming, not sure if useful
nonl();
/// @todo block all signals, this thread does not handle any
readPtyClientThread =
std::thread(&PtyWindow::readFromPtyClientThreadMethod, this);
}
PtyWindow::~PtyWindow()
{
close(fdPtyHost);
close(fdPtyClient);
vterm_free(pseudoTerminal);
}
int PtyWindow::getFdPtyClient() const
{
return fdPtyClient;
}
void PtyWindow::writeToPtyClient(const char *output, size_t length)
{
size_t bytesWritten = write(fdPtyHost, output, length);
/// @todo this is a bit too simple; partial write is possible
if(bytesWritten != length)
{
string basicError("Could not write to PTY client (i. e. the shell).");
throw system_error(errno, system_category(), basicError);
}
//__debug_log("written to PTY client: '" + __debug_make_bytes_printable(std::string(output, length)) + '\'');
}
void PtyWindow::writeUnicodeCharToClient(wint_t character)
{
scoped_lock lock(ptyMutex);
vterm_keyboard_unichar(pseudoTerminal, character, VTERM_MOD_NONE);
}
void PtyWindow::writeFunctionKeyToClient(VTermKey key)
{
scoped_lock lock(ptyMutex);
//__debug_log("writing key: " + std::to_string(key));
vterm_keyboard_key(pseudoTerminal, key, VTERM_MOD_NONE);
}
void PtyWindow::readFromPtyClientThreadMethod()
{
/// @todo in theory, there is no need for a timeout or select …
/// file descriptor is blocking
while(1)
{
readFromPtyClient();
struct timeval timeout = { /*.tv_sec =*/ 0, /*.tv_usec =*/ 200000 };
fd_set readFds;
FD_ZERO(&readFds);
FD_SET(fdPtyHost, &readFds);
if(select(fdPtyHost + 1, &readFds, NULL, NULL, &timeout) < 0)
break;
}
}
void PtyWindow::readFromPtyClient()
{
size_t bytesRead = read(fdPtyHost, ptyClientOutputBuffer, PTY_CLIENT_OUTPUT_BUFFER_SIZE);
if(bytesRead != (size_t) -1 && bytesRead != 0)
{
scoped_lock lock(ptyMutex, *ncursesMutex);
vterm_input_write(pseudoTerminal, ptyClientOutputBuffer, bytesRead);
}
//__debug_log("read from PTY client: '" + __debug_make_bytes_printable(std::string(ptyClientOutputBuffer, bytesRead)) + '\'');
}
VTermScreenCallbacks PtyWindow::screenCallbacks =
{
/*.damage =*/ staticHandlerDamage
, /*.moverect =*/ staticHandlerMoveRect
, /*.movecursor =*/ staticHandlerMoveCursor
, /*.settermprop =*/ staticHandlerSetTermProp
, /*.bell =*/ staticHandlerBell
, /*.resize =*/ staticHandlerResize
, /*.sb_pushline =*/ staticHandlerPushLine
, /*.sb_popline =*/ staticHandlerPopLine
};
int PtyWindow::handlerDamage(VTermRect rect)
{
scoped_lock lock(ptyMutex, *ncursesMutex);
for(int x = rect.start_col; x < rect.end_col; ++x)
for(int y = rect.start_row; y < rect.end_row; ++y)
copyPtyCellToNCursesWindow(x, y);
refresh();
return 0;
}
int PtyWindow::handlerMoveRect(VTermRect dest, VTermRect src)
{
(void) dest;
(void) src;
__debug_log("(unimplemented) move content in rectangle from ("
+ std::to_string(src.start_col) + ", "
+ std::to_string(src.start_row) + ", "
+ std::to_string(src.end_col) + ", "
+ std::to_string(src.end_row) + ") "
+ "size: " + std::to_string((src.start_col - src.end_col) * (src.start_row - src.end_row))
+ " to ("
+ std::to_string(dest.start_col) + ", "
+ std::to_string(dest.start_row) + ", "
+ std::to_string(dest.end_col) + ", "
+ std::to_string(dest.end_row) + ") "
+ "size: " + std::to_string((dest.start_col - dest.end_col) * (dest.start_row - dest.end_row)));
return 0;
}
int PtyWindow::handlerMoveCursor(VTermPos pos, VTermPos oldpos, int visible)
{
(void) oldpos;
(void) visible;
scoped_lock lock(*ncursesMutex);
cursorX = pos.col;
cursorY = pos.row;
refresh();
return 0;
}
int PtyWindow::handlerSetTermProp(VTermProp prop, VTermValue *val)
{
(void) prop;
(void) val;
/// @todo maybe use "vterm_get_prop_type() —> bool, number, string"?
__debug_stringify_switch_begin(handlerSetTermProp, prop, val);
__debug_stringify_switch_case(VTERM_PROP_CURSORVISIBLE);
__debug_stringify_switch_case(VTERM_PROP_CURSORBLINK);
__debug_stringify_switch_case(VTERM_PROP_ALTSCREEN);
__debug_stringify_switch_last_case_bool(VTERM_PROP_REVERSE, val->boolean);
__debug_stringify_switch_case(VTERM_PROP_TITLE);
__debug_stringify_switch_last_case_string(VTERM_PROP_ICONNAME, val->string);
__debug_stringify_switch_case(VTERM_PROP_CURSORSHAPE);
__debug_stringify_switch_last_case_number(VTERM_PROP_MOUSE, val->number);
__debug_stringify_switch_end(prop);
__debug_log(std::string("unimplemented handler called: ")
+ __debug_stringify_get_string(handlerSetTermProp)
);
return 0;
}
int PtyWindow::handlerBell()
{
beep();
return 0;
}
int PtyWindow::handlerResize(int rows, int cols)
{
(void) rows;
(void) cols;
__debug_log("unimplemented handler called");
return 0;
}
int PtyWindow::handlerPushLine(int cols, const VTermScreenCell *cells)
{
(void) cols;
(void) cells;
__debug_log("(unimplemented) push line with " + std::to_string(cols) + " columns");
return 0;
}
int PtyWindow::handlerPopLine(int cols, VTermScreenCell *cells)
{
(void) cols;
(void) cells;
__debug_log("unimplemented handler called");
return 0;
}
attr_t PtyWindow::extractAttributesFromVTermCell(VTermScreenCell vTermCell)
{
(void) vTermCell;
attr_t result = A_NORMAL;
//__debug_log("unimplemented method called");
return result;
}
short PtyWindow::extractColorFromVTermCell(VTermScreenCell vTermCell)
{
(void) vTermCell;
//__debug_log("unimplemented method called");
return 0;
}
void PtyWindow::copyPtyCellToNCursesWindow(int x, int y)
{
VTermPos cellPosition = {y, x};
VTermScreenCell vTermCell;
cchar_t converted;
attr_t formatting;
short colorPair;
wchar_t character;
vterm_screen_get_cell(pseudoTerminalScreen, cellPosition, &vTermCell);
formatting = extractAttributesFromVTermCell(vTermCell);
colorPair = extractColorFromVTermCell(vTermCell);
if(vTermCell.chars[0] == 0)
character = ' ';
else
character = *vTermCell.chars;
//__debug_log(std::string("written '") + (char) character + std::string("' ") + std::to_string(x) + ", " + std::to_string(y));
setcchar(&converted, &character, formatting, colorPair, NULL);
{
move(cellPosition.row, cellPosition.col);
chgat(1, formatting, colorPair, NULL);
add_wch(&converted);
}
}
int PtyWindow::refresh()
{
//__debug_log("refreshing");
scoped_lock lock(*ncursesMutex);
move(cursorY, cursorX);
return Window::refresh();
}
/// @todo potential racing condition where drawing into terminal while
/// resizing?
int PtyWindow::resize(int rows, int cols)
{
scoped_lock lock(ptyMutex, *ncursesMutex);
winsize windowSize =
{
/*.ws_row =*/ narrow<unsigned short>(rows)
, /*.ws_col =*/ narrow<unsigned short>(cols)
, 0
, 0
};
ioctl(fdPtyHost, TIOCSWINSZ, &windowSize);
vterm_set_size(pseudoTerminal, rows, cols);
return Window::resize(rows, cols);
}
int PtyWindow::staticHandlerDamage(VTermRect rect, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerDamage(rect);
}
int PtyWindow::staticHandlerMoveRect(VTermRect dest, VTermRect src, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerMoveRect(dest, src);
}
int PtyWindow::staticHandlerMoveCursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerMoveCursor(pos, oldpos, visible);
}
int PtyWindow::staticHandlerSetTermProp(VTermProp prop, VTermValue *val, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerSetTermProp(prop, val);
}
int PtyWindow::staticHandlerBell(void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerBell();
}
int PtyWindow::staticHandlerResize(int rows, int cols, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerResize(rows, cols);
}
int PtyWindow::staticHandlerPushLine(int cols, const VTermScreenCell *cells, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerPushLine(cols, cells);
}
int PtyWindow::staticHandlerPopLine(int cols, VTermScreenCell *cells, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
return instance->handlerPopLine(cols, cells);
}
void PtyWindow::staticHandlerOutput(const char *s, size_t len, void *user)
{
PtyWindow *instance = static_cast<PtyWindow *>(user);
instance->writeToPtyClient(s, len);
}
}