// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "TerminalDispatch.hpp" using namespace ::Microsoft::Terminal::Core; using namespace ::Microsoft::Console::VirtualTerminal; // clang-format off const BYTE RED_ATTR = 0x01; const BYTE GREEN_ATTR = 0x02; const BYTE BLUE_ATTR = 0x04; const BYTE BRIGHT_ATTR = 0x08; const BYTE DARK_BLACK = 0; const BYTE DARK_RED = RED_ATTR; const BYTE DARK_GREEN = GREEN_ATTR; const BYTE DARK_YELLOW = RED_ATTR | GREEN_ATTR; const BYTE DARK_BLUE = BLUE_ATTR; const BYTE DARK_MAGENTA = RED_ATTR | BLUE_ATTR; const BYTE DARK_CYAN = GREEN_ATTR | BLUE_ATTR; const BYTE DARK_WHITE = RED_ATTR | GREEN_ATTR | BLUE_ATTR; const BYTE BRIGHT_BLACK = BRIGHT_ATTR; const BYTE BRIGHT_RED = BRIGHT_ATTR | RED_ATTR; const BYTE BRIGHT_GREEN = BRIGHT_ATTR | GREEN_ATTR; const BYTE BRIGHT_YELLOW = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR; const BYTE BRIGHT_BLUE = BRIGHT_ATTR | BLUE_ATTR; const BYTE BRIGHT_MAGENTA = BRIGHT_ATTR | RED_ATTR | BLUE_ATTR; const BYTE BRIGHT_CYAN = BRIGHT_ATTR | GREEN_ATTR | BLUE_ATTR; const BYTE BRIGHT_WHITE = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR | BLUE_ATTR; // clang-format on // Routine Description: // Returns true if the GraphicsOption represents an extended color option. // These are followed by up to 4 more values which compose the entire option. // Return Value: // - true if the opt is the indicator for an extended color sequence, false otherwise. bool TerminalDispatch::s_IsRgbColorOption(const DispatchTypes::GraphicsOptions opt) noexcept { return opt == DispatchTypes::GraphicsOptions::ForegroundExtended || opt == DispatchTypes::GraphicsOptions::BackgroundExtended; } // Routine Description: // Returns true if the GraphicsOption represents an extended color option. // These are followed by up to 4 more values which compose the entire option. // Return Value: // - true if the opt is the indicator for an extended color sequence, false otherwise. bool TerminalDispatch::s_IsBoldColorOption(const DispatchTypes::GraphicsOptions opt) noexcept { return opt == DispatchTypes::GraphicsOptions::BoldBright || opt == DispatchTypes::GraphicsOptions::UnBold; } // Function Description: // - checks if this graphics option should set either the console's FG or BG to //the default attributes. // Return Value: // - true if the opt sets either/or attribute to the defaults, false otherwise. bool TerminalDispatch::s_IsDefaultColorOption(const DispatchTypes::GraphicsOptions opt) noexcept { return opt == DispatchTypes::GraphicsOptions::Off || opt == DispatchTypes::GraphicsOptions::ForegroundDefault || opt == DispatchTypes::GraphicsOptions::BackgroundDefault; } // Routine Description: // - Helper to parse extended graphics options, which start with 38 (FG) or 48 (BG) // These options are followed by either a 2 (RGB) or 5 (xterm index) // RGB sequences then take 3 MORE params to designate the R, G, B parts of the color // Xterm index will use the param that follows to use a color from the preset 256 color xterm color table. // Arguments: // - rgOptions - An array of options that will be used to generate the RGB color // - cOptions - The count of options // - pcOptionsConsumed - a pointer to place the number of options we consumed parsing this option. // Return Value: // Returns true if we successfully parsed an extended color option from the options array. // - This corresponds to the following number of options consumed (pcOptionsConsumed): // 1 - false, not enough options to parse. // 2 - false, not enough options to parse. // 3 - true, parsed an xterm index to a color // 5 - true, parsed an RGB color. bool TerminalDispatch::_SetRgbColorsHelper(_In_reads_(cOptions) const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions, _Out_ size_t* const pcOptionsConsumed) { COLORREF color = 0; bool isForeground = false; bool fSuccess = false; *pcOptionsConsumed = 1; if (cOptions >= 2 && s_IsRgbColorOption(rgOptions[0])) { *pcOptionsConsumed = 2; DispatchTypes::GraphicsOptions extendedOpt = rgOptions[0]; DispatchTypes::GraphicsOptions typeOpt = rgOptions[1]; if (extendedOpt == DispatchTypes::GraphicsOptions::ForegroundExtended) { isForeground = true; } else if (extendedOpt == DispatchTypes::GraphicsOptions::BackgroundExtended) { isForeground = false; } if (typeOpt == DispatchTypes::GraphicsOptions::RGBColor && cOptions >= 5) { *pcOptionsConsumed = 5; // ensure that each value fits in a byte unsigned int red = rgOptions[2] > 255 ? 255 : rgOptions[2]; unsigned int green = rgOptions[3] > 255 ? 255 : rgOptions[3]; unsigned int blue = rgOptions[4] > 255 ? 255 : rgOptions[4]; color = RGB(red, green, blue); fSuccess = _terminalApi.SetTextRgbColor(color, isForeground); } else if (typeOpt == DispatchTypes::GraphicsOptions::Xterm256Index && cOptions >= 3) { *pcOptionsConsumed = 3; if (rgOptions[2] <= 255) // ensure that the provided index is on the table { unsigned int tableIndex = rgOptions[2]; fSuccess = isForeground ? _terminalApi.SetTextForegroundIndex((BYTE)tableIndex) : _terminalApi.SetTextBackgroundIndex((BYTE)tableIndex); } } } return fSuccess; } bool TerminalDispatch::_SetBoldColorHelper(const DispatchTypes::GraphicsOptions option) { const bool bold = (option == DispatchTypes::GraphicsOptions::BoldBright); return _terminalApi.BoldText(bold); } bool TerminalDispatch::_SetDefaultColorHelper(const DispatchTypes::GraphicsOptions option) { const bool fg = option == DispatchTypes::GraphicsOptions::Off || option == DispatchTypes::GraphicsOptions::ForegroundDefault; const bool bg = option == DispatchTypes::GraphicsOptions::Off || option == DispatchTypes::GraphicsOptions::BackgroundDefault; bool success = _terminalApi.SetTextToDefaults(fg, bg); if (success && fg && bg) { // If we're resetting both the FG & BG, also reset the meta attributes (underline) // as well as the boldness success = _terminalApi.UnderlineText(false) && _terminalApi.ReverseText(false) && _terminalApi.BoldText(false); } return success; } // Routine Description: // - Helper to apply the actual flags to each text attributes field. // - Placed as a helper so it can be recursive/re-entrant for some of the convenience flag methods that perform similar/multiple operations in one command. // Arguments: // - opt - Graphics option sent to us by the parser/requestor. // - pAttr - Pointer to the font attribute field to adjust // Return Value: // - void TerminalDispatch::_SetGraphicsOptionHelper(const DispatchTypes::GraphicsOptions opt) { switch (opt) { case DispatchTypes::GraphicsOptions::Off: FAIL_FAST_MSG("GraphicsOptions::Off should be handled by _SetDefaultColorHelper"); break; // MSFT:16398982 - These two are now handled by _SetBoldColorHelper // case DispatchTypes::GraphicsOptions::BoldBright: // case DispatchTypes::GraphicsOptions::UnBold: case DispatchTypes::GraphicsOptions::Negative: _terminalApi.ReverseText(true); break; case DispatchTypes::GraphicsOptions::Underline: _terminalApi.UnderlineText(true); break; case DispatchTypes::GraphicsOptions::Positive: _terminalApi.ReverseText(false); break; case DispatchTypes::GraphicsOptions::NoUnderline: _terminalApi.UnderlineText(false); break; case DispatchTypes::GraphicsOptions::ForegroundBlack: _terminalApi.SetTextForegroundIndex(DARK_BLACK); break; case DispatchTypes::GraphicsOptions::ForegroundBlue: _terminalApi.SetTextForegroundIndex(DARK_BLUE); break; case DispatchTypes::GraphicsOptions::ForegroundGreen: _terminalApi.SetTextForegroundIndex(DARK_GREEN); break; case DispatchTypes::GraphicsOptions::ForegroundCyan: _terminalApi.SetTextForegroundIndex(DARK_CYAN); break; case DispatchTypes::GraphicsOptions::ForegroundRed: _terminalApi.SetTextForegroundIndex(DARK_RED); break; case DispatchTypes::GraphicsOptions::ForegroundMagenta: _terminalApi.SetTextForegroundIndex(DARK_MAGENTA); break; case DispatchTypes::GraphicsOptions::ForegroundYellow: _terminalApi.SetTextForegroundIndex(DARK_YELLOW); break; case DispatchTypes::GraphicsOptions::ForegroundWhite: _terminalApi.SetTextForegroundIndex(DARK_WHITE); break; case DispatchTypes::GraphicsOptions::ForegroundDefault: FAIL_FAST_MSG("GraphicsOptions::ForegroundDefault should be handled by _SetDefaultColorHelper"); break; case DispatchTypes::GraphicsOptions::BackgroundBlack: _terminalApi.SetTextBackgroundIndex(DARK_BLACK); break; case DispatchTypes::GraphicsOptions::BackgroundBlue: _terminalApi.SetTextBackgroundIndex(DARK_BLUE); break; case DispatchTypes::GraphicsOptions::BackgroundGreen: _terminalApi.SetTextBackgroundIndex(DARK_GREEN); break; case DispatchTypes::GraphicsOptions::BackgroundCyan: _terminalApi.SetTextBackgroundIndex(DARK_CYAN); break; case DispatchTypes::GraphicsOptions::BackgroundRed: _terminalApi.SetTextBackgroundIndex(DARK_RED); break; case DispatchTypes::GraphicsOptions::BackgroundMagenta: _terminalApi.SetTextBackgroundIndex(DARK_MAGENTA); break; case DispatchTypes::GraphicsOptions::BackgroundYellow: _terminalApi.SetTextBackgroundIndex(DARK_YELLOW); break; case DispatchTypes::GraphicsOptions::BackgroundWhite: _terminalApi.SetTextBackgroundIndex(DARK_WHITE); break; case DispatchTypes::GraphicsOptions::BackgroundDefault: FAIL_FAST_MSG("GraphicsOptions::BackgroundDefault should be handled by _SetDefaultColorHelper"); break; case DispatchTypes::GraphicsOptions::BrightForegroundBlack: _terminalApi.SetTextForegroundIndex(BRIGHT_BLACK); break; case DispatchTypes::GraphicsOptions::BrightForegroundBlue: _terminalApi.SetTextForegroundIndex(BRIGHT_BLUE); break; case DispatchTypes::GraphicsOptions::BrightForegroundGreen: _terminalApi.SetTextForegroundIndex(BRIGHT_GREEN); break; case DispatchTypes::GraphicsOptions::BrightForegroundCyan: _terminalApi.SetTextForegroundIndex(BRIGHT_CYAN); break; case DispatchTypes::GraphicsOptions::BrightForegroundRed: _terminalApi.SetTextForegroundIndex(BRIGHT_RED); break; case DispatchTypes::GraphicsOptions::BrightForegroundMagenta: _terminalApi.SetTextForegroundIndex(BRIGHT_MAGENTA); break; case DispatchTypes::GraphicsOptions::BrightForegroundYellow: _terminalApi.SetTextForegroundIndex(BRIGHT_YELLOW); break; case DispatchTypes::GraphicsOptions::BrightForegroundWhite: _terminalApi.SetTextForegroundIndex(BRIGHT_WHITE); break; case DispatchTypes::GraphicsOptions::BrightBackgroundBlack: _terminalApi.SetTextBackgroundIndex(BRIGHT_BLACK); break; case DispatchTypes::GraphicsOptions::BrightBackgroundBlue: _terminalApi.SetTextBackgroundIndex(BRIGHT_BLUE); break; case DispatchTypes::GraphicsOptions::BrightBackgroundGreen: _terminalApi.SetTextBackgroundIndex(BRIGHT_GREEN); break; case DispatchTypes::GraphicsOptions::BrightBackgroundCyan: _terminalApi.SetTextBackgroundIndex(BRIGHT_CYAN); break; case DispatchTypes::GraphicsOptions::BrightBackgroundRed: _terminalApi.SetTextBackgroundIndex(BRIGHT_RED); break; case DispatchTypes::GraphicsOptions::BrightBackgroundMagenta: _terminalApi.SetTextBackgroundIndex(BRIGHT_MAGENTA); break; case DispatchTypes::GraphicsOptions::BrightBackgroundYellow: _terminalApi.SetTextBackgroundIndex(BRIGHT_YELLOW); break; case DispatchTypes::GraphicsOptions::BrightBackgroundWhite: _terminalApi.SetTextBackgroundIndex(BRIGHT_WHITE); break; } } bool TerminalDispatch::SetGraphicsRendition(const DispatchTypes::GraphicsOptions* const rgOptions, const size_t cOptions) { bool fSuccess = false; // Run through the graphics options and apply them for (size_t i = 0; i < cOptions; i++) { DispatchTypes::GraphicsOptions opt = rgOptions[i]; if (s_IsDefaultColorOption(opt)) { fSuccess = _SetDefaultColorHelper(opt); } else if (s_IsBoldColorOption(opt)) { fSuccess = _SetBoldColorHelper(rgOptions[i]); } else if (s_IsRgbColorOption(opt)) { size_t cOptionsConsumed = 0; // _SetRgbColorsHelper will call the appropriate ConApi function fSuccess = _SetRgbColorsHelper(&(rgOptions[i]), cOptions - i, &cOptionsConsumed); i += (cOptionsConsumed - 1); // cOptionsConsumed includes the opt we're currently on. } else { _SetGraphicsOptionHelper(opt); // Make sure we un-bold if (fSuccess && opt == DispatchTypes::GraphicsOptions::Off) { fSuccess = _SetBoldColorHelper(opt); } } } return fSuccess; }