setup_color.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2016 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. from __future__ import print_function
  6. import ctypes
  7. import os
  8. import platform
  9. import subprocess
  10. import sys
  11. from third_party import colorama
  12. IS_TTY = None
  13. OUT_TYPE = 'unknown'
  14. def enable_native_ansi():
  15. """Enables native ANSI sequences in console. Windows 10 only.
  16. Returns whether successful.
  17. """
  18. kernel32 = ctypes.windll.kernel32
  19. ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x04
  20. out_handle = kernel32.GetStdHandle(subprocess.STD_OUTPUT_HANDLE)
  21. # GetConsoleMode fails if the terminal isn't native.
  22. mode = ctypes.wintypes.DWORD()
  23. if kernel32.GetConsoleMode(out_handle, ctypes.byref(mode)) == 0:
  24. return False
  25. if not (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING):
  26. if kernel32.SetConsoleMode(
  27. out_handle, mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
  28. print(
  29. 'kernel32.SetConsoleMode to enable ANSI sequences failed',
  30. file=sys.stderr)
  31. return False
  32. return True
  33. def init():
  34. # should_wrap instructs colorama to wrap stdout/stderr with an ANSI colorcode
  35. # interpreter that converts them to SetConsoleTextAttribute calls. This only
  36. # should be True in cases where we're connected to cmd.exe's console. Setting
  37. # this to True on non-windows systems has no effect.
  38. should_wrap = False
  39. global IS_TTY, OUT_TYPE
  40. IS_TTY = sys.stdout.isatty()
  41. is_windows = sys.platform.startswith('win')
  42. if IS_TTY:
  43. # Yay! We detected a console in the normal way. It doesn't really matter
  44. # if it's windows or not, we win.
  45. OUT_TYPE = 'console'
  46. should_wrap = True
  47. elif is_windows:
  48. # assume this is some sort of file
  49. OUT_TYPE = 'file (win)'
  50. import msvcrt
  51. h = msvcrt.get_osfhandle(sys.stdout.fileno())
  52. # h is the win32 HANDLE for stdout.
  53. ftype = ctypes.windll.kernel32.GetFileType(h)
  54. if ftype == 2: # FILE_TYPE_CHAR
  55. # This is a normal cmd console, but we'll only get here if we're running
  56. # inside a `git command` which is actually git->bash->command. Not sure
  57. # why isatty doesn't detect this case.
  58. OUT_TYPE = 'console (cmd via msys)'
  59. IS_TTY = True
  60. should_wrap = True
  61. elif ftype == 3: # FILE_TYPE_PIPE
  62. OUT_TYPE = 'pipe (win)'
  63. # This is some kind of pipe on windows. This could either be a real pipe
  64. # or this could be msys using a pipe to emulate a pty. We use the same
  65. # algorithm that msys-git uses to determine if it's connected to a pty or
  66. # not.
  67. # This function and the structures are defined in the MSDN documentation
  68. # using the same names.
  69. def NT_SUCCESS(status):
  70. # The first two bits of status are the severity. The success
  71. # severities are 0 and 1, and the !success severities are 2 and 3.
  72. # Therefore since ctypes interprets the default restype of the call
  73. # to be an 'C int' (which is guaranteed to be signed 32 bits), All
  74. # success codes are positive, and all !success codes are negative.
  75. return status >= 0
  76. class UNICODE_STRING(ctypes.Structure):
  77. _fields_ = [('Length', ctypes.c_ushort),
  78. ('MaximumLength', ctypes.c_ushort),
  79. ('Buffer', ctypes.c_wchar_p)]
  80. class OBJECT_NAME_INFORMATION(ctypes.Structure):
  81. _fields_ = [('Name', UNICODE_STRING),
  82. ('NameBuffer', ctypes.c_wchar_p)]
  83. buf = ctypes.create_string_buffer(1024)
  84. # Ask NT what the name of the object our stdout HANDLE is. It would be
  85. # possible to use GetFileInformationByHandleEx, but it's only available
  86. # on Vista+. If you're reading this in 2017 or later, feel free to
  87. # refactor this out.
  88. #
  89. # The '1' here is ObjectNameInformation
  90. if NT_SUCCESS(ctypes.windll.ntdll.NtQueryObject(h, 1, buf, len(buf)-2,
  91. None)):
  92. out = OBJECT_NAME_INFORMATION.from_buffer(buf)
  93. name = out.Name.Buffer.split('\\')[-1]
  94. IS_TTY = name.startswith('msys-') and '-pty' in name
  95. if IS_TTY:
  96. OUT_TYPE = 'bash (msys)'
  97. else:
  98. # A normal file, or an unknown file type.
  99. pass
  100. else:
  101. # This is non-windows, so we trust isatty.
  102. OUT_TYPE = 'pipe or file'
  103. if IS_TTY and is_windows:
  104. # Wrapping may cause errors on some Windows versions (crbug.com/1114548).
  105. if platform.release() != '10' or enable_native_ansi():
  106. should_wrap = False
  107. colorama.init(wrap=should_wrap)
  108. if __name__ == '__main__':
  109. init()
  110. print('IS_TTY:', IS_TTY)
  111. print('OUT_TYPE:', OUT_TYPE)