classification
Title: Printing ANSI Escape Sequences on Windows 10
Type: enhancement Stage: resolved
Components: IO, Windows Versions: Python 3.7
process
Status: closed Resolution: duplicate
Dependencies: Superseder: Windows: Python not using ANSI compatible console
View: 29059
Assigned To: Nosy List: Tithen Firion, eryksun, martin.panter, paul.moore, steve.dower, terry.reedy, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2017-04-15 16:21 by Tithen Firion, last changed 2017-04-21 23:43 by terry.reedy. This issue is now closed.

Files
File name Uploaded Description Edit
example.png Tithen Firion, 2017-04-15 16:20 output of Windows 10 console
Messages (4)
msg291719 - (view) Author: Tithen Firion (Tithen Firion) Date: 2017-04-15 16:20
Windows 10 supports ANSI Escape Sequences ( http://stackoverflow.com/a/38617204/2428152 https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx ) but Python just prints escape character. Adding `subprocess.call('', shell=True)` before printing solved the issue.

Test code:

    import subprocess
    print('\033[0;31mTEST\033[0m')
    subprocess.call('', shell=True)
    print('\033[0;31mTEST\033[0m')

output in attachment.

Haven't tested it on other Python versions but it probably occurs on them too.
msg291731 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-04-15 22:50
Maybe this is related to Issue 29059. If so, there seems to be resistance to enabling the feature by default, and preference to use existing APIs rather than adding a new API that enables it.
msg291732 - (view) Author: Eryk Sun (eryksun) * Date: 2017-04-16 02:38
cmd.exe enables virtual terminal mode, but only for itself. It disables VT mode before starting other programs, and also at shutdown. It appears you've found a bug in the case of "cmd.exe /c ...". You can get the same result via os.system(''). It's failing to disable VT mode after it exits.

Enabling VT mode by default is potentially a problem because a console  buffer's mode is shared, inherited state. Adding a set_console_mode method on console files would be a useful convenience to manage this state. There could also be a couple IntFlag enums for the available input and output mode values. 

Here's some code to enable VT mode in Windows 10 -- assuming you're not using the legacy console. If you're using an older version of Windows, or the legacy console in Windows 10, then enabling VT mode will fail as an invalid parameter. This is handled by raising NotImplementedError. On success, it returns the previous console mode, which can be restored by calling set_conout_mode(mode). Depending on your needs, that could done in an atexit function.

    import os
    import msvcrt
    import ctypes

    from ctypes import wintypes

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

    ERROR_INVALID_PARAMETER = 0x0057
    ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004

    def _check_bool(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args

    LPDWORD = ctypes.POINTER(wintypes.DWORD)
    kernel32.GetConsoleMode.errcheck = _check_bool
    kernel32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD)
    kernel32.SetConsoleMode.errcheck = _check_bool
    kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD)

    def set_conout_mode(new_mode, mask=0xffffffff):
        # don't assume StandardOutput is a console.
        # open CONOUT$ instead
        fdout = os.open('CONOUT$', os.O_RDWR)
        try:
            hout = msvcrt.get_osfhandle(fdout)
            old_mode = wintypes.DWORD()
            kernel32.GetConsoleMode(hout, ctypes.byref(old_mode))
            mode = (new_mode & mask) | (old_mode.value & ~mask)
            kernel32.SetConsoleMode(hout, mode)
            return old_mode.value
        finally:
            os.close(fdout)

    def enable_vt_mode():
        mode = mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING
        try:
            return set_conout_mode(mode, mask)
        except WindowsError as e:
            if e.winerror == ERROR_INVALID_PARAMETER:
                raise NotImplementedError
            raise

Please don't use the code in issue 29059. It's simpler, but there are several problems with it. (1) There's no error handling. (2) It passes handles incorrectly as 32-bit int values, for which ctypes in 64-bit Python 2 may corrupt the high DWORD (if it works, it's only by accident; ctypes doesn't zero the stack in ffi_prep_args). (3) It assumes the StandardOutput handle is a console output buffer, but maybe it's a pipe or file, and the program has manually opened CONOUT$ to write debug text in color. (4) It uses windll, which causes problems when multiple libraries contend for the same functions (e.g. some library may have set incompatible argtypes, restype, or errcheck values on windll.kernel32.GetConsoleMode).
msg292083 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2017-04-21 23:43
This is a (duplicate) enhancement request, not a bug report.  The superseder field is for when an issue is closed as a duplicate; I decided that this should be.  Further discussion should be on python-ideas.
History
Date User Action Args
2017-04-21 23:43:22terry.reedysetstatus: open -> closed

type: behavior -> enhancement
versions: + Python 3.7, - Python 2.7, Python 3.6
nosy: + terry.reedy

messages: + msg292083
resolution: duplicate
stage: resolved
2017-04-16 02:38:21eryksunsetnosy: + eryksun
messages: + msg291732
2017-04-15 22:50:43martin.pantersetnosy: + paul.moore, tim.golden, martin.panter, zach.ware, steve.dower
messages: + msg291731

components: + Windows
superseder: Windows: Python not using ANSI compatible console
2017-04-15 16:21:00Tithen Firioncreate