fixes #5, touches on #8

* using `get_wch()` now and mapping most of the function keys documented
in `vterm_keycodes.h` (not num keypad)
* there are still a lot of mappings from ncurses missing (in conjunction
with modifier keys, related issue #11)
* using mutex to make sure writing to terminal client is serialized
This commit is contained in:
Christian Burger 2022-04-13 10:46:44 +02:00
parent 34cebb1019
commit ef07c3ac06
7 changed files with 217 additions and 21 deletions

25
.vscode/launch.json vendored
View file

@ -5,7 +5,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "(gdb) cursesPty starten", "name": "(gdb) start NCursesPtyApp",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/build/NCursesPtyApp", "program": "${workspaceFolder}/build/NCursesPtyApp",
@ -27,7 +27,26 @@
"ignoreFailures": true "ignoreFailures": true
} }
] ]
} },
{
"name": "(gdb) attach to running NCursesPtyApp",
"type": "cppdbg",
"request": "attach",
"program": "${workspaceFolder}/build/NCursesPtyApp",
"processId": "${command:pickProcess}",
"MIMode": "gdb",
"setupCommands": [
{
"description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Disassemblierungsvariante auf Intel festlegen",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
] ]
} }

View file

@ -14,7 +14,7 @@ include("cmake/gsl.cmake")
find_library(LIBVTERM_LIBRARY vterm) find_library(LIBVTERM_LIBRARY vterm)
find_library(UTIL_LIBRARY util) find_library(UTIL_LIBRARY util)
add_library(NCursesPtyWindow NCursesPtyWindow.cpp Debug.cpp) add_library(NCursesPtyWindow NCursesPtyWindow.cpp SingleUserInput.cpp Debug.cpp)
target_link_libraries(NCursesPtyWindow ${CURSES_LIBRARIES} ${LIBVTERM_LIBRARY} ${UTIL_LIBRARY}) target_link_libraries(NCursesPtyWindow ${CURSES_LIBRARIES} ${LIBVTERM_LIBRARY} ${UTIL_LIBRARY})
### threads ### threads

View file

@ -42,17 +42,18 @@ namespace krikkel::NCursesPtyWindow
exit(1); exit(1);
} }
int input;
while(true) while(true)
{ {
input = ptyWindow->getch(); SingleUserInput input = ptyWindow->readSingleUserInput();
if(input == KEY_RESIZE) if(input.isNormalKey())
ptyWindow->writeUnicodeCharToClient(input.getRawInput());
if(input.isFunctionKey())
{ {
ptyWindow->wresize(Root_Window->lines(), Root_Window->cols()); if(input.getRawInput() == KEY_RESIZE)
continue; ptyWindow->wresize(Root_Window->lines(), Root_Window->cols());
else
ptyWindow->writeKeyToClient(input.mapKeyNcursesToVTerm());
} }
ptyWindow->writeToClient((char *) &input, 1);
} }
return 0; return 0;

View file

