|  | /* 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 <cstddef> | 
|  | #  include <cstdlib> | 
|  | #  include <ios> | 
|  | #  include <streambuf> | 
|  | #  include <utility> | 
|  | #  include <vector> | 
|  |  | 
|  | #  include <cm/memory> | 
|  |  | 
|  | #  include <windows.h> | 
|  |  | 
|  | #  include <fcntl.h> // for _O_BINARY | 
|  | #  include <io.h>    // 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<char> Buf_; | 
|  | std::vector<wchar_t> 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<char> Buf_; | 
|  | std::vector<wchar_t> 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<unsigned char>(*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> 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<Stream&>(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<ConsoleBufRead>(is.Console(), mode); | 
|  | } | 
|  | this->Init(); | 
|  | } | 
|  |  | 
|  | Console::Impl::RAII::RAII(OStream& os) | 
|  | : RAII(static_cast<Stream&>(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<ConsoleBufWrite>(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<Impl>()) | 
|  | { | 
|  | } | 
|  | #else | 
|  | Console::Console() = default; | 
|  | #endif | 
|  |  | 
|  | Console::~Console() = default; | 
|  |  | 
|  | Console::Console(Console&&) noexcept = default; | 
|  | Console& Console::operator=(Console&&) noexcept = default; | 
|  |  | 
|  | } | 
|  | } |