winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }