/*============================================================================ 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 #include #include #include #include #include #if defined(_WIN32) # include # if __cplusplus >= 201103L # include # endif #endif namespace @KWSYS_NAMESPACE@ { #if defined(_WIN32) template > class @KWSYS_NAMESPACE@_EXPORT BasicConsoleBuf : public std::basic_streambuf { public: typedef typename Traits::int_type int_type; typedef typename Traits::char_type char_type; class Manager { public: Manager(std::basic_ios &ios, const bool err = false) : m_consolebuf(0) { m_ios = &ios; try { m_consolebuf = new BasicConsoleBuf(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 *m_ios; std::basic_streambuf *m_streambuf; BasicConsoleBuf *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 m_ibuffer; std::basic_string 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 buffer) { return Encoding::ToWide(buffer); } std::wstring getBuffer(const std::basic_string buffer) { return buffer; } void setBuffer(const std::wstring wbuffer, std::basic_string &target) { target = Encoding::ToNarrow(wbuffer); } void setBuffer(const std::wstring wbuffer, std::basic_string &target) { target = wbuffer; } }; // BasicConsoleBuf class typedef BasicConsoleBuf ConsoleBuf; typedef BasicConsoleBuf WConsoleBuf; #endif } // KWSYS_NAMESPACE #endif