winget-cli

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

ExecutionProgress.cpp (29489B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ExecutionProgress.h"
      5 #include "VTSupport.h"
      6 #include "AppInstallerRuntime.h"
      7 #include "Sixel.h"
      8 
      9 using namespace AppInstaller::Settings;
     10 using namespace AppInstaller::CLI::VirtualTerminal;
     11 using namespace std::string_view_literals;
     12 
     13 namespace AppInstaller::CLI::Execution
     14 {
     15     namespace
     16     {
     17         static constexpr size_t s_ProgressBarCellWidth = 30;
     18 
     19         struct BytesFormatData
     20         {
     21             uint64_t PowerOfTwo;
     22             std::string_view Name;
     23         };
     24 
     25         BytesFormatData s_bytesFormatData[] =
     26         {
     27             // Multi-terabyte installers should be fairly rare for the foreseeable future...
     28             { 40, "TB"sv },
     29             { 30, "GB"sv },
     30             { 20, "MB"sv },
     31             { 10, "KB"sv },
     32             { 0, "B"sv },
     33         };
     34 
     35         const BytesFormatData& GetFormatForSize(uint64_t bytes)
     36         {
     37             for (const auto& format : s_bytesFormatData)
     38             {
     39                 if (bytes > (1ull << format.PowerOfTwo))
     40                 {
     41                     return format;
     42                 }
     43             }
     44 
     45             // Just to make the compiler happy, return the last in the list if we get here.
     46             return s_bytesFormatData[ARRAYSIZE(s_bytesFormatData) - 1];
     47         }
     48 
     49         void OutputBytes(BaseStream& out, uint64_t byteCount)
     50         {
     51             const BytesFormatData& bfd = GetFormatForSize(byteCount);
     52 
     53             uint64_t integralAmount = byteCount >> bfd.PowerOfTwo;
     54             uint64_t remainder = byteCount & ((1ull << bfd.PowerOfTwo) - 1);
     55             size_t remainderDigits = 0;
     56 
     57             if (integralAmount < 10)
     58             {
     59                 remainder *= 100;
     60                 remainderDigits = 2;
     61             }
     62             else if (integralAmount < 100)
     63             {
     64                 remainder *= 10;
     65                 remainderDigits = 1;
     66             }
     67             else if (integralAmount < 1000)
     68             {
     69                 // Put an extra space to ensure a consistent 4 chars per numeric output
     70                 out << ' ';
     71             }
     72 
     73             out << integralAmount;
     74 
     75             if (remainderDigits)
     76             {
     77                 remainder = remainder >> bfd.PowerOfTwo;
     78                 out << '.' << std::setw(remainderDigits) << std::setfill('0') << remainder;
     79             }
     80 
     81             out << ' ' << bfd.Name;
     82         }
     83 
     84         void SetColor(BaseStream& out, const TextFormat::Color& color, bool foregroundOnly)
     85         {
     86             out << TextFormat::Foreground::Extended(color);
     87 
     88             if (!foregroundOnly)
     89             {
     90                 constexpr uint8_t divisor = 3;
     91 
     92                 auto reduced = color;
     93                 reduced.R /= divisor;
     94                 reduced.G /= divisor;
     95                 reduced.B /= divisor;
     96 
     97                 out << TextFormat::Background::Extended(reduced);
     98             }
     99         }
    100 
    101         void SetRainbowColor(BaseStream& out, size_t i, size_t max, bool foregroundOnly)
    102         {
    103             TextFormat::Color rainbow[] =
    104             {
    105                 { 0xff, 0x00, 0x00 },
    106                 { 0xff, 0x77, 0x00 },
    107                 { 0xff, 0xdd, 0x00 },
    108                 { 0x00, 0xff, 0x00 },
    109                 { 0x00, 0x00, 0xff },
    110                 { 0x8a, 0x2b, 0xe2 },
    111                 { 0xc7, 0x7d, 0xf3 },
    112             };
    113 
    114             double target = (static_cast<double>(i) / (max - 1)) * (ARRAYSIZE(rainbow) - 1);
    115             size_t lower = static_cast<size_t>(std::floor(target));
    116             const auto& lowerVal = rainbow[lower];
    117             TextFormat::Color result;
    118 
    119             if (lower == (ARRAYSIZE(rainbow) - 1))
    120             {
    121                 result = lowerVal;
    122             }
    123             else
    124             {
    125                 double upperContribution = target - lower;
    126 
    127 #define AICLI_AVERAGE(v) static_cast<uint8_t>(((lowerVal.v * (1.0 - upperContribution)) + (rainbow[lower + 1].v * upperContribution)))
    128                 result = { AICLI_AVERAGE(R), AICLI_AVERAGE(G), AICLI_AVERAGE(B) };
    129             }
    130 
    131             SetColor(out, result, foregroundOnly);
    132         }
    133     }
    134 
    135     // Shared functionality for progress visualizers.
    136     struct ProgressVisualizerBase
    137     {
    138         ProgressVisualizerBase(BaseStream& stream, bool enableVT) :
    139             m_out(stream), m_enableVT(enableVT) {}
    140 
    141         void SetMessage(std::string_view message)
    142         {
    143             std::atomic_store(&m_message, std::make_shared<Utility::NormalizedString>(message));
    144         }
    145 
    146         std::shared_ptr<Utility::NormalizedString> Message()
    147         {
    148             return std::atomic_load(&m_message);
    149         }
    150 
    151     protected:
    152         BaseStream& m_out;
    153 
    154         bool VT_Enabled() const { return m_enableVT; }
    155 
    156         void ClearLine()
    157         {
    158             if (VT_Enabled())
    159             {
    160                 m_out << TextModification::EraseLineEntirely << '\r';
    161             }
    162             else
    163             {
    164                 m_out << '\r' << std::string(GetConsoleWidth(), ' ') << '\r';
    165             }
    166         }
    167 
    168     private:
    169         bool m_enableVT = false;
    170         std::shared_ptr<Utility::NormalizedString> m_message;
    171     };
    172 
    173     // Shared functionality for progress visualizers.
    174     struct CharacterProgressVisualizerBase : public ProgressVisualizerBase
    175     {
    176         CharacterProgressVisualizerBase(BaseStream& stream, bool enableVT, VisualStyle style) :
    177             ProgressVisualizerBase(stream, enableVT && style != AppInstaller::Settings::VisualStyle::NoVT), m_style(style) {}
    178 
    179     protected:
    180         Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent;
    181 
    182         // Applies the selected visual style.
    183         void ApplyStyle(size_t i, size_t max, bool foregroundOnly)
    184         {
    185             if (!VT_Enabled())
    186             {
    187                 // Either no style set or VT disabled
    188                 return;
    189             }
    190             switch (m_style)
    191             {
    192             case VisualStyle::Retro:
    193                 m_out << TextFormat::Default;
    194                 break;
    195             case VisualStyle::Accent:
    196                 SetColor(m_out, TextFormat::Color::GetAccentColor(), foregroundOnly);
    197                 break;
    198             case VisualStyle::Rainbow:
    199                 SetRainbowColor(m_out, i, max, foregroundOnly);
    200                 break;
    201             default:
    202                 LOG_HR(E_UNEXPECTED);
    203             }
    204         }
    205     };
    206 
    207     // Displays an indefinite spinner via a character.
    208     struct CharacterIndefiniteSpinner : public CharacterProgressVisualizerBase, public IIndefiniteSpinner
    209     {
    210         CharacterIndefiniteSpinner(BaseStream& stream, bool enableVT, VisualStyle style) :
    211             CharacterProgressVisualizerBase(stream, enableVT, style) {}
    212 
    213         void ShowSpinner() override
    214         {
    215             if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled)
    216             {
    217                 m_spinnerRunning = true;
    218                 m_spinnerJob = std::async(std::launch::async, &CharacterIndefiniteSpinner::ShowSpinnerInternal, this);
    219             }
    220         }
    221 
    222         void StopSpinner() override
    223         {
    224             if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning)
    225             {
    226                 m_canceled = true;
    227                 m_spinnerJob.get();
    228             }
    229         }
    230 
    231         void SetMessage(std::string_view message) override
    232         {
    233             ProgressVisualizerBase::SetMessage(message);
    234         }
    235 
    236         std::shared_ptr<Utility::NormalizedString> Message() override
    237         {
    238             return ProgressVisualizerBase::Message();
    239         }
    240 
    241     private:
    242         std::atomic<bool> m_canceled = false;
    243         std::atomic<bool> m_spinnerRunning = false;
    244         std::future<void> m_spinnerJob;
    245 
    246         void ShowSpinnerInternal()
    247         {
    248             char spinnerChars[] = { '-', '\\', '|', '/' };
    249 
    250             // First wait for a small amount of time to enable a fast task to skip
    251             // showing anything, or a progress task to skip straight to progress.
    252             Sleep(100);
    253 
    254             if (!m_canceled)
    255             {
    256                 if (VT_Enabled())
    257                 {
    258                     // Additional VT-based progress reporting, for terminals that support it
    259                     m_out << Progress::Construct(Progress::State::Indeterminate);
    260                 }
    261 
    262                 // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop.
    263                 std::string_view indent = "   ";
    264                 std::shared_ptr<Utility::NormalizedString> message = ProgressVisualizerBase::Message();
    265                 size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0;
    266 
    267                 for (size_t i = 0; !m_canceled; ++i)
    268                 {
    269                     constexpr size_t repetitionCount = 20;
    270                     ApplyStyle(i % repetitionCount, repetitionCount, true);
    271                     m_out << '\r' << indent << spinnerChars[i % ARRAYSIZE(spinnerChars)];
    272                     m_out.RestoreDefault();
    273 
    274                     std::shared_ptr<Utility::NormalizedString> newMessage = ProgressVisualizerBase::Message();
    275                     std::string eraser;
    276                     if (newMessage)
    277                     {
    278                         size_t newLength = Utility::UTF8ColumnWidth(*newMessage);
    279 
    280                         if (newLength < messageLength)
    281                         {
    282                             eraser = std::string(messageLength - newLength, ' ');
    283                         }
    284 
    285                         message = newMessage;
    286                         messageLength = newLength;
    287                     }
    288 
    289                     m_out << ' ' << (message ? *message : std::string{}) << eraser << std::flush;
    290                     Sleep(250);
    291                 }
    292 
    293                 ClearLine();
    294 
    295                 if (VT_Enabled())
    296                 {
    297                     m_out << Progress::Construct(Progress::State::None);
    298                 }
    299             }
    300 
    301             m_canceled = false;
    302             m_spinnerRunning = false;
    303         }
    304     };
    305 
    306     // Displays progress via character output.
    307     class CharacterProgressBar : public CharacterProgressVisualizerBase, public IProgressBar
    308     {
    309     public:
    310         CharacterProgressBar(BaseStream& stream, bool enableVT, VisualStyle style) :
    311             CharacterProgressVisualizerBase(stream, enableVT, style) {}
    312 
    313         void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override
    314         {
    315             if (current < m_lastCurrent)
    316             {
    317                 ClearLine();
    318             }
    319 
    320             // TODO: Progress bar does not currently use message
    321             if (VT_Enabled())
    322             {
    323                 ShowProgressWithVT(current, maximum, type);
    324             }
    325             else
    326             {
    327                 ShowProgressNoVT(current, maximum, type);
    328             }
    329 
    330             m_lastCurrent = current;
    331             m_isVisible = true;
    332         }
    333 
    334         void EndProgress(bool hideProgressWhenDone) override
    335         {
    336             if (m_isVisible)
    337             {
    338                 if (hideProgressWhenDone)
    339                 {
    340                     ClearLine();
    341                 }
    342                 else
    343                 {
    344                     m_out << std::endl;
    345                 }
    346 
    347                 if (VT_Enabled())
    348                 {
    349                     // We always clear the VT-based progress bar, even if hideProgressWhenDone is false
    350                     // since it would be confusing for users if progress continues to be shown after winget exits
    351                     // (it is typically not automatically cleared by terminals on process exit)
    352                     m_out << Progress::Construct(Progress::State::None);
    353                 }
    354 
    355                 m_isVisible = false;
    356             }
    357         }
    358 
    359     private:
    360         std::atomic<bool> m_isVisible = false;
    361         uint64_t m_lastCurrent = 0;
    362 
    363         void ShowProgressNoVT(uint64_t current, uint64_t maximum, ProgressType type)
    364         {
    365             m_out << "\r  ";
    366 
    367             if (maximum)
    368             {
    369                 const char* const blockOn = u8"\x2588";
    370                 const char* const blockOff = u8"\x2592";
    371                 constexpr size_t blockWidth = 30;
    372 
    373                 double percentage = static_cast<double>(current) / maximum;
    374                 size_t blocksOn = static_cast<size_t>(std::floor(percentage * blockWidth));
    375 
    376                 for (size_t i = 0; i < blocksOn; ++i)
    377                 {
    378                     m_out << blockOn;
    379                 }
    380 
    381                 for (size_t i = 0; i < blockWidth - blocksOn; ++i)
    382                 {
    383                     m_out << blockOff;
    384                 }
    385 
    386                 m_out << "  ";
    387 
    388                 switch (type)
    389                 {
    390                 case AppInstaller::ProgressType::Bytes:
    391                     OutputBytes(m_out, current);
    392                     m_out << " / ";
    393                     OutputBytes(m_out, maximum);
    394                     break;
    395                 case AppInstaller::ProgressType::Percent:
    396                 default:
    397                     m_out << static_cast<int>(percentage * 100) << '%';
    398                     break;
    399                 }
    400             }
    401             else
    402             {
    403                 switch (type)
    404                 {
    405                 case AppInstaller::ProgressType::Bytes:
    406                     OutputBytes(m_out, current);
    407                     break;
    408                 case AppInstaller::ProgressType::Percent:
    409                     m_out << current << '%';
    410                     break;
    411                 default:
    412                     m_out << current << " unknowns";
    413                     break;
    414                 }
    415             }
    416         }
    417 
    418         void ShowProgressWithVT(uint64_t current, uint64_t maximum, ProgressType type)
    419         {
    420             m_out << TextFormat::Default;
    421 
    422             m_out << "\r  ";
    423 
    424             if (maximum)
    425             {
    426                 const char* const blocks[] =
    427                 {
    428                     u8" ",      // block off
    429                     u8"\x258F", // block 1/8
    430                     u8"\x258E", // block 2/8
    431                     u8"\x258D", // block 3/8
    432                     u8"\x258C", // block 4/8
    433                     u8"\x258B", // block 5/8
    434                     u8"\x258A", // block 6/8
    435                     u8"\x2589", // block 7/8
    436                     u8"\x2588"  // block on
    437                 };
    438                 const char* const blockOn = blocks[8];
    439                 const char* const blockOff = blocks[0];
    440                 constexpr size_t blockWidth = s_ProgressBarCellWidth;
    441 
    442                 double percentage = static_cast<double>(current) / maximum;
    443                 size_t blocksOn = static_cast<size_t>(std::floor(percentage * blockWidth));
    444                 size_t partialBlockIndex = static_cast<size_t>((percentage * blockWidth - blocksOn) * 8);
    445                 TextFormat::Color accent = TextFormat::Color::GetAccentColor();
    446 
    447                 for (size_t i = 0; i < blockWidth; ++i)
    448                 {
    449                     ApplyStyle(i, blockWidth, false);
    450 
    451                     if (i < blocksOn)
    452                     {
    453                         m_out << blockOn;
    454                     }
    455                     else if (i == blocksOn)
    456                     {
    457                         m_out << blocks[partialBlockIndex];
    458                     }
    459                     else
    460                     {
    461                         m_out << blockOff;
    462                     }
    463                 }
    464 
    465                 m_out << TextFormat::Default;
    466 
    467                 m_out << "  ";
    468 
    469                 switch (type)
    470                 {
    471                 case AppInstaller::ProgressType::Bytes:
    472                     OutputBytes(m_out, current);
    473                     m_out << " / ";
    474                     OutputBytes(m_out, maximum);
    475                     break;
    476                 case AppInstaller::ProgressType::Percent:
    477                 default:
    478                     m_out << static_cast<int>(percentage * 100) << '%';
    479                     break;
    480                 }
    481 
    482                 // Additional VT-based progress reporting, for terminals that support it
    483                 m_out << Progress::Construct(Progress::State::Normal, static_cast<int>(percentage * 100));
    484             }
    485             else
    486             {
    487                 switch (type)
    488                 {
    489                 case AppInstaller::ProgressType::Bytes:
    490                     OutputBytes(m_out, current);
    491                     break;
    492                 case AppInstaller::ProgressType::Percent:
    493                     m_out << current << '%';
    494                     break;
    495                 default:
    496                     m_out << current << " unknowns";
    497                     break;
    498                 }
    499             }
    500         }
    501     };
    502 
    503     // Displays an indefinite spinner via a sixel.
    504     struct SixelIndefiniteSpinner : public ProgressVisualizerBase, public IIndefiniteSpinner
    505     {
    506         SixelIndefiniteSpinner(BaseStream& stream, bool enableVT) :
    507             ProgressVisualizerBase(stream, enableVT)
    508         {
    509             Sixel::RenderControls& renderControls = m_compositor.Controls();
    510             renderControls.RenderSizeInCells(2, 1);
    511 
    512             // Create palette from full image
    513             std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets);
    514             THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty());
    515 
    516             // This image matches the target pixel size. If changing the target size, choose the most appropriate image.
    517             Sixel::ImageSource wingetIcon{ imageAssetsRoot / "AppList.targetsize-20.png" };
    518             wingetIcon.Resize(renderControls);
    519             Sixel::Palette palette = wingetIcon.CreatePalette(renderControls);
    520 
    521             m_folder = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/folders_only.png" };
    522             m_arrow = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/arrow_only.png" };
    523 
    524             m_folder.Resize(renderControls);
    525             m_folder.ApplyPalette(palette);
    526 
    527             Sixel::RenderControls arrowControls = renderControls;
    528             arrowControls.InterpolationMode = Sixel::InterpolationMode::Linear;
    529             m_arrow.Resize(arrowControls);
    530             m_arrow.ApplyPalette(palette);
    531 
    532             m_compositor.Palette(std::move(palette));
    533             m_compositor.AddView(m_arrow.Copy());
    534             m_compositor.AddView(m_folder.Copy());
    535         }
    536 
    537         void ShowSpinner() override
    538         {
    539             if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled)
    540             {
    541                 m_spinnerRunning = true;
    542                 m_spinnerJob = std::async(std::launch::async, &SixelIndefiniteSpinner::ShowSpinnerInternal, this);
    543             }
    544         }
    545 
    546         void StopSpinner() override
    547         {
    548             if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning)
    549             {
    550                 m_canceled = true;
    551                 m_spinnerJob.get();
    552             }
    553         }
    554 
    555         void SetMessage(std::string_view message) override
    556         {
    557             ProgressVisualizerBase::SetMessage(message);
    558         }
    559 
    560         std::shared_ptr<Utility::NormalizedString> Message() override
    561         {
    562             return ProgressVisualizerBase::Message();
    563         }
    564 
    565     private:
    566         std::atomic<bool> m_canceled = false;
    567         std::atomic<bool> m_spinnerRunning = false;
    568         std::future<void> m_spinnerJob;
    569         Sixel::ImageSource m_folder;
    570         Sixel::ImageSource m_arrow;
    571         Sixel::Compositor m_compositor;
    572 
    573         void ShowSpinnerInternal()
    574         {
    575             // First wait for a small amount of time to enable a fast task to skip
    576             // showing anything, or a progress task to skip straight to progress.
    577             Sleep(100);
    578 
    579             if (!m_canceled)
    580             {
    581                 // Additional VT-based progress reporting, for terminals that support it
    582                 m_out << Progress::Construct(Progress::State::Indeterminate);
    583 
    584                 // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop.
    585                 std::string_view indent = "  ";
    586                 std::shared_ptr<Utility::NormalizedString> message = ProgressVisualizerBase::Message();
    587                 size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0;
    588 
    589                 UINT imageHeight = m_compositor.Controls().PixelHeight;
    590 
    591                 for (size_t i = 0; !m_canceled; ++i)
    592                 {
    593                     m_out << '\r' << indent;
    594 
    595                     // Move arrow down one pixel each time
    596                     m_compositor[0].Translate(0, i % imageHeight, true);
    597                     m_compositor.RenderTo(m_out);
    598 
    599                     message = ProgressVisualizerBase::Message();
    600                     size_t newLength = (message ? Utility::UTF8ColumnWidth(*message) : 0);
    601 
    602                     std::string eraser;
    603                     if (newLength < messageLength)
    604                     {
    605                         eraser = std::string(messageLength - newLength, ' ');
    606                     }
    607 
    608                     messageLength = newLength;
    609 
    610                     m_out << VirtualTerminal::Cursor::Position::Forward(3) << (message ? *message : std::string{}) << eraser << std::flush;
    611                     Sleep(100);
    612                 }
    613 
    614                 ClearLine();
    615 
    616                 m_out << Progress::Construct(Progress::State::None);
    617             }
    618 
    619             m_canceled = false;
    620             m_spinnerRunning = false;
    621         }
    622     };
    623 
    624     // Displays progress with a sixel image.
    625     class SixelProgressBar : public ProgressVisualizerBase, public IProgressBar
    626     {
    627     public:
    628         SixelProgressBar(BaseStream& stream, bool enableVT) :
    629             ProgressVisualizerBase(stream, enableVT)
    630         {
    631             static constexpr UINT s_colorsForBelt = 20;
    632 
    633             Sixel::RenderControls imageRenderControls;
    634             imageRenderControls.RenderSizeInCells(2, 1);
    635 
    636             // This image matches the target pixel size. If changing the target size, choose the most appropriate image.
    637             std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets);
    638             THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty());
    639 
    640             m_icon = Sixel::ImageSource{ imageAssetsRoot / "AppList.targetsize-20.png" };
    641             m_icon.Resize(imageRenderControls);
    642             imageRenderControls.ColorCount = Sixel::Palette::MaximumColorCount - s_colorsForBelt;
    643             Sixel::Palette iconPalette = m_icon.CreatePalette(imageRenderControls);
    644 
    645             // TODO: Move to real location
    646             m_belt = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/conveyor.png" };
    647             m_belt.Resize(imageRenderControls);
    648             imageRenderControls.ColorCount = s_colorsForBelt;
    649             imageRenderControls.InterpolationMode = Sixel::InterpolationMode::Linear;
    650             Sixel::Palette beltPalette = m_belt.CreatePalette(imageRenderControls);
    651 
    652             Sixel::Palette combinedPalette{ iconPalette, beltPalette };
    653 
    654             m_icon.ApplyPalette(combinedPalette);
    655             m_belt.ApplyPalette(combinedPalette);
    656 
    657             m_compositor.Palette(std::move(combinedPalette));
    658             m_compositor.AddView(m_icon.Copy());
    659             m_compositor.AddView(m_belt.Copy());
    660             m_compositor.Controls().TransparencyEnabled = false;
    661             m_compositor.Controls().RenderSizeInCells(s_ProgressBarCellWidth, 1);
    662         }
    663 
    664         void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override
    665         {
    666             if (current < m_lastCurrent)
    667             {
    668                 ClearLine();
    669             }
    670 
    671             m_out << TextFormat::Default;
    672 
    673             m_out << "\r  ";
    674 
    675             if (maximum)
    676             {
    677 
    678                 double percentage = static_cast<double>(current) / maximum;
    679 
    680                 // Translate icon so that its leading edge is the progress line
    681                 INT translation = static_cast<INT>((percentage * m_compositor.Controls().PixelWidth) - m_compositor[0].Width());
    682 
    683                 m_compositor[0].Translate(translation, 0, false);
    684                 m_compositor[1].Translate(translation, 0, true);
    685                 m_compositor.RenderTo(m_out);
    686 
    687                 m_out << VirtualTerminal::Cursor::Position::Forward(s_ProgressBarCellWidth + 2);
    688 
    689                 switch (type)
    690                 {
    691                 case AppInstaller::ProgressType::Bytes:
    692                     OutputBytes(m_out, current);
    693                     m_out << " / ";
    694                     OutputBytes(m_out, maximum);
    695                     break;
    696                 case AppInstaller::ProgressType::Percent:
    697                 default:
    698                     m_out << static_cast<int>(percentage * 100) << '%';
    699                     break;
    700                 }
    701 
    702                 // Additional VT-based progress reporting, for terminals that support it
    703                 m_out << Progress::Construct(Progress::State::Normal, static_cast<int>(percentage * 100));
    704             }
    705             else
    706             {
    707                 switch (type)
    708                 {
    709                 case AppInstaller::ProgressType::Bytes:
    710                     OutputBytes(m_out, current);
    711                     break;
    712                 case AppInstaller::ProgressType::Percent:
    713                     m_out << current << '%';
    714                     break;
    715                 default:
    716                     m_out << current << " unknowns";
    717                     break;
    718                 }
    719             }
    720 
    721             m_lastCurrent = current;
    722             m_isVisible = true;
    723         }
    724 
    725         void EndProgress(bool hideProgressWhenDone) override
    726         {
    727             if (m_isVisible)
    728             {
    729                 if (hideProgressWhenDone)
    730                 {
    731                     ClearLine();
    732                 }
    733                 else
    734                 {
    735                     m_out << std::endl;
    736                 }
    737 
    738                 if (VT_Enabled())
    739                 {
    740                     // We always clear the VT-based progress bar, even if hideProgressWhenDone is false
    741                     // since it would be confusing for users if progress continues to be shown after winget exits
    742                     // (it is typically not automatically cleared by terminals on process exit)
    743                     m_out << Progress::Construct(Progress::State::None);
    744                 }
    745 
    746                 m_isVisible = false;
    747             }
    748         }
    749 
    750     private:
    751         std::atomic<bool> m_isVisible = false;
    752         uint64_t m_lastCurrent = 0;
    753         Sixel::ImageSource m_icon;
    754         Sixel::ImageSource m_belt;
    755         Sixel::Compositor m_compositor;
    756     };
    757 
    758     std::unique_ptr<IIndefiniteSpinner> IIndefiniteSpinner::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function<bool()>& sixelSupported)
    759     {
    760         std::unique_ptr<IIndefiniteSpinner> result;
    761 
    762         switch (style)
    763         {
    764         case VisualStyle::NoVT:
    765         case VisualStyle::Retro:
    766         case VisualStyle::Accent:
    767         case VisualStyle::Rainbow:
    768             result = std::make_unique<CharacterIndefiniteSpinner>(stream, enableVT, style);
    769             break;
    770         case VisualStyle::Sixel:
    771             if (sixelSupported())
    772             {
    773                 try
    774                 {
    775                     result = std::make_unique<SixelIndefiniteSpinner>(stream, enableVT);
    776                 }
    777                 CATCH_LOG();
    778             }
    779 
    780             if (!result)
    781             {
    782                 result = std::make_unique<CharacterIndefiniteSpinner>(stream, enableVT, VisualStyle::Accent);
    783             }
    784             break;
    785         case VisualStyle::Disabled:
    786             break;
    787         default:
    788             THROW_HR(E_NOTIMPL);
    789         }
    790 
    791         return result;
    792     }
    793 
    794     std::unique_ptr<IProgressBar> IProgressBar::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function<bool()>& sixelSupported)
    795     {
    796         std::unique_ptr<IProgressBar> result;
    797 
    798         switch (style)
    799         {
    800         case VisualStyle::NoVT:
    801         case VisualStyle::Retro:
    802         case VisualStyle::Accent:
    803         case VisualStyle::Rainbow:
    804             result = std::make_unique<CharacterProgressBar>(stream, enableVT, style);
    805             break;
    806         case VisualStyle::Sixel:
    807             if (sixelSupported())
    808             {
    809                 try
    810                 {
    811                     result = std::make_unique<SixelProgressBar>(stream, enableVT);
    812                 }
    813                 CATCH_LOG();
    814             }
    815 
    816             if (!result)
    817             {
    818                 result = std::make_unique<CharacterProgressBar>(stream, enableVT, VisualStyle::Accent);
    819             }
    820             break;
    821         case VisualStyle::Disabled:
    822             break;
    823         default:
    824             THROW_HR(E_NOTIMPL);
    825         }
    826 
    827         return result;
    828     }
    829 }