From 669e3a069e309eba155faf3a2af85802147a18a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= <davispuh@gmail.com>
Date: Wed, 27 Jul 2016 19:54:04 +0300
Subject: [PATCH] ConsoleBuf: Use a custom std::streambuf for console output on
 Windows

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.

Change-Id: I33053aa229796e84088aa3beb0ebe8bdbebaf3d1
---
 CMakeLists.txt          |  20 +-
 ConsoleBuf.hxx.in       | 348 +++++++++++++++++++++++
 testConsoleBuf.cxx      | 603 ++++++++++++++++++++++++++++++++++++++++
 testConsoleBuf.hxx      |  24 ++
 testConsoleBufChild.cxx |  61 ++++
 5 files changed, 1055 insertions(+), 1 deletion(-)
 create mode 100644 ConsoleBuf.hxx.in
 create mode 100644 testConsoleBuf.cxx
 create mode 100644 testConsoleBuf.hxx
 create mode 100644 testConsoleBufChild.cxx

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 87f6048..c4fe5e8 100644
--- a/CMakeLists.txt
+++ b/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
 # 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,20 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
         testFStream
         )
     ENDIF()
+    IF(KWSYS_USE_ConsoleBuf)
+      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
+        )
+      IF("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xMSVC" AND
+         NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.0")
+        set_property(SOURCE testConsoleBuf.cxx testConsoleBufChild.cxx PROPERTY COMPILE_FLAGS /utf-8)
+      ENDIF()
+      SET_PROPERTY(SOURCE testConsoleBuf.cxx APPEND PROPERTY COMPILE_DEFINITIONS
+        KWSYS_ENCODING_DEFAULT_CODEPAGE=${KWSYS_ENCODING_DEFAULT_CODEPAGE})
+    ENDIF()
     IF(KWSYS_USE_SystemInformation)
       SET(KWSYS_CXX_TESTS ${KWSYS_CXX_TESTS} testSystemInformation)
     ENDIF()
diff --git a/ConsoleBuf.hxx.in b/ConsoleBuf.hxx.in
new file mode 100644
index 0000000..8aeeda1
--- /dev/null
+++ b/ConsoleBuf.hxx.in
@@ -0,0 +1,348 @@
+/*============================================================================
+  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
diff --git a/testConsoleBuf.cxx b/testConsoleBuf.cxx
new file mode 100644
index 0000000..591a2d1
--- /dev/null
+++ b/testConsoleBuf.cxx
@@ -0,0 +1,603 @@
+/*============================================================================
+  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 <string.h>
+#include <wchar.h>
+#include <iostream>
+#include <stdexcept>
+#include "testConsoleBuf.hxx"
+
+#if defined(_MSC_VER) && _MSC_VER >= 1800
+# define KWSYS_WINDOWS_DEPRECATED_GetVersion
+#endif
+// يونيكود
+static const WCHAR UnicodeInputTestString[] = L"\u064A\u0648\u0646\u064A\u0643\u0648\u062F!";
+static UINT TestCodepage = KWSYS_ENCODING_DEFAULT_CODEPAGE;
+
+static const DWORD waitTimeout = 10 * 1000;
+static STARTUPINFO startupInfo;
+static PROCESS_INFORMATION processInfo;
+static HANDLE syncEvent;
+static std::string encodedInputTestString;
+static std::string encodedTestString;
+
+//----------------------------------------------------------------------------
+static bool createProcess(HANDLE hIn, HANDLE hOut, HANDLE hErr)
+{
+  BOOL bInheritHandles = FALSE;
+  DWORD dwCreationFlags = 0;
+  memset(&processInfo, 0, sizeof(processInfo));
+  memset(&startupInfo, 0, 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(cmd, cmdConsoleBufChild);
+  wcscat(cmd, L".exe");
+
+  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) {
+    DWORD lastError = GetLastError();
+    std::cerr.setf(std::ios::hex, std::ios::basefield);
+    std::cerr << "CreateProcess(" << kwsys::Encoding::ToNarrow(cmd)
+              << ") failed with error: 0x" << lastError << "!" << std::endl;
+    LPWSTR message;
+    if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+                       NULL,
+                       lastError,
+                       0,
+                       (LPWSTR)&message, 0,
+                       NULL)
+    ) {
+      std::cerr << "Error message: " << kwsys::Encoding::ToNarrow(message) << std::endl;
+      HeapFree(GetProcessHeap(), 0, message);
+    } else {
+      std::cerr << "FormatMessage() failed with error: 0x" << GetLastError() << "!" << std::endl;
+    }
+    std::cerr.unsetf(std::ios::hex);
+  }
+  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);
+  }
+}
+
+//----------------------------------------------------------------------------
+
+#ifndef MAPVK_VK_TO_VSC
+#  define MAPVK_VK_TO_VSC  (0)
+#endif
+
+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];
+  try {
+    if (!createPipe(&inPipeRead, &inPipeWrite) ||
+        !createPipe(&outPipeRead, &outPipeWrite) ||
+        !createPipe(&errPipeRead, &errPipeWrite)) {
+      throw std::runtime_error("createFile failed!");
+    }
+    if (TestCodepage == CP_ACP) {
+      TestCodepage = GetACP();
+    }
+    if (!SetConsoleCP(TestCodepage)) {
+      throw std::runtime_error("SetConsoleCP failed!");
+    }
+
+    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 ((bytesRead < encodedTestString.size() + 1 + encodedInputTestString.size()
+             && !ReadFile(outPipeRead, buffer + bytesRead,
+                          sizeof(buffer) - bytesRead, &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;
+        }
+        if (didFail != 0) {
+          std::cerr << "Pipe's output didn't match expected output!" << std::endl << std::flush;
+        }
+      } 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(TestCodepage, 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;
+      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 (memcmp(buffer, encodedTestString.c_str(),
+                   encodedTestString.size()) == 0 &&
+            memcmp(buffer + encodedTestString.size() + 1,
+                   encodedInputTestString.c_str(),
+                   encodedInputTestString.size() - 1) == 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;
+          didFail = encodedTestString.compare(buffer) == 0 ? 0 : 1;
+        }
+        if (didFail != 0) {
+          std::cerr << "File's output didn't match expected output!" << std::endl << std::flush;
+        }
+      } 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;
+}
+
+#ifndef _WIN32_WINNT_VISTA
+# define _WIN32_WINNT_VISTA 0x0600
+#endif
+
+//----------------------------------------------------------------------------
+static int testConsole()
+{
+  int didFail = 1;
+  HANDLE parentIn = GetStdHandle(STD_INPUT_HANDLE);
+  HANDLE parentOut = GetStdHandle(STD_OUTPUT_HANDLE);
+  HANDLE parentErr = GetStdHandle(STD_ERROR_HANDLE);
+  HANDLE hIn = parentIn;
+  HANDLE hOut = parentOut;
+  DWORD consoleMode;
+  bool newConsole = false;
+  bool forceNewConsole = false;
+  bool restoreConsole = false;
+  LPCWSTR TestFaceName = L"Lucida Console";
+  const DWORD TestFontFamily = 0x00000036;
+  const DWORD TestFontSize = 0x000c0000;
+  HKEY hConsoleKey;
+  WCHAR FaceName[200];
+  DWORD FaceNameSize = sizeof(FaceName);
+  DWORD FontFamily = TestFontFamily;
+  DWORD FontSize = TestFontSize;
+#ifdef KWSYS_WINDOWS_DEPRECATED_GetVersion
+# pragma warning (push)
+# ifdef __INTEL_COMPILER
+#  pragma warning (disable:1478)
+# else
+#  pragma warning (disable:4996)
+# endif
+#endif
+  const bool isVistaOrGreater = LOBYTE(LOWORD(GetVersion())) >= HIBYTE(_WIN32_WINNT_VISTA);
+#ifdef KWSYS_WINDOWS_DEPRECATED_GetVersion
+# pragma warning (pop)
+#endif
+  if (!isVistaOrGreater) {
+    if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Console", 0, KEY_READ | KEY_WRITE,
+                      &hConsoleKey) == ERROR_SUCCESS) {
+      DWORD dwordSize = sizeof(DWORD);
+      if (RegQueryValueExW(hConsoleKey, L"FontFamily", NULL, NULL,
+                           (LPBYTE)&FontFamily, &dwordSize) == ERROR_SUCCESS) {
+        if (FontFamily != TestFontFamily) {
+          RegQueryValueExW(hConsoleKey, L"FaceName", NULL, NULL,
+                           (LPBYTE)FaceName, &FaceNameSize);
+          RegQueryValueExW(hConsoleKey, L"FontSize", NULL, NULL,
+                           (LPBYTE)&FontSize, &dwordSize);
+
+          RegSetValueExW(hConsoleKey, L"FontFamily", 0, REG_DWORD,
+                         (BYTE *)&TestFontFamily, sizeof(TestFontFamily));
+          RegSetValueExW(hConsoleKey, L"FaceName", 0, REG_SZ,
+                         (BYTE *)TestFaceName, (DWORD)((wcslen(TestFaceName) + 1) * sizeof(WCHAR)));
+          RegSetValueExW(hConsoleKey, L"FontSize", 0, REG_DWORD,
+                         (BYTE *)&TestFontSize, sizeof(TestFontSize));
+
+          restoreConsole = true;
+          forceNewConsole = true;
+        }
+      } else {
+        std::cerr << "RegGetValueW(FontFamily) failed!" << std::endl << std::flush;
+      }
+      RegCloseKey(hConsoleKey);
+    } else {
+      std::cerr << "RegOpenKeyExW(HKEY_CURRENT_USER\\Console) failed!" << std::endl << std::flush;
+    }
+  }
+  if (forceNewConsole || GetConsoleMode(parentOut, &consoleMode) == 0) {
+    // Not a real console, let's create new one.
+    FreeConsole();
+    if (!AllocConsole()) {
+      std::cerr << "AllocConsole failed!" << std::endl << std::flush;
+      return didFail;
+    }
+    SECURITY_ATTRIBUTES securityAttributes;
+    securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+    securityAttributes.bInheritHandle = TRUE;
+    securityAttributes.lpSecurityDescriptor = NULL;
+    hIn  = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, 0,
+                       &securityAttributes, OPEN_EXISTING, 0, NULL);
+    hOut = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, 0,
+                       &securityAttributes, OPEN_EXISTING, 0, NULL);
+    SetStdHandle(STD_INPUT_HANDLE, hIn);
+    SetStdHandle(STD_OUTPUT_HANDLE, hOut);
+    SetStdHandle(STD_ERROR_HANDLE, hOut);
+    newConsole = true;
+  }
+
+#if _WIN32_WINNT >= _WIN32_WINNT_VISTA
+  if (isVistaOrGreater) {
+    CONSOLE_FONT_INFOEX consoleFont;
+    memset(&consoleFont, 0, sizeof(consoleFont));
+    consoleFont.cbSize = sizeof(consoleFont);
+    HMODULE kernel32 = LoadLibraryW(L"kernel32.dll");
+    typedef BOOL (WINAPI *GetCurrentConsoleFontExFunc)(HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
+    typedef BOOL (WINAPI *SetCurrentConsoleFontExFunc)(HANDLE hConsoleOutput, BOOL bMaximumWindow, PCONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);
+    GetCurrentConsoleFontExFunc getConsoleFont = (GetCurrentConsoleFontExFunc)GetProcAddress(kernel32, "GetCurrentConsoleFontEx");
+    SetCurrentConsoleFontExFunc setConsoleFont = (SetCurrentConsoleFontExFunc)GetProcAddress(kernel32, "SetCurrentConsoleFontEx");
+    if (getConsoleFont(hOut, FALSE, &consoleFont)) {
+      if (consoleFont.FontFamily != TestFontFamily) {
+        consoleFont.FontFamily = TestFontFamily;
+        wcscpy(consoleFont.FaceName, TestFaceName);
+        if (!setConsoleFont(hOut, FALSE, &consoleFont)) {
+          std::cerr << "SetCurrentConsoleFontEx failed!" << std::endl << std::flush;
+        }
+      }
+    } else {
+      std::cerr << "GetCurrentConsoleFontEx failed!" << std::endl << std::flush;
+    }
+  } else {
+#endif
+    if (restoreConsole && RegOpenKeyExW(HKEY_CURRENT_USER, L"Console", 0,
+                                        KEY_WRITE, &hConsoleKey) == ERROR_SUCCESS) {
+      RegSetValueExW(hConsoleKey, L"FontFamily", 0, REG_DWORD,
+                     (BYTE *)&FontFamily, sizeof(FontFamily));
+      RegSetValueExW(hConsoleKey, L"FaceName", 0, REG_SZ,
+                     (BYTE *)FaceName, FaceNameSize);
+      RegSetValueExW(hConsoleKey, L"FontSize", 0, REG_DWORD,
+                     (BYTE *)&FontSize, sizeof(FontSize));
+      RegCloseKey(hConsoleKey);
+    }
+#if _WIN32_WINNT >= _WIN32_WINNT_VISTA
+  }
+#endif
+
+  if (createProcess(NULL, NULL, NULL)) {
+    try {
+      if (WaitForSingleObject(syncEvent, waitTimeout) != WAIT_OBJECT_0) {
+        throw std::runtime_error("WaitForSingleObject failed!");
+      }
+      INPUT_RECORD inputBuffer[(sizeof(UnicodeInputTestString) /
+                                sizeof(UnicodeInputTestString[0])) * 2];
+      memset(&inputBuffer, 0, 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!");
+      }
+      std::wstring wideTestString = kwsys::Encoding::ToWide(encodedTestString);
+      std::wstring wideInputTestString = kwsys::Encoding::ToWide(encodedInputTestString);
+      if (memcmp(outputBuffer, wideTestString.c_str(), wideTestString.size()) == 0 &&
+          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 1,
+                 wideTestString.c_str(), wideTestString.size()) == 0 &&
+          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 2,
+                 UnicodeInputTestString, sizeof(UnicodeInputTestString) -
+                                         sizeof(WCHAR)) == 0 &&
+          memcmp(outputBuffer + screenBufferInfo.dwSize.X * 3,
+                 wideInputTestString.c_str(), wideInputTestString.size() - 1) == 0
+      ) {
+        didFail = 0;
+      } else {
+        std::cerr << "Console's output didn't match expected output!" << std::endl << std::flush;
+      }
+      delete[] outputBuffer;
+    } catch (const std::runtime_error &ex) {
+      std::cerr << ex.what() << std::endl << std::flush;
+    }
+    finishProcess(didFail == 0);
+  }
+  if (newConsole) {
+    SetStdHandle(STD_INPUT_HANDLE, parentIn);
+    SetStdHandle(STD_OUTPUT_HANDLE, parentOut);
+    SetStdHandle(STD_ERROR_HANDLE, parentErr);
+    CloseHandle(hIn);
+    CloseHandle(hOut);
+    FreeConsole();
+  }
+  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;
+  }
+
+  encodedTestString = kwsys::Encoding::ToNarrow(UnicodeTestString);
+  encodedInputTestString = kwsys::Encoding::ToNarrow(UnicodeInputTestString);
+  encodedInputTestString += "\n";
+
+  ret |= testPipe();
+  ret |= testFile();
+  ret |= testConsole();
+
+  CloseHandle(syncEvent);
+#endif
+
+  return ret;
+}
diff --git a/testConsoleBuf.hxx b/testConsoleBuf.hxx
new file mode 100644
index 0000000..855028b
--- /dev/null
+++ b/testConsoleBuf.hxx
@@ -0,0 +1,24 @@
+/*============================================================================
+  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/testConsoleBufChild.cxx b/testConsoleBufChild.cxx
new file mode 100644
index 0000000..3b9ab71
--- /dev/null
+++ b/testConsoleBufChild.cxx
@@ -0,0 +1,61 @@
+/*============================================================================
+  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);
+  }
+#else
+  static_cast<void>(argc);
+  static_cast<void>(argv);
+#endif
+  return 0;
+}
-- 
GitLab