Skip to content

Commit

Permalink
Sixel support (#4828)
Browse files Browse the repository at this point in the history
## Hackathon
As part of a yearly event to work on personal choice projects, I
implemented support for rendering
[sixels](https://en.wikipedia.org/wiki/Sixel) in a few different
scenarios. You can try it out in any terminal that supports sixels, such
as the latest [Windows Terminal Preview
build](https://github.com/microsoft/terminal/releases/tag/v1.22.2362.0).
Both @lhecker and @j4james were very helpful with guidance on the
nuances of sixels.

## Change
The foundational work is a wrapper around the [Windows Imaging Component
(WIC) APIs](https://learn.microsoft.com/en-us/windows/win32/api/_wic/)
and a renderer for converting the indexed images to sixel format. WIC
does all the heavy lifting of decoding images, palette optimization and
dithering. My additions are a simple compositor and sixel renderer.
There is also some new code for detecting VT extension support,
including only enabling sixel rendering if the terminal advertises the
extension.

Sixel use was added in various ways. The winget icon is output any time
the header would be (during `winget --info` and any time we output
help):

<img width="435" alt="winget-info"
src="https://github.com/user-attachments/assets/7e6f8548-1c09-4108-aa5e-810bfeef6c4f">

The package icon (if present) is displayed during `winget show`. This is
an example using a local manifest updated to point to an image located
on the github repository of the package; actual package icon will likely
be different. Package icons extracted from actual installs are expected
to arrive soon:

<img width="266" alt="winget-show"
src="https://github.com/user-attachments/assets/fdbcf1fa-c2a3-4094-9415-4761702a44fa">

A new progress visualization is available using sixels. The indefinite
progress (the current character spinner that iterates through various
slashes, dash and pipe) looks like:


![winget-indefinite](https://github.com/user-attachments/assets/2f4e66e2-ecf6-4137-8d5b-ceddf4df11a2)

while the progress bar is a conveyor belt bringing your package to you:


![winget-progress](https://github.com/user-attachments/assets/c18b734c-808c-4ebd-a938-795cdde22154)
_The conveyor belt base image is only the finest of dev art; it is very
open to someone with more artistic skill to improve upon._

The tearing present in this gif does exist, but it is exaggerated by the
capture and the slow steady progress. Actual progress is much less
steady, making any tearing that may occur far less noticeable.

## Settings
Use of sixels (including even the attempt to detect support in the
terminal) is gated behind enabling them in the settings. Two new/updated
settings are available.

- `.visual.enableSixels` (`true` or `false`)
- This controls the winget icon during help and the package icon during
show.
- `.visual.progressBar`
  - `"sixel"` causes progress to use the sixel versions shown above.
  - `"disabled"` disables progress output.
  • Loading branch information
JohnMcPMS authored Sep 30, 2024
1 parent a0f4864 commit d148be1
Show file tree
Hide file tree
Showing 38 changed files with 2,396 additions and 360 deletions.
2 changes: 2 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ silentwithprogress
Silverlight
simplesave
simpletest
sixel
sixels
sln
sqlbuilder
sqliteicu
Expand Down
3 changes: 3 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ ISQ
ISVs
iswgp
itr
IWIC
iwr
iwgc
JArray
Expand Down Expand Up @@ -615,8 +616,10 @@ wesome
wfsopen
wgetenv
Whatif
WIC
wildcards
WINAPI
wincodec
windir
windowsdeveloper
winerror
Expand Down
22 changes: 16 additions & 6 deletions doc/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ Replaces some known folder paths with their respective environment variable. Def
},
```

### enableSixels

Enables output of sixel images in certain contexts. Defaults to false.

```json
"visual": {
"enableSixels": true
},
```

## Install Behavior

The `installBehavior` settings affect the default behavior of installing and upgrading (where applicable) packages.
Expand Down Expand Up @@ -97,12 +107,12 @@ The 'skipDependencies' behavior affects whether dependencies are installed for a
"installBehavior": {
"skipDependencies": true
},
```

### Archive Extraction Method
The 'archiveExtractionMethod' behavior affects how installer archives are extracted. Currently there are two supported values: `Tar` or `ShellApi`.
`Tar` indicates that the archive should be extracted using the tar executable ('tar.exe') while `shellApi` indicates using the Windows Shell API. Defaults to `shellApi` if value is not set or is invalid.

```

### Archive Extraction Method
The 'archiveExtractionMethod' behavior affects how installer archives are extracted. Currently there are two supported values: `Tar` or `ShellApi`.
`Tar` indicates that the archive should be extracted using the tar executable ('tar.exe') while `shellApi` indicates using the Windows Shell API. Defaults to `shellApi` if value is not set or is invalid.

```json
"installBehavior": {
"archiveExtractionMethod": "tar" | "shellApi"
Expand Down
9 changes: 8 additions & 1 deletion schemas/JSON/settings/settings.schema.0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@
"enum": [
"accent",
"rainbow",
"retro"
"retro",
"sixel",
"disabled"
]
},
"anonymizeDisplayedPaths": {
"description": "Replaces some known folder paths with their respective environment variable",
"type": "boolean",
"default": true
},
"enableSixels": {
"description": "Enables output of sixel images in certain contexts",
"type": "boolean",
"default": false
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
<ClInclude Include="ContextOrchestrator.h" />
<ClInclude Include="COMContext.h" />
<ClInclude Include="Public\ConfigurationSetProcessorFactoryRemoting.h" />
<ClInclude Include="Sixel.h" />
<ClInclude Include="Workflows\ConfigurationFlow.h" />
<ClInclude Include="Workflows\DependenciesFlow.h" />
<ClInclude Include="ExecutionArgs.h" />
Expand Down Expand Up @@ -451,6 +452,7 @@
<ClCompile Include="ConfigurationWingetDscModuleUnitValidation.cpp" />
<ClCompile Include="ConfigureExportCommand.cpp" />
<ClCompile Include="ContextOrchestrator.cpp" />
<ClCompile Include="Sixel.cpp" />
<ClCompile Include="Workflows\ConfigurationFlow.cpp" />
<ClCompile Include="Workflows\DependenciesFlow.cpp" />
<ClCompile Include="PackageCollection.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@
<ClInclude Include="Commands\ConfigureListCommand.h">
<Filter>Commands</Filter>
</ClInclude>
<ClInclude Include="Sixel.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
Expand Down Expand Up @@ -484,6 +487,9 @@
<ClCompile Include="Commands\ConfigureListCommand.cpp">
<Filter>Commands</Filter>
</ClCompile>
<ClCompile Include="Sixel.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
Expand Down
12 changes: 11 additions & 1 deletion src/AppInstallerCLICore/ChannelStreams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ namespace AppInstaller::CLI::Execution
m_enabled = false;
}

std::ostream& BaseStream::Get()
{
return m_out;
}

OutputStream::OutputStream(BaseStream& out, bool enabled, bool VTEnabled) :
m_out(out),
m_enabled(enabled),
Expand All @@ -82,6 +87,11 @@ namespace AppInstaller::CLI::Execution
m_format.Append(sequence);
}

void OutputStream::ClearFormat()
{
m_format.Clear();
}

void OutputStream::ApplyFormat()
{
// Only apply format if m_applyFormatAtOne == 1 coming into this function.
Expand Down Expand Up @@ -152,4 +162,4 @@ namespace AppInstaller::CLI::Execution
return *this;

}
}
}
5 changes: 5 additions & 0 deletions src/AppInstallerCLICore/ChannelStreams.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ namespace AppInstaller::CLI::Execution

void Disable();

std::ostream& Get();

private:
template <typename T>
void Write(const T& t, bool bypass)
Expand All @@ -60,6 +62,9 @@ namespace AppInstaller::CLI::Execution
// Adds a format to the current value.
void AddFormat(const VirtualTerminal::Sequence& sequence);

// Clears the current format value.
void ClearFormat();

template <typename T>
OutputStream& operator<<(const T& t)
{
Expand Down
34 changes: 33 additions & 1 deletion src/AppInstallerCLICore/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "pch.h"
#include "Command.h"
#include "Resources.h"
#include "Sixel.h"
#include <winget/UserSettings.h>
#include <AppInstallerRuntime.h>
#include <winget/Locale.h>
Expand Down Expand Up @@ -42,8 +43,39 @@ namespace AppInstaller::CLI

void Command::OutputIntroHeader(Execution::Reporter& reporter) const
{
auto infoOut = reporter.Info();
VirtualTerminal::ConstructedSequence indent;

if (reporter.SixelsEnabled())
{
try
{
std::filesystem::path imagePath = Runtime::GetPathTo(Runtime::PathName::ImageAssets);

if (!imagePath.empty())
{
// This image matches the target pixel size. If changing the target size, choose the most appropriate image.
imagePath /= "AppList.targetsize-40.png";

VirtualTerminal::Sixel::Image wingetIcon{ imagePath };

// Using a height of 2 to match the two lines of header.
UINT imageHeightCells = 2;
UINT imageWidthCells = 2 * imageHeightCells;

wingetIcon.RenderSizeInCells(imageWidthCells, imageHeightCells);
wingetIcon.RenderTo(infoOut);

indent = VirtualTerminal::Cursor::Position::Forward(static_cast<int16_t>(imageWidthCells));
infoOut << VirtualTerminal::Cursor::Position::Up(static_cast<int16_t>(imageHeightCells) - 1);
}
}
CATCH_LOG();
}

auto productName = Runtime::IsReleaseBuild() ? Resource::String::WindowsPackageManager : Resource::String::WindowsPackageManagerPreview;
reporter.Info() << productName(Runtime::GetClientVersion()) << std::endl << Resource::String::MainCopyrightNotice << std::endl;
infoOut << indent << productName(Runtime::GetClientVersion()) << std::endl
<< indent << Resource::String::MainCopyrightNotice << std::endl;
}

void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const
Expand Down
Loading

0 comments on commit d148be1

Please sign in to comment.