From a4ddfc4a58db63094fd7badca800f98c6500a108 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Jun 2016 20:53:00 +0530 Subject: [PATCH] Windows console output: Handle consoles with non black and white default colors correctly --- src/calibre/utils/terminal.py | 39 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/terminal.py b/src/calibre/utils/terminal.py index c32f125551..b5b5a720e9 100644 --- a/src/calibre/utils/terminal.py +++ b/src/calibre/utils/terminal.py @@ -12,6 +12,17 @@ from itertools import izip from calibre.constants import iswindows +if iswindows: + import ctypes.wintypes + class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + _fields_ = [ + ('dwSize', ctypes.wintypes._COORD), + ('dwCursorPosition', ctypes.wintypes._COORD), + ('wAttributes', ctypes.wintypes.WORD), + ('srWindow', ctypes.wintypes._SMALL_RECT), + ('dwMaximumWindowSize', ctypes.wintypes._COORD) + ] + def fmt(code): return ('\033[%dm'%code).encode('ascii') @@ -111,9 +122,16 @@ class Detect(object): if f(self.file_handle, byref(mode)): # Stream is a console self.set_console = windll.kernel32.SetConsoleTextAttribute - self.write_console = WinDLL('kernel32', use_last_error=True).WriteConsoleW + kernel32 = WinDLL('kernel32', use_last_error=True) + self.write_console = kernel32.WriteConsoleW self.write_console.argtypes = [wintypes.HANDLE, wintypes.c_wchar_p, wintypes.DWORD, POINTER(wintypes.DWORD), wintypes.LPVOID] self.write_console.restype = wintypes.BOOL + kernel32.GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)] + kernel32.GetConsoleScreenBufferInfo.restype = wintypes.BOOL + csbi = CONSOLE_SCREEN_BUFFER_INFO() + self.default_console_text_attributes = WCOLORS['white'] + if kernel32.GetConsoleScreenBufferInfo(self.file_handle, byref(csbi)): + self.default_console_text_attributes = csbi.wAttributes self.is_console = True except: pass @@ -131,7 +149,7 @@ class Detect(object): if not self.write_console(self.file_handle, wt, len(t), byref(written), None): # Older versions of windows can fail to write large strings # to console with WriteConsoleW (seen it happen on Win XP) - import ctypes, winerror + import winerror err = ctypes.get_last_error() if err == winerror.ERROR_NOT_ENOUGH_MEMORY and chunk >= 128: # Retry with a smaller chunk size (give up if chunk < 128) @@ -159,6 +177,8 @@ class ColoredStream(Detect): self.fg, self.bg, self.bold = fg, bg, bold if self.set_console is not None: self.wval = to_flag(self.fg, self.bg, bold) + if not self.bg: + self.wval |= self.default_console_text_attributes & 0xF0 def __enter__(self): if not self.isatty: @@ -183,7 +203,7 @@ class ColoredStream(Detect): if self.isansi: self.stream.write(RESET) elif self.set_console is not None: - self.set_console(self.file_handle, WCOLORS['white']) + self.set_console(self.file_handle, self.default_console_text_attributes) class ANSIStream(Detect): @@ -192,6 +212,7 @@ class ANSIStream(Detect): def __init__(self, stream=None): super(ANSIStream, self).__init__(stream) self.encoding = getattr(self.stream, 'encoding', 'utf-8') or 'utf-8' + self.last_state = (None, None, False) def write(self, text): if isinstance(text, type(u'')): @@ -217,7 +238,6 @@ class ANSIStream(Detect): sequences from the text, and optionally converting them into win32 calls. ''' - self.last_state = (None, None, False) cursor = 0 for match in self.ANSI_RE.finditer(text): start, end = match.span() @@ -225,6 +245,7 @@ class ANSIStream(Detect): self.convert_ansi(*match.groups()) cursor = end self.write_plain_text(text, cursor, len(text)) + self.set_console(self.file_handle, self.default_console_text_attributes) self.stream.flush() def write_plain_text(self, text, start, end): @@ -263,14 +284,16 @@ class ANSIStream(Detect): elif param == 1: bold = True elif param == 0: - fg = 'white' - bg, bold = None, False + fg, bg, bold = None, None, False self.last_state = (fg, bg, bold) if fg or bg or bold: - self.set_console(self.file_handle, to_flag(fg, bg, bold)) + val = to_flag(fg, bg, bold) + if not bg: + val |= self.default_console_text_attributes & 0xF0 + self.set_console(self.file_handle, val) else: - self.set_console(self.file_handle, WCOLORS['white']) + self.set_console(self.file_handle, self.default_console_text_attributes) def windows_terminfo(): from ctypes import Structure, byref