/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmStdIoConsole.h" #ifdef _WIN32 # include # include # include # include # include # include # include # include # include // for _O_BINARY # include // for _setmode # include "cm_utf8.h" # include "cmStdIoStream.h" #endif namespace cm { namespace StdIo { namespace { #ifdef _WIN32 // Base class for a streambuf that reads or writes a Windows Console. class ConsoleBuf : public std::streambuf { public: ConsoleBuf(HANDLE console) : console_(console) { } ~ConsoleBuf() throw() override {} protected: HANDLE console_ = nullptr; }; // A streambuf that reads from a Windows Console using wide-character // encoding to avoid conversion through the console output code page. class ConsoleBufRead : public ConsoleBuf { public: ConsoleBufRead(HANDLE console, DWORD consoleMode) : ConsoleBuf(console) , ConsoleMode_(consoleMode) { } ~ConsoleBufRead() throw() override {} protected: // Called to read an input character when the input buffer may be empty. int_type underflow() override { // If the input buffer is not empty, return the next input character. if (this->gptr() < this->egptr()) { return traits_type::to_int_type(*this->gptr()); } // The input buffer is empty. Read more input from the console. static constexpr std::size_t kBufSize = 4096; this->TmpW_.resize(kBufSize); DWORD wlen = 0; if (!ReadConsoleW(this->console_, this->TmpW_.data(), DWORD(this->TmpW_.size()), &wlen, nullptr)) { // Failure. Nothing was read. return traits_type::eof(); } // Emulate ReadFile behavior when the console is in "cooked mode". // Treat a leading Ctrl+Z as EOF. static constexpr char ctrl_z = 26; // Ctrl+Z is Ctrl + 26th letter. if ((this->ConsoleMode_ & ENABLE_LINE_INPUT) && (wlen > 0 && this->TmpW_.front() == ctrl_z)) { wlen = 0; } // Convert the wide-character encoding from the console to our // internal UTF-8 narrow encoding. if (int nlen = WideCharToMultiByte(CP_UTF8, 0, this->TmpW_.data(), int(wlen), nullptr, 0, nullptr, nullptr)) { this->Buf_.resize(nlen); if (WideCharToMultiByte(CP_UTF8, 0, this->TmpW_.data(), int(wlen), this->Buf_.data(), int(nlen), nullptr, nullptr)) { // The converted content is now in the input buffer. this->setg_(); // Success. Return the next input character. return traits_type::to_int_type(*this->gptr()); } } // Failure. Nothing was read. return traits_type::eof(); } private: DWORD ConsoleMode_ = 0; std::vector Buf_; std::vector TmpW_; // Set input buffer pointers. void setg_() { this->setg(this->Buf_.data(), this->Buf_.data(), this->Buf_.data() + this->Buf_.size()); } }; // A streambuf that writes to a Windows Console using wide-character // encoding to avoid conversion through the console output code page. class ConsoleBufWrite : public ConsoleBuf { public: ConsoleBufWrite(HANDLE console) : ConsoleBuf(console) { this->setp_(); } ~ConsoleBufWrite() throw() override { sync(); } protected: // Called to sync input and output buffers with the underlying device. int sync() override { // Flush buffered output, if any. if (this->pptr() != this->pbase()) { // Use overflow() to flush the entire output buffer. // It returns eof on failure. if (traits_type::eq_int_type(this->overflow(), traits_type::eof())) { return -1; } } return 0; } // Called to flush at least some content from the output buffer. int_type overflow(int_type ch = traits_type::eof()) override { std::size_t nlen; // Number of chars to emit. std::size_t rlen = 0; // Number of chars to roll over. if (traits_type::eq_int_type(ch, traits_type::eof())) { // Our caller wants to flush the entire buffer. If there is a // trailing partial codepoint, it's the caller's fault. nlen = this->pptr() - this->pbase(); // If the buffer is empty, trivially succeed. if (nlen == 0) { return traits_type::not_eof(ch); } } else { // Our caller had no room for this character in the buffer. // However, setp_() reserved one byte for us to store it. *this->pptr() = traits_type::to_char_type(ch); this->pbump(1); // Flush all complete codepoints, of which we expect at least one. // If there is a trailing partial codepoint, roll over those chars. char const* p = this->pptr_(); nlen = p - this->pbase(); rlen = this->pptr() - p; } // Fail unless we emit at least one (wide) character. int_type result = traits_type::eof(); // Convert our internal UTF-8 narrow encoding to wide-character // encoding to write to the console. if (int wlen = MultiByteToWideChar(CP_UTF8, 0, this->pbase(), int(nlen), nullptr, 0)) { this->TmpW_.resize(wlen); if (MultiByteToWideChar(CP_UTF8, 0, this->pbase(), int(nlen), this->TmpW_.data(), int(wlen)) && WriteConsoleW(this->console_, this->TmpW_.data(), wlen, nullptr, nullptr)) { result = traits_type::not_eof(ch); } } // Remove emitted contents from the buffer. this->Buf_.erase(this->Buf_.begin(), this->Buf_.begin() + nlen); // Re-initialize the output buffer. this->setp_(); // Move the put-pointer past the rollover content. this->pbump(rlen); return result; } private: std::vector Buf_; std::vector TmpW_; // Initialize the output buffer and set its put-pointer. void setp_() { // Allocate the output buffer. static constexpr std::size_t kBufSize = 4096; this->Buf_.resize(kBufSize); // Reserve one byte for the overflow() character. this->setp(this->Buf_.data(), this->Buf_.data() + this->Buf_.size() - 1); } // Return pptr() adjusted backward past a partial codepoint. char const* pptr_() const { char const* p = this->pptr(); while (p != this->pbase()) { --p; switch (cm_utf8_ones[static_cast(*p)]) { case 0: // 0xxx xxxx: starts codepoint of size 1 return p + 1; case 1: // 10xx xxxx: continues a codepoint continue; case 2: // 110x xxxx: starts codepoint of size 2 return ((p + 2) <= this->pptr()) ? (p + 2) : p; case 3: // 1110 xxxx: starts codepoint of size 3 return ((p + 3) <= this->pptr()) ? (p + 3) : p; case 4: // 1111 0xxx: starts codepoint of size 4 return ((p + 4) <= this->pptr()) ? (p + 4) : p; default: // invalid byte // Roll over the invalid byte. // The next overflow() will fail to convert it. return p; } } // No complete codepoint found. This overflow() will fail. return p; } }; #endif } // anonymous namespace #ifdef _WIN32 class Console::Impl { protected: class RAII { std::ios* IOS_ = nullptr; int FD_ = -1; std::unique_ptr ConsoleBuf_; std::streambuf* OldStreamBuf_ = nullptr; int OldMode_ = 0; RAII(Stream& s); void Init(); public: RAII(IStream& is); RAII(OStream& os); ~RAII(); }; RAII In_; RAII Out_; RAII Err_; public: Impl(); ~Impl(); }; Console::Impl::RAII::RAII(Stream& s) : IOS_(&s.IOS()) , FD_(s.FD()) { } Console::Impl::RAII::RAII(IStream& is) : RAII(static_cast(is)) { DWORD mode; if (is.Console() && GetConsoleMode(is.Console(), &mode) && GetConsoleCP() != CP_UTF8) { // The input stream reads from a console whose input code page is not // UTF-8. Use a ConsoleBufRead to read wide-character encoding. this->ConsoleBuf_ = cm::make_unique(is.Console(), mode); } this->Init(); } Console::Impl::RAII::RAII(OStream& os) : RAII(static_cast(os)) { DWORD mode; if (os.Console() && GetConsoleMode(os.Console(), &mode) && GetConsoleOutputCP() != CP_UTF8) { // The output stream writes to a console whose output code page is not // UTF-8. Use a ConsoleBufWrite to write wide-character encoding. this->ConsoleBuf_ = cm::make_unique(os.Console()); } this->Init(); } void Console::Impl::RAII::Init() { if (this->ConsoleBuf_) { this->OldStreamBuf_ = this->IOS_->rdbuf(this->ConsoleBuf_.get()); } else if (this->FD_ >= 0) { // The stream reads/writes a pipe, a file, or a console whose code // page is UTF-8. Read/write UTF-8 using the default streambuf, // but disable newline conversion to match ConsoleBuf behavior. this->OldMode_ = _setmode(this->FD_, _O_BINARY); } } Console::Impl::RAII::~RAII() { if (this->ConsoleBuf_) { this->IOS_->rdbuf(this->OldStreamBuf_); this->OldStreamBuf_ = nullptr; this->ConsoleBuf_.reset(); } else if (this->FD_ >= 0) { this->IOS_->rdbuf()->pubsync(); _setmode(this->FD_, this->OldMode_); this->OldMode_ = 0; } this->FD_ = -1; this->IOS_ = nullptr; } Console::Impl::Impl() : In_(In()) , Out_(Out()) , Err_(Err()) { } Console::Impl::~Impl() = default; Console::Console() : Impl_(cm::make_unique()) { } #else Console::Console() = default; #endif Console::~Console() = default; Console::Console(Console&&) noexcept = default; Console& Console::operator=(Console&&) noexcept = default; } }