[cmake-developers] [PATCH v5] For consoles output on Windows use our own std::streambuf

Dāvis Mosāns davispuh at gmail.com
Wed Jul 27 12:54:04 EDT 2016


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                 |   6 +
 Source/kwsys/CMakeLists.txt          |  17 +-
 Source/kwsys/ConsoleBuf.hxx.in       | 318 ++++++++++++++++++++++++++
 Source/kwsys/testConsoleBuf.cxx      | 424 +++++++++++++++++++++++++++++++++++
 Source/kwsys/testConsoleBuf.hxx      |  25 +++
 Source/kwsys/testConsoleBufChild.cxx |  59 +++++
 bootstrap                            |   1 +
 8 files changed, 850 insertions(+), 1 deletion(-)
 create mode 100644 Source/kwsys/ConsoleBuf.hxx.in
 create mode 100644 Source/kwsys/testConsoleBuf.cxx
 create mode 100644 Source/kwsys/testConsoleBuf.hxx
 create mode 100644 Source/kwsys/testConsoleBufChild.cxx

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..8b10d50 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,11 @@ 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::Manager consoleOut(std::cout);
+  cmsys::ConsoleBuf::Manager consoleErr(std::cerr, true);
+#endif
   cmsys::Encoding::CommandLineArguments args =
     cmsys::Encoding::CommandLineArguments::Main(ac, av);
   ac = args.argc();
diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt
index 5fce50f..a211425 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)
@@ -673,7 +677,7 @@ SET(KWSYS_HXX_FILES Configure String ProcessOutput
 # 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})
@@ -926,6 +930,17 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
         testFStream
         )
     ENDIF()
+    IF(KWSYS_USE_ConsoleBuf)
+      IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
+        add_compile_options(/utf-8)
+      ENDIF()
+      ADD_EXECUTABLE(testConsoleBufChild testConsoleBufChild.cxx)
+      SET_PROPERTY(TARGET testConsoleBufChild PROPERTY LABELS ${KWSYS_LABELS_EXE})
+      TARGET_LINK_LIBRARIES(testConsoleBufChild ${KWSYS_NAMESPACE})
+      SET(KWSYS_CXX_TESTS ${KWSYS_CXX_TESTS}
+        testConsoleBuf
+        )
+    ENDIF()
     IF(KWSYS_USE_SystemInformation)
       SET(KWSYS_CXX_TESTS ${KWSYS_CXX_TESTS} testSystemInformation)
     ENDIF()
