| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing#kwsys for details. */ |
| #ifndef @KWSYS_NAMESPACE@_ConsoleBuf_hxx |
| #define @KWSYS_NAMESPACE@_ConsoleBuf_hxx |
| |
| #include <@KWSYS_NAMESPACE@/Configure.hxx> |
| |
| #include <@KWSYS_NAMESPACE@/Encoding.hxx> |
| |
| #include <cstring> |
| #include <iostream> |
| #include <sstream> |
| #include <stdexcept> |
| #include <streambuf> |
| #include <string> |
| |
| #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 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; |
| }; |
| } |
| |
| BasicConsoleBuf<CharT, Traits>* GetConsoleBuf() { return m_consolebuf; } |
| |
| void SetUTF8Pipes() |
| { |
| if (m_consolebuf) { |
| m_consolebuf->input_pipe_codepage = CP_UTF8; |
| m_consolebuf->output_pipe_codepage = CP_UTF8; |
| m_consolebuf->activateCodepageChange(); |
| } |
| } |
| |
| ~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) { |
| // ReadConsole doesn't tell if there's more input available |
| // don't support reading more characters than this |
| wchar_t wbuffer[8192]; |
| DWORD charsRead; |
| if (ReadConsoleW(m_hInput, wbuffer, |
| (sizeof(wbuffer) / sizeof(wbuffer[0])), &charsRead, |
| NULL) == 0 || |
| charsRead == 0) { |
| _setg(true); |
| return Traits::eof(); |
| } |
| setBuffer(std::wstring(wbuffer, charsRead), m_ibuffer); |
| } else { |
| std::wstring wbuffer; |
| std::string strbuffer; |
| DWORD bytesRead; |
| LARGE_INTEGER size; |
| if (GetFileSizeEx(m_hInput, &size) == 0) { |
| _setg(true); |
| return Traits::eof(); |
| } |
| char* buffer = new char[size.LowPart]; |
| while (ReadFile(m_hInput, buffer, size.LowPart, &bytesRead, NULL) == |
| 0) { |
| if (GetLastError() == ERROR_MORE_DATA) { |
| strbuffer += std::string(buffer, bytesRead); |
| continue; |
| } |
| _setg(true); |
| delete[] buffer; |
| return Traits::eof(); |
| } |
| if (bytesRead > 0) { |
| strbuffer += std::string(buffer, bytesRead); |
| } |
| delete[] buffer; |
| if (!decodeInputBuffer(strbuffer, wbuffer)) { |
| _setg(true); |
| return Traits::eof(); |
| } |
| setBuffer(wbuffer, 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: |
| // Check for actual console. |
| DWORD consoleMode; |
| m_isConsoleInput = |
| GetConsoleMode(m_hInput, &consoleMode) == 0 ? false : true; |
| if (m_isConsoleInput) { |
| break; |
| } |
| @KWSYS_NAMESPACE@_FALLTHROUGH; |
| 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: |
| // Check for actual console. |
| DWORD consoleMode; |
| m_isConsoleOutput = |
| GetConsoleMode(m_hOutput, &consoleMode) == 0 ? false : true; |
| if (m_isConsoleOutput) { |
| break; |
| } |
| @KWSYS_NAMESPACE@_FALLTHROUGH; |
| 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) |
| { |
| if (wbuffer.size() == 0) { |
| buffer = std::string(); |
| return true; |
| } |
| const int length = |
| WideCharToMultiByte(m_activeOutputCodepage, 0, wbuffer.c_str(), |
| (int)wbuffer.size(), NULL, 0, NULL, NULL); |
| char* buf = new char[length]; |
| const bool success = |
| WideCharToMultiByte(m_activeOutputCodepage, 0, wbuffer.c_str(), |
| (int)wbuffer.size(), buf, length, NULL, NULL) > 0 |
| ? true |
| : false; |
| buffer = std::string(buf, length); |
| delete[] buf; |
| return success; |
| } |
| bool decodeInputBuffer(const std::string buffer, std::wstring& wbuffer) |
| { |
| size_t length = buffer.length(); |
| if (length == 0) { |
| wbuffer = std::wstring(); |
| return true; |
| } |
| int actualCodepage = m_activeInputCodepage; |
| const char BOM_UTF8[] = { char(0xEF), char(0xBB), char(0xBF) }; |
| const char* data = buffer.data(); |
| const size_t BOMsize = sizeof(BOM_UTF8); |
| if (length >= BOMsize && std::memcmp(data, BOM_UTF8, BOMsize) == 0) { |
| // PowerShell uses UTF-8 with BOM for pipes |
| actualCodepage = CP_UTF8; |
| data += BOMsize; |
| length -= BOMsize; |
| } |
| const size_t wlength = static_cast<size_t>(MultiByteToWideChar( |
| actualCodepage, 0, data, static_cast<int>(length), NULL, 0)); |
| wchar_t* wbuf = new wchar_t[wlength]; |
| const bool success = |
| MultiByteToWideChar(actualCodepage, 0, data, static_cast<int>(length), |
| wbuf, static_cast<int>(wlength)) > 0 |
| ? true |
| : false; |
| wbuffer = std::wstring(wbuf, wlength); |
| 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 |