|
@@ -0,0 +1,361 @@
|
|
|
+/* 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;
|
|
|
+
|
|
|
+}
|
|
|
+}
|