diff --git a/rich/_windows.py b/rich/_windows.py new file mode 100644 index 00000000..ef2e9204 --- /dev/null +++ b/rich/_windows.py @@ -0,0 +1,64 @@ +import sys + +from dataclasses import dataclass + + +@dataclass +class WindowsConsoleFeatures: + """Windows features available.""" + + vt: bool + truecolor: bool + + +try: + import ctypes + from ctypes import wintypes + from ctypes import LibraryLoader + + windll = LibraryLoader(ctypes.WinDLL) +except (AttributeError, ImportError): + + # Fallback if we can't load the Windows DLL + def get_windows_console_features() -> WindowsConsoleFeatures: + features = WindowsConsoleFeatures() + return features + + +else: + + STDOUT = -11 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + _GetConsoleMode = windll.kernel32.GetConsoleMode + _GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD] + _GetConsoleMode.restype = wintypes.BOOL + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + def get_windows_console_features() -> WindowsConsoleFeatures: + """Get windows console fatures. + + Returns: + WindowsConsoleFeatures: An instance of WindowsConsoleFeatures. + """ + handle = _GetStdHandle(STDOUT) + console_mode = wintypes.DWORD() + result = _GetConsoleMode(handle, console_mode) + vt = bool(result and console_mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + truecolor = False + if vt: + win_version = sys.getwindowsversion() + truecolor = win_version.major > 10 or ( + win_version.major == 10 and win_version.build > 15063 + ) + features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor) + return features + + +if __name__ == "__main__": + print(get_windows_console_features()) + diff --git a/rich/console.py b/rich/console.py index 6119ef89..78efe71b 100644 --- a/rich/console.py +++ b/rich/console.py @@ -21,6 +21,7 @@ from typing import ( Iterable, List, Optional, + TYPE_CHECKING, NamedTuple, Union, ) @@ -44,6 +45,8 @@ from .segment import Segment from .text import Text, TextType from .theme import Theme +if TYPE_CHECKING: + from ._windows import WindowsConsoleFeatures WINDOWS = platform.system() == "Windows" @@ -244,9 +247,22 @@ class RenderHook: return renderables +_windows_console_features: Optional["WindowsConsoleFeatures"] = None + + +def get_windows_console_features() -> "WindowsConsoleFeatures": + global _windows_console_features + if _windows_console_features is not None: + return _windows_console_features + from ._windows import get_windows_console_features + + _windows_console_features = get_windows_console_features() + return _windows_console_features + + def detect_legacy_windows() -> bool: """Detect legacy Windows.""" - return "WINDIR" in os.environ and "WT_SESSION" not in os.environ + return WINDOWS and not get_windows_console_features().vt if detect_legacy_windows(): # pragma: no cover @@ -364,19 +380,26 @@ class Console: def _detect_color_system(self) -> Optional[ColorSystem]: """Detect color system from env vars.""" - if not self.is_terminal: + if not self.is_terminal or "NO_COLOR" in os.environ: return None - if self.legacy_windows: # pragma: no cover - return ColorSystem.WINDOWS - if "WT_SESSION" in os.environ: - # Exception for Windows terminal - return ColorSystem.TRUECOLOR - color_term = os.environ.get("COLORTERM", "").strip().lower() - return ( - ColorSystem.TRUECOLOR - if color_term in ("truecolor", "24bit") - else ColorSystem.EIGHT_BIT - ) + if WINDOWS: # pragma: no cover + if self.legacy_windows: # pragma: no cover + return ColorSystem.WINDOWS + windows_console_features = get_windows_console_features() + return ( + ColorSystem.TRUECOLOR + if windows_console_features.truecolor + else ColorSystem.EIGHT_BIT + ) + else: + if self.is_jupyter: + return ColorSystem.TRUECOLOR + color_term = os.environ.get("COLORTERM", "").strip().lower() + return ( + ColorSystem.TRUECOLOR + if color_term in ("truecolor", "24bit") + else ColorSystem.EIGHT_BIT + ) def _enter_buffer(self) -> None: """Enter in to a buffer context, and buffer all output.""" diff --git a/rich/style.py b/rich/style.py index cf46a684..1277ddd8 100644 --- a/rich/style.py +++ b/rich/style.py @@ -247,9 +247,9 @@ class Style: for bit in range(9, 13): if attributes & (1 << bit): append(_style_map[bit]) - if self._color is not None: + if self._color is not None and color_system: sgr.extend(self._color.downgrade(color_system).get_ansi_codes()) - if self._bgcolor is not None: + if self._bgcolor is not None and color_system: sgr.extend( self._bgcolor.downgrade(color_system).get_ansi_codes( foreground=False