| /* 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; | |
| } | |
| } |