diff --git a/Source/kwsys/ConsoleBuf.hxx.in b/Source/kwsys/ConsoleBuf.hxx.in
new file mode 100644
index 0000000..ed95bb0
--- /dev/null
+++ b/Source/kwsys/ConsoleBuf.hxx.in
@@ -0,0 +1,318 @@
+/*============================================================================
+  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 at _ConsoleBuf_hxx
+#define @KWSYS_NAMESPACE at _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 at _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");
+        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();
+      }
+
+      void activateCodepageChange()
+      {
+        setActiveCodepage(true, "");
+        setActiveCodepage(false, "");
+      }
+
+    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/Source/kwsys/testConsoleBuf.cxx b/Source/kwsys/testConsoleBuf.cxx
new file mode 100644
index 0000000..3dc395a
--- /dev/null
+++ b/Source/kwsys/testConsoleBuf.cxx
@@ -0,0 +1,424 @@
+/*============================================================================
+  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.
+============================================================================*/
+#include "kwsysPrivate.h"
+
+#include KWSYS_HEADER(Encoding.hxx)
+
+// Work-around CMake dependency scanning limitation.  This must
+// duplicate the above list of headers.
+#if 0
+# include "Encoding.hxx.in"
+#endif
+
+#if defined(_WIN32)
+
+#include <windows.h>
+#include <iostream>
+#include <locale>
+#include <codecvt>
+#include "testConsoleBuf.hxx"
+
+// يونيكود
+static const WCHAR UnicodeInputTestString[] = L"\u064A\u0648\u0646\u064A\u0643\u0648\u062F!";
+static const UINT TestCodepage = 1251; // ANSI Cyrillic
+
+static const DWORD waitTimeout = 10 * 1000;
+static STARTUPINFO startupInfo;
+static PROCESS_INFORMATION processInfo;
+static HANDLE syncEvent;
+
+//----------------------------------------------------------------------------
+static bool createProcess(HANDLE hIn, HANDLE hOut, HANDLE hErr)
+{
+  BOOL bInheritHandles = FALSE;
+  DWORD dwCreationFlags = 0;
+  SecureZeroMemory(&processInfo, sizeof(processInfo));
+  SecureZeroMemory(&startupInfo, sizeof(startupInfo));
+  startupInfo.cb = sizeof(startupInfo);
+  startupInfo.dwFlags = STARTF_USESHOWWINDOW;
+  startupInfo.wShowWindow = SW_HIDE;
+  if (hIn || hOut || hErr) {
+    startupInfo.dwFlags |= STARTF_USESTDHANDLES;
+    startupInfo.hStdInput = hIn;
+    startupInfo.hStdOutput = hOut;
+    startupInfo.hStdError = hErr;
+    bInheritHandles = TRUE;
+  }
+
+  WCHAR cmd[MAX_PATH];
+  if (GetModuleFileNameW(NULL, cmd, MAX_PATH) == 0) {
+    std::cerr << "GetModuleFileName failed!" << std::endl;
+    return false;
+  }
+  WCHAR *p = cmd + wcslen(cmd);
+  while (p > cmd && *p != L'\\') p--;
+  *(p+1) = 0;
+  wcscat_s(cmd, MAX_PATH, cmdConsoleBufChild);
+
+  bool success = CreateProcessW(NULL,             // No module name (use command line)
+                                cmd,              // Command line
+                                NULL,             // Process handle not inheritable
+                                NULL,             // Thread handle not inheritable
+                                bInheritHandles,  // Set handle inheritance
+                                dwCreationFlags,
+                                NULL,             // Use parent's environment block
+                                NULL,             // Use parent's starting directory
+                                &startupInfo,     // Pointer to STARTUPINFO structure
+                                &processInfo) != 0; // Pointer to PROCESS_INFORMATION structure
+  if (!success) {
+      std::cerr << "CreateProcess(" << kwsys::Encoding::ToNarrow(cmd) << ") failed!" << std::endl;
+  }
+  return success;
+}
+
+//----------------------------------------------------------------------------
+static void finishProcess(bool success)
+{
+  if (success) {
+    success = WaitForSingleObject(processInfo.hProcess, waitTimeout) == WAIT_OBJECT_0;
+  };
+  if (!success) {
+    TerminateProcess(processInfo.hProcess, 1);
+  }
+  CloseHandle(processInfo.hProcess);
+  CloseHandle(processInfo.hThread);
+}
+
+//----------------------------------------------------------------------------
+static bool createPipe(PHANDLE readPipe, PHANDLE writePipe)
+{
+  SECURITY_ATTRIBUTES securityAttributes;
+  securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+  securityAttributes.bInheritHandle = TRUE;
+  securityAttributes.lpSecurityDescriptor = NULL;
+  return CreatePipe(readPipe, writePipe, &securityAttributes, 0) == 0 ? false : true;
+}
+
+//----------------------------------------------------------------------------
+static void finishPipe(HANDLE readPipe, HANDLE writePipe)
+{
+  if (readPipe != INVALID_HANDLE_VALUE) {
+    CloseHandle(readPipe);
+  }
+  if (writePipe != INVALID_HANDLE_VALUE) {
+    CloseHandle(writePipe);
+  }
+}
+
+//----------------------------------------------------------------------------
+static HANDLE createFile(LPCWSTR fileName)
+{
+  SECURITY_ATTRIBUTES securityAttributes;
+  securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+  securityAttributes.bInheritHandle = TRUE;
+  securityAttributes.lpSecurityDescriptor = NULL;
+
+  HANDLE file = CreateFileW(fileName,
+                            GENERIC_READ | GENERIC_WRITE,
+                            0,                    // do not share
+                            &securityAttributes,
+                            CREATE_ALWAYS,        // overwrite existing
+                            FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
+                            NULL);                // no template
+  if (file == INVALID_HANDLE_VALUE) {
+    std::cerr << "CreateFile(" << kwsys::Encoding::ToNarrow(fileName) << ") failed!" << std::endl;
+  }
+  return file;
+}
+
+//----------------------------------------------------------------------------
+static void finishFile(HANDLE file)
+{
+  if (file != INVALID_HANDLE_VALUE) {
+    CloseHandle(file);
+  }
+}
+
+//----------------------------------------------------------------------------
+static void writeInputKeyEvent(INPUT_RECORD inputBuffer[], WCHAR chr)
+{
+  inputBuffer[0].EventType = KEY_EVENT;
+  inputBuffer[0].Event.KeyEvent.bKeyDown = TRUE;
+  inputBuffer[0].Event.KeyEvent.wRepeatCount = 1;
+  SHORT keyCode = VkKeyScanW(chr);
+  if (keyCode == -1) {
+    // Character can't be entered with current keyboard layout
+    // Just set any, it doesn't really matter
+    keyCode = 'K';
+  }
+  inputBuffer[0].Event.KeyEvent.wVirtualKeyCode = LOBYTE(keyCode);
+  inputBuffer[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(inputBuffer[0].Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC);
+  inputBuffer[0].Event.KeyEvent.uChar.UnicodeChar = chr;
+  inputBuffer[0].Event.KeyEvent.dwControlKeyState = 0;
+  if ((HIBYTE(keyCode) & 1) == 1) {
+    inputBuffer[0].Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
+  }
+  if ((HIBYTE(keyCode) & 2) == 2) {
+    inputBuffer[0].Event.KeyEvent.dwControlKeyState |= RIGHT_CTRL_PRESSED;
+  }
+  if ((HIBYTE(keyCode) & 4) == 4) {
+    inputBuffer[0].Event.KeyEvent.dwControlKeyState |= RIGHT_ALT_PRESSED;
+  }
+  inputBuffer[1].EventType = inputBuffer[0].EventType;
+  inputBuffer[1].Event.KeyEvent.bKeyDown = FALSE;
+  inputBuffer[1].Event.KeyEvent.wRepeatCount = 1;
+  inputBuffer[1].Event.KeyEvent.wVirtualKeyCode = inputBuffer[0].Event.KeyEvent.wVirtualKeyCode;
+  inputBuffer[1].Event.KeyEvent.wVirtualScanCode = inputBuffer[0].Event.KeyEvent.wVirtualScanCode;
+  inputBuffer[1].Event.KeyEvent.uChar.UnicodeChar = inputBuffer[0].Event.KeyEvent.uChar.UnicodeChar;
+  inputBuffer[1].Event.KeyEvent.dwControlKeyState = 0;
+}
+
+//----------------------------------------------------------------------------
+static int testPipe()
+{
+  int didFail = 1;
+  HANDLE inPipeRead = INVALID_HANDLE_VALUE;
+  HANDLE inPipeWrite = INVALID_HANDLE_VALUE;
+  HANDLE outPipeRead = INVALID_HANDLE_VALUE;
+  HANDLE outPipeWrite = INVALID_HANDLE_VALUE;
+  HANDLE errPipeRead = INVALID_HANDLE_VALUE;
+  HANDLE errPipeWrite = INVALID_HANDLE_VALUE;
+  UINT currentCodepage = GetConsoleCP();
+  char buffer[200];
+  std::string encodedInputTestString;
+  std::string encodedTestString;
+  try {
+    if (!createPipe(&inPipeRead, &inPipeWrite) ||
+        !createPipe(&outPipeRead, &outPipeWrite) ||
+        !createPipe(&errPipeRead, &errPipeWrite)) {
+      throw std::runtime_error("createFile failed!");
+    }
+    if (!SetConsoleCP(TestCodepage)) {
+      throw std::runtime_error("SetConsoleCP failed!");
+    }
+
+    if (WideCharToMultiByte(TestCodepage, 0, UnicodeTestString, -1, buffer, sizeof(buffer), NULL, NULL) == 0) {
+      throw std::runtime_error("WideCharToMultiByte failed!");
+    }
+    encodedTestString = buffer;
+
+    if (WideCharToMultiByte(TestCodepage, 0, UnicodeInputTestString, -1, buffer, sizeof(buffer), NULL, NULL) == 0) {
+      throw std::runtime_error("WideCharToMultiByte failed!");
+    }
+    encodedInputTestString = buffer;
+    encodedInputTestString += "\n";
+
+    DWORD bytesWritten = 0;
+    if (!WriteFile(inPipeWrite, encodedInputTestString.c_str(), (DWORD)encodedInputTestString.size(), &bytesWritten, NULL) || bytesWritten == 0) {
+      throw std::runtime_error("WriteFile failed!");
+    }
+
+    if (createProcess(inPipeRead, outPipeWrite, errPipeWrite)) {
+      try {
+        Sleep(100);
+        if (WaitForSingleObject(syncEvent, waitTimeout) != WAIT_OBJECT_0) {
+          throw std::runtime_error("WaitForSingleObject failed!");
+        }
+        DWORD bytesRead = 0;
+        if (!ReadFile(outPipeRead, buffer, sizeof(buffer), &bytesRead, NULL) || bytesRead == 0) {
+          throw std::runtime_error("ReadFile failed!");
+        }
+        if (memcmp(buffer, encodedTestString.c_str(), encodedTestString.size()) == 0 &&
+            memcmp(buffer + encodedTestString.size() + 1, encodedInputTestString.c_str(), encodedInputTestString.size()) == 0) {
+          bytesRead = 0;
+          if (!ReadFile(errPipeRead, buffer, sizeof(buffer), &bytesRead, NULL) || bytesRead == 0) {
+            throw std::runtime_error("ReadFile failed!");
+          }
+          buffer[bytesRead - 1] = 0;
+          didFail = encodedTestString.compare(buffer) == 0 ? 0 : 1;
+        }
+      } catch (const std::runtime_error &ex) {
+        std::cerr << ex.what() << std::endl << std::flush;
+      }
+      finishProcess(didFail == 0);
+    }
+  } catch (const std::runtime_error &ex) {
+    std::cerr << ex.what() << std::endl << std::flush;
+  }
+  finishPipe(inPipeRead, inPipeWrite);
+  finishPipe(outPipeRead, outPipeWrite);
+  finishPipe(errPipeRead, errPipeWrite);
+  SetConsoleCP(currentCodepage);
+  return didFail;
+}
+
+//----------------------------------------------------------------------------
+static int testFile()
+{
+  int didFail = 1;
+  HANDLE inFile = INVALID_HANDLE_VALUE;
+  HANDLE outFile = INVALID_HANDLE_VALUE;
+  HANDLE errFile = INVALID_HANDLE_VALUE;
+  try {
+    if ((inFile = createFile(L"stdinFile.txt")) == INVALID_HANDLE_VALUE ||
+        (outFile = createFile(L"stdoutFile.txt")) == INVALID_HANDLE_VALUE ||
+        (errFile = createFile(L"stderrFile.txt")) == INVALID_HANDLE_VALUE) {
+      throw std::runtime_error("createFile failed!");
+    }
+    int length = 0;
+    DWORD bytesWritten = 0;
+    char buffer[200];
+
+    if ((length = WideCharToMultiByte(CP_UTF8, 0, UnicodeInputTestString, -1, buffer, sizeof(buffer), NULL, NULL)) == 0) {
+      throw std::runtime_error("WideCharToMultiByte failed!");
+    }
+    buffer[length - 1] = '\n';
+    if (!WriteFile(inFile, buffer, length, &bytesWritten, NULL) || bytesWritten == 0) {
+      throw std::runtime_error("WriteFile failed!");
+    }
+    if (SetFilePointer(inFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {
+      throw std::runtime_error("SetFilePointer failed!");
+    }
+
+    if (createProcess(inFile, outFile, errFile)) {
+      DWORD bytesRead = 0;
+      WCHAR wbuffer[200];
+      std::wstring wstr;
+      try {
+        Sleep(100);
+        if (WaitForSingleObject(syncEvent, waitTimeout) != WAIT_OBJECT_0) {
+          throw std::runtime_error("WaitForSingleObject failed!");
+        }
+        if (SetFilePointer(outFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {
+          throw std::runtime_error("SetFilePointer failed!");
+        }
+        if (!ReadFile(outFile, buffer, sizeof(buffer), &bytesRead, NULL) || bytesRead == 0) {
+          throw std::runtime_error("ReadFile failed!");
+        }
+        buffer[bytesRead - 1] = 0;
+        if (MultiByteToWideChar(CP_UTF8, 0, buffer, -1, wbuffer, sizeof(wbuffer)) == 0) {
+          throw std::runtime_error("MultiByteToWideChar failed!");
+        }
+        wstr = wbuffer;
+        if (wstr.compare(std::wstring(UnicodeTestString) + L"\n" + UnicodeInputTestString) == 0) {
+          bytesRead = 0;
+          if (SetFilePointer(errFile, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {
+            throw std::runtime_error("SetFilePointer failed!");
+          }
+          if (!ReadFile(errFile, buffer, sizeof(buffer), &bytesRead, NULL) || bytesRead == 0) {
+            throw std::runtime_error("ReadFile failed!");
+          }
+          buffer[bytesRead - 1] = 0;
+          if (MultiByteToWideChar(CP_UTF8, 0, buffer, -1, wbuffer, sizeof(wbuffer)) == 0) {
+            throw std::runtime_error("MultiByteToWideChar failed!");
+          }
+          wstr = wbuffer;
+          didFail = wstr.compare(UnicodeTestString) == 0 ? 0 : 1;
+        }
+      } catch (const std::runtime_error &ex) {
+        std::cerr << ex.what() << std::endl << std::flush;
+      }
+      finishProcess(didFail == 0);
+    }
+  } catch (const std::runtime_error &ex) {
+    std::cerr << ex.what() << std::endl << std::flush;
+  }
+  finishFile(inFile);
+  finishFile(outFile);
+  finishFile(errFile);
+  return didFail;
+}
+
+//----------------------------------------------------------------------------
+static int testConsole()
+{
+  int didFail = 1;
+  if (createProcess(NULL, NULL, NULL)) {
+    try {
+      if (WaitForSingleObject(syncEvent, waitTimeout) != WAIT_OBJECT_0) {
+        throw std::runtime_error("WaitForSingleObject failed!");
+      }
+      HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+      HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+      if (hIn  == INVALID_HANDLE_VALUE || hIn == NULL ||
+          hOut == INVALID_HANDLE_VALUE || hOut == NULL) {
+        throw std::runtime_error("GetStdHandle failed!");
+      }
+      if (GetFileType(hOut) != FILE_TYPE_CHAR) {
+        // Not a console so we can't test this, just skip
+        // we might be running under MSYS2 (mintty)
+        didFail = 0;
+        TerminateProcess(processInfo.hProcess, 0);
+        throw std::runtime_error("Not a Console, skipping this test!");
+      }
+
+      INPUT_RECORD inputBuffer[(sizeof(UnicodeInputTestString) / sizeof(UnicodeInputTestString[0])) * 2];
+      SecureZeroMemory(&inputBuffer, sizeof(inputBuffer));
+      unsigned int i = 0;
+      for (i = 0; i < (sizeof(UnicodeInputTestString) / sizeof(UnicodeInputTestString[0]) - 1); i++) {
+        writeInputKeyEvent(&inputBuffer[i*2], UnicodeInputTestString[i]);
+      }
+      writeInputKeyEvent(&inputBuffer[i*2], VK_RETURN);
+      DWORD eventsWritten = 0;
+      if (!WriteConsoleInputW(hIn, inputBuffer, sizeof(inputBuffer) / sizeof(inputBuffer[0]), &eventsWritten) || eventsWritten == 0) {
+        throw std::runtime_error("WriteConsoleInput failed!");
+      }
+      if (WaitForSingleObject(syncEvent, waitTimeout) != WAIT_OBJECT_0) {
+        throw std::runtime_error("WaitForSingleObject failed!");
+      }
+      CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
+      if (!GetConsoleScreenBufferInfo(hOut, &screenBufferInfo)) {
+        throw std::runtime_error("GetConsoleScreenBufferInfo failed!");
+      }
+
+      COORD coord;
+      DWORD charsRead = 0;
+      coord.X = 0;
+      coord.Y = screenBufferInfo.dwCursorPosition.Y - 4;
+      WCHAR *outputBuffer = new WCHAR[screenBufferInfo.dwSize.X * 4];
+      if (!ReadConsoleOutputCharacterW(hOut, outputBuffer, screenBufferInfo.dwSize.X * 4, coord, &charsRead) || charsRead == 0) {
+        delete[] outputBuffer;
+        throw std::runtime_error("ReadConsoleOutputCharacter failed!");
+      }
+      if (memcmp(outputBuffer, UnicodeTestString, sizeof(UnicodeTestString) - sizeof(WCHAR)) == 0 &&
+          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 1, UnicodeTestString, sizeof(UnicodeTestString) - sizeof(WCHAR)) == 0 &&
+          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 2, UnicodeInputTestString, sizeof(UnicodeInputTestString) - sizeof(WCHAR)) == 0 &&
+          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 3, UnicodeInputTestString, sizeof(UnicodeInputTestString) - sizeof(WCHAR)) == 0
+      ) {
+        didFail = 0;
+      }
+      delete[] outputBuffer;
+    } catch (const std::runtime_error &ex) {
+      std::cerr << ex.what() << std::endl << std::flush;
+    }
+    finishProcess(didFail == 0);
+  }
+  return didFail;
+}
+
+#endif
+
+//----------------------------------------------------------------------------
+int testConsoleBuf(int, char*[])
+{
+  int ret = 0;
+
+#if defined(_WIN32)
+  syncEvent = CreateEventW(NULL,
+                           FALSE,  // auto-reset event
+                           FALSE,  // initial state is nonsignaled
+                           SyncEventName);  // object name
+  if (!syncEvent) {
+    std::cerr << "CreateEvent failed " << GetLastError() << std::endl;
+    return 1;
+  }
+
+  ret |= testPipe();
+  ret |= testFile();
+  ret |= testConsole();
+
+  CloseHandle(syncEvent);
+#endif
+
+  return ret;
+}
+
diff --git a/Source/kwsys/testConsoleBuf.hxx b/Source/kwsys/testConsoleBuf.hxx
new file mode 100644
index 0000000..6736be6
--- /dev/null
+++ b/Source/kwsys/testConsoleBuf.hxx
@@ -0,0 +1,25 @@
+/*============================================================================
+  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 testConsoleBuf_hxx
+#define testConsoleBuf_hxx
+
+static const wchar_t cmdConsoleBufChild[] = L"testConsoleBufChild";
+
+static const wchar_t SyncEventName[] = L"SyncEvent";
+
+// यूनिकोड είναι здорово!
+static const wchar_t UnicodeTestString[] = L"\u092F\u0942\u0928\u093F\u0915\u094B\u0921 "
+                                           L"\u03B5\u03AF\u03BD\u03B1\u03B9 "
+                                           L"\u0437\u0434\u043E\u0440\u043E\u0432\u043E!";
+
+#endif
+
diff --git a/Source/kwsys/testConsoleBufChild.cxx b/Source/kwsys/testConsoleBufChild.cxx
new file mode 100644
index 0000000..8608b72
--- /dev/null
+++ b/Source/kwsys/testConsoleBufChild.cxx
@@ -0,0 +1,59 @@
+/*============================================================================
+  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.
+============================================================================*/
+#include "kwsysPrivate.h"
+
+#include KWSYS_HEADER(ConsoleBuf.hxx)
+#include KWSYS_HEADER(Encoding.hxx)
+
+// Work-around CMake dependency scanning limitation.  This must
+// duplicate the above list of headers.
+#if 0
+# include "ConsoleBuf.hxx.in"
+# include "Encoding.hxx.in"
+#endif
+
+#include <iostream>
+#include "testConsoleBuf.hxx"
+
+//----------------------------------------------------------------------------
+int main(int argc, const char* argv[])
+{
+#if defined(_WIN32)
+  kwsys::ConsoleBuf::Manager out(std::cout);
+  kwsys::ConsoleBuf::Manager err(std::cerr, true);
+  kwsys::ConsoleBuf::Manager in(std::cin);
+
+  if (argc > 1) {
+    std::cout << argv[1] << std::endl;
+    std::cerr << argv[1] << std::endl;
+  } else {
+    std::string str = kwsys::Encoding::ToNarrow(UnicodeTestString);
+    std::cout << str << std::endl;
+    std::cerr << str << std::endl;
+  }
+
+  std::string input;
+  HANDLE syncEvent = OpenEventW(EVENT_MODIFY_STATE, FALSE, SyncEventName);
+  if (syncEvent) {
+    SetEvent(syncEvent);
+  }
+
+  std::cin >> input;
+  std::cout << input << std::endl;
+  if (syncEvent) {
+    SetEvent(syncEvent);
+    CloseHandle(syncEvent);
+  }
+#endif
+  return 0;
+}
+
diff --git a/bootstrap b/bootstrap
index aa7307d..5fe4e0e 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



More information about the cmake-developers mailing list