Currently Microsoft's C++ libraries implementation of std::cout/cerr
can't output Unicode characters but only ASCII or ANSI if locale is set
so we implement and use our own ConsoleBuf which can output Unicode
characters to console and it doesn't matter what locale or console's
codepage is set.
---
 CMakeLists.txt                 |   1 +
 Source/cmakemain.cxx           |  27 ++++
 Source/kwsys/CMakeLists.txt    |   6 +-
 Source/kwsys/ConsoleBuf.hxx.in | 282 +++++++++++++++++++++++++++++++++++++++++
 bootstrap                      |   1 +
 5 files changed, 316 insertions(+), 1 deletion(-)
 create mode 100644 Source/kwsys/ConsoleBuf.hxx.in

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae5990e..792b5a5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -258,6 +258,7 @@ macro (CMAKE_BUILD_UTILITIES)
   set(KWSYS_USE_MD5 1)
   set(KWSYS_USE_Process 1)
   set(KWSYS_USE_CommandLineArguments 1)
+  set(KWSYS_USE_ConsoleBuf 1)
   set(KWSYS_HEADER_ROOT ${CMake_BINARY_DIR}/Source)
   set(KWSYS_INSTALL_DOC_DIR "${CMAKE_DOC_DIR}")
   add_subdirectory(Source/kwsys)
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index 521a5bf..95eb154 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -26,6 +26,7 @@
 #include "cmake.h"
 #include "cmcmd.h"
 #include <cmsys/Encoding.hxx>
