From c0ef2d6280acbe6b8abfec279ff5e4af2e39e4a5 Mon Sep 17 00:00:00 2001 From: ttibsi Date: Sun, 7 Jun 2026 13:12:19 +0100 Subject: [PATCH 1/5] attempt to handle character count in read --- rawterm/core.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rawterm/core.cpp b/rawterm/core.cpp index 3745522..e3c906c 100644 --- a/rawterm/core.cpp +++ b/rawterm/core.cpp @@ -142,12 +142,13 @@ namespace rawterm { } [[nodiscard]] const std::optional process_keypress() { - std::string characters = std::string(32, '\0'); + std::string characters = std::string(); int pollResult = poll(&detail::fd, 1, 0); // input available if (pollResult > 0) { - if (read(STDIN_FILENO, characters.data(), 32) < 0) { + const int count = read(STDIN_FILENO, characters.data(), 0); + if (count < 0) { throw rawterm::KeypressError("An error occured during reading user input"); } From 6c38e3ca18fd07f05f4b5aa4f7cd1ab414120576 Mon Sep 17 00:00:00 2001 From: ttibsi Date: Mon, 8 Jun 2026 08:31:01 +0100 Subject: [PATCH 2/5] temp testing --- examples/keys.cpp | 36 +++++++++++++++++++------------- rawterm/core.cpp | 52 +++++++++++++++++++++++++++++++---------------- rawterm/core.h | 2 +- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/examples/keys.cpp b/examples/keys.cpp index dd60816..9c97807 100644 --- a/examples/keys.cpp +++ b/examples/keys.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include // This is a demonstration on how `rawterm` works. Any keypress you enter // will show it's output as a Key object in the terminal. Press `q` to quit. @@ -21,20 +23,26 @@ int main() { std::cout << rawterm::set_header(header) << std::flush; - while (true) { - auto k = rawterm::wait_for_input(); - if (k == rawterm::Key('q')) { - break; - } else { - std::string mods = "["; - while (!(k.mod.empty())) - mods += " " + rawterm::to_string(k.getMod()); - mods += " ]"; - - std::cout << "Key{ code: " << k.code << ", mods: " << mods << ", raw: " << k.raw - << "}\r\n"; - } - } + // while (true) { + auto k = rawterm::wait_for_input(); + // if (k == rawterm::Key('q')) { + // break; + // } else { + std::string mods = "["; + while (!(k.mod.empty())) + mods += " " + rawterm::to_string(k.getMod()); + mods += " ]"; + + std::cout << "Key{ code: " << k.code << ", mods: " << mods << ", raw: " << k.raw << "}\r\n"; + + using namespace std::this_thread; // sleep_for, sleep_until + using namespace std::chrono_literals; // ns, us, ms, s, h, etc. + using std::chrono::system_clock; + + sleep_for(10ns); + sleep_until(system_clock::now() + 1s); + // } + // } // Optional explicit call to exit_alt_screen to return to standard screen. // This will happen even without calling this function explicitly diff --git a/rawterm/core.cpp b/rawterm/core.cpp index e3c906c..3754cc7 100644 --- a/rawterm/core.cpp +++ b/rawterm/core.cpp @@ -141,29 +141,47 @@ namespace rawterm { } } - [[nodiscard]] const std::optional process_keypress() { - std::string characters = std::string(); - int pollResult = poll(&detail::fd, 1, 0); + [[nodiscard]] std::string read_input() { + std::vector buffer; + buffer.reserve(1024); + const int pollResult = poll(&detail::fd, 1, 0); + + if (pollResult < 0) { + if (errno == EINTR) return {}; // Interrupted by signal + throw rawterm::KeypressError( + std::format("A poll error occured: {} - {}", errno, std::strerror(errno))); + } + + // no input found + if (pollResult == 0) { + return {}; + } + + while (true) { + std::vector chunk; + chunk.reserve(1024); + + const std::size_t bytes_read = read(STDIN_FILENO, chunk.data(), chunk.size()); - // input available - if (pollResult > 0) { - const int count = read(STDIN_FILENO, characters.data(), 0); - if (count < 0) { + if (bytes_read == 0) { + break; + } // EOF + if (bytes_read < 0) { + if (errno == EINTR) continue; // Interrupted, try again throw rawterm::KeypressError("An error occured during reading user input"); } - // no input found - } else if (pollResult == 0) { - return {}; + std::cout << "BYTES READ: " << bytes_read << "\n"; + buffer.insert(buffer.end(), chunk.begin(), chunk.begin() + bytes_read); + } - // interrupted system call -- SIGWINCH interrupting poll() - } else if (errno == 4) { - return {}; + return std::string(buffer.begin(), buffer.end()); + } - // Error - } else { - throw rawterm::KeypressError( - std::format("A poll error occured: {} - {}", errno, std::strerror(errno))); + [[nodiscard]] const std::optional process_keypress() { + const std::string characters = read_input(); + if (!characters.size()) { + return {}; } std::stringstream ss; diff --git a/rawterm/core.h b/rawterm/core.h index 5e6c1f4..fcb1f18 100644 --- a/rawterm/core.h +++ b/rawterm/core.h @@ -42,7 +42,7 @@ namespace rawterm { inline Signal sig_sent = Signal::NONE; // Used for polling in process_keypress() - inline pollfd fd {STDIN_FILENO, POLLIN, POLLOUT}; + inline pollfd fd = {STDIN_FILENO, POLLIN, 0}; // used for debugging [[nodiscard]] bool is_debug(); From 0c3b22b1d185718994f938dc94d2c0138b009ca7 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Mon, 8 Jun 2026 10:42:41 +0000 Subject: [PATCH 3/5] Remove 32-byte limit on read --- examples/keys.cpp | 36 ++++++++++++++---------------------- rawterm/core.cpp | 26 +++++++++++++++++++++----- rawterm/core.h | 1 + 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/examples/keys.cpp b/examples/keys.cpp index 9c97807..dd60816 100644 --- a/examples/keys.cpp +++ b/examples/keys.cpp @@ -3,9 +3,7 @@ #include #include -#include #include -#include // This is a demonstration on how `rawterm` works. Any keypress you enter // will show it's output as a Key object in the terminal. Press `q` to quit. @@ -23,26 +21,20 @@ int main() { std::cout << rawterm::set_header(header) << std::flush; - // while (true) { - auto k = rawterm::wait_for_input(); - // if (k == rawterm::Key('q')) { - // break; - // } else { - std::string mods = "["; - while (!(k.mod.empty())) - mods += " " + rawterm::to_string(k.getMod()); - mods += " ]"; - - std::cout << "Key{ code: " << k.code << ", mods: " << mods << ", raw: " << k.raw << "}\r\n"; - - using namespace std::this_thread; // sleep_for, sleep_until - using namespace std::chrono_literals; // ns, us, ms, s, h, etc. - using std::chrono::system_clock; - - sleep_for(10ns); - sleep_until(system_clock::now() + 1s); - // } - // } + while (true) { + auto k = rawterm::wait_for_input(); + if (k == rawterm::Key('q')) { + break; + } else { + std::string mods = "["; + while (!(k.mod.empty())) + mods += " " + rawterm::to_string(k.getMod()); + mods += " ]"; + + std::cout << "Key{ code: " << k.code << ", mods: " << mods << ", raw: " << k.raw + << "}\r\n"; + } + } // Optional explicit call to exit_alt_screen to return to standard screen. // This will happen even without calling this function explicitly diff --git a/rawterm/core.cpp b/rawterm/core.cpp index 3754cc7..b0c9909 100644 --- a/rawterm/core.cpp +++ b/rawterm/core.cpp @@ -9,6 +9,10 @@ #include "cursor.h" #include "exceptions.h" +#if __linux__ +#include +#endif + namespace rawterm { namespace detail { @@ -45,6 +49,10 @@ namespace rawterm { void disable_raw_mode() { #if __linux__ + if (detail::stdin_orig_flags != -1) { + fcntl(STDIN_FILENO, F_SETFL, detail::stdin_orig_flags); + detail::stdin_orig_flags = -1; + } if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawterm::detail::orig) == -1) { Cursor c; c.reset(); @@ -73,6 +81,11 @@ namespace rawterm { if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { std::perror("tcsetattr"); } + const int flags = fcntl(STDIN_FILENO, F_GETFL); + if (flags != -1) { + detail::stdin_orig_flags = flags; + fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); + } return 0; #elif _WIN32 // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-sgr-terminal-sequences @@ -156,23 +169,26 @@ namespace rawterm { if (pollResult == 0) { return {}; } - while (true) { std::vector chunk; - chunk.reserve(1024); + chunk.resize(1024); - const std::size_t bytes_read = read(STDIN_FILENO, chunk.data(), chunk.size()); + const int bytes_read = read(STDIN_FILENO, chunk.data(), chunk.size()); if (bytes_read == 0) { break; } // EOF if (bytes_read < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } if (errno == EINTR) continue; // Interrupted, try again throw rawterm::KeypressError("An error occured during reading user input"); } - std::cout << "BYTES READ: " << bytes_read << "\n"; - buffer.insert(buffer.end(), chunk.begin(), chunk.begin() + bytes_read); + if (bytes_read) { + buffer.insert(buffer.end(), chunk.begin(), chunk.begin() + bytes_read); + } } return std::string(buffer.begin(), buffer.end()); diff --git a/rawterm/core.h b/rawterm/core.h index fcb1f18..d550da9 100644 --- a/rawterm/core.h +++ b/rawterm/core.h @@ -38,6 +38,7 @@ namespace rawterm { namespace detail { #if __linux__ inline termios orig; + inline int stdin_orig_flags = -1; #endif inline Signal sig_sent = Signal::NONE; From 5715fe736d2b038bf883cf04aeb153dcfb574569 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Mon, 8 Jun 2026 14:04:26 +0000 Subject: [PATCH 4/5] add todo --- rawterm/core.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rawterm/core.cpp b/rawterm/core.cpp index b0c9909..1c3331b 100644 --- a/rawterm/core.cpp +++ b/rawterm/core.cpp @@ -194,6 +194,9 @@ namespace rawterm { return std::string(buffer.begin(), buffer.end()); } + // TODO: Possibly update to return a vector instead? + // This will be a breaking change as we'll be updating the core interface + // Users will have to iterate over the returned keys [[nodiscard]] const std::optional process_keypress() { const std::string characters = read_input(); if (!characters.size()) { From e7fa892c9aeb75e17e2e38c889eacf48bf7c95bb Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Mon, 8 Jun 2026 14:05:23 +0000 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b10626d..deae7c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ### main/HEAD +* Update keypress reading logic to better handle copy/paste in the future ### v4.0.8 - 25/Dec/2025 * Add new Cursor::move overload to accept two integers