@ -11,10 +11,12 @@
#include <gsl/gsl> #include <gsl/gsl>
#include <cctype> #include <cctype>
#include <termios.h> #include <termios.h>
#include <fcntl.h>
namespace krikkel::NCursesPtyWindow namespace krikkel::NCursesPtyWindow
{ {
using gsl::narrow; using gsl::narrow;
using std::lock_guard;
NCursesPtyWindow::NCursesPtyWindow(int lines, int columns, int y, int x) NCursesPtyWindow::NCursesPtyWindow(int lines, int columns, int y, int x)
: NCursesWindow(lines, columns, y, x) : NCursesWindow(lines, columns, y, x)
@ -38,13 +40,12 @@ namespace krikkel::NCursesPtyWindow
vterm_screen_reset(pseudoTerminalScreen, true); vterm_screen_reset(pseudoTerminalScreen, true);
vterm_screen_set_callbacks(pseudoTerminalScreen, &screenCallbacks, this); vterm_screen_set_callbacks(pseudoTerminalScreen, &screenCallbacks, this);
vterm_output_set_callback(pseudoTerminal, &staticHandlerOutput, this); vterm_output_set_callback(pseudoTerminal, &staticHandlerOutput, this);
//vterm_screen_enable_altscreen(pseudoTerminalScreen, true); vterm_screen_enable_altscreen(pseudoTerminalScreen, true);
// the terminal is doing most of the work
//raw(); //— cbreak might suffice //raw(); //— cbreak might suffice
//noecho(); — already set //noecho(); — already set
//nodelay(true); — @todo needs some reprogramming //nodelay(true); — @todo needs some reprogramming
keypad(false); keypad(true);
nonl(); nonl();
/// @todo block all signals, this thread does not handle any /// @todo block all signals, this thread does not handle any
@ -64,12 +65,31 @@ namespace krikkel::NCursesPtyWindow
return fdPtyClient; return fdPtyClient;
} }
void NCursesPtyWindow::writeToClient(const char *output, size_t length) const void NCursesPtyWindow::writeToClient(const char *output, size_t length)
{ {
write(fdPtyHost, output, length); lock_guard writeLock(writeToPseudoTerminalMutex);
write(fdPtyHost, output, length);
__debug_log("written to PTY client: '" + __debug_make_bytes_printable(std::string(output, length)) + '\''); __debug_log("written to PTY client: '" + __debug_make_bytes_printable(std::string(output, length)) + '\'');
} }
void NCursesPtyWindow::writeUnicodeCharToClient(wint_t character)
{
vterm_keyboard_unichar(pseudoTerminal, character, VTERM_MOD_NONE);
}
void NCursesPtyWindow::writeKeyToClient(VTermKey key)
{
__debug_log("writing key: " + std::to_string(key));
vterm_keyboard_key(pseudoTerminal, key, VTERM_MOD_NONE);
}
SingleUserInput NCursesPtyWindow::readSingleUserInput()
{
wint_t input;
int result = get_wch(&input);
return SingleUserInput(result, input);
}
void NCursesPtyWindow::readFromPtyClientThreadMethod() void NCursesPtyWindow::readFromPtyClientThreadMethod()
{ {
/// @todo in theory, there is no need for a timeout or select … /// @todo in theory, there is no need for a timeout or select …
@ -91,7 +111,10 @@ namespace krikkel::NCursesPtyWindow
{ {
size_t bytesRead = read(fdPtyHost, ptyClientOutputBuffer, PTY_CLIENT_OUTPUT_BUFFER_SIZE); size_t bytesRead = read(fdPtyHost, ptyClientOutputBuffer, PTY_CLIENT_OUTPUT_BUFFER_SIZE);
if(bytesRead != -1 && bytesRead != 0) if(bytesRead != -1 && bytesRead != 0)
{
lock_guard writeLock(writeToPseudoTerminalMutex);
vterm_input_write(pseudoTerminal, ptyClientOutputBuffer, bytesRead); vterm_input_write(pseudoTerminal, ptyClientOutputBuffer, bytesRead);
}
__debug_log("read from PTY client: '" + __debug_make_bytes_printable(std::string(ptyClientOutputBuffer, bytesRead)) + '\''); __debug_log("read from PTY client: '" + __debug_make_bytes_printable(std::string(ptyClientOutputBuffer, bytesRead)) + '\'');
} }
@ -233,6 +256,11 @@ namespace krikkel::NCursesPtyWindow
return ::wadd_wch(w, character); return ::wadd_wch(w, character);
} }
int NCursesPtyWindow::get_wch(wint_t *character)
{
return ::wget_wch(w, character);
}
int NCursesPtyWindow::refresh() int NCursesPtyWindow::refresh()
{ {
move(cursorY, cursorX); move(cursorY, cursorX);
@ -243,6 +271,7 @@ namespace krikkel::NCursesPtyWindow
/// resizing? /// resizing?
int NCursesPtyWindow::wresize(int rows, int cols) int NCursesPtyWindow::wresize(int rows, int cols)
{ {
std::lock_guard writeLock(writeToPseudoTerminalMutex);
winsize windowSize = winsize windowSize =
{ {
.ws_row = narrow<unsigned short>(rows) .ws_row = narrow<unsigned short>(rows)
@ -305,7 +334,7 @@ namespace krikkel::NCursesPtyWindow
void NCursesPtyWindow::staticHandlerOutput(const char *s, size_t len, void *user) void NCursesPtyWindow::staticHandlerOutput(const char *s, size_t len, void *user)
{ {
NCursesPtyWindow *instance = static_cast<NCursesPtyWindow *>(user); NCursesPtyWindow *instance = static_cast<NCursesPtyWindow *>(user);
for(size_t index = 0; index < len; ++index) __debug_log("output handler writing to client: '" + __debug_make_bytes_printable(std::string(s, len)) + "'");
instance->writeToClient(s, len); instance->writeToClient(s, len);
} }
} }

View file

@ -3,11 +3,14 @@
* @author Christian Burger (christian@krikkel.de) * @author Christian Burger (christian@krikkel.de)
*/ */
#include "SingleUserInput.hpp"
#include <cursesw.h> #include <cursesw.h>
#include <pty.h> #include <pty.h>
#include <vterm.h> #include <vterm.h>
#include <string> #include <string>
#include <thread> #include <thread>
#include <mutex>
#ifdef add_wch #ifdef add_wch
inline void UNDEF(add_wch)(const cchar_t *character) { add_wch(character); } inline void UNDEF(add_wch)(const cchar_t *character) { add_wch(character); }
@ -15,6 +18,12 @@ inline void UNDEF(add_wch)(const cchar_t *character) { add_wch(character); }
#define add_wch UNDEF(add_wch) #define add_wch UNDEF(add_wch)
#endif #endif
#ifdef get_wch
inline void UNDEF(get_wch)(wint_t *character) { get_wch(character); }
#undef get_wch
#define get_wch UNDEF(get_wch)
#endif
namespace krikkel::NCursesPtyWindow namespace krikkel::NCursesPtyWindow
{ {
class NCursesPtyWindow : public NCursesWindow class NCursesPtyWindow : public NCursesWindow
@ -24,9 +33,14 @@ namespace krikkel::NCursesPtyWindow
~NCursesPtyWindow(); ~NCursesPtyWindow();
int getFdPtyClient() const; int getFdPtyClient() const;
void writeToClient(const char * string, size_t length) const; void writeToClient(const char * string, size_t length);
void writeUnicodeCharToClient(wint_t character);
void writeKeyToClient(VTermKey key);
SingleUserInput readSingleUserInput();
int add_wch(const cchar_t *character); int add_wch(const cchar_t *character);
int get_wch(wint_t *character);
int refresh() override; int refresh() override;
int wresize(int rows, int cols); int wresize(int rows, int cols);
@ -34,10 +48,11 @@ namespace krikkel::NCursesPtyWindow
int fdPtyHost, fdPtyClient; int fdPtyHost, fdPtyClient;
struct termios terminalParameters; struct termios terminalParameters;
VTerm *pseudoTerminal; VTerm *pseudoTerminal;
std::mutex writeToPseudoTerminalMutex;
VTermScreen *pseudoTerminalScreen; VTermScreen *pseudoTerminalScreen;
static VTermScreenCallbacks screenCallbacks; static VTermScreenCallbacks screenCallbacks;
/// @todo one line is at most 4096 chars long /// @todo one line is at most 4096 chars long
static const size_t PTY_CLIENT_OUTPUT_BUFFER_SIZE = 8192; static const uint16_t PTY_CLIENT_OUTPUT_BUFFER_SIZE = 2 * 4096;
char ptyClientOutputBuffer[PTY_CLIENT_OUTPUT_BUFFER_SIZE]; char ptyClientOutputBuffer[PTY_CLIENT_OUTPUT_BUFFER_SIZE];
uint16_t cursorX, cursorY; uint16_t cursorX, cursorY;
@ -67,5 +82,5 @@ namespace krikkel::NCursesPtyWindow
static int staticHandlerPushLine(int cols, const VTermScreenCell *cells, void *user); static int staticHandlerPushLine(int cols, const VTermScreenCell *cells, void *user);
static int staticHandlerPopLine(int cols, VTermScreenCell *cells, void *user); static int staticHandlerPopLine(int cols, VTermScreenCell *cells, void *user);
static void staticHandlerOutput(const char *s, size_t len, void *user); static void staticHandlerOutput(const char *s, size_t len, void *user);
}; };
} }

106
SingleUserInput.cpp Normal file
View file

@ -0,0 +1,106 @@
/**
* @author Christian Burger (christian@krikkel.de)
*/
#include "SingleUserInput.hpp"
#include "Debug.cpp"
#include <ncurses.h>
#include <vterm.h>
#define KEY_TAB '\t'
#define KEY_ESCAPE 27
#define KEY_RETURN 10
#define KEY_ALT_BACKSPACE 127
#define KEY_ALT_ALT_BACKSPACE '\b'
namespace krikkel::NCursesPtyWindow
{
SingleUserInput::SingleUserInput(int _resultGetWchCall, wint_t _input)
: input(_input), resultGetWchCall(_resultGetWchCall)
{
}
bool SingleUserInput::isNormalKey()
{
return resultGetWchCall == OK;
}
bool SingleUserInput::isFunctionKey()
{
return resultGetWchCall == KEY_CODE_YES;
}
VTermKey SingleUserInput::mapKeyNcursesToVTerm()
{
if(input >= KEY_MAX)
return VTERM_KEY_NONE;
debug();
// thought about array mapping instead of `switch()` statements, but as
// it is only processing user input, it is not time critical enough for
// the wasted space
// @tode unmapped keys: keys erase
if(resultGetWchCall == OK)
switch(input)
{
case KEY_TAB:
return VTERM_KEY_TAB;
case KEY_ESCAPE:
return VTERM_KEY_ESCAPE;
default:
; // we cannot translate
}
if(resultGetWchCall == KEY_CODE_YES)
switch(input)
{
case KEY_ENTER:
return VTERM_KEY_ENTER;
case KEY_BACKSPACE:
return VTERM_KEY_BACKSPACE;
case KEY_UP:
return VTERM_KEY_UP;
case KEY_DOWN:
return VTERM_KEY_DOWN;
case KEY_LEFT:
return VTERM_KEY_LEFT;
case KEY_RIGHT:
return VTERM_KEY_RIGHT;
case KEY_IC:
return VTERM_KEY_INS;
case KEY_DC:
return VTERM_KEY_DEL;
case KEY_HOME:
return VTERM_KEY_HOME;
case KEY_END:
return VTERM_KEY_END;
case KEY_PPAGE:
return VTERM_KEY_PAGEUP;
case KEY_NPAGE:
return VTERM_KEY_PAGEDOWN;
default:
if(input >= KEY_F0 && input < KEY_F0 + 12)
return static_cast<VTermKey>(VTERM_KEY_FUNCTION(input - KEY_F0));
}
__debug_log("previous ncurses input could not be decoded to vterm input");
return VTERM_KEY_NONE;
}
wint_t SingleUserInput::getRawInput()
{
return input;
}
void SingleUserInput::debug()
{
#ifndef NDEGUG
char octalRepresentation[16];
snprintf(octalRepresentation, 16, "%o", input);
__debug_log("mapping ncurses key: " + std::to_string(input) + " octal: " + octalRepresentation);
#endif // NDEBUG
}
}

26
SingleUserInput.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef F0E30ED4_3883_40D6_A6EE_08BA4DF9E92E
#define F0E30ED4_3883_40D6_A6EE_08BA4DF9E92E
#include <cursesw.h>
#include <vterm_keycodes.h>
namespace krikkel::NCursesPtyWindow
{
class SingleUserInput
{
public:
SingleUserInput(int resultGetWchCall, wint_t input);
bool isNormalKey();
bool isFunctionKey();
VTermKey mapKeyNcursesToVTerm();
wint_t getRawInput();
private:
wint_t input;
int resultGetWchCall;
void debug();
};
}
#endif /* F0E30ED4_3883_40D6_A6EE_08BA4DF9E92E */