Windows console output: Handle consoles with non black and white default colors correctly

This commit is contained in:
Kovid Goyal 2016-06-16 20:53:00 +05:30
parent 8211335683
commit a4ddfc4a58

View File

@ -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