From c357419a5c19a3ad77b957a5f31d829e38aef5d3 Mon Sep 17 00:00:00 2001 From: flagarde Date: Fri, 27 Oct 2023 14:29:52 +0200 Subject: [PATCH] Raw/Cooked (#323) * better raw/cooked mode handling --- cpp-terminal/iostream.cpp | 2 + cpp-terminal/platforms/terminal.cpp | 131 ++++++++++++++++------------ cpp-terminal/terminal.cpp | 3 +- cpp-terminal/terminal.hpp | 2 +- 4 files changed, 81 insertions(+), 57 deletions(-) diff --git a/cpp-terminal/iostream.cpp b/cpp-terminal/iostream.cpp index 53ad815e..68f67c4e 100644 --- a/cpp-terminal/iostream.cpp +++ b/cpp-terminal/iostream.cpp @@ -9,6 +9,8 @@ #include "cpp-terminal/iostream.hpp" +#include "cpp-terminal/buffer.hpp" + #include #include diff --git a/cpp-terminal/platforms/terminal.cpp b/cpp-terminal/platforms/terminal.cpp index 798b83a8..0fba2927 100644 --- a/cpp-terminal/platforms/terminal.cpp +++ b/cpp-terminal/platforms/terminal.cpp @@ -9,20 +9,20 @@ #include "cpp-terminal/terminal.hpp" -#include "cpp-terminal/exception.hpp" #include "cpp-terminal/platforms/env.hpp" +#include "cpp-terminal/platforms/exception.hpp" #include "cpp-terminal/platforms/file.hpp" #ifdef _WIN32 #include #include - #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING + #if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #endif - #ifndef DISABLE_NEWLINE_AUTO_RETURN + #if !defined(DISABLE_NEWLINE_AUTO_RETURN) #define DISABLE_NEWLINE_AUTO_RETURN 0x0008 #endif - #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT + #if !defined(ENABLE_VIRTUAL_TERMINAL_INPUT) #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 #endif #else @@ -37,18 +37,16 @@ void Term::Terminal::set_unset_utf8() static UINT in_code_page{0}; if(!enabled) { - out_code_page = GetConsoleOutputCP(); - if(out_code_page == 0) throw Term::Exception("GetConsoleOutputCP() failed"); - if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Exception("SetConsoleOutputCP(CP_UTF8) failed"); - in_code_page = GetConsoleCP(); - if(out_code_page == 0) throw Term::Exception("GetConsoleCP() failed"); - if(!SetConsoleCP(CP_UTF8)) throw Term::Exception("SetConsoleCP(CP_UTF8) failed"); + if((out_code_page = GetConsoleOutputCP()) == 0) throw Term::Private::WindowsError(GetLastError()); + if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Private::WindowsError(GetLastError()); + if((in_code_page = GetConsoleCP()) == 0) throw Term::Private::WindowsError(GetLastError()); + if(!SetConsoleCP(CP_UTF8)) throw Term::Private::WindowsError(GetLastError()); enabled = true; } else { - if(!SetConsoleOutputCP(out_code_page)) throw Term::Exception("SetConsoleOutputCP(out_code_page) failed"); - if(!SetConsoleCP(in_code_page)) throw Term::Exception("SetConsoleCP(in_code_page) failed"); + if(!SetConsoleOutputCP(out_code_page)) throw Term::Private::WindowsError(GetLastError()); + if(!SetConsoleCP(in_code_page)) throw Term::Private::WindowsError(GetLastError()); } #else if(!enabled) @@ -64,35 +62,34 @@ void Term::Terminal::set_unset_utf8() #endif } +/// +///@brief Store and restore the default state of the terminal. Configure the default mode for cpp-terminal. +/// void Term::Terminal::store_and_restore() { static bool enabled{false}; -#ifdef _WIN32 - static DWORD dwOriginalOutMode{0}; - static DWORD dwOriginalInMode{0}; +#if defined(_WIN32) + static DWORD originalOut{0}; + static DWORD originalIn{0}; if(!enabled) { - if(!Private::out.null()) - { - if(GetConsoleMode(Private::out.handle(), &dwOriginalOutMode) == 0) { throw Term::Exception("GetConsoleMode() failed"); } - if(GetConsoleMode(Private::in.handle(), &dwOriginalInMode) == 0) { throw Term::Exception("GetConsoleMode() failed"); } - } - DWORD in{(dwOriginalInMode & ~ENABLE_QUICK_EDIT_MODE) | (ENABLE_EXTENDED_FLAGS | activateFocusEvents() | activateMouseEvents())}; - DWORD out{dwOriginalOutMode}; + if(GetConsoleMode(Private::out.handle(), &originalOut) == 0) { throw Term::Private::WindowsError(GetLastError()); } + if(GetConsoleMode(Private::in.handle(), &originalIn) == 0) { throw Term::Private::WindowsError(GetLastError()); } + DWORD in{(originalIn & ~ENABLE_QUICK_EDIT_MODE) | (ENABLE_EXTENDED_FLAGS | activateFocusEvents() | activateMouseEvents())}; + DWORD out{originalOut}; if(!m_terminfo.isLegacy()) { out |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; in |= ENABLE_VIRTUAL_TERMINAL_INPUT; } - if(!SetConsoleMode(Private::out.handle(), out)) { throw Term::Exception("SetConsoleMode() failed in destructor"); } - if(!SetConsoleMode(Private::in.handle(), in)) { throw Term::Exception("SetConsoleMode() failed"); } + if(!SetConsoleMode(Private::out.handle(), out)) { throw Term::Private::WindowsError(GetLastError()); } + if(!SetConsoleMode(Private::in.handle(), in)) { throw Term::Private::WindowsError(GetLastError()); } enabled = true; } else { - if(!SetConsoleMode(Private::out.handle(), dwOriginalOutMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); } - if(!SetConsoleMode(Private::in.handle(), dwOriginalInMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); } - enabled = false; + if(!SetConsoleMode(Private::out.handle(), originalOut)) { throw Term::Private::WindowsError(GetLastError()); } + if(!SetConsoleMode(Private::in.handle(), originalIn)) { throw Term::Private::WindowsError(GetLastError()); } } #else static termios orig_termios; @@ -101,19 +98,16 @@ void Term::Terminal::store_and_restore() if(!Private::out.null()) if(tcgetattr(Private::out.fd(), &orig_termios) == -1) { throw Term::Exception("tcgetattr() failed"); } termios term = orig_termios; - term.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common) - term.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common) - term.c_cflag &= ~CSIZE; // Clear all the size bits, then use one of the statements below - term.c_cflag |= CS8; // 8 bits per byte (most common) if(!Private::out.null()) if(tcsetattr(Private::out.fd(), TCSAFLUSH, &term) == -1) { throw Term::Exception("tcsetattr() failed in destructor"); } enabled = true; } else { + desactivateMouseEvents(); + desactivateFocusEvents(); if(!Private::out.null()) if(tcsetattr(Private::out.fd(), TCSAFLUSH, &orig_termios) == -1) { throw Term::Exception("tcsetattr() failed in destructor"); } - enabled = false; } #endif } @@ -132,7 +126,7 @@ int Term::Terminal::desactivateMouseEvents() #if defined(_WIN32) return ENABLE_MOUSE_INPUT; #else - return Term::Private::out.write("\033[?1003l\033[?1006l"); + return Term::Private::out.write("\033[?1006l\033[?1003l\033[?1002l"); #endif } @@ -167,34 +161,61 @@ void Term::Terminal::setBadStateReturnCode() } } -void Term::Terminal::setRawMode() +/// +///@brief Set mode raw/cooked. +///First call is to save the good state set-up by cpp-terminal. +/// +void Term::Terminal::setMode() { + static bool activated{false}; #if defined(_WIN32) - DWORD flags{0}; - if(!Private::out.null()) - if(!GetConsoleMode(Private::in.handle(), &flags)) { throw Term::Exception("GetConsoleMode() failed"); } - if(m_options.has(Option::NoSignalKeys)) { flags &= ~ENABLE_PROCESSED_INPUT; } - flags &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); + static DWORD flags{0}; + if(!activated) + { + if(!Private::out.null()) + if(!GetConsoleMode(Private::in.handle(), &flags)) { throw Term::Private::WindowsError(GetLastError()); } + activated = true; + } + DWORD send = flags; + if(m_options.has(Option::Raw)) { send &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); } + else if(m_options.has(Option::Cooked)) { send |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); } + if(m_options.has(Option::NoSignalKeys)) { send &= ~ENABLE_PROCESSED_INPUT; } + else if(m_options.has(Option::SignalKeys)) { send |= ENABLE_PROCESSED_INPUT; } if(!Private::out.null()) - if(!SetConsoleMode(Private::in.handle(), flags)) { throw Term::Exception("SetConsoleMode() failed"); } + if(!SetConsoleMode(Private::in.handle(), send)) { throw Term::Private::WindowsError(GetLastError()); } #else if(!Private::out.null()) { - ::termios raw; - if(tcgetattr(Private::out.fd(), &raw) == -1) { throw Term::Exception("tcgetattr() failed"); } - // Put terminal in raw mode - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - // This disables output post-processing, requiring explicit \r\n. We - // keep it enabled, so that in C++, one can still just use std::endl - // for EOL instead of "\r\n". - // raw.c_oflag &= ~(OPOST); - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); - if(m_options.has(Option::NoSignalKeys)) { raw.c_lflag &= ~ISIG; } - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; - if(tcsetattr(Private::out.fd(), TCSAFLUSH, &raw) == -1) { throw Term::Exception("tcsetattr() failed"); } - activateMouseEvents(); - activateFocusEvents(); + static ::termios raw; + if(!activated) + { + if(tcgetattr(Private::out.fd(), &raw) == -1) { throw Term::Exception("tcgetattr() failed"); } + activated = true; + } + ::termios send = raw; + if(m_options.has(Option::Raw)) + { + // Put terminal in raw mode + send.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + // This disables output post-processing, requiring explicit \r\n. We + // keep it enabled, so that in C++, one can still just use std::endl + // for EOL instead of "\r\n". + // raw.c_oflag &= ~(OPOST); + send.c_lflag &= ~(ECHO | ICANON | IEXTEN); + send.c_cc[VMIN] = 1; + send.c_cc[VTIME] = 0; + activateMouseEvents(); + activateFocusEvents(); + } + else if(m_options.has(Option::Cooked)) + { + send = raw; + desactivateMouseEvents(); + desactivateFocusEvents(); + } + if(m_options.has(Option::NoSignalKeys)) { send.c_lflag &= ~ISIG; } //FIXME need others flags ! + else if(m_options.has(Option::NoSignalKeys)) { send.c_lflag |= ISIG; } + if(tcsetattr(Private::out.fd(), TCSAFLUSH, &send) == -1) { throw Term::Exception("tcsetattr() failed"); } } #endif } diff --git a/cpp-terminal/terminal.cpp b/cpp-terminal/terminal.cpp index 0c95142a..a76bfc61 100644 --- a/cpp-terminal/terminal.cpp +++ b/cpp-terminal/terminal.cpp @@ -47,6 +47,7 @@ Term::Terminal::Terminal() setBadStateReturnCode(); Term::Private::m_fileInitializer.init(); store_and_restore(); + setMode(); //Save the default cpp-terminal mode done in store_and_restore(); set_unset_utf8(); m_terminfo.checkUTF8(); } @@ -85,7 +86,7 @@ void Term::Terminal::applyOptions() { if(m_options.has(Option::ClearScreen)) Term::Private::out.write(screen_save() + clear_buffer() + style(Style::Reset) + cursor_move(1, 1)); if(m_options.has(Option::NoCursor)) Term::Private::out.write(cursor_off()); - if(m_options.has(Option::Raw)) setRawMode(); + setMode(); } std::string Term::terminal_title(const std::string& title) { return "\033]0;" + title + '\a'; } diff --git a/cpp-terminal/terminal.hpp b/cpp-terminal/terminal.hpp index 14bc07b8..acd3f52a 100644 --- a/cpp-terminal/terminal.hpp +++ b/cpp-terminal/terminal.hpp @@ -21,7 +21,7 @@ class Terminal void setBadStateReturnCode(); void setOptions(); void applyOptions(); - void setRawMode(); + void setMode(); int activateMouseEvents(); int desactivateMouseEvents(); int activateFocusEvents();