VTSupport.cpp (12649B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "VTSupport.h" 5 #include <AppInstallerLogging.h> 6 #include <AppInstallerStrings.h> 7 8 namespace AppInstaller::CLI::VirtualTerminal 9 { 10 namespace 11 { 12 TextFormat::Color GetAccentColorFromSystem() 13 { 14 using namespace winrt::Windows::UI::ViewManagement; 15 16 UISettings settings; 17 auto color = settings.GetColorValue(UIColorType::Accent); 18 return { color.R, color.G, color.B }; 19 } 20 21 bool InitializeMode(DWORD handle, DWORD& previousMode, std::initializer_list<DWORD> modeModifierFallbacks, DWORD disabledFlags = 0) 22 { 23 HANDLE hStd = GetStdHandle(handle); 24 if (hStd == INVALID_HANDLE_VALUE) 25 { 26 LOG_LAST_ERROR(); 27 } 28 else if (hStd == NULL) 29 { 30 AICLI_LOG(CLI, Info, << "VT not enabled due to null handle [" << handle << "]"); 31 } 32 else 33 { 34 if (!GetConsoleMode(hStd, &previousMode)) 35 { 36 // If the user redirects output, the handle will be invalid for this function. 37 // Don't log it in that case. 38 LOG_LAST_ERROR_IF(GetLastError() != ERROR_INVALID_HANDLE); 39 } 40 else 41 { 42 for (DWORD mode : modeModifierFallbacks) 43 { 44 DWORD outMode = (previousMode & ~disabledFlags) | mode; 45 if (!SetConsoleMode(hStd, outMode)) 46 { 47 // Even if it is a different error, log it and try to carry on. 48 LOG_LAST_ERROR_IF(GetLastError() != STATUS_INVALID_PARAMETER); 49 } 50 else 51 { 52 return true; 53 } 54 } 55 } 56 } 57 58 return false; 59 } 60 61 // Extracts a VT sequence, expected one of the form ESCAPE + prefix + result + suffix, returning the result part. 62 std::string ExtractSequence(std::istream& inStream, std::string_view prefix, std::string_view suffix) 63 { 64 // Force discovery of available input 65 std::ignore = inStream.peek(); 66 67 static constexpr std::streamsize s_bufferSize = 1024; 68 char buffer[s_bufferSize]; 69 std::streamsize bytesRead = inStream.readsome(buffer, s_bufferSize); 70 THROW_HR_IF(E_UNEXPECTED, bytesRead >= s_bufferSize); 71 72 std::string_view resultView{ buffer, static_cast<size_t>(bytesRead) }; 73 size_t escapeIndex = resultView.find(AICLI_VT_ESCAPE[0]); 74 if (escapeIndex == std::string_view::npos) 75 { 76 return {}; 77 } 78 79 resultView = resultView.substr(escapeIndex); 80 size_t overheadLength = 1 + prefix.length() + suffix.length(); 81 if (resultView.length() <= overheadLength || 82 resultView.substr(1, prefix.length()) != prefix || 83 resultView.substr(resultView.length() - suffix.length()) != suffix) 84 { 85 return {}; 86 } 87 88 return std::string{ resultView.substr(1 + prefix.length(), resultView.length() - overheadLength) }; 89 } 90 } 91 92 ConsoleModeRestoreBase::ConsoleModeRestoreBase(DWORD handle) : m_handle(handle) {} 93 94 ConsoleModeRestoreBase::~ConsoleModeRestoreBase() 95 { 96 if (m_token) 97 { 98 LOG_LAST_ERROR_IF(!SetConsoleMode(GetStdHandle(m_handle), m_previousMode)); 99 m_token = false; 100 } 101 } 102 103 ConsoleModeRestore::ConsoleModeRestore() : ConsoleModeRestoreBase(STD_OUTPUT_HANDLE) 104 { 105 m_token = InitializeMode(STD_OUTPUT_HANDLE, m_previousMode, { ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN, ENABLE_VIRTUAL_TERMINAL_PROCESSING }); 106 } 107 108 const ConsoleModeRestore& ConsoleModeRestore::Instance() 109 { 110 static ConsoleModeRestore s_instance; 111 return s_instance; 112 } 113 114 ConsoleInputModeRestore::ConsoleInputModeRestore() : ConsoleModeRestoreBase(STD_INPUT_HANDLE) 115 { 116 m_token = InitializeMode(STD_INPUT_HANDLE, m_previousMode, { ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT }, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 117 } 118 119 void ConstructedSequence::Append(const Sequence& sequence) 120 { 121 if (!sequence.Get().empty()) 122 { 123 m_str += sequence.Get(); 124 Set(m_str); 125 } 126 } 127 128 void ConstructedSequence::Clear() 129 { 130 m_str.clear(); 131 Set(m_str); 132 } 133 134 // The beginning of a Control Sequence Introducer 135 #define AICLI_VT_CSI AICLI_VT_ESCAPE "[" 136 137 // The beginning of an Operating system command 138 #define AICLI_VT_OSC AICLI_VT_ESCAPE "]" 139 140 PrimaryDeviceAttributes::PrimaryDeviceAttributes(std::ostream& outStream, std::istream& inStream) 141 { 142 try 143 { 144 ConsoleInputModeRestore inputMode; 145 if (!inputMode.IsVTEnabled()) 146 { 147 return; 148 } 149 150 // Send DA1 Primary Device Attributes request 151 outStream << AICLI_VT_CSI << "0c"; 152 outStream.flush(); 153 154 // Response is of the form AICLI_VT_CSI ? <conformance level> ; (<extension number> ;)* c 155 std::string sequence = ExtractSequence(inStream, "[?", "c"); 156 std::vector<std::string> values = Utility::Split(sequence, ';'); 157 158 if (!values.empty()) 159 { 160 m_conformanceLevel = std::stoul(values[0]); 161 } 162 163 for (size_t i = 1; i < values.size(); ++i) 164 { 165 m_extensions |= 1ull << std::stoul(values[i]); 166 } 167 } 168 CATCH_LOG(); 169 } 170 171 bool PrimaryDeviceAttributes::Supports(Extension extension) const 172 { 173 uint64_t extensionMask = 1ull << ToIntegral(extension); 174 return (m_extensions & extensionMask) == extensionMask; 175 } 176 177 namespace Cursor 178 { 179 namespace Position 180 { 181 ConstructedSequence Up(int16_t cells) 182 { 183 THROW_HR_IF(E_INVALIDARG, cells < 0); 184 std::ostringstream result; 185 result << AICLI_VT_CSI << cells << 'A'; 186 return ConstructedSequence{ std::move(result).str() }; 187 } 188 189 ConstructedSequence Down(int16_t cells) 190 { 191 THROW_HR_IF(E_INVALIDARG, cells < 0); 192 std::ostringstream result; 193 result << AICLI_VT_CSI << cells << 'B'; 194 return ConstructedSequence{ std::move(result).str() }; 195 } 196 197 ConstructedSequence Forward(int16_t cells) 198 { 199 THROW_HR_IF(E_INVALIDARG, cells < 0); 200 std::ostringstream result; 201 result << AICLI_VT_CSI << cells << 'C'; 202 return ConstructedSequence{ std::move(result).str() }; 203 } 204 205 ConstructedSequence Backward(int16_t cells) 206 { 207 THROW_HR_IF(E_INVALIDARG, cells < 0); 208 std::ostringstream result; 209 result << AICLI_VT_CSI << cells << 'D'; 210 return ConstructedSequence{ std::move(result).str() }; 211 } 212 } 213 214 namespace Visibility 215 { 216 const Sequence EnableBlink{ AICLI_VT_CSI "?12h" }; 217 const Sequence DisableBlink{ AICLI_VT_CSI "?12l" }; 218 const Sequence EnableShow{ AICLI_VT_CSI "?25h" }; 219 const Sequence DisableShow{ AICLI_VT_CSI "?25l" }; 220 } 221 } 222 223 namespace TextFormat 224 { 225 // Define a text formatting sequence with an integer id 226 #define AICLI_VT_TEXTFORMAT(_id_) AICLI_VT_CSI #_id_ "m" 227 228 const Sequence Default{ AICLI_VT_TEXTFORMAT(0) }; 229 const Sequence Negative{ AICLI_VT_TEXTFORMAT(7) }; 230 231 Color Color::GetAccentColor() 232 { 233 static Color accent{ GetAccentColorFromSystem() }; 234 return accent; 235 } 236 237 namespace Foreground 238 { 239 const Sequence Bright{ AICLI_VT_TEXTFORMAT(1) }; 240 const Sequence NoBright{ AICLI_VT_TEXTFORMAT(22) }; 241 242 const Sequence BrightRed{ AICLI_VT_TEXTFORMAT(91) }; 243 const Sequence BrightGreen{ AICLI_VT_TEXTFORMAT(92) }; 244 const Sequence BrightYellow{ AICLI_VT_TEXTFORMAT(93) }; 245 const Sequence BrightBlue{ AICLI_VT_TEXTFORMAT(94) }; 246 const Sequence BrightMagenta{ AICLI_VT_TEXTFORMAT(95) }; 247 const Sequence BrightCyan{ AICLI_VT_TEXTFORMAT(96) }; 248 const Sequence BrightWhite{ AICLI_VT_TEXTFORMAT(97) }; 249 250 ConstructedSequence Extended(const Color& color) 251 { 252 std::ostringstream result; 253 result << AICLI_VT_CSI "38;2;" << static_cast<uint32_t>(color.R) << ';' << static_cast<uint32_t>(color.G) << ';' << static_cast<uint32_t>(color.B) << 'm'; 254 return ConstructedSequence{ std::move(result).str() }; 255 } 256 } 257 258 namespace Background 259 { 260 ConstructedSequence Extended(const Color& color) 261 { 262 std::ostringstream result; 263 result << AICLI_VT_CSI "48;2;" << static_cast<uint32_t>(color.R) << ';' << static_cast<uint32_t>(color.G) << ';' << static_cast<uint32_t>(color.B) << 'm'; 264 return ConstructedSequence{ std::move(result).str() }; 265 } 266 } 267 268 ConstructedSequence Hyperlink(const std::string& text, const std::string& ref) 269 { 270 std::ostringstream result; 271 result << AICLI_VT_OSC "8;;" << ref << AICLI_VT_ESCAPE << "\\" << text << AICLI_VT_OSC << "8;;" << AICLI_VT_ESCAPE << "\\"; 272 return ConstructedSequence{ std::move(result).str() }; 273 } 274 } 275 276 namespace TextModification 277 { 278 const Sequence EraseLineForward{ AICLI_VT_CSI "0K" }; 279 const Sequence EraseLineBackward{ AICLI_VT_CSI "1K" }; 280 const Sequence EraseLineEntirely{ AICLI_VT_CSI "2K" }; 281 } 282 283 namespace Progress 284 { 285 ConstructedSequence Construct(State state, std::optional<uint32_t> percentage) 286 { 287 // See https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC 288 289 THROW_HR_IF(E_BOUNDS, percentage.has_value() && percentage > 100u); 290 291 // Workaround some quirks in the Windows Terminal implementation of the progress OSC sequence 292 switch (state) 293 { 294 case State::None: 295 case State::Indeterminate: 296 // Windows Terminal does not recognize the OSC sequence if the progress value is left out. 297 // As a workaround, we can specify an arbitrary value since it does not matter for None and Indeterminate states. 298 percentage = percentage.value_or(0); 299 break; 300 case State::Normal: 301 case State::Error: 302 case State::Paused: 303 // Windows Terminal does not support switching progress states without also setting a progress value at the same time, 304 // so we disallow this case for now. 305 THROW_HR_IF(E_INVALIDARG, !percentage.has_value()); 306 break; 307 } 308 309 int stateId; 310 switch (state) 311 { 312 case State::None: 313 stateId = 0; 314 break; 315 case State::Indeterminate: 316 stateId = 3; 317 break; 318 case State::Normal: 319 stateId = 1; 320 break; 321 case State::Error: 322 stateId = 2; 323 break; 324 case State::Paused: 325 stateId = 4; 326 break; 327 default: 328 THROW_HR(E_UNEXPECTED); 329 } 330 331 std::ostringstream result; 332 result << AICLI_VT_OSC "9;4;" << stateId << ";"; 333 if (percentage.has_value()) 334 { 335 result << percentage.value(); 336 } 337 result << AICLI_VT_ESCAPE << "\\"; 338 return ConstructedSequence{ std::move(result).str() }; 339 } 340 } 341 }