Author eryksun
Recipients Tithen Firion, eryksun, martin.panter, paul.moore, steve.dower, tim.golden, zach.ware
Date 2017-04-16.02:38:20
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1492310301.73.0.194578361512.issue30075@psf.upfronthosting.co.za>
In-reply-to
Content
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).
History
Date User Action Args
2017-04-16 02:38:21eryksunsetrecipients: + eryksun, paul.moore, tim.golden, martin.panter, zach.ware, steve.dower, Tithen Firion
2017-04-16 02:38:21eryksunsetmessageid: <1492310301.73.0.194578361512.issue30075@psf.upfronthosting.co.za>
2017-04-16 02:38:21eryksunlinkissue30075 messages
2017-04-16 02:38:20eryksuncreate