Skip to content
Snippets Groups Projects
ConsoleBuf.hxx.in 11.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
       file Copyright.txt or https://cmake.org/licensing#kwsys for details.  */
    
    #ifndef @KWSYS_NAMESPACE@_ConsoleBuf_hxx
    #define @KWSYS_NAMESPACE@_ConsoleBuf_hxx
    
    #include <@KWSYS_NAMESPACE@/Configure.hxx>
    
    #include <@KWSYS_NAMESPACE@/Encoding.hxx>
    
    #include <cstring>
    #include <iostream>
    
    #include <streambuf>
    #include <string>
    
    #include <windows.h>
    #if __cplusplus >= 201103L
    #include <system_error>
    #endif
    
    namespace @KWSYS_NAMESPACE@ {
    
    template <class CharT, class Traits = std::char_traits<CharT> >
    
    class BasicConsoleBuf : public std::basic_streambuf<CharT, Traits>
    
    {
    public:
      typedef typename Traits::int_type int_type;
      typedef typename Traits::char_type char_type;
    
      class Manager
      {
      public:
        Manager(std::basic_ios<CharT, Traits>& ios, const bool err = false)
          : m_consolebuf(0)
        {
          m_ios = &ios;
          try {
            m_consolebuf = new BasicConsoleBuf<CharT, Traits>(err);
            m_streambuf = m_ios->rdbuf(m_consolebuf);
          } catch (const std::runtime_error& ex) {
            std::cerr << "Failed to create ConsoleBuf!" << std::endl
                      << ex.what() << std::endl;
    
        BasicConsoleBuf<CharT, Traits>* GetConsoleBuf() { return m_consolebuf; }
    
    
        void SetUTF8Pipes()
        {
          if (m_consolebuf) {
            m_consolebuf->input_pipe_codepage = CP_UTF8;
            m_consolebuf->output_pipe_codepage = CP_UTF8;
            m_consolebuf->activateCodepageChange();
          }
        }
    
    
        ~Manager()
        {
          if (m_consolebuf) {
            delete m_consolebuf;
            m_ios->rdbuf(m_streambuf);
    
      private:
        std::basic_ios<CharT, Traits>* m_ios;
        std::basic_streambuf<CharT, Traits>* m_streambuf;
        BasicConsoleBuf<CharT, Traits>* m_consolebuf;
      };
    
      BasicConsoleBuf(const bool err = false)
        : flush_on_newline(true)
        , input_pipe_codepage(0)
        , output_pipe_codepage(0)
        , input_file_codepage(CP_UTF8)
        , output_file_codepage(CP_UTF8)
        , m_consolesCodepage(0)
      {
        m_hInput = ::GetStdHandle(STD_INPUT_HANDLE);
        checkHandle(true, "STD_INPUT_HANDLE");
        if (!setActiveInputCodepage()) {
          throw std::runtime_error("setActiveInputCodepage failed!");
        }
        m_hOutput = err ? ::GetStdHandle(STD_ERROR_HANDLE)
                        : ::GetStdHandle(STD_OUTPUT_HANDLE);
        checkHandle(false, err ? "STD_ERROR_HANDLE" : "STD_OUTPUT_HANDLE");
        if (!setActiveOutputCodepage()) {
          throw std::runtime_error("setActiveOutputCodepage failed!");
        }
        _setg();
        _setp();
      }
    
      ~BasicConsoleBuf() throw() { sync(); }
    
      bool activateCodepageChange()
      {
        return setActiveInputCodepage() && setActiveOutputCodepage();
      }
    
    protected:
      virtual int sync()
      {
        bool success = true;
        if (m_hInput && m_isConsoleInput &&
            ::FlushConsoleInputBuffer(m_hInput) == 0) {
          success = false;
        }
        if (m_hOutput && !m_obuffer.empty()) {
          const std::wstring wbuffer = getBuffer(m_obuffer);
          if (m_isConsoleOutput) {
            DWORD charsWritten;
            success =
              ::WriteConsoleW(m_hOutput, wbuffer.c_str(), (DWORD)wbuffer.size(),
                              &charsWritten, NULL) == 0
              ? false
              : true;
          } else {
            DWORD bytesWritten;
            std::string buffer;
            success = encodeOutputBuffer(wbuffer, buffer);
            if (success) {
              success = ::WriteFile(m_hOutput, buffer.c_str(),
                                    (DWORD)buffer.size(), &bytesWritten, NULL) == 0
                ? false
                : true;
    
        }
        m_ibuffer.clear();
        m_obuffer.clear();
        _setg();
        _setp();
        return success ? 0 : -1;
      }
    
      virtual int_type underflow()
      {
        if (this->gptr() >= this->egptr()) {
          if (!m_hInput) {
            _setg(true);
            return Traits::eof();
          }
          if (m_isConsoleInput) {
    
            // ReadConsole doesn't tell if there's more input available
            // don't support reading more characters than this
            wchar_t wbuffer[8192];
    
            DWORD charsRead;
    
            if (ReadConsoleW(m_hInput, wbuffer,
                             (sizeof(wbuffer) / sizeof(wbuffer[0])), &charsRead,
                             NULL) == 0 ||
    
                charsRead == 0) {
              _setg(true);
              return Traits::eof();
            }
    
            setBuffer(std::wstring(wbuffer, charsRead), m_ibuffer);
    
          } else {
            std::wstring wbuffer;
    
            DWORD bytesRead;
    
            LARGE_INTEGER size;
            if (GetFileSizeEx(m_hInput, &size) == 0) {
              _setg(true);
              return Traits::eof();
            }
            char* buffer = new char[size.LowPart];
            while (ReadFile(m_hInput, buffer, size.LowPart, &bytesRead, NULL) ==
                   0) {
              if (GetLastError() == ERROR_MORE_DATA) {
                strbuffer += std::string(buffer, bytesRead);
                continue;
    
              return Traits::eof();
    
            if (bytesRead > 0) {
              strbuffer += std::string(buffer, bytesRead);
            }
            delete[] buffer;
            if (!decodeInputBuffer(strbuffer, wbuffer)) {
    
              _setg(true);
              return Traits::eof();
            }
    
            setBuffer(wbuffer, m_ibuffer);
    
          _setg();
        }
        return Traits::to_int_type(*this->gptr());
      }
    
      virtual int_type overflow(int_type ch = Traits::eof())
      {
        if (!Traits::eq_int_type(ch, Traits::eof())) {
          char_type chr = Traits::to_char_type(ch);
          m_obuffer += chr;
          if ((flush_on_newline && Traits::eq(chr, '\n')) ||
              Traits::eq_int_type(ch, 0x00)) {
    
          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";
    
          throw std::system_error(::GetLastError(), std::system_category(),
                                  errmsg);
    
          throw std::runtime_error(errmsg);
    
        }
      }
      UINT getConsolesCodepage()
      {
        if (!m_consolesCodepage) {
          m_consolesCodepage = GetConsoleCP();
          if (!m_consolesCodepage) {
            m_consolesCodepage = GetACP();
    
        }
        return m_consolesCodepage;
      }
      bool setActiveInputCodepage()
      {
        m_isConsoleInput = false;
        switch (GetFileType(m_hInput)) {
          case FILE_TYPE_DISK:
            m_activeInputCodepage = input_file_codepage;
            break;
          case FILE_TYPE_CHAR:
            // Check for actual console.
            DWORD consoleMode;
            m_isConsoleInput =
              GetConsoleMode(m_hInput, &consoleMode) == 0 ? false : true;
            if (m_isConsoleInput) {
              break;
    
            @KWSYS_NAMESPACE@_FALLTHROUGH;
    
          case FILE_TYPE_PIPE:
            m_activeInputCodepage = input_pipe_codepage;
            break;
          default:
            return false;
        }
        if (!m_isConsoleInput && m_activeInputCodepage == 0) {
          m_activeInputCodepage = getConsolesCodepage();
        }
        return true;
      }
      bool setActiveOutputCodepage()
      {
        m_isConsoleOutput = false;
        switch (GetFileType(m_hOutput)) {
          case FILE_TYPE_DISK:
            m_activeOutputCodepage = output_file_codepage;
            break;
          case FILE_TYPE_CHAR:
            // Check for actual console.
            DWORD consoleMode;
            m_isConsoleOutput =
              GetConsoleMode(m_hOutput, &consoleMode) == 0 ? false : true;
            if (m_isConsoleOutput) {
              break;
    
            @KWSYS_NAMESPACE@_FALLTHROUGH;
    
          case FILE_TYPE_PIPE:
            m_activeOutputCodepage = output_pipe_codepage;
            break;
          default:
            return false;
        }
        if (!m_isConsoleOutput && m_activeOutputCodepage == 0) {
          m_activeOutputCodepage = getConsolesCodepage();
        }
        return true;
      }
      void _setg(bool empty = false)
      {
        if (!empty) {
          this->setg((char_type*)m_ibuffer.data(), (char_type*)m_ibuffer.data(),
                     (char_type*)m_ibuffer.data() + m_ibuffer.size());
        } else {
          this->setg((char_type*)m_ibuffer.data(),
                     (char_type*)m_ibuffer.data() + m_ibuffer.size(),
                     (char_type*)m_ibuffer.data() + m_ibuffer.size());
        }
      }
      void _setp()
      {
        this->setp((char_type*)m_obuffer.data(),
                   (char_type*)m_obuffer.data() + m_obuffer.size());
      }
      bool encodeOutputBuffer(const std::wstring wbuffer, std::string& buffer)
      {
    
        if (wbuffer.size() == 0) {
          buffer = std::string();
          return true;
        }
    
        const int length =
          WideCharToMultiByte(m_activeOutputCodepage, 0, wbuffer.c_str(),
                              (int)wbuffer.size(), NULL, 0, NULL, NULL);
    
        char* buf = new char[length];
    
        const bool success =
          WideCharToMultiByte(m_activeOutputCodepage, 0, wbuffer.c_str(),
                              (int)wbuffer.size(), buf, length, NULL, NULL) > 0
          ? true
          : false;
    
        buffer = std::string(buf, length);
    
        delete[] buf;
        return success;
      }
    
      bool decodeInputBuffer(const std::string buffer, std::wstring& wbuffer)
    
        size_t length = buffer.length();
    
        if (length == 0) {
          wbuffer = std::wstring();
          return true;
        }
    
        int actualCodepage = m_activeInputCodepage;
        const char BOM_UTF8[] = { char(0xEF), char(0xBB), char(0xBF) };
    
        const char* data = buffer.data();
        const size_t BOMsize = sizeof(BOM_UTF8);
        if (length >= BOMsize && std::memcmp(data, BOM_UTF8, BOMsize) == 0) {
    
          // PowerShell uses UTF-8 with BOM for pipes
          actualCodepage = CP_UTF8;
    
          data += BOMsize;
          length -= BOMsize;
    
        const size_t wlength = static_cast<size_t>(MultiByteToWideChar(
          actualCodepage, 0, data, static_cast<int>(length), NULL, 0));
    
        wchar_t* wbuf = new wchar_t[wlength];
        const bool success =
    
          MultiByteToWideChar(actualCodepage, 0, data, static_cast<int>(length),
                              wbuf, static_cast<int>(wlength)) > 0
    
        wbuffer = std::wstring(wbuf, wlength);
    
        delete[] wbuf;
        return success;
      }
      std::wstring getBuffer(const std::basic_string<char> buffer)
      {
        return Encoding::ToWide(buffer);
      }
      std::wstring getBuffer(const std::basic_string<wchar_t> buffer)
      {
        return buffer;
      }
      void setBuffer(const std::wstring wbuffer, std::basic_string<char>& target)
      {
        target = Encoding::ToNarrow(wbuffer);
      }
      void setBuffer(const std::wstring wbuffer,
                     std::basic_string<wchar_t>& target)
      {
        target = wbuffer;
      }
    
    }; // BasicConsoleBuf class
    
    typedef BasicConsoleBuf<char> ConsoleBuf;
    typedef BasicConsoleBuf<wchar_t> WConsoleBuf;