1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 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;
}
}
|