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 diff --git a/rawterm/core.cpp b/rawterm/core.cpp index 3745522..1c3331b 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 @@ -141,28 +154,53 @@ namespace rawterm { } } - [[nodiscard]] const std::optional process_keypress() { - std::string characters = std::string(32, '\0'); - 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.resize(1024); - // input available - if (pollResult > 0) { - if (read(STDIN_FILENO, characters.data(), 32) < 0) { + 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"); } - // no input found - } else if (pollResult == 0) { - return {}; + if (bytes_read) { + 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))); + // 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()) { + return {}; } std::stringstream ss; diff --git a/rawterm/core.h b/rawterm/core.h index 5e6c1f4..d550da9 100644 --- a/rawterm/core.h +++ b/rawterm/core.h @@ -38,11 +38,12 @@ namespace rawterm { namespace detail { #if __linux__ inline termios orig; + inline int stdin_orig_flags = -1; #endif 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();