diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt index 87f604844..c4fe5e881 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 # 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/Source/kwsys/ConsoleBuf.hxx.in b/Source/kwsys/ConsoleBuf.hxx.in new file mode 100644 index 000000000..8aeeda179 --- /dev/null +++ b/Source/kwsys/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 +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +# if __cplusplus >= 201103L +# include +# endif +#endif + +namespace @KWSYS_NAMESPACE@ +{ +#if defined(_WIN32) + + template > + class @KWSYS_NAMESPACE@_EXPORT BasicConsoleBuf : + public std::basic_streambuf { + public: + typedef typename Traits::int_type int_type; + typedef typename Traits::char_type char_type; + + class Manager { + public: + Manager(std::basic_ios &ios, const bool err = false) + : m_consolebuf(0) + { + m_ios = &ios; + try { + m_consolebuf = new BasicConsoleBuf(err); + m_streambuf = m_ios->rdbuf(m_consolebuf); + } catch (const std::runtime_error& ex) { + std::cerr << "Failed to create ConsoleBuf!" << std::endl + << ex.what() << std::endl; + }; + } + + ~Manager() + { + if (m_consolebuf) { + delete m_consolebuf; + m_ios->rdbuf(m_streambuf); + } + } + + private: + std::basic_ios *m_ios; + std::basic_streambuf *m_streambuf; + BasicConsoleBuf *m_consolebuf; + }; + + BasicConsoleBuf(const bool err = false) : + flush_on_newline(true), + input_pipe_codepage(0), + output_pipe_codepage(0), + input_file_codepage(CP_UTF8), + output_file_codepage(CP_UTF8), + m_consolesCodepage(0) + { + m_hInput = ::GetStdHandle(STD_INPUT_HANDLE); + checkHandle(true, "STD_INPUT_HANDLE"); + if (!setActiveInputCodepage()) { + throw std::runtime_error("setActiveInputCodepage failed!"); + } + m_hOutput = err ? ::GetStdHandle(STD_ERROR_HANDLE) : + ::GetStdHandle(STD_OUTPUT_HANDLE); + checkHandle(false, err ? "STD_ERROR_HANDLE" : "STD_OUTPUT_HANDLE"); + if (!setActiveOutputCodepage()) { + throw std::runtime_error("setActiveOutputCodepage failed!"); + } + _setg(); + _setp(); + } + + ~BasicConsoleBuf() throw() + { + sync(); + } + + bool activateCodepageChange() + { + return setActiveInputCodepage() && setActiveOutputCodepage(); + } + + protected: + virtual int sync() { + bool success = true; + if (m_hInput && m_isConsoleInput && + ::FlushConsoleInputBuffer(m_hInput) == 0) { + success = false; + } + if (m_hOutput && !m_obuffer.empty()) { + const std::wstring wbuffer = getBuffer(m_obuffer); + if (m_isConsoleOutput) { + DWORD charsWritten; + success = ::WriteConsoleW(m_hOutput, wbuffer.c_str(), + (DWORD)wbuffer.size(), &charsWritten, + NULL) == 0 ? false : true; + } else { + DWORD bytesWritten; + std::string buffer; + success = encodeOutputBuffer(wbuffer, buffer); + if (success) { + success = ::WriteFile(m_hOutput, buffer.c_str(), + (DWORD)buffer.size(), &bytesWritten, + NULL) == 0 ? false : true; + } + } + } + m_ibuffer.clear(); + m_obuffer.clear(); + _setg(); + _setp(); + return success ? 0 : -1; + } + + virtual int_type underflow() { + if (this->gptr() >= this->egptr()) { + if (!m_hInput) { + _setg(true); + return Traits::eof(); + } + if (m_isConsoleInput) { + wchar_t wbuffer[128]; + DWORD charsRead; + if (::ReadConsoleW(m_hInput, wbuffer, (sizeof(wbuffer) / sizeof(wbuffer[0])) - 1, + &charsRead, NULL) == 0 || charsRead == 0) { + _setg(true); + return Traits::eof(); + } + wbuffer[charsRead] = L'\0'; + setBuffer(wbuffer, m_ibuffer); + } else { + std::wstring totalBuffer; + std::wstring wbuffer; + char buffer[128]; + DWORD bytesRead; + while (::ReadFile(m_hInput, buffer, (sizeof(buffer) / sizeof(buffer[0])) - 1, + &bytesRead, NULL) == 0) { + if (::GetLastError() == ERROR_MORE_DATA) { + buffer[bytesRead] = '\0'; + if (decodeInputBuffer(buffer, wbuffer)) { + totalBuffer += wbuffer; + continue; + } + } + _setg(true); + return Traits::eof(); + } + buffer[bytesRead] = '\0'; + if (!decodeInputBuffer(buffer, wbuffer)) { + _setg(true); + return Traits::eof(); + } + totalBuffer += wbuffer; + setBuffer(totalBuffer, m_ibuffer); + } + _setg(); + } + return Traits::to_int_type(*this->gptr()); + } + + virtual int_type overflow(int_type ch = Traits::eof()) { + if (!Traits::eq_int_type(ch, Traits::eof())) { + char_type chr = Traits::to_char_type(ch); + m_obuffer += chr; + if ((flush_on_newline && Traits::eq(chr, '\n')) || + Traits::eq_int_type(ch, 0x00)) { + sync(); + } + return ch; + } + sync(); + return Traits::eof(); + } + + public: + bool flush_on_newline; + UINT input_pipe_codepage; + UINT output_pipe_codepage; + UINT input_file_codepage; + UINT output_file_codepage; + + private: + HANDLE m_hInput; + HANDLE m_hOutput; + std::basic_string m_ibuffer; + std::basic_string m_obuffer; + bool m_isConsoleInput; + bool m_isConsoleOutput; + UINT m_activeInputCodepage; + UINT m_activeOutputCodepage; + UINT m_consolesCodepage; + void checkHandle(bool input, std::string handleName) { + if ((input && m_hInput == INVALID_HANDLE_VALUE) || + (!input && m_hOutput == INVALID_HANDLE_VALUE)) { + std::string errmsg = "GetStdHandle(" + handleName + + ") returned INVALID_HANDLE_VALUE"; +#if __cplusplus >= 201103L + throw std::system_error(::GetLastError(), + std::system_category(), errmsg); +#else + throw std::runtime_error(errmsg); +#endif + } + } + UINT getConsolesCodepage() { + if (!m_consolesCodepage) { + m_consolesCodepage = GetConsoleCP(); + if (!m_consolesCodepage) { + m_consolesCodepage = GetACP(); + } + } + return m_consolesCodepage; + } + bool setActiveInputCodepage() { + m_isConsoleInput = false; + switch (GetFileType(m_hInput)) { + case FILE_TYPE_DISK: + m_activeInputCodepage = input_file_codepage; + break; + case FILE_TYPE_CHAR: + m_isConsoleInput = true; + break; + case FILE_TYPE_PIPE: + m_activeInputCodepage = input_pipe_codepage; + break; + default: + return false; + } + if (!m_isConsoleInput && m_activeInputCodepage == 0) { + m_activeInputCodepage = getConsolesCodepage(); + } + return true; + } + bool setActiveOutputCodepage() { + m_isConsoleOutput = false; + switch (GetFileType(m_hOutput)) { + case FILE_TYPE_DISK: + m_activeOutputCodepage = output_file_codepage; + break; + case FILE_TYPE_CHAR: + m_isConsoleOutput = true; + break; + case FILE_TYPE_PIPE: + m_activeOutputCodepage = output_pipe_codepage; + break; + default: + return false; + } + if (!m_isConsoleOutput && m_activeOutputCodepage == 0) { + m_activeOutputCodepage = getConsolesCodepage(); + } + return true; + } + void _setg(bool empty = false) { + if (!empty) { + this->setg((char_type *)m_ibuffer.data(), + (char_type *)m_ibuffer.data(), + (char_type *)m_ibuffer.data() + m_ibuffer.size()); + } else { + this->setg((char_type *)m_ibuffer.data(), + (char_type *)m_ibuffer.data() + m_ibuffer.size(), + (char_type *)m_ibuffer.data() + m_ibuffer.size()); + } + } + void _setp() { + this->setp((char_type *)m_obuffer.data(), + (char_type *)m_obuffer.data() + m_obuffer.size()); + } + bool encodeOutputBuffer(const std::wstring wbuffer, + std::string &buffer) { + const int length = WideCharToMultiByte(m_activeOutputCodepage, 0, + wbuffer.c_str(), + (int)wbuffer.size(), NULL, 0, + NULL, NULL); + char *buf = new char[length + 1]; + const bool success = WideCharToMultiByte(m_activeOutputCodepage, 0, + wbuffer.c_str(), + (int)wbuffer.size(), buf, + length, NULL, NULL) > 0 + ? true : false; + buf[length] = '\0'; + buffer = buf; + delete[] buf; + return success; + } + bool decodeInputBuffer(const char *buffer, std::wstring &wbuffer) { + int actualCodepage = m_activeInputCodepage; + const char BOM_UTF8[] = { char(0xEF), char(0xBB), char(0xBF) }; + if (std::memcmp(buffer, BOM_UTF8, sizeof(BOM_UTF8)) == 0) { + // PowerShell uses UTF-8 with BOM for pipes + actualCodepage = CP_UTF8; + buffer += sizeof(BOM_UTF8); + } + const int wlength = MultiByteToWideChar(actualCodepage, 0, buffer, + -1, NULL, 0); + wchar_t *wbuf = new wchar_t[wlength]; + const bool success = MultiByteToWideChar(actualCodepage, 0, buffer, + -1, wbuf, wlength) > 0 + ? true : false; + wbuffer = wbuf; + delete[] wbuf; + return success; + } + std::wstring getBuffer(const std::basic_string buffer) { + return Encoding::ToWide(buffer); + } + std::wstring getBuffer(const std::basic_string buffer) { + return buffer; + } + void setBuffer(const std::wstring wbuffer, + std::basic_string &target) { + target = Encoding::ToNarrow(wbuffer); + } + void setBuffer(const std::wstring wbuffer, + std::basic_string &target) { + target = wbuffer; + } + + }; // BasicConsoleBuf class + + typedef BasicConsoleBuf ConsoleBuf; + typedef BasicConsoleBuf WConsoleBuf; + +#endif +} // KWSYS_NAMESPACE + +#endif diff --git a/Source/kwsys/testConsoleBuf.cxx b/Source/kwsys/testConsoleBuf.cxx new file mode 100644 index 000000000..3dec09733 --- /dev/null +++ b/Source/kwsys/testConsoleBuf.cxx @@ -0,0 +1,609 @@ +/*============================================================================ + 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" + +// Ignore Windows version levels defined by command-line flags. This +// source needs access to all APIs available on the host in order for +// the test to run properly. The test binary is not installed anyway. +#undef _WIN32_WINNT +#undef NTDDI_VERSION + +#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 +#include +#include +#include +#include +#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/Source/kwsys/testConsoleBuf.hxx b/Source/kwsys/testConsoleBuf.hxx new file mode 100644 index 000000000..855028b3b --- /dev/null +++ b/Source/kwsys/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/Source/kwsys/testConsoleBufChild.cxx b/Source/kwsys/testConsoleBufChild.cxx new file mode 100644 index 000000000..3b9ab71a5 --- /dev/null +++ b/Source/kwsys/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 +#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(argc); + static_cast(argv); +#endif + return 0; +}