/** * @author Christian Burger (christian@krikkel.de) */ #include "NCursesPtyWindow.hpp" #include "Debug.hpp" #include #include #include #include #include #include namespace krikkel::NCursesPty { using gsl::narrow; NCursesPtyWindow::NCursesPtyWindow(int lines, int columns, int y, int x) : NCursesWindow(lines, columns, y, x) { // to get the original terminal we need to shutdown ncurses for a moment ::endwin(); tcgetattr(STDIN_FILENO, &terminalParameters); ::refresh(); winsize windowSize = { .ws_row = narrow(this->height()) , .ws_col = narrow(this->width()) }; openpty(&fdPtyHost, &fdPtyClient, NULL, &terminalParameters, &windowSize); pseudoTerminal = vterm_new(windowSize.ws_row, windowSize.ws_col); __debug_log("window size (x: " + std::to_string(windowSize.ws_col) + ", y: " + std::to_string(windowSize.ws_row) + ")"); vterm_set_utf8(pseudoTerminal, true); pseudoTerminalScreen = vterm_obtain_screen(pseudoTerminal); vterm_screen_reset(pseudoTerminalScreen, true); vterm_screen_set_callbacks(pseudoTerminalScreen, &screenCallbacks, this); // the terminal is doing most of the work keypad(false); /// @todo block all signals, this thread does not handle any readPtyClientThread = std::thread(&NCursesPtyWindow::readFromPtyClientThreadMethod, this); } NCursesPtyWindow::~NCursesPtyWindow() { close(fdPtyHost); close(fdPtyClient); vterm_free(pseudoTerminal); } int NCursesPtyWindow::getFdPtyClient() const { return fdPtyClient; } /// @todo maybe implement a function with a string buffer void NCursesPtyWindow::writeToClient(char character) { //__debug_log(std::string("written '") + character + "' to client"); write(fdPtyHost, &character, sizeof(character)); } void NCursesPtyWindow::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 NCursesPtyWindow::readFromPtyClient() { size_t bytesRead = read(fdPtyHost, clientOutputBuffer, CLIENT_OUTPUT_BUFFER_SIZE); if(bytesRead) vterm_input_write(pseudoTerminal, clientOutputBuffer, bytesRead); __debug_log("read " + std::to_string(bytesRead) + " bytes from PTY client"); /*string readable; for(char character : clientOutputBuffer) if(std::isprint((unsigned char) character)) readable += character; else readable += "\\" + std::to_string((int) character) + "/"; __debug_log("read: '" + readable + "'");*/ } VTermScreenCallbacks NCursesPtyWindow::screenCallbacks = { .damage = staticHandlerDamage, /* .moverect = staticHandlerMoveRect, .movecursor = staticHandlerMoveCursor, .settermprop = staticHandlerSetTermProp, .bell = staticHandlerBell, .resize = staticHandlerResize, .sb_pushline = staticHandlerPushLine, .sb_popline = staticHandlerPopLine,*/ }; int NCursesPtyWindow::handlerDamage(VTermRect rect) { __debug_log("damage to rectangle (" + std::to_string(rect.start_col) + ", " + std::to_string(rect.start_row) + ", " + std::to_string(rect.end_col) + ", " + std::to_string(rect.end_row) + ") " + "size: " + std::to_string((rect.start_col - rect.end_col) * (rect.start_row - rect.end_row)) ); int cursorX, cursorY; getyx(cursorY, cursorX); 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); move(cursorY, cursorX); return 0; } int NCursesPtyWindow::handlerMoveRect(VTermRect dest, VTermRect 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 NCursesPtyWindow::handlerMoveCursor(VTermPos pos, VTermPos oldpos, int visible) { __debug_log("cursor moved to (" + std::to_string(pos.col) + ", " + std::to_string(pos.row) + ") " + "visible: " + (visible ? "true" : "false")); move(pos.row, pos.col); return 0; } int NCursesPtyWindow::handlerSetTermProp(VTermProp prop, VTermValue *val) { __debug_log(std::string("(unimplemented) set terminal property: ") + (prop == VTERM_PROP_CURSORVISIBLE ? std::string("cursor visible = ") + (val->boolean ? "true" : "false") : "") + (prop == VTERM_PROP_CURSORBLINK ? std::string("cursor blink = ") + (val->boolean ? "true" : "false") : "") + (prop == VTERM_PROP_ALTSCREEN ? std::string("alt screen = ") + (val->boolean ? "true" : "false") : "") + (prop == VTERM_PROP_TITLE ? std::string("title = ") + val->string : "") + (prop == VTERM_PROP_ICONNAME ? std::string("icon name = ") + val->string : "") + (prop == VTERM_PROP_REVERSE ? std::string("alt screen = ") + (val->boolean ? "true" : "false") : "") + (prop == VTERM_PROP_CURSORSHAPE ? std::string("icon name = ") + std::to_string(val->number) : "") + (prop == VTERM_PROP_MOUSE ? std::string("mouse = ") + std::to_string(val->number) : "") + (prop == VTERM_N_PROPS ? "n props" : "") ); return 0; } int NCursesPtyWindow::handlerBell() { beep(); return 0; } int NCursesPtyWindow::handlerResize(int rows, int cols) { __debug_log("unimplemented handler called"); return 0; } int NCursesPtyWindow::handlerPushLine(int cols, const VTermScreenCell *cells) { __debug_log("(unimplemented) push line with " + std::to_string(cols) + " columns"); return 0; } int NCursesPtyWindow::handlerPopLine(int cols, VTermScreenCell *cells) { __debug_log("unimplemented handler called"); return 0; } void NCursesPtyWindow::copyPtyCellToNCursesWindow(int x, int y) { VTermPos cellPosition = {y, x}; VTermScreenCell cell; vterm_screen_get_cell(pseudoTerminalScreen, cellPosition, &cell); move(y, x); addch(cell.chars[0] ? cell.chars[0] : ' ' ); /// @todo boy, is this expensive … refresh(); } int NCursesPtyWindow::staticHandlerDamage(VTermRect rect, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerDamage(rect); } int NCursesPtyWindow::staticHandlerMoveRect(VTermRect dest, VTermRect src, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerMoveRect(dest, src); } int NCursesPtyWindow::staticHandlerMoveCursor(VTermPos pos, VTermPos oldpos, int visible, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerMoveCursor(pos, oldpos, visible); } int NCursesPtyWindow::staticHandlerSetTermProp(VTermProp prop, VTermValue *val, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerSetTermProp(prop, val); } int NCursesPtyWindow::staticHandlerBell(void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerBell(); } int NCursesPtyWindow::staticHandlerResize(int rows, int cols, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerResize(rows, cols); } int NCursesPtyWindow::staticHandlerPushLine(int cols, const VTermScreenCell *cells, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerPushLine(cols, cells); } int NCursesPtyWindow::staticHandlerPopLine(int cols, VTermScreenCell *cells, void *user) { NCursesPtyWindow *instance = static_cast(user); return instance->handlerPopLine(cols, cells); } }