349 lines
12 KiB
C++
349 lines
12 KiB
C++
/*============================================================================
|
|
KWSys - Kitware System Library
|
|
Copyright 2000-2016 Kitware, Inc., Insight Software Consortium
|
|
|
|
Distributed under the OSI-approved BSD License (the "License");
|
|
see accompanying file Copyright.txt for details.
|
|
|
|
This software is distributed WITHOUT ANY WARRANTY; without even the
|
|
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
See the License for more information.
|
|
============================================================================*/
|
|
#ifndef @KWSYS_NAMESPACE@_ConsoleBuf_hxx
|
|
#define @KWSYS_NAMESPACE@_ConsoleBuf_hxx
|
|
|
|
#include <@KWSYS_NAMESPACE@/Configure.hxx>
|
|
#include <@KWSYS_NAMESPACE@/Encoding.hxx>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <streambuf>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
#if defined(_WIN32)
|
|
# include <windows.h>
|
|
# if __cplusplus >= 201103L
|
|
# include <system_error>
|
|
# endif
|
|
#endif
|
|
|
|
namespace @KWSYS_NAMESPACE@
|
|
{
|
|
#if defined(_WIN32)
|
|
|
|
template<class CharT, class Traits = std::char_traits<CharT> >
|
|
class @KWSYS_NAMESPACE@_EXPORT BasicConsoleBuf :
|
|
public std::basic_streambuf<CharT, Traits> {
|
|
public:
|
|
typedef typename Traits::int_type int_type;
|
|
typedef typename Traits::char_type char_type;
|
|
|
|
class Manager {
|
|
public:
|
|
Manager(std::basic_ios<CharT, Traits> &ios, const bool err = false)
|
|
: m_consolebuf(0)
|
|
{
|
|
m_ios = &ios;
|
|
try {
|
|
m_consolebuf = new BasicConsoleBuf<CharT, Traits>(err);
|
|
m_streambuf = m_ios->rdbuf(m_consolebuf);
|
|
} catch (const std::runtime_error& ex) {
|
|
std::cerr << "Failed to create ConsoleBuf!" << std::endl
|
|
<< ex.what() << std::endl;
|
|
};
|
|
}
|
|
|
|
~Manager()
|
|
{
|
|
if (m_consolebuf) {
|
|
delete m_consolebuf;
|
|
m_ios->rdbuf(m_streambuf);
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::basic_ios<CharT, Traits> *m_ios;
|
|
std::basic_streambuf<CharT, Traits> *m_streambuf;
|
|
BasicConsoleBuf<CharT, Traits> *m_consolebuf;
|
|
};
|
|
|
|
BasicConsoleBuf(const bool err = false) :
|
|
flush_on_newline(true),
|
|
input_pipe_codepage(0),
|
|
output_pipe_codepage(0),
|
|
input_file_codepage(CP_UTF8),
|
|
output_file_codepage(CP_UTF8),
|
|
m_consolesCodepage(0)
|
|
{
|
|
m_hInput = ::GetStdHandle(STD_INPUT_HANDLE);
|
|
checkHandle(true, "STD_INPUT_HANDLE");
|
|
if (!setActiveInputCodepage()) {
|
|
throw std::runtime_error("setActiveInputCodepage failed!");
|
|
}
|
|
m_hOutput = err ? ::GetStdHandle(STD_ERROR_HANDLE) :
|
|
::GetStdHandle(STD_OUTPUT_HANDLE);
|
|
checkHandle(false, err ? "STD_ERROR_HANDLE" : "STD_OUTPUT_HANDLE");
|
|
if (!setActiveOutputCodepage()) {
|
|
throw std::runtime_error("setActiveOutputCodepage failed!");
|
|
}
|
|
_setg();
|
|
_setp();
|
|
}
|
|
|
|
~BasicConsoleBuf() throw()
|
|
{
|
|
sync();
|
|
}
|
|
|
|
bool activateCodepageChange()
|
|
{
|
|
return setActiveInputCodepage() && setActiveOutputCodepage();
|
|
}
|
|
|
|
protected:
|
|
virtual int sync() {
|
|
bool success = true;
|
|
if (m_hInput && m_isConsoleInput &&
|
|
::FlushConsoleInputBuffer(m_hInput) == 0) {
|
|
success = false;
|
|
}
|
|
if (m_hOutput && !m_obuffer.empty()) {
|
|
const std::wstring wbuffer = getBuffer(m_obuffer);
|
|
if (m_isConsoleOutput) {
|
|
DWORD charsWritten;
|
|
success = ::WriteConsoleW(m_hOutput, wbuffer.c_str(),
|
|
(DWORD)wbuffer.size(), &charsWritten,
|
|
NULL) == 0 ? false : true;
|
|
} else {
|
|
DWORD bytesWritten;
|
|
std::string buffer;
|
|
success = encodeOutputBuffer(wbuffer, buffer);
|
|
if (success) {
|
|
success = ::WriteFile(m_hOutput, buffer.c_str(),
|
|
(DWORD)buffer.size(), &bytesWritten,
|
|
NULL) == 0 ? false : true;
|
|
}
|
|
}
|
|
}
|
|
m_ibuffer.clear();
|
|
m_obuffer.clear();
|
|
_setg();
|
|
_setp();
|
|
return success ? 0 : -1;
|
|
}
|
|
|
|
virtual int_type underflow() {
|
|
if (this->gptr() >= this->egptr()) {
|
|
if (!m_hInput) {
|
|
_setg(true);
|
|
return Traits::eof();
|
|
}
|
|
if (m_isConsoleInput) {
|
|
wchar_t wbuffer[128];
|
|
DWORD charsRead;
|
|
if (::ReadConsoleW(m_hInput, wbuffer, (sizeof(wbuffer) / sizeof(wbuffer[0])) - 1,
|
|
&charsRead, NULL) == 0 || charsRead == 0) {
|
|
_setg(true);
|
|
return Traits::eof();
|
|
}
|
|
wbuffer[charsRead] = L'\0';
|
|
setBuffer(wbuffer, m_ibuffer);
|
|
} else {
|
|
std::wstring totalBuffer;
|
|
std::wstring wbuffer;
|
|
char buffer[128];
|
|
DWORD bytesRead;
|
|
while (::ReadFile(m_hInput, buffer, (sizeof(buffer) / sizeof(buffer[0])) - 1,
|
|
&bytesRead, NULL) == 0) {
|
|
if (::GetLastError() == ERROR_MORE_DATA) {
|
|
buffer[bytesRead] = '\0';
|
|
if (decodeInputBuffer(buffer, wbuffer)) {
|
|
totalBuffer += wbuffer;
|
|
continue;
|
|
}
|
|
}
|
|
_setg(true);
|
|
return Traits::eof();
|
|
}
|
|
buffer[bytesRead] = '\0';
|
|
if (!decodeInputBuffer(buffer, wbuffer)) {
|
|
_setg(true);
|
|
return Traits::eof();
|
|
}
|
|
totalBuffer += wbuffer;
|
|
setBuffer(totalBuffer, m_ibuffer);
|
|
}
|
|
_setg();
|
|
}
|
|
return Traits::to_int_type(*this->gptr());
|
|
}
|
|
|
|
virtual int_type overflow(int_type ch = Traits::eof()) {
|
|
if (!Traits::eq_int_type(ch, Traits::eof())) {
|
|
char_type chr = Traits::to_char_type(ch);
|
|
m_obuffer += chr;
|
|
if ((flush_on_newline && Traits::eq(chr, '\n')) ||
|
|
Traits::eq_int_type(ch, 0x00)) {
|
|
sync();
|
|
}
|
|
return ch;
|
|
}
|
|
sync();
|
|
return Traits::eof();
|
|
}
|
|
|
|
public:
|
|
bool flush_on_newline;
|
|
UINT input_pipe_codepage;
|
|
UINT output_pipe_codepage;
|
|
UINT input_file_codepage;
|
|
UINT output_file_codepage;
|
|
|
|
private:
|
|
HANDLE m_hInput;
|
|
HANDLE m_hOutput;
|
|
std::basic_string<char_type> m_ibuffer;
|
|
std::basic_string<char_type> m_obuffer;
|
|
bool m_isConsoleInput;
|
|
bool m_isConsoleOutput;
|
|
UINT m_activeInputCodepage;
|
|
UINT m_activeOutputCodepage;
|
|
UINT m_consolesCodepage;
|
|
void checkHandle(bool input, std::string handleName) {
|
|
if ((input && m_hInput == INVALID_HANDLE_VALUE) ||
|
|
(!input && m_hOutput == INVALID_HANDLE_VALUE)) {
|
|
std::string errmsg = "GetStdHandle(" + handleName +
|
|
") returned INVALID_HANDLE_VALUE";
|
|
#if __cplusplus >= 201103L
|
|
throw std::system_error(::GetLastError(),
|
|
std::system_category(), errmsg);
|
|
#else
|
|
throw std::runtime_error(errmsg);
|
|
#endif
|
|
}
|
|
}
|
|
UINT getConsolesCodepage() {
|
|
if (!m_consolesCodepage) {
|
|
m_consolesCodepage = GetConsoleCP();
|
|
if (!m_consolesCodepage) {
|
|
m_consolesCodepage = GetACP();
|
|
}
|
|
}
|
|
return m_consolesCodepage;
|
|
}
|
|
bool setActiveInputCodepage() {
|
|
m_isConsoleInput = false;
|
|
switch (GetFileType(m_hInput)) {
|
|
case FILE_TYPE_DISK:
|
|
m_activeInputCodepage = input_file_codepage;
|
|
break;
|
|
case FILE_TYPE_CHAR:
|
|
m_isConsoleInput = true;
|
|
break;
|
|
case FILE_TYPE_PIPE:
|
|
m_activeInputCodepage = input_pipe_codepage;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (!m_isConsoleInput && m_activeInputCodepage == 0) {
|
|
m_activeInputCodepage = getConsolesCodepage();
|
|
}
|
|
return true;
|
|
}
|
|
bool setActiveOutputCodepage() {
|
|
m_isConsoleOutput = false;
|
|
switch (GetFileType(m_hOutput)) {
|
|
case FILE_TYPE_DISK:
|
|
m_activeOutputCodepage = output_file_codepage;
|
|
break;
|
|
case FILE_TYPE_CHAR:
|
|
m_isConsoleOutput = true;
|
|
break;
|
|
case FILE_TYPE_PIPE:
|
|
m_activeOutputCodepage = output_pipe_codepage;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if (!m_isConsoleOutput && m_activeOutputCodepage == 0) {
|
|
m_activeOutputCodepage = getConsolesCodepage();
|
|
}
|
|
return true;
|
|
}
|
|
void _setg(bool empty = false) {
|
|
if (!empty) {
|
|
this->setg((char_type *)m_ibuffer.data(),
|
|
(char_type *)m_ibuffer.data(),
|
|
(char_type *)m_ibuffer.data() + m_ibuffer.size());
|
|
} else {
|
|
this->setg((char_type *)m_ibuffer.data(),
|
|
(char_type *)m_ibuffer.data() + m_ibuffer.size(),
|
|
(char_type *)m_ibuffer.data() + m_ibuffer.size());
|
|
}
|
|
}
|
|
void _setp() {
|
|
this->setp((char_type *)m_obuffer.data(),
|
|
(char_type *)m_obuffer.data() + m_obuffer.size());
|
|
}
|
|
bool encodeOutputBuffer(const std::wstring wbuffer,
|
|
std::string &buffer) {
|
|
const int length = WideCharToMultiByte(m_activeOutputCodepage, 0,
|
|
wbuffer.c_str(),
|
|
(int)wbuffer.size(), NULL, 0,
|
|
NULL, NULL);
|
|
char *buf = new char[length + 1];
|
|
const bool success = WideCharToMultiByte(m_activeOutputCodepage, 0,
|
|
wbuffer.c_str(),
|
|
(int)wbuffer.size(), buf,
|
|
length, NULL, NULL) > 0
|
|
? true : false;
|
|
buf[length] = '\0';
|
|
buffer = buf;
|
|
delete[] buf;
|
|
return success;
|
|
}
|
|
bool decodeInputBuffer(const char *buffer, std::wstring &wbuffer) {
|
|
int actualCodepage = m_activeInputCodepage;
|
|
const char BOM_UTF8[] = { char(0xEF), char(0xBB), char(0xBF) };
|
|
if (std::memcmp(buffer, BOM_UTF8, sizeof(BOM_UTF8)) == 0) {
|
|
// PowerShell uses UTF-8 with BOM for pipes
|
|
actualCodepage = CP_UTF8;
|
|
buffer += sizeof(BOM_UTF8);
|
|
}
|
|
const int wlength = MultiByteToWideChar(actualCodepage, 0, buffer,
|
|
-1, NULL, 0);
|
|
wchar_t *wbuf = new wchar_t[wlength];
|
|
const bool success = MultiByteToWideChar(actualCodepage, 0, buffer,
|
|
-1, wbuf, wlength) > 0
|
|
? true : false;
|
|
wbuffer = wbuf;
|
|
delete[] wbuf;
|
|
return success;
|
|
}
|
|
std::wstring getBuffer(const std::basic_string<char> buffer) {
|
|
return Encoding::ToWide(buffer);
|
|
}
|
|
std::wstring getBuffer(const std::basic_string<wchar_t> buffer) {
|
|
return buffer;
|
|
}
|
|
void setBuffer(const std::wstring wbuffer,
|
|
std::basic_string<char> &target) {
|
|
target = Encoding::ToNarrow(wbuffer);
|
|
}
|
|
void setBuffer(const std::wstring wbuffer,
|
|
std::basic_string<wchar_t> &target) {
|
|
target = wbuffer;
|
|
}
|
|
|
|
}; // BasicConsoleBuf class
|
|
|
|
typedef BasicConsoleBuf<char> ConsoleBuf;
|
|
typedef BasicConsoleBuf<wchar_t> WConsoleBuf;
|
|
|
|
#endif
|
|
} // KWSYS_NAMESPACE
|
|
|
|
#endif
|