+#include <cmsys/ConsoleBuf.hxx>
 
 #ifdef CMAKE_BUILD_WITH_CMAKE
 static const char* cmDocumentationName[][2] = {
@@ -153,6 +154,22 @@ static void cmakemainProgressCallback(const char* m, float 
prog,
 
 int main(int ac, char const* const* av)
 {
+#if defined(_WIN32)
+  // Replace streambuf so we can output Unicode to console
+  cmsys::ConsoleBuf *cbufio = CM_NULLPTR;
+  cmsys::ConsoleBuf *cbuferr = CM_NULLPTR;
+  std::streambuf *coutbuf = std::cout.rdbuf();
+  std::streambuf *cerrbuf = std::cerr.rdbuf();
+  try {
+    cbufio = new cmsys::ConsoleBuf();
+    coutbuf = std::cout.rdbuf(cbufio);
+    cbuferr = new cmsys::ConsoleBuf(true);
+    cerrbuf = std::cerr.rdbuf(cbuferr);
+  } catch (const std::runtime_error& ex) {
+    std::cerr << "Failed to create ConsoleBuf!" << std::endl
+              << ex.what() << std::endl;
+  };
+#endif
   cmsys::Encoding::CommandLineArguments args =
     cmsys::Encoding::CommandLineArguments::Main(ac, av);
   ac = args.argc();
@@ -171,6 +188,16 @@ int main(int ac, char const* const* av)
 #ifdef CMAKE_BUILD_WITH_CMAKE
   cmDynamicLoader::FlushCache();
 #endif
+#if defined(_WIN32)
+  if (cbufio) {
+    delete cbufio;
+    std::cout.rdbuf(coutbuf);
+  }
+  if (cbuferr) {
+    delete cbuferr;
+    std::cerr.rdbuf(cerrbuf);
+  }
+#endif
   return ret;
 }
 
diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt
index 65203c0..33a97e6 100644
--- a/Source/kwsys/CMakeLists.txt
+++ b/Source/kwsys/CMakeLists.txt
@@ -123,6 +123,7 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
   SET(KWSYS_USE_FStream 1)
   SET(KWSYS_USE_String 1)
   SET(KWSYS_USE_SystemInformation 1)
+  SET(KWSYS_USE_ConsoleBuf 1)
 ENDIF()
 
 # Enforce component dependencies.
@@ -154,6 +155,9 @@ ENDIF()
 IF(KWSYS_USE_FStream)
   SET(KWSYS_USE_Encoding 1)
 ENDIF()
+IF(KWSYS_USE_ConsoleBuf)
+  SET(KWSYS_USE_Encoding 1)
+ENDIF()
 
 # Setup the large file support default.
 IF(KWSYS_LFS_DISABLE)
@@ -668,7 +672,7 @@ SET(KWSYS_HXX_FILES Configure String
 # Add selected C++ classes.
 SET(cppclasses
   Directory DynamicLoader Encoding Glob RegularExpression SystemTools
-  CommandLineArguments IOStream FStream SystemInformation
+  CommandLineArguments IOStream FStream SystemInformation ConsoleBuf
   )
 FOREACH(cpp ${cppclasses})
   IF(KWSYS_USE_${cpp})
diff --git a/Source/kwsys/ConsoleBuf.hxx.in b/Source/kwsys/ConsoleBuf.hxx.in
new file mode 100644
index 0000000..4ee37dd
--- /dev/null
+++ b/Source/kwsys/ConsoleBuf.hxx.in
@@ -0,0 +1,282 @@
+/*============================================================================
+  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 <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;
+
+      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");
+        setActiveCodepage(true, "STD_INPUT_HANDLE");
+        m_hOutput = err ? ::GetStdHandle(STD_ERROR_HANDLE) :
+                          ::GetStdHandle(STD_OUTPUT_HANDLE);
+        checkHandle(false, err ? "STD_ERROR_HANDLE" : "STD_OUTPUT_HANDLE");
+        setActiveCodepage(false, err ? "STD_ERROR_HANDLE" : 
"STD_OUTPUT_HANDLE");
+        _setg();
+        _setp();
+      }
+
+      ~BasicConsoleBuf() {
+        sync();
+      }
+
+    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, ARRAYSIZE(wbuffer) - 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, ARRAYSIZE(buffer) - 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;
+      }
+      void setActiveCodepage(bool input, std::string handleName) {
+        std::ostringstream errmsg;
+        DWORD ft;
+        switch (ft = GetFileType(input ? m_hInput : m_hOutput)) {
+          case FILE_TYPE_DISK:
+            if (input) {
+               m_isConsoleInput = false;
+               m_activeInputCodepage = input_file_codepage;
+            } else {
+               m_isConsoleOutput = false;
+               m_activeOutputCodepage = output_file_codepage;
+            };
+            break;
+          case FILE_TYPE_CHAR:
+            if (input) {
+              m_isConsoleInput = true;
+            } else {
+              m_isConsoleOutput = true;
+            };
+            break;
+          case FILE_TYPE_PIPE:
+            if (input) {
+              m_isConsoleInput = false;
+              m_activeInputCodepage = input_pipe_codepage;
+            } else {
+              m_isConsoleOutput = false;
+              m_activeOutputCodepage = output_pipe_codepage;
+            };
+            break;
+          default:
+            errmsg << "GetFileType for " << handleName << " returned unknown 
file type " << ft;
+#if __cplusplus >= 201103L
+            throw std::system_error(::GetLastError(), std::system_category(), 
errmsg.str());
+#else
+            throw std::runtime_error(errmsg.str());
+#endif
+        }
+        if (input && !m_isConsoleInput && m_activeInputCodepage == 0) {
+           m_activeInputCodepage = getConsolesCodepage();
+        } else if (!input && !m_isConsoleOutput && m_activeOutputCodepage == 
0) {
+           m_activeOutputCodepage = getConsolesCodepage();
+        }
+      }
+      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
+
diff --git a/bootstrap b/bootstrap
index ad0c8ec..0411c64 100755
--- a/bootstrap
+++ b/bootstrap
@@ -365,6 +365,7 @@ KWSYS_CXX_SOURCES="\
   SystemTools"
 
 KWSYS_FILES="\
+  ConsoleBuf.hxx \
   Directory.hxx \
   Encoding.h \
   Encoding.hxx \
-- 
2.9.0

-- 

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: 
http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more 
information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at 
http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/cmake-developers

Reply via email to