瀏覽代碼

Implement basic color support for the Windows console

Only \033[*m (SGR) is supported, with a 16+16 color terminal.
Nils Maier 12 年之前
父節點
當前提交
87ea4904a0
共有 3 個文件被更改,包括 230 次插入59 次删除
  1. 1 7
      src/Logger.cc
  2. 192 37
      src/WinConsoleFile.cc
  3. 37 15
      src/WinConsoleFile.h

+ 1 - 7
src/Logger.cc

@@ -54,13 +54,7 @@ Logger::Logger()
   : logLevel_(Logger::A2_DEBUG),
     consoleLogLevel_(Logger::A2_NOTICE),
     consoleOutput_(true),
-#ifdef __MINGW32__
-    // Windows DOS prompt does not handle ANSI color code, so make
-    // this false.
-    useColor_(false)
-#else // !__MINGW32__
-    useColor_(isatty(STDOUT_FILENO) == 1)
-#endif // !__MINGW32__
+    useColor_(global::cout()->supportsColor())
 {}
 
 Logger::~Logger()

+ 192 - 37
src/WinConsoleFile.cc

@@ -32,44 +32,98 @@
  * files in the program, then also delete it here.
  */
 /* copyright --> */
+
 #include "WinConsoleFile.h"
 
 #include <cstring>
 #include <cstdio>
 #include <cstdarg>
-#include <string>
+#include <vector>
 
 #include "a2io.h"
 #include "util.h"
 
+namespace {
+
+#define FOREGROUND_BLACK 0
+#define FOREGROUND_WHITE FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE
+
+#define BACKGROUND_BLACK 0
+#define BACKGROUND_WHITE BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE
+
+const WORD kForeground[] = {
+  FOREGROUND_BLACK,                   // black
+  FOREGROUND_RED,                     // red
+  FOREGROUND_GREEN,                   // green
+  FOREGROUND_RED | FOREGROUND_GREEN,  // yellow
+  FOREGROUND_BLUE,                    // blue
+  FOREGROUND_BLUE | FOREGROUND_RED,   // magenta
+  FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan
+  FOREGROUND_WHITE                    // white
+};
+
+const WORD kBackground[] = {
+  BACKGROUND_BLACK,                   // black
+  BACKGROUND_RED,                     // red
+  BACKGROUND_GREEN,                   // green
+  BACKGROUND_RED | BACKGROUND_GREEN,  // yellow
+  BACKGROUND_BLUE,                    // blue
+  BACKGROUND_BLUE | BACKGROUND_RED,   // magenta
+  BACKGROUND_BLUE | BACKGROUND_GREEN, // cyan
+  BACKGROUND_WHITE                    // white
+};
+
+} // namespace
+
 namespace aria2 {
 
 WinConsoleFile::WinConsoleFile(DWORD stdHandle)
-  : stdHandle_(stdHandle)
-{}
+  : stdHandle_(stdHandle), bold_(false), underline_(false), reverse_(false),
+    fg_(FOREGROUND_WHITE), bg_(BACKGROUND_BLACK)
+{
+  if (supportsColor()) {
+    CONSOLE_SCREEN_BUFFER_INFO info;
+    GetConsoleScreenBufferInfo(handle(), &info);
+    info.wAttributes &= ~(COMMON_LVB_LEADING_BYTE |
+                          COMMON_LVB_TRAILING_BYTE |
+                          COMMON_LVB_GRID_HORIZONTAL |
+                          COMMON_LVB_GRID_LVERTICAL |
+                          COMMON_LVB_GRID_RVERTICAL |
+                          COMMON_LVB_REVERSE_VIDEO |
+                          COMMON_LVB_UNDERSCORE);
+    fg_ = info.wAttributes & ~(BACKGROUND_BLUE |
+                               BACKGROUND_GREEN |
+                               BACKGROUND_RED |
+                               BACKGROUND_INTENSITY);
+    bg_ = info.wAttributes & ~(FOREGROUND_BLUE |
+                               FOREGROUND_GREEN |
+                               FOREGROUND_RED |
+                               FOREGROUND_INTENSITY);
+    bg_ = (bg_ >>  4) & 0x0F;
+    bold_ = info.wAttributes & FOREGROUND_INTENSITY;
+    underline_ = info.wAttributes & BACKGROUND_INTENSITY;
+  }
 
-WinConsoleFile::~WinConsoleFile() {}
+  deffg_ = fg_;
+  defbg_ = bg_;
+}
 
-namespace {
-bool console(DWORD stdHandle)
+bool WinConsoleFile::supportsColor()
 {
   DWORD mode;
-  return GetConsoleMode(GetStdHandle(stdHandle), &mode);
+  return GetConsoleMode(handle(), &mode);
 }
-} // namespace
 
 size_t WinConsoleFile::write(const char* str)
 {
-  DWORD written;
-  if(console(stdHandle_)) {
-    std::wstring msg = utf8ToWChar(str);
-    WriteConsoleW(GetStdHandle(stdHandle_),
-                  msg.c_str(), msg.size(), &written, 0);
-  } else {
-    WriteFile(GetStdHandle(stdHandle_),
-              str, strlen(str), &written, 0);
+  if (!supportsColor()) {
+    DWORD written = 0;
+    WriteFile(handle(), str, strlen(str), &written, 0);
+    return written;
   }
-  return written;
+
+  auto msg = utf8ToWChar(str);
+  return writeColorful(msg);
 }
 
 int WinConsoleFile::vprintf(const char* format, va_list va)
@@ -78,33 +132,134 @@ int WinConsoleFile::vprintf(const char* format, va_list va)
   if (r <= 0) {
     return 0;
   }
-  char *buf = new char[++r];
-  r = vsnprintf(buf, r, format, va);
+  auto buf = make_unique<char[]>(++r);
+  r = vsnprintf(buf.get(), r, format, va);
   if (r < 0) {
-    delete [] buf;
     return 0;
   }
-  DWORD written;
-  if(console(stdHandle_)) {
-    std::wstring msg = utf8ToWChar(buf);
-    WriteConsoleW(GetStdHandle(stdHandle_),
-                  msg.c_str(), msg.size(), &written, 0);
-  } else {
-    WriteFile(GetStdHandle(stdHandle_),
-              buf, r, &written, 0);
-  }
-  delete [] buf;
-  return written;
+  return write(buf.get());
 }
 
-int WinConsoleFile::flush()
+size_t WinConsoleFile::writeColorful(const std::wstring& str)
 {
-  return 0;
-}
+  size_t written = 0;
+  DWORD cw;
 
-bool WinConsoleFile::supportsColor()
-{
-  return false;
+  wchar_t suffix;
+  int arg = 0;
+  std::vector<int> args;
+  std::vector<wchar_t> buffer;
+  buffer.reserve(str.length());
+
+  enum state_ {
+    ePrefix, ePreFin, eNum0, eNum
+  } state = ePrefix;
+
+  for (const wchar_t ch : str) {
+    if (state == ePrefix) {
+      if (ch == '\033') {
+        state = ePreFin;
+      }
+      else {
+        buffer.push_back(ch);
+        continue;
+      }
+    }
+    else if (state == ePreFin) {
+      if (ch == '\033');
+      else if (ch == '[') {
+        state = eNum0;
+      }
+      else {
+        state = ePrefix;
+      }
+    }
+    else if (state == eNum0 || state == eNum) {
+      if (isdigit(ch)) {
+        arg = (arg * 10) + (ch - '0');
+        state = eNum;
+      }
+      else if (ch == ';') {
+        args.push_back(arg);
+        arg = 0;
+        state = eNum0;
+      }
+      else if (ch != '?') {
+        if (state == eNum) {
+          args.push_back(arg);
+        }
+        suffix = ch;
+        goto out;
+      }
+    }
+
+    ++written;
+    continue;
+
+out:
+    cw = 0;
+    if (!buffer.empty()) {
+      WriteConsoleW(handle(), buffer.data(), buffer.size(), &cw, nullptr);
+    }
+    written += cw;
+
+    if (suffix == 'm') {
+      if (args.empty()) {
+        args.push_back(0);
+      }
+      for (const int a: args) {
+        if (a == 0) {
+            fg_ = deffg_;
+            bg_ = defbg_;
+            bold_ = underline_ = reverse_ = false;
+        }
+        else if (30 <= a && a <= 37) {
+          fg_ = a - 30;
+        }
+        else if (40 <= a && a <= 47) {
+          bg_ = a - 40;
+        }
+        else if (a == 1 || a == 21) {
+          bold_ = a == 1;
+        }
+        else if (a == 4 || a == 24) {
+          underline_ = a == 4;
+        }
+        else if (a == 7 || a == 27) {
+          reverse_ = a == 7;
+        }
+      }
+      WORD attribute = 0;
+      if (reverse_) {
+        attribute = kForeground[bg_] | kBackground[fg_];
+      }
+      else {
+        attribute = kForeground[fg_] | kBackground[bg_];
+      }
+      if (bold_) {
+        attribute |= FOREGROUND_INTENSITY;
+      }
+      if (underline_) {
+        attribute |= BACKGROUND_INTENSITY;
+      }
+      SetConsoleTextAttribute(handle(), attribute);
+    }
+
+    suffix = 0;
+    state = ePrefix;
+    arg = 0;
+    args.clear();
+    buffer.clear();
+    ++written;
+  }
+
+  if (!buffer.empty()) {
+    cw = 0;
+    WriteConsoleW(handle(), buffer.data(), buffer.size(), &cw, nullptr);
+    written += cw;
+  }
+
+  return written;
 }
 
 } // namespace aria2

+ 37 - 15
src/WinConsoleFile.h

@@ -32,28 +32,50 @@
  * files in the program, then also delete it here.
  */
 /* copyright --> */
+
 #ifndef D_WIN_CONSOLE_FILE_H
 #define D_WIN_CONSOLE_FILE_H
 
+#include <string>
+
 #include "OutputFile.h"
 
 namespace aria2 {
 
-// This is a wrapper class for WriteConsoleW
-class WinConsoleFile:public OutputFile {
-public:
-  WinConsoleFile(DWORD stdHandle);
-  virtual ~WinConsoleFile();
-  virtual size_t write(const char* str) CXX11_OVERRIDE;
-  virtual int vprintf(const char* format, va_list va) CXX11_OVERRIDE;
-  virtual int flush() CXX11_OVERRIDE;
-  virtual bool supportsColor() CXX11_OVERRIDE;
-private:
-  DWORD stdHandle_;
-  // Don't allow copying
-  WinConsoleFile(const WinConsoleFile&);
-  WinConsoleFile& operator=(const WinConsoleFile&);
-};
+  // This is a wrapper class for WriteConsoleW
+  class WinConsoleFile: public OutputFile
+  {
+  public:
+    WinConsoleFile(DWORD stdHandle);
+    virtual ~WinConsoleFile() {}
+
+    virtual size_t write(const char* str) CXX11_OVERRIDE;
+    virtual int vprintf(const char* format, va_list va) CXX11_OVERRIDE;
+    virtual bool supportsColor() CXX11_OVERRIDE;
+    virtual int flush() CXX11_OVERRIDE
+    {
+      return 0;
+    }
+
+  private:
+    DWORD stdHandle_;
+    bool bold_;
+    bool underline_;
+    bool reverse_;
+    WORD fg_, deffg_;
+    WORD bg_, defbg_;
+
+    size_t writeColorful(const std::wstring& str);
+    inline HANDLE handle() const
+    {
+      return ::GetStdHandle(stdHandle_);
+    }
+
+  private:
+    // Don't allow copying
+    WinConsoleFile(const WinConsoleFile&) = delete;
+    WinConsoleFile& operator=(const WinConsoleFile&) = delete;
+  };
 
 } // namespace aria2