From 9f0bb4301f0514a9367ca9289ceb6a5cf2b3bfa0 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 30 Nov 2022 18:45:06 -0500 Subject: [PATCH] Support ASCI control Windows CMD prompt does not automatically support ASCI color control characters on the console from Python. Enable this behavior by accessing the current console and allowing the interpreation of ASCI control characters from Python via the win32 API. --- lib/spack/llnl/util/tty/color.py | 58 ++++++++++++++++++++++++++++++++ lib/spack/spack/main.py | 4 +++ 2 files changed, 62 insertions(+) diff --git a/lib/spack/llnl/util/tty/color.py b/lib/spack/llnl/util/tty/color.py index 83e8316914..a468d19634 100644 --- a/lib/spack/llnl/util/tty/color.py +++ b/lib/spack/llnl/util/tty/color.py @@ -107,6 +107,64 @@ def __init__(self, message): _force_color = None +def try_enable_terminal_color_on_windows(): + """Turns coloring in Windows terminal by enabling VTP in Windows consoles (CMD/PWSH/CONHOST) + Method based on the link below + https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing + + Note: No-op on non windows platforms + """ + if sys.platform == "win32": + import ctypes + import msvcrt + from ctypes import wintypes + + try: + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + kernel32 = ctypes.WinDLL("kernel32") + + def _err_check(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + kernel32.GetConsoleMode.errcheck = _err_check + kernel32.GetConsoleMode.argtypes = ( + wintypes.HANDLE, # hConsoleHandle, i.e. GetStdHandle output type + ctypes.POINTER(wintypes.DWORD), # result of GetConsoleHandle + ) + kernel32.SetConsoleMode.errcheck = _err_check + kernel32.SetConsoleMode.argtypes = ( + wintypes.HANDLE, # hConsoleHandle, i.e. GetStdHandle output type + wintypes.DWORD, # result of GetConsoleHandle + ) + # Use conout$ here to handle a redirectired stdout/get active console associated + # with spack + with open(r"\\.\CONOUT$", "w") as conout: + # Link above would use kernel32.GetStdHandle(-11) however this would not handle + # a redirected stdout appropriately, so we always refer to the current CONSOLE out + # which is defined as conout$ on Windows. + # linked example is follow more or less to the letter beyond this point + con_handle = msvcrt.get_osfhandle(conout.fileno()) + dw_orig_mode = wintypes.DWORD() + kernel32.GetConsoleMode(con_handle, ctypes.byref(dw_orig_mode)) + dw_new_mode_request = ( + ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN + ) + dw_new_mode = dw_new_mode_request | dw_orig_mode.value + kernel32.SetConsoleMode(con_handle, wintypes.DWORD(dw_new_mode)) + except OSError: + # We failed to enable color support for associated console + # report and move on but spack will no longer attempt to + # color + global _force_color + _force_color = False + from . import debug + + debug("Unable to support color on Windows terminal") + + def _color_when_value(when): """Raise a ValueError for an invalid color setting. diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py index b95a8562eb..8153839abc 100644 --- a/lib/spack/spack/main.py +++ b/lib/spack/spack/main.py @@ -605,6 +605,10 @@ def setup_main_options(args): for config_var in args.config_vars or []: spack.config.add(fullpath=config_var, scope="command_line") + # On Windows10 console handling for ASCI/VT100 sequences is not + # on by default. Turn on before we try to write to console + # with color + color.try_enable_terminal_color_on_windows() # when to use color (takes always, auto, or never) color.set_color_when(args.color)