Add support for more OSC color formats (#7578)

* Correct the behaviour of parsing `rgb:R/G/B`. It should be interpreted
  as `RR/GG/BB` instead of `0R/0G/0B`
* Add support for `rgb:RRR/GGG/BBB` and `rgb:RRRR/GGGG/BBBB`. The
  behaviour of 12 bit variants is to repeat the first digit at the end,
  e.g. `rgb:123/456/789` becomes `rgb:1231/4564/7897`.
* Add support for `#` formats. We are following the rules of
  [XParseColor] by interpreting `#RGB` as `R000G000B000`.
* Add support for XOrg app color names, which are supported by xterm, VTE
  and many other terminal emulators.
* Multi-parameter OSC 4 is now supported.
* The chaining of OSC 10-12 is not yet supported. But the parameter
  validation is relaxed by parsing the parameters as multi-params but
  only use the first one, which means `\e]10;rgb:R/G/B;` and
  `\e]10:rgb:R/G/B;invalid` will execute `OSC 10` with the first color
  correctly. This fixes some of the issues mentioned in #942 but not
  all of them.

[XParseColor]: https://linux.die.net/man/3/xparsecolor

Closes #3715
This commit is contained in:
Chester Liu 2020-10-15 08:29:10 +08:00 committed by GitHub
parent f86045e041
commit 02b120236c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2849 additions and 641 deletions

View File

@ -18,3 +18,4 @@ it'll be accepted).
| [Microsoft](microsoft.txt) | Microsoft brand items |
| [Fonts](fonts.txt) | Font names |
| [Names](names.txt) | Names of people |
| [Colors](colors.txt) | Names of color |

View File

@ -54,3 +54,4 @@ userenv
wcstoui
XDocument
XElement
XParse

View File

@ -0,0 +1,782 @@
snow
ghost-white
ghostwhite
white-smoke
whitesmoke
gainsboro
floral-white
floralwhite
old-lace
oldlace
linen
antique-white
antiquewhite
papaya-whip
papayawhip
blanched-almond
blanchedalmond
bisque
peach-puff
peachpuff
navajo-white
navajowhite
moccasin
cornsilk
ivory
lemon-chiffon
lemonchiffon
seashell
honeydew
mint-cream
mintcream
azure
alice-blue
aliceblue
lavender
lavender-blush
lavenderblush
misty-rose
mistyrose
white
black
dark-slate-gray
darkslategray
dark-slate-grey
darkslategrey
dim-gray
dimgray
dim-grey
dimgrey
slate-gray
slategray
slate-grey
slategrey
light-slate-gray
lightslategray
light-slate-grey
lightslategrey
gray
grey
xray
x11gray
xrey
x11grey
web-gray
webgray
web-grey
webgrey
light-grey
lightgrey
light-gray
lightgray
midnight-blue
midnightblue
navy
navy-blue
navyblue
cornflower-blue
cornflowerblue
dark-slate-blue
darkslateblue
slate-blue
slateblue
medium-slate-blue
mediumslateblue
light-slate-blue
lightslateblue
medium-blue
mediumblue
royal-blue
royalblue
blue
dodger-blue
dodgerblue
deep-sky-blue
deepskyblue
sky-blue
skyblue
light-sky-blue
lightskyblue
steel-blue
steelblue
light-steel-blue
lightsteelblue
light-blue
lightblue
powder-blue
powderblue
pale-turquoise
paleturquoise
dark-turquoise
darkturquoise
medium-turquoise
mediumturquoise
turquoise
cyan
aqua
light-cyan
lightcyan
cadet-blue
cadetblue
medium-aquamarine
mediumaquamarine
aquamarine
dark-green
darkgreen
dark-olive-green
darkolivegreen
dark-sea-green
darkseagreen
sea-green
seagreen
medium-sea-green
mediumseagreen
light-sea-green
lightseagreen
pale-green
palegreen
spring-green
springgreen
lawn-green
lawngreen
green
lime
xreen
x11green
web-green
webgreen
chartreuse
medium-spring-green
mediumspringgreen
green-yellow
greenyellow
lime-green
limegreen
yellow-green
yellowgreen
forest-green
forestgreen
olive-drab
olivedrab
dark-khaki
darkkhaki
khaki
pale-goldenrod
palegoldenrod
light-goldenrod-yellow
lightgoldenrodyellow
light-yellow
lightyellow
yellow
gold
light-goldenrod
lightgoldenrod
goldenrod
dark-goldenrod
darkgoldenrod
rosy-brown
rosybrown
indian-red
indianred
saddle-brown
saddlebrown
sienna
peru
burlywood
beige
wheat
sandy-brown
sandybrown
tan
chocolate
firebrick
brown
dark-salmon
darksalmon
salmon
light-salmon
lightsalmon
orange
dark-orange
darkorange
coral
light-coral
lightcoral
tomato
orange-red
orangered
red
hot-pink
hotpink
deep-pink
deeppink
pink
light-pink
lightpink
pale-violet-red
palevioletred
maroon
xaroon
x11maroon
web-maroon
webmaroon
medium-violet-red
mediumvioletred
violet-red
violetred
magenta
fuchsia
violet
plum
orchid
medium-orchid
mediumorchid
dark-orchid
darkorchid
dark-violet
darkviolet
blue-violet
blueviolet
purple
xurple
x11purple
web-purple
webpurple
medium-purple
mediumpurple
thistle
snow1
snow2
snow3
snow4
seashell1
seashell2
seashell3
seashell4
antiquewhite1
antiquewhite2
antiquewhite3
antiquewhite4
bisque1
bisque2
bisque3
bisque4
peachpuff1
peachpuff2
peachpuff3
peachpuff4
navajowhite1
navajowhite2
navajowhite3
navajowhite4
lemonchiffon1
lemonchiffon2
lemonchiffon3
lemonchiffon4
cornsilk1
cornsilk2
cornsilk3
cornsilk4
ivory1
ivory2
ivory3
ivory4
honeydew1
honeydew2
honeydew3
honeydew4
lavenderblush1
lavenderblush2
lavenderblush3
lavenderblush4
mistyrose1
mistyrose2
mistyrose3
mistyrose4
azure1
azure2
azure3
azure4
slateblue1
slateblue2
slateblue3
slateblue4
royalblue1
royalblue2
royalblue3
royalblue4
blue1
blue2
blue3
blue4
dodgerblue1
dodgerblue2
dodgerblue3
dodgerblue4
steelblue1
steelblue2
steelblue3
steelblue4
deepskyblue1
deepskyblue2
deepskyblue3
deepskyblue4
skyblue1
skyblue2
skyblue3
skyblue4
lightskyblue1
lightskyblue2
lightskyblue3
lightskyblue4
slategray1
slategray2
slategray3
slategray4
lightsteelblue1
lightsteelblue2
lightsteelblue3
lightsteelblue4
lightblue1
lightblue2
lightblue3
lightblue4
lightcyan1
lightcyan2
lightcyan3
lightcyan4
paleturquoise1
paleturquoise2
paleturquoise3
paleturquoise4
cadetblue1
cadetblue2
cadetblue3
cadetblue4
turquoise1
turquoise2
turquoise3
turquoise4
cyan1
cyan2
cyan3
cyan4
darkslategray1
darkslategray2
darkslategray3
darkslategray4
aquamarine1
aquamarine2
aquamarine3
aquamarine4
darkseagreen1
darkseagreen2
darkseagreen3
darkseagreen4
seagreen1
seagreen2
seagreen3
seagreen4
palegreen1
palegreen2
palegreen3
palegreen4
springgreen1
springgreen2
springgreen3
springgreen4
green1
green2
green3
green4
chartreuse1
chartreuse2
chartreuse3
chartreuse4
olivedrab1
olivedrab2
olivedrab3
olivedrab4
darkolivegreen1
darkolivegreen2
darkolivegreen3
darkolivegreen4
khaki1
khaki2
khaki3
khaki4
lightgoldenrod1
lightgoldenrod2
lightgoldenrod3
lightgoldenrod4
lightyellow1
lightyellow2
lightyellow3
lightyellow4
yellow1
yellow2
yellow3
yellow4
gold1
gold2
gold3
gold4
goldenrod1
goldenrod2
goldenrod3
goldenrod4
darkgoldenrod1
darkgoldenrod2
darkgoldenrod3
darkgoldenrod4
rosybrown1
rosybrown2
rosybrown3
rosybrown4
indianred1
indianred2
indianred3
indianred4
sienna1
sienna2
sienna3
sienna4
burlywood1
burlywood2
burlywood3
burlywood4
wheat1
wheat2
wheat3
wheat4
tan1
tan2
tan3
tan4
chocolate1
chocolate2
chocolate3
chocolate4
firebrick1
firebrick2
firebrick3
firebrick4
brown1
brown2
brown3
brown4
salmon1
salmon2
salmon3
salmon4
lightsalmon1
lightsalmon2
lightsalmon3
lightsalmon4
orange1
orange2
orange3
orange4
darkorange1
darkorange2
darkorange3
darkorange4
coral1
coral2
coral3
coral4
tomato1
tomato2
tomato3
tomato4
orangered1
orangered2
orangered3
orangered4
red1
red2
red3
red4
deeppink1
deeppink2
deeppink3
deeppink4
hotpink1
hotpink2
hotpink3
hotpink4
pink1
pink2
pink3
pink4
lightpink1
lightpink2
lightpink3
lightpink4
palevioletred1
palevioletred2
palevioletred3
palevioletred4
maroon1
maroon2
maroon3
maroon4
violetred1
violetred2
violetred3
violetred4
magenta1
magenta2
magenta3
magenta4
orchid1
orchid2
orchid3
orchid4
plum1
plum2
plum3
plum4
mediumorchid1
mediumorchid2
mediumorchid3
mediumorchid4
darkorchid1
darkorchid2
darkorchid3
darkorchid4
purple1
purple2
purple3
purple4
mediumpurple1
mediumpurple2
mediumpurple3
mediumpurple4
thistle1
thistle2
thistle3
thistle4
gray0
grey0
gray1
grey1
gray2
grey2
gray3
grey3
gray4
grey4
gray5
grey5
gray6
grey6
gray7
grey7
gray8
grey8
gray9
grey9
gray10
grey10
gray11
grey11
gray12
grey12
gray13
grey13
gray14
grey14
gray15
grey15
gray16
grey16
gray17
grey17
gray18
grey18
gray19
grey19
gray20
grey20
gray21
grey21
gray22
grey22
gray23
grey23
gray24
grey24
gray25
grey25
gray26
grey26
gray27
grey27
gray28
grey28
gray29
grey29
gray30
grey30
gray31
grey31
gray32
grey32
gray33
grey33
gray34
grey34
gray35
grey35
gray36
grey36
gray37
grey37
gray38
grey38
gray39
grey39
gray40
grey40
gray41
grey41
gray42
grey42
gray43
grey43
gray44
grey44
gray45
grey45
gray46
grey46
gray47
grey47
gray48
grey48
gray49
grey49
gray50
grey50
gray51
grey51
gray52
grey52
gray53
grey53
gray54
grey54
gray55
grey55
gray56
grey56
gray57
grey57
gray58
grey58
gray59
grey59
gray60
grey60
gray61
grey61
gray62
grey62
gray63
grey63
gray64
grey64
gray65
grey65
gray66
grey66
gray67
grey67
gray68
grey68
gray69
grey69
gray70
grey70
gray71
grey71
gray72
grey72
gray73
grey73
gray74
grey74
gray75
grey75
gray76
grey76
gray77
grey77
gray78
grey78
gray79
grey79
gray80
grey80
gray81
grey81
gray82
grey82
gray83
grey83
gray84
grey84
gray85
grey85
gray86
grey86
gray87
grey87
gray88
grey88
gray89
grey89
gray90
grey90
gray91
grey91
gray92
grey92
gray93
grey93
gray94
grey94
gray95
grey95
gray96
grey96
gray97
grey97
gray98
grey98
gray99
grey99
gray100
grey100
dark-grey
darkgrey
dark-gray
darkgray
dark-blue
darkblue
dark-cyan
darkcyan
dark-magenta
darkmagenta
dark-red
darkred
light-green
lightgreen
crimson
indigo
olive
rebecca-purple
rebeccapurple
silver
teal

View File

@ -961,6 +961,8 @@ hfile
hfont
hglobal
hh
hhh
hhhh
hhook
hhx
HIBYTE
@ -2793,6 +2795,9 @@ XCount
xdy
xe
XEncoding
xes
Xes
XES
xff
XFile
xlang
@ -2802,6 +2807,8 @@ XMFLOAT
xml
xmlns
xor
xorg
XOrg
Xpath
XPosition
XResource

View File

@ -16,6 +16,7 @@ Scro\&ll
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
(?:hostSm|mach)\.ProcessString\(L"[^"]+"
\b([A-Za-z])\1{3,}\b
0x[0-9A-Za-z]+
Base64::s_(?:En|De)code\(L"[^"]+"
VERIFY_ARE_EQUAL\(L"[^"]+"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"

View File

@ -9,6 +9,7 @@
#include "../../inc/DefaultSettings.h"
#include "../../inc/argb.h"
#include "../../types/inc/utils.hpp"
#include "../../types/inc/colorTable.hpp"
#include <winrt/Microsoft.Terminal.TerminalControl.h>

View File

@ -7,6 +7,7 @@
#include "../TerminalSettingsModel/Profile.h"
#include "../TerminalSettingsModel/CascadiaSettings.h"
#include "../LocalTests_SettingsModel/JsonTestClass.h"
#include "../types/inc/colorTable.hpp"
using namespace Microsoft::Console;
using namespace WEX::Logging;

View File

@ -5,7 +5,7 @@
#include "settings.hpp"
#include "..\interactivity\inc\ServiceLocator.hpp"
#include "../types/inc/utils.hpp"
#include "../types/inc/colorTable.hpp"
#pragma hdrstop

View File

@ -1566,13 +1566,13 @@ void ScreenBufferTests::VtSetColorTable()
L"Process some valid sequences for setting the table"));
stateMachine.ProcessString(L"\x1b]4;0;rgb:1/1/1\x7");
VERIFY_ARE_EQUAL(RGB(1, 1, 1), gci.GetColorTableEntry(::XtermToWindowsIndex(0)));
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), gci.GetColorTableEntry(::XtermToWindowsIndex(0)));
stateMachine.ProcessString(L"\x1b]4;1;rgb:1/23/1\x7");
VERIFY_ARE_EQUAL(RGB(1, 0x23, 1), gci.GetColorTableEntry(::XtermToWindowsIndex(1)));
VERIFY_ARE_EQUAL(RGB(0x11, 0x23, 0x11), gci.GetColorTableEntry(::XtermToWindowsIndex(1)));
stateMachine.ProcessString(L"\x1b]4;2;rgb:1/23/12\x7");
VERIFY_ARE_EQUAL(RGB(1, 0x23, 0x12), gci.GetColorTableEntry(::XtermToWindowsIndex(2)));
VERIFY_ARE_EQUAL(RGB(0x11, 0x23, 0x12), gci.GetColorTableEntry(::XtermToWindowsIndex(2)));
stateMachine.ProcessString(L"\x1b]4;3;rgb:12/23/12\x7");
VERIFY_ARE_EQUAL(RGB(0x12, 0x23, 0x12), gci.GetColorTableEntry(::XtermToWindowsIndex(3)));
@ -1587,7 +1587,7 @@ void ScreenBufferTests::VtSetColorTable()
L"Try a bunch of invalid sequences."));
Log::Comment(NoThrowString().Format(
L"First start by setting an entry to a known value to compare to."));
stateMachine.ProcessString(L"\x1b]4;5;rgb:9/9/9\x1b\\");
stateMachine.ProcessString(L"\x1b]4;5;rgb:09/09/09\x1b\\");
VERIFY_ARE_EQUAL(RGB(9, 9, 9), gci.GetColorTableEntry(::XtermToWindowsIndex(5)));
Log::Comment(NoThrowString().Format(
@ -1595,11 +1595,6 @@ void ScreenBufferTests::VtSetColorTable()
stateMachine.ProcessString(L"\x1b]4;5;rgb:/1/1\x1b\\");
VERIFY_ARE_EQUAL(RGB(9, 9, 9), gci.GetColorTableEntry(::XtermToWindowsIndex(5)));
Log::Comment(NoThrowString().Format(
L"invalid: too many characters in a component"));
stateMachine.ProcessString(L"\x1b]4;5;rgb:111/1/1\x1b\\");
VERIFY_ARE_EQUAL(RGB(9, 9, 9), gci.GetColorTableEntry(::XtermToWindowsIndex(5)));
Log::Comment(NoThrowString().Format(
L"invalid: too many components"));
stateMachine.ProcessString(L"\x1b]4;5;rgb:1/1/1/1\x1b\\");
@ -2845,17 +2840,8 @@ void ScreenBufferTests::SetDefaultForegroundColor()
newColor = gci.GetDefaultForegroundColor();
VERIFY_ARE_EQUAL(testColor, newColor);
Log::Comment(L"Invalid Decimal Notation");
originalColor = newColor;
testColor = RGB(153, 102, 51);
stateMachine.ProcessString(L"\x1b]10;rgb:153/102/51\x1b\\");
newColor = gci.GetDefaultForegroundColor();
VERIFY_ARE_NOT_EQUAL(testColor, newColor);
// it will, in fact leave the color the way it was
VERIFY_ARE_EQUAL(originalColor, newColor);
Log::Comment(L"Invalid syntax");
originalColor = newColor;
testColor = RGB(153, 102, 51);
stateMachine.ProcessString(L"\x1b]10;99/66/33\x1b\\");
@ -2899,17 +2885,8 @@ void ScreenBufferTests::SetDefaultBackgroundColor()
newColor = gci.GetDefaultBackgroundColor();
VERIFY_ARE_EQUAL(testColor, newColor);
Log::Comment(L"Invalid Decimal Notation");
originalColor = newColor;
testColor = RGB(153, 102, 51);
stateMachine.ProcessString(L"\x1b]11;rgb:153/102/51\x1b\\");
newColor = gci.GetDefaultBackgroundColor();
VERIFY_ARE_NOT_EQUAL(testColor, newColor);
// it will, in fact leave the color the way it was
VERIFY_ARE_EQUAL(originalColor, newColor);
Log::Comment(L"Invalid Syntax");
originalColor = newColor;
testColor = RGB(153, 102, 51);
stateMachine.ProcessString(L"\x1b]11;99/66/33\x1b\\");

View File

@ -39,6 +39,7 @@ namespace til
{
#pragma warning(suppress : 26482) // Suppress bounds.2 check for indexing with constant expressions
#pragma warning(suppress : 26446) // Suppress bounds.4 check for subscript operator.
#pragma warning(suppress : 26445) // Suppress lifetime check for a reference to gsl::span or std::string_view
return cont[i];
}

View File

@ -39,7 +39,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
using const_iterator = typename std::array<std::pair<K, V>, N>::const_iterator;
template<typename... Args>
constexpr explicit static_map(const Args&... args) :
constexpr explicit static_map(const Args&... args) noexcept :
_predicate{},
_array{ { args... } }
{

View File

@ -8,6 +8,7 @@
#include "base64.hpp"
#include "ascii.hpp"
#include "../../types/inc/utils.hpp"
using namespace Microsoft::Console;
using namespace Microsoft::Console::VirtualTerminal;
@ -734,8 +735,8 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
std::wstring params;
std::wstring uri;
bool queryClipboard = false;
size_t tableIndex = 0;
DWORD color = 0;
std::vector<size_t> tableIndexes;
std::vector<DWORD> colors;
switch (parameter)
{
@ -745,19 +746,17 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
success = _GetOscTitle(string, title);
break;
case OscActionCodes::SetColor:
success = _GetOscSetColorTable(string, tableIndex, color);
success = _GetOscSetColorTable(string, tableIndexes, colors);
break;
case OscActionCodes::SetForegroundColor:
case OscActionCodes::SetBackgroundColor:
case OscActionCodes::SetCursorColor:
success = _GetOscSetColor(string, color);
success = _GetOscSetColor(string, colors);
break;
case OscActionCodes::SetClipboard:
success = _GetOscSetClipboard(string, setClipboardContent, queryClipboard);
break;
case OscActionCodes::ResetCursorColor:
// the console uses 0xffffffff as an "invalid color" value
color = 0xffffffff;
success = true;
break;
case OscActionCodes::Hyperlink:
@ -779,19 +778,33 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCWT);
break;
case OscActionCodes::SetColor:
success = _dispatch->SetColorTableEntry(tableIndex, color);
for (size_t i = 0; i < tableIndexes.size(); i++)
{
const auto tableIndex = til::at(tableIndexes, i);
const auto rgb = til::at(colors, i);
success = _dispatch->SetColorTableEntry(tableIndex, rgb);
}
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCCT);
break;
case OscActionCodes::SetForegroundColor:
success = _dispatch->SetDefaultForeground(color);
if (colors.size() > 0)
{
success = _dispatch->SetDefaultForeground(til::at(colors, 0));
}
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCFG);
break;
case OscActionCodes::SetBackgroundColor:
success = _dispatch->SetDefaultBackground(color);
if (colors.size() > 0)
{
success = _dispatch->SetDefaultBackground(til::at(colors, 0));
}
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCBG);
break;
case OscActionCodes::SetCursorColor:
success = _dispatch->SetCursorColor(color);
if (colors.size() > 0)
{
success = _dispatch->SetCursorColor(til::at(colors, 0));
}
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCC);
break;
case OscActionCodes::SetClipboard:
@ -802,7 +815,8 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCB);
break;
case OscActionCodes::ResetCursorColor:
success = _dispatch->SetCursorColor(color);
// the console uses 0xffffffff as an "invalid color" value
success = _dispatch->SetCursorColor(0xffffffff);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCRCC);
break;
case OscActionCodes::Hyperlink:
@ -1347,248 +1361,51 @@ bool OutputStateMachineEngine::DispatchIntermediatesFromEscape() const noexcept
return false;
}
// Routine Description:
// - Converts a hex character to its equivalent integer value.
// Arguments:
// - wch - Character to convert.
// - value - receives the int value of the char
// Return Value:
// - true iff the character is a hex character.
bool OutputStateMachineEngine::s_HexToUint(const wchar_t wch,
unsigned int& value) noexcept
{
value = 0;
bool success = false;
if (wch >= L'0' && wch <= L'9')
{
value = wch - L'0';
success = true;
}
else if (wch >= L'A' && wch <= L'F')
{
value = (wch - L'A') + 10;
success = true;
}
else if (wch >= L'a' && wch <= L'f')
{
value = (wch - L'a') + 10;
success = true;
}
return success;
}
#pragma warning(push)
#pragma warning(disable : 26497) // We don't use any of these "constexprable" functions in that fashion
// Routine Description:
// - Determines if a character is a valid number character, 0-9.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isNumber(const wchar_t wch) noexcept
{
return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39
}
// Routine Description:
// - Determines if a character is a valid hex character, 0-9a-fA-F.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isHexNumber(const wchar_t wch) noexcept
{
return (wch >= L'0' && wch <= L'9') || // 0x30 - 0x39
(wch >= L'A' && wch <= L'F') ||
(wch >= L'a' && wch <= L'f');
}
#pragma warning(pop)
// Routine Description:
// - Given a color spec string, attempts to parse the color that's encoded.
// The only supported spec currently is the following:
// spec: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// where <color> is one or two hex digits, upper or lower case.
// Arguments:
// - string - The string containing the color spec string to parse.
// - rgb - receives the color that we parsed
// Return Value:
// - True if a color was successfully parsed
bool OutputStateMachineEngine::s_ParseColorSpec(const std::wstring_view string,
DWORD& rgb) noexcept
{
bool foundRGB = false;
bool foundValidColorSpec = false;
std::array<unsigned int, 3> colorValues = { 0 };
bool success = false;
// We can have anywhere between [11,15] characters
// 9 "rgb:h/h/h"
// 12 "rgb:hh/hh/hh"
// Any fewer cannot be valid, and any more will be too many.
// Return early in this case.
// We'll still have to bounds check when parsing the hh/hh/hh values
if (string.size() < 9 || string.size() > 12)
{
return false;
}
// Now we look for "rgb:"
// Other colorspaces are theoretically possible, but we don't support them.
auto curr = string.cbegin();
if ((*curr++ == L'r') &&
(*curr++ == L'g') &&
(*curr++ == L'b') &&
(*curr++ == L':'))
{
foundRGB = true;
}
if (foundRGB)
{
// Colorspecs are up to hh/hh/hh, for 1-2 h's
for (size_t component = 0; component < 3; component++)
{
bool foundColor = false;
auto& value = til::at(colorValues, component);
for (size_t i = 0; i < 3; i++)
{
const wchar_t wch = *curr++;
if (_isHexNumber(wch))
{
value *= 16;
unsigned int intVal = 0;
if (s_HexToUint(wch, intVal))
{
value += intVal;
}
else
{
// Encountered something weird oh no
foundColor = false;
break;
}
// If we're on the blue component, we're not going to see a /.
// Break out once we hit the end.
if (component == 2 && curr >= string.cend())
{
foundValidColorSpec = true;
break;
}
}
else if (wch == L'/')
{
// Break this component, and start the next one.
foundColor = true;
break;
}
else
{
// Encountered something weird oh no
foundColor = false;
break;
}
}
if (!foundColor || curr >= string.cend())
{
// Indicates there was a some error parsing color
// or we're at the end of the string.
break;
}
}
}
// Only if we find a valid colorspec can we pass it out successfully.
if (foundValidColorSpec)
{
DWORD color = RGB(LOBYTE(colorValues.at(0)),
LOBYTE(colorValues.at(1)),
LOBYTE(colorValues.at(2)));
rgb = color;
success = true;
}
return success;
}
// Routine Description:
// - OSC 4 ; c ; spec ST
// c: the index of the ansi color table
// spec: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// where <color> is two hex digits
// spec: The colors are specified by name or RGB specification as per XParseColor
//
// It's possible to have multiple "c ; spec" pairs, which will set the index "c" of the color table
// with color parsed from "spec" for each pair respectively.
// Arguments:
// - string - the Osc String to parse
// - tableIndex - receives the table index
// - rgb - receives the color that we parsed in the format: 0x00BBGGRR
// - tableIndexes - receives the table indexes
// - rgbs - receives the colors that we parsed in the format: 0x00BBGGRR
// Return Value:
// - True if a table index and color was parsed successfully. False otherwise.
// - True if at least one table index and color was parsed successfully. False otherwise.
bool OutputStateMachineEngine::_GetOscSetColorTable(const std::wstring_view string,
size_t& tableIndex,
DWORD& rgb) const noexcept
std::vector<size_t>& tableIndexes,
std::vector<DWORD>& rgbs) const noexcept
try
{
tableIndex = 0;
rgb = 0;
size_t _TableIndex = 0;
bool foundTableIndex = false;
bool success = false;
// We can have anywhere between [11,16] characters
// 11 "#;rgb:h/h/h"
// 16 "###;rgb:hh/hh/hh"
// Any fewer cannot be valid, and any more will be too many.
// Return early in this case.
// We'll still have to bounds check when parsing the hh/hh/hh values
if (string.size() < 11 || string.size() > 16)
const auto parts = Utils::SplitString(string, L';');
if (parts.size() < 2)
{
return false;
}
// First try to get the table index, a number between [0,256]
size_t current = 0;
for (size_t i = 0; i < 4; i++)
{
const wchar_t wch = string.at(current);
if (_isNumber(wch))
{
_TableIndex *= 10;
_TableIndex += wch - L'0';
std::vector<size_t> newTableIndexes;
std::vector<DWORD> newRgbs;
++current;
}
else if (wch == L';' && i > 0)
{
// We need to explicitly pass in a number, we can't default to 0 if
// there's no param
++current;
foundTableIndex = true;
break;
}
else
{
// Found an unexpected character, fail.
break;
}
}
// Now we look for "rgb:"
// Other colorspaces are theoretically possible, but we don't support them.
if (foundTableIndex)
for (size_t i = 0, j = 1; j < parts.size(); i += 2, j += 2)
{
DWORD color = 0;
success = s_ParseColorSpec(string.substr(current), color);
if (success)
unsigned int tableIndex = 0;
const bool indexSuccess = Utils::StringToUint(til::at(parts, i), tableIndex);
const auto colorOptional = Utils::ColorFromXTermColor(til::at(parts, j));
if (indexSuccess && colorOptional.has_value())
{
tableIndex = _TableIndex;
rgb = color;
newTableIndexes.push_back(tableIndex);
newRgbs.push_back(colorOptional.value());
}
}
return success;
tableIndexes.swap(newTableIndexes);
rgbs.swap(newRgbs);
return tableIndexes.size() > 0 && rgbs.size() > 0;
}
CATCH_LOG_RETURN_FALSE()
// Routine Description:
// - Given a hyperlink string, attempts to parse the URI encoded. An 'id' parameter
@ -1630,31 +1447,51 @@ bool OutputStateMachineEngine::_ParseHyperlink(const std::wstring_view string,
// Routine Description:
// - OSC 10, 11, 12 ; spec ST
// spec: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// where <color> is two hex digits
// spec: The colors are specified by name or RGB specification as per XParseColor
//
// It's possible to have multiple "spec", which by design equals to a series of OSC command
// with accumulated Ps. For example "OSC 10;color1;color2" is effectively an "OSC 10;color1"
// and an "OSC 11;color2".
//
// However, we do not support the chaining of OSC 10-17 yet. Right now only the first parameter
// will take effect.
// Arguments:
// - string - the Osc String to parse
// - rgb - receives the color that we parsed in the format: 0x00BBGGRR
// - rgbs - receives the colors that we parsed in the format: 0x00BBGGRR
// Return Value:
// - True if a table index and color was parsed successfully. False otherwise.
// - True if the first table index and color was parsed successfully. False otherwise.
bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string,
DWORD& rgb) const noexcept
std::vector<DWORD>& rgbs) const noexcept
try
{
rgb = 0;
bool success = false;
DWORD color = 0;
success = s_ParseColorSpec(string, color);
if (success)
const auto parts = Utils::SplitString(string, L';');
if (parts.size() < 1)
{
rgb = color;
return false;
}
std::vector<DWORD> newRgbs;
for (size_t i = 0; i < parts.size(); i++)
{
const auto colorOptional = Utils::ColorFromXTermColor(til::at(parts, i));
if (colorOptional.has_value())
{
newRgbs.push_back(colorOptional.value());
// Only mark the parsing as a success iff the first color is a success.
if (i == 0)
{
success = true;
}
}
}
rgbs.swap(newRgbs);
return success;
}
CATCH_LOG_RETURN_FALSE()
// Method Description:
// - Retrieves the type of window manipulation operation from the parameter pool

View File

@ -227,17 +227,12 @@ namespace Microsoft::Console::VirtualTerminal
bool _GetWindowManipulationType(const gsl::span<const size_t> parameters,
unsigned int& function) const noexcept;
static bool s_HexToUint(const wchar_t wch,
unsigned int& value) noexcept;
bool _GetOscSetColorTable(const std::wstring_view string,
size_t& tableIndex,
DWORD& rgb) const noexcept;
static bool s_ParseColorSpec(const std::wstring_view string,
DWORD& rgb) noexcept;
std::vector<size_t>& tableIndexes,
std::vector<DWORD>& rgbs) const noexcept;
bool _GetOscSetColor(const std::wstring_view string,
DWORD& rgb) const noexcept;
std::vector<DWORD>& rgbs) const noexcept;
static constexpr DispatchTypes::CursorStyle DefaultCursorStyle = DispatchTypes::CursorStyle::UserDefault;
bool _GetCursorStyle(const gsl::span<const size_t> parameters,

View File

@ -1008,8 +1008,14 @@ public:
_isDECCOLMAllowed{ false },
_windowWidth{ 80 },
_win32InputMode{ false },
_setDefaultForeground(false),
_defaultForegroundColor{ RGB(0, 0, 0) },
_setDefaultBackground(false),
_defaultBackgroundColor{ RGB(0, 0, 0) },
_hyperlinkMode{ false },
_options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) } // fill with cleared option
_options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) }, // fill with cleared option
_colorTable{},
_setColorTableEntry{ false }
{
}
@ -1355,6 +1361,27 @@ public:
return true;
}
bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) noexcept override
{
_setColorTableEntry = true;
_colorTable.at(tableIndex) = color;
return true;
}
bool SetDefaultForeground(const DWORD color) noexcept override
{
_setDefaultForeground = true;
_defaultForegroundColor = color;
return true;
}
bool SetDefaultBackground(const DWORD color) noexcept override
{
_setDefaultBackground = true;
_defaultBackgroundColor = color;
return true;
}
bool SetClipboard(std::wstring_view content) noexcept override
{
_copyContent = { content.begin(), content.end() };
@ -1426,6 +1453,11 @@ public:
bool _isDECCOLMAllowed;
size_t _windowWidth;
bool _win32InputMode;
bool _setDefaultForeground;
DWORD _defaultForegroundColor;
bool _setDefaultBackground;
DWORD _defaultBackgroundColor;
bool _setColorTableEntry;
bool _hyperlinkMode;
std::wstring _copyContent;
std::wstring _uri;
@ -1433,7 +1465,9 @@ public:
static const size_t s_cMaxOptions = 16;
static const size_t s_uiGraphicsCleared = UINT_MAX;
static const size_t XTERM_COLOR_TABLE_SIZE = 256;
std::vector<DispatchTypes::GraphicsOptions> _options;
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
};
class StateMachineExternalTest final
@ -2608,6 +2642,324 @@ class StateMachineExternalTest final
VERIFY_IS_TRUE(pDispatch->_vt52DeviceAttributes);
}
TEST_METHOD(TestOscSetDefaultForeground)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]10;rgb:1/1/1\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;rgb:12/34/56\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#123456\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
// Multiple params.
mach.ProcessString(L"\033]10;#111;rgb:2/2/2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
// Partially valid sequences. Only the first color works.
mach.ProcessString(L"\033]10;#111;\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;rgb:\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#111;#2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
pDispatch->ClearState();
// Invalid sequences.
mach.ProcessString(L"\033]10;rgb:1/1/\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
pDispatch->ClearState();
// Invalid multi-param sequences.
mach.ProcessString(L"\033]10;;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
pDispatch->ClearState();
mach.ProcessString(L"\033]10;#1;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
pDispatch->ClearState();
}
TEST_METHOD(TestOscSetDefaultBackground)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]11;rgb:1/1/1\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;rgb:12/34/56\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#123456\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;DarkOrange\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
// Partially valid sequences. Only the first color works.
mach.ProcessString(L"\033]11;#111;\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;rgb:\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#111;#2\033\\");
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
pDispatch->ClearState();
// Invalid sequences.
mach.ProcessString(L"\033]11;rgb:1/1/\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
pDispatch->ClearState();
// Invalid multi-param sequences.
mach.ProcessString(L"\033]11;;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
pDispatch->ClearState();
mach.ProcessString(L"\033]11;#1;rgb:1/1/1\033\\");
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
pDispatch->ClearState();
}
TEST_METHOD(TestOscSetColorTableEntry)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\033]4;0;rgb:1/1/1\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;16;rgb:11/11/11\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(16));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;64;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(64));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;128;orange\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(128));
pDispatch->ClearState();
// Invalid sequences.
mach.ProcessString(L"\033]4;\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;;\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;111\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;#111\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;1;111\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
mach.ProcessString(L"\033]4;1;rgb:\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
pDispatch->ClearState();
// Multiple params.
mach.ProcessString(L"\033]4;0;rgb:1/1/1;16;rgb:2/2/2\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(16));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;rgb:1/1/1;16;rgb:2/2/2;64;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(16));
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(64));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;rgb:1/1/1;16;rgb:2/2/2;64;#111;128;orange\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(16));
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(64));
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(128));
pDispatch->ClearState();
// Partially valid sequences. Valid colors should not be affected by invalid colors.
mach.ProcessString(L"\033]4;0;rgb:11;1;rgb:2/2/2;2;#111;3;orange;4;#111\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(1));
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(2));
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(3));
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(4));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;rgb:1/1/1;1;rgb:2/2/2;2;#111;3;orange;4;111\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(1));
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(2));
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(3));
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(4));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;rgb:1/1/1;1;rgb:2;2;#111;3;orange;4;#222\033\\");
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(1));
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(2));
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(3));
VERIFY_ARE_EQUAL(RGB(0x20, 0x20, 0x20), pDispatch->_colorTable.at(4));
pDispatch->ClearState();
// Invalid multi-param sequences
mach.ProcessString(L"\033]4;0;;1;;\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(1));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;;;;;1;;;;;\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(1));
pDispatch->ClearState();
mach.ProcessString(L"\033]4;0;rgb:1/1/;16;rgb:2/2/;64;#11\033\\");
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(16));
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(64));
pDispatch->ClearState();
}
TEST_METHOD(TestSetClipboard)
{
auto dispatch = std::make_unique<StatefulDispatch>();

1071
src/types/colorTable.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
/*++
Copyright (c) Microsoft Corporation
Module Name:
- colorTable.hpp
Abstract:
- Helper for color tables
--*/
#pragma once
namespace Microsoft::Console::Utils
{
void InitializeCampbellColorTable(const gsl::span<COLORREF> table);
void InitializeCampbellColorTableForConhost(const gsl::span<COLORREF> table);
void SwapANSIColorOrderForConhost(const gsl::span<COLORREF> table);
void Initialize256ColorTable(const gsl::span<COLORREF> table);
std::optional<til::color> ColorFromXOrgAppColorName(const std::wstring_view wstr) noexcept;
// Function Description:
// - Fill the alpha byte of the colors in a given color table with the given value.
// Arguments:
// - table: a color table
// - newAlpha: the new value to use as the alpha for all the entries in that table.
// Return Value:
// - <none>
constexpr void SetColorTableAlpha(const gsl::span<COLORREF> table, const BYTE newAlpha) noexcept
{
const auto shiftedAlpha = newAlpha << 24;
for (auto& color : table)
{
WI_UpdateFlagsInMask(color, 0xff000000, shiftedAlpha);
}
}
}

View File

@ -45,27 +45,12 @@ namespace Microsoft::Console::Utils
std::string ColorToHexString(const til::color color);
til::color ColorFromHexString(const std::string_view wstr);
std::optional<til::color> ColorFromXTermColor(const std::wstring_view wstr) noexcept;
std::optional<til::color> ColorFromXParseColorSpec(const std::wstring_view wstr) noexcept;
void InitializeCampbellColorTable(const gsl::span<COLORREF> table);
void InitializeCampbellColorTableForConhost(const gsl::span<COLORREF> table);
void SwapANSIColorOrderForConhost(const gsl::span<COLORREF> table);
void Initialize256ColorTable(const gsl::span<COLORREF> table);
// Function Description:
// - Fill the alpha byte of the colors in a given color table with the given value.
// Arguments:
// - table: a color table
// - newAlpha: the new value to use as the alpha for all the entries in that table.
// Return Value:
// - <none>
constexpr void SetColorTableAlpha(const gsl::span<COLORREF> table, const BYTE newAlpha) noexcept
{
const auto shiftedAlpha = newAlpha << 24;
for (auto& color : table)
{
WI_UpdateFlagsInMask(color, 0xff000000, shiftedAlpha);
}
}
bool HexToUint(const wchar_t wch, unsigned int& value) noexcept;
bool StringToUint(const std::wstring_view wstr, unsigned int& value);
std::vector<std::wstring_view> SplitString(const std::wstring_view wstr, const wchar_t delimiter);
constexpr uint16_t EndianSwap(uint16_t value)
{

View File

@ -12,6 +12,7 @@
<ItemGroup>
<ClCompile Include="..\CodepointWidthDetector.cpp" />
<ClCompile Include="..\convert.cpp" />
<ClCompile Include="..\colorTable.cpp" />
<ClCompile Include="..\Environment.cpp" />
<ClCompile Include="..\GlyphWidth.cpp" />
<ClCompile Include="..\MouseEvent.cpp" />
@ -39,6 +40,7 @@
<ClInclude Include="..\IControlAccessibilityInfo.h" />
<ClInclude Include="..\inc\CodepointWidthDetector.hpp" />
<ClInclude Include="..\inc\convert.hpp" />
<ClInclude Include="..\inc\colorTable.hpp" />
<ClInclude Include="..\inc\Environment.hpp" />
<ClInclude Include="..\inc\GlyphWidth.hpp" />
<ClInclude Include="..\inc\IInputEvent.hpp" />

View File

@ -18,6 +18,9 @@
<ClCompile Include="..\convert.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\colorTable.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\MouseEvent.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -89,6 +92,9 @@
<ClInclude Include="..\inc\convert.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\colorTable.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\precomp.h">
<Filter>Header Files</Filter>
</ClInclude>

View File

@ -39,6 +39,7 @@ SOURCES= \
..\Viewport.cpp \
..\WindowBufferSizeEvent.cpp \
..\convert.cpp \
..\colorTable.cpp \
..\Utf16Parser.cpp \
..\utils.cpp \
..\ThemeUtils.cpp \

View File

@ -6,6 +6,7 @@
#include "..\..\inc\consoletaeftemplates.hpp"
#include "..\inc\utils.hpp"
#include "..\inc\colorTable.hpp"
#include <conattrs.hpp>
using namespace WEX::Common;
@ -21,6 +22,12 @@ class UtilsTests
TEST_METHOD(TestClampToShortMax);
TEST_METHOD(TestSwapColorPalette);
TEST_METHOD(TestGuidToString);
TEST_METHOD(TestSplitString);
TEST_METHOD(TestStringToUint);
TEST_METHOD(TestColorFromXTermColor);
void _VerifyXTermColorResult(const std::wstring_view wstr, DWORD colorValue);
void _VerifyXTermColorInvalid(const std::wstring_view wstr);
};
void UtilsTests::TestClampToShortMax()
@ -89,3 +96,158 @@ void UtilsTests::TestGuidToString()
VERIFY_ARE_EQUAL(constantGuidString.size(), generatedGuid.size());
VERIFY_ARE_EQUAL(constantGuidString, generatedGuid);
}
void UtilsTests::TestSplitString()
{
std::vector<std::wstring_view> result;
result = SplitString(L"", L';');
VERIFY_ARE_EQUAL(0u, result.size());
result = SplitString(L"1", L';');
VERIFY_ARE_EQUAL(1u, result.size());
result = SplitString(L"123", L';');
VERIFY_ARE_EQUAL(1u, result.size());
result = SplitString(L";123", L';');
VERIFY_ARE_EQUAL(2u, result.size());
VERIFY_ARE_EQUAL(L"", result.at(0));
VERIFY_ARE_EQUAL(L"123", result.at(1));
result = SplitString(L"123;", L';');
VERIFY_ARE_EQUAL(2u, result.size());
VERIFY_ARE_EQUAL(L"123", result.at(0));
VERIFY_ARE_EQUAL(L"", result.at(1));
result = SplitString(L"123;456", L';');
VERIFY_ARE_EQUAL(2u, result.size());
VERIFY_ARE_EQUAL(L"123", result.at(0));
VERIFY_ARE_EQUAL(L"456", result.at(1));
result = SplitString(L"123;456;789", L';');
VERIFY_ARE_EQUAL(3u, result.size());
VERIFY_ARE_EQUAL(L"123", result.at(0));
VERIFY_ARE_EQUAL(L"456", result.at(1));
VERIFY_ARE_EQUAL(L"789", result.at(2));
}
void UtilsTests::TestStringToUint()
{
bool success = false;
unsigned int value = 0;
success = StringToUint(L"", value);
VERIFY_IS_FALSE(success);
success = StringToUint(L"xyz", value);
VERIFY_IS_FALSE(success);
success = StringToUint(L";", value);
VERIFY_IS_FALSE(success);
success = StringToUint(L"1", value);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(1u, value);
success = StringToUint(L"123", value);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(123u, value);
success = StringToUint(L"123456789", value);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(123456789u, value);
}
void UtilsTests::TestColorFromXTermColor()
{
_VerifyXTermColorResult(L"rgb:1/1/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rGb:1/1/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"RGB:1/1/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:111/1/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1111/1/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/11/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/111/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/1111/1", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/1/11", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/1/111", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/1/1111", RGB(0x11, 0x11, 0x11));
_VerifyXTermColorResult(L"rgb:1/23/4", RGB(0x11, 0x23, 0x44));
_VerifyXTermColorResult(L"rgb:1/23/45", RGB(0x11, 0x23, 0x45));
_VerifyXTermColorResult(L"rgb:1/23/456", RGB(0x11, 0x23, 0x45));
_VerifyXTermColorResult(L"rgb:12/34/5", RGB(0x12, 0x34, 0x55));
_VerifyXTermColorResult(L"rgb:12/34/56", RGB(0x12, 0x34, 0x56));
_VerifyXTermColorResult(L"rgb:12/345/67", RGB(0x12, 0x34, 0x67));
_VerifyXTermColorResult(L"rgb:12/345/678", RGB(0x12, 0x34, 0x67));
_VerifyXTermColorResult(L"rgb:123/456/789", RGB(0x12, 0x45, 0x78));
_VerifyXTermColorResult(L"rgb:123/4564/789", RGB(0x12, 0x45, 0x78));
_VerifyXTermColorResult(L"rgb:123/4564/7897", RGB(0x12, 0x45, 0x78));
_VerifyXTermColorResult(L"rgb:1231/4564/7897", RGB(0x12, 0x45, 0x78));
_VerifyXTermColorResult(L"#111", RGB(0x10, 0x10, 0x10));
_VerifyXTermColorResult(L"#123456", RGB(0x12, 0x34, 0x56));
_VerifyXTermColorResult(L"#123456789", RGB(0x12, 0x45, 0x78));
_VerifyXTermColorResult(L"#123145647897", RGB(0x12, 0x45, 0x78));
_VerifyXTermColorResult(L"orange", RGB(255, 165, 0));
_VerifyXTermColorResult(L"dark green", RGB(0, 100, 0));
_VerifyXTermColorResult(L"medium sea green", RGB(60, 179, 113));
_VerifyXTermColorResult(L"LightYellow", RGB(255, 255, 224));
// Invalid sequences.
_VerifyXTermColorInvalid(L"");
_VerifyXTermColorInvalid(L"r:");
_VerifyXTermColorInvalid(L"rg:");
_VerifyXTermColorInvalid(L"rgb:");
_VerifyXTermColorInvalid(L"rgb:/");
_VerifyXTermColorInvalid(L"rgb://");
_VerifyXTermColorInvalid(L"rgb:///");
_VerifyXTermColorInvalid(L"rgb:1");
_VerifyXTermColorInvalid(L"rgb:1/");
_VerifyXTermColorInvalid(L"rgb:/1");
_VerifyXTermColorInvalid(L"rgb:1/1");
_VerifyXTermColorInvalid(L"rgb:1/1/");
_VerifyXTermColorInvalid(L"rgb:1/11/");
_VerifyXTermColorInvalid(L"rgb:/1/1");
_VerifyXTermColorInvalid(L"rgb:1/1/1/");
_VerifyXTermColorInvalid(L"rgb:1/1/1/1");
_VerifyXTermColorInvalid(L"rgb:111111111");
_VerifyXTermColorInvalid(L"rgb:this/is/invalid");
_VerifyXTermColorInvalid(L"rgba:1/1/1");
_VerifyXTermColorInvalid(L"rgbi:1/1/1");
_VerifyXTermColorInvalid(L"cmyk:1/1/1/1");
_VerifyXTermColorInvalid(L"rgb#111");
_VerifyXTermColorInvalid(L"rgb:#111");
_VerifyXTermColorInvalid(L"rgb:rgb:1/1/1");
_VerifyXTermColorInvalid(L"rgb:rgb:#111");
_VerifyXTermColorInvalid(L"#");
_VerifyXTermColorInvalid(L"#1");
_VerifyXTermColorInvalid(L"#1111");
_VerifyXTermColorInvalid(L"#11111");
_VerifyXTermColorInvalid(L"#1/1/1");
_VerifyXTermColorInvalid(L"#11/1/");
_VerifyXTermColorInvalid(L"#1111111");
_VerifyXTermColorInvalid(L"#/1/1/1");
_VerifyXTermColorInvalid(L"#rgb:1/1/1");
_VerifyXTermColorInvalid(L"#111invalid");
_VerifyXTermColorInvalid(L"#invalid111");
_VerifyXTermColorInvalid(L"#1111111111111111");
_VerifyXTermColorInvalid(L"12/34/56");
_VerifyXTermColorInvalid(L"123456");
_VerifyXTermColorInvalid(L"rgb1/1/1");
_VerifyXTermColorInvalid(L"中文rgb:1/1/1");
_VerifyXTermColorInvalid(L"rgb中文:1/1/1");
_VerifyXTermColorInvalid(L"这是一句中文");
_VerifyXTermColorInvalid(L"RGBİ1/1/1");
_VerifyXTermColorInvalid(L"rgbİ1/1/1");
_VerifyXTermColorInvalid(L"rgbİ:1/1/1");
_VerifyXTermColorInvalid(L"rgß:1/1/1");
_VerifyXTermColorInvalid(L"rgẞ:1/1/1");
}
void UtilsTests::_VerifyXTermColorResult(const std::wstring_view wstr, DWORD colorValue)
{
std::optional<til::color> color = ColorFromXTermColor(wstr);
VERIFY_IS_TRUE(color.has_value());
VERIFY_ARE_EQUAL(colorValue, (COLORREF)color.value());
}
void UtilsTests::_VerifyXTermColorInvalid(const std::wstring_view wstr)
{
std::optional<til::color> color = ColorFromXTermColor(wstr);
VERIFY_IS_FALSE(color.has_value());
}

View File

@ -3,286 +3,20 @@
#include "precomp.h"
#include "inc/utils.hpp"
#include "inc/colorTable.hpp"
using namespace Microsoft::Console;
static constexpr std::array<til::color, 16> campbellColorTable{
til::color{ 0x0C, 0x0C, 0x0C },
til::color{ 0xC5, 0x0F, 0x1F },
til::color{ 0x13, 0xA1, 0x0E },
til::color{ 0xC1, 0x9C, 0x00 },
til::color{ 0x00, 0x37, 0xDA },
til::color{ 0x88, 0x17, 0x98 },
til::color{ 0x3A, 0x96, 0xDD },
til::color{ 0xCC, 0xCC, 0xCC },
til::color{ 0x76, 0x76, 0x76 },
til::color{ 0xE7, 0x48, 0x56 },
til::color{ 0x16, 0xC6, 0x0C },
til::color{ 0xF9, 0xF1, 0xA5 },
til::color{ 0x3B, 0x78, 0xFF },
til::color{ 0xB4, 0x00, 0x9E },
til::color{ 0x61, 0xD6, 0xD6 },
til::color{ 0xF2, 0xF2, 0xF2 },
};
static constexpr std::array<til::color, 256> standardXterm256ColorTable{
til::color{ 0x00, 0x00, 0x00 },
til::color{ 0x80, 0x00, 0x00 },
til::color{ 0x00, 0x80, 0x00 },
til::color{ 0x80, 0x80, 0x00 },
til::color{ 0x00, 0x00, 0x80 },
til::color{ 0x80, 0x00, 0x80 },
til::color{ 0x00, 0x80, 0x80 },
til::color{ 0xC0, 0xC0, 0xC0 },
til::color{ 0x80, 0x80, 0x80 },
til::color{ 0xFF, 0x00, 0x00 },
til::color{ 0x00, 0xFF, 0x00 },
til::color{ 0xFF, 0xFF, 0x00 },
til::color{ 0x00, 0x00, 0xFF },
til::color{ 0xFF, 0x00, 0xFF },
til::color{ 0x00, 0xFF, 0xFF },
til::color{ 0xFF, 0xFF, 0xFF },
til::color{ 0x00, 0x00, 0x00 },
til::color{ 0x00, 0x00, 0x5F },
til::color{ 0x00, 0x00, 0x87 },
til::color{ 0x00, 0x00, 0xAF },
til::color{ 0x00, 0x00, 0xD7 },
til::color{ 0x00, 0x00, 0xFF },
til::color{ 0x00, 0x5F, 0x00 },
til::color{ 0x00, 0x5F, 0x5F },
til::color{ 0x00, 0x5F, 0x87 },
til::color{ 0x00, 0x5F, 0xAF },
til::color{ 0x00, 0x5F, 0xD7 },
til::color{ 0x00, 0x5F, 0xFF },
til::color{ 0x00, 0x87, 0x00 },
til::color{ 0x00, 0x87, 0x5F },
til::color{ 0x00, 0x87, 0x87 },
til::color{ 0x00, 0x87, 0xAF },
til::color{ 0x00, 0x87, 0xD7 },
til::color{ 0x00, 0x87, 0xFF },
til::color{ 0x00, 0xAF, 0x00 },
til::color{ 0x00, 0xAF, 0x5F },
til::color{ 0x00, 0xAF, 0x87 },
til::color{ 0x00, 0xAF, 0xAF },
til::color{ 0x00, 0xAF, 0xD7 },
til::color{ 0x00, 0xAF, 0xFF },
til::color{ 0x00, 0xD7, 0x00 },
til::color{ 0x00, 0xD7, 0x5F },
til::color{ 0x00, 0xD7, 0x87 },
til::color{ 0x00, 0xD7, 0xAF },
til::color{ 0x00, 0xD7, 0xD7 },
til::color{ 0x00, 0xD7, 0xFF },
til::color{ 0x00, 0xFF, 0x00 },
til::color{ 0x00, 0xFF, 0x5F },
til::color{ 0x00, 0xFF, 0x87 },
til::color{ 0x00, 0xFF, 0xAF },
til::color{ 0x00, 0xFF, 0xD7 },
til::color{ 0x00, 0xFF, 0xFF },
til::color{ 0x5F, 0x00, 0x00 },
til::color{ 0x5F, 0x00, 0x5F },
til::color{ 0x5F, 0x00, 0x87 },
til::color{ 0x5F, 0x00, 0xAF },
til::color{ 0x5F, 0x00, 0xD7 },
til::color{ 0x5F, 0x00, 0xFF },
til::color{ 0x5F, 0x5F, 0x00 },
til::color{ 0x5F, 0x5F, 0x5F },
til::color{ 0x5F, 0x5F, 0x87 },
til::color{ 0x5F, 0x5F, 0xAF },
til::color{ 0x5F, 0x5F, 0xD7 },
til::color{ 0x5F, 0x5F, 0xFF },
til::color{ 0x5F, 0x87, 0x00 },
til::color{ 0x5F, 0x87, 0x5F },
til::color{ 0x5F, 0x87, 0x87 },
til::color{ 0x5F, 0x87, 0xAF },
til::color{ 0x5F, 0x87, 0xD7 },
til::color{ 0x5F, 0x87, 0xFF },
til::color{ 0x5F, 0xAF, 0x00 },
til::color{ 0x5F, 0xAF, 0x5F },
til::color{ 0x5F, 0xAF, 0x87 },
til::color{ 0x5F, 0xAF, 0xAF },
til::color{ 0x5F, 0xAF, 0xD7 },
til::color{ 0x5F, 0xAF, 0xFF },
til::color{ 0x5F, 0xD7, 0x00 },
til::color{ 0x5F, 0xD7, 0x5F },
til::color{ 0x5F, 0xD7, 0x87 },
til::color{ 0x5F, 0xD7, 0xAF },
til::color{ 0x5F, 0xD7, 0xD7 },
til::color{ 0x5F, 0xD7, 0xFF },
til::color{ 0x5F, 0xFF, 0x00 },
til::color{ 0x5F, 0xFF, 0x5F },
til::color{ 0x5F, 0xFF, 0x87 },
til::color{ 0x5F, 0xFF, 0xAF },
til::color{ 0x5F, 0xFF, 0xD7 },
til::color{ 0x5F, 0xFF, 0xFF },
til::color{ 0x87, 0x00, 0x00 },
til::color{ 0x87, 0x00, 0x5F },
til::color{ 0x87, 0x00, 0x87 },
til::color{ 0x87, 0x00, 0xAF },
til::color{ 0x87, 0x00, 0xD7 },
til::color{ 0x87, 0x00, 0xFF },
til::color{ 0x87, 0x5F, 0x00 },
til::color{ 0x87, 0x5F, 0x5F },
til::color{ 0x87, 0x5F, 0x87 },
til::color{ 0x87, 0x5F, 0xAF },
til::color{ 0x87, 0x5F, 0xD7 },
til::color{ 0x87, 0x5F, 0xFF },
til::color{ 0x87, 0x87, 0x00 },
til::color{ 0x87, 0x87, 0x5F },
til::color{ 0x87, 0x87, 0x87 },
til::color{ 0x87, 0x87, 0xAF },
til::color{ 0x87, 0x87, 0xD7 },
til::color{ 0x87, 0x87, 0xFF },
til::color{ 0x87, 0xAF, 0x00 },
til::color{ 0x87, 0xAF, 0x5F },
til::color{ 0x87, 0xAF, 0x87 },
til::color{ 0x87, 0xAF, 0xAF },
til::color{ 0x87, 0xAF, 0xD7 },
til::color{ 0x87, 0xAF, 0xFF },
til::color{ 0x87, 0xD7, 0x00 },
til::color{ 0x87, 0xD7, 0x5F },
til::color{ 0x87, 0xD7, 0x87 },
til::color{ 0x87, 0xD7, 0xAF },
til::color{ 0x87, 0xD7, 0xD7 },
til::color{ 0x87, 0xD7, 0xFF },
til::color{ 0x87, 0xFF, 0x00 },
til::color{ 0x87, 0xFF, 0x5F },
til::color{ 0x87, 0xFF, 0x87 },
til::color{ 0x87, 0xFF, 0xAF },
til::color{ 0x87, 0xFF, 0xD7 },
til::color{ 0x87, 0xFF, 0xFF },
til::color{ 0xAF, 0x00, 0x00 },
til::color{ 0xAF, 0x00, 0x5F },
til::color{ 0xAF, 0x00, 0x87 },
til::color{ 0xAF, 0x00, 0xAF },
til::color{ 0xAF, 0x00, 0xD7 },
til::color{ 0xAF, 0x00, 0xFF },
til::color{ 0xAF, 0x5F, 0x00 },
til::color{ 0xAF, 0x5F, 0x5F },
til::color{ 0xAF, 0x5F, 0x87 },
til::color{ 0xAF, 0x5F, 0xAF },
til::color{ 0xAF, 0x5F, 0xD7 },
til::color{ 0xAF, 0x5F, 0xFF },
til::color{ 0xAF, 0x87, 0x00 },
til::color{ 0xAF, 0x87, 0x5F },
til::color{ 0xAF, 0x87, 0x87 },
til::color{ 0xAF, 0x87, 0xAF },
til::color{ 0xAF, 0x87, 0xD7 },
til::color{ 0xAF, 0x87, 0xFF },
til::color{ 0xAF, 0xAF, 0x00 },
til::color{ 0xAF, 0xAF, 0x5F },
til::color{ 0xAF, 0xAF, 0x87 },
til::color{ 0xAF, 0xAF, 0xAF },
til::color{ 0xAF, 0xAF, 0xD7 },
til::color{ 0xAF, 0xAF, 0xFF },
til::color{ 0xAF, 0xD7, 0x00 },
til::color{ 0xAF, 0xD7, 0x5F },
til::color{ 0xAF, 0xD7, 0x87 },
til::color{ 0xAF, 0xD7, 0xAF },
til::color{ 0xAF, 0xD7, 0xD7 },
til::color{ 0xAF, 0xD7, 0xFF },
til::color{ 0xAF, 0xFF, 0x00 },
til::color{ 0xAF, 0xFF, 0x5F },
til::color{ 0xAF, 0xFF, 0x87 },
til::color{ 0xAF, 0xFF, 0xAF },
til::color{ 0xAF, 0xFF, 0xD7 },
til::color{ 0xAF, 0xFF, 0xFF },
til::color{ 0xD7, 0x00, 0x00 },
til::color{ 0xD7, 0x00, 0x5F },
til::color{ 0xD7, 0x00, 0x87 },
til::color{ 0xD7, 0x00, 0xAF },
til::color{ 0xD7, 0x00, 0xD7 },
til::color{ 0xD7, 0x00, 0xFF },
til::color{ 0xD7, 0x5F, 0x00 },
til::color{ 0xD7, 0x5F, 0x5F },
til::color{ 0xD7, 0x5F, 0x87 },
til::color{ 0xD7, 0x5F, 0xAF },
til::color{ 0xD7, 0x5F, 0xD7 },
til::color{ 0xD7, 0x5F, 0xFF },
til::color{ 0xD7, 0x87, 0x00 },
til::color{ 0xD7, 0x87, 0x5F },
til::color{ 0xD7, 0x87, 0x87 },
til::color{ 0xD7, 0x87, 0xAF },
til::color{ 0xD7, 0x87, 0xD7 },
til::color{ 0xD7, 0x87, 0xFF },
til::color{ 0xD7, 0xAF, 0x00 },
til::color{ 0xD7, 0xAF, 0x5F },
til::color{ 0xD7, 0xAF, 0x87 },
til::color{ 0xD7, 0xAF, 0xAF },
til::color{ 0xD7, 0xAF, 0xD7 },
til::color{ 0xD7, 0xAF, 0xFF },
til::color{ 0xD7, 0xD7, 0x00 },
til::color{ 0xD7, 0xD7, 0x5F },
til::color{ 0xD7, 0xD7, 0x87 },
til::color{ 0xD7, 0xD7, 0xAF },
til::color{ 0xD7, 0xD7, 0xD7 },
til::color{ 0xD7, 0xD7, 0xFF },
til::color{ 0xD7, 0xFF, 0x00 },
til::color{ 0xD7, 0xFF, 0x5F },
til::color{ 0xD7, 0xFF, 0x87 },
til::color{ 0xD7, 0xFF, 0xAF },
til::color{ 0xD7, 0xFF, 0xD7 },
til::color{ 0xD7, 0xFF, 0xFF },
til::color{ 0xFF, 0x00, 0x00 },
til::color{ 0xFF, 0x00, 0x5F },
til::color{ 0xFF, 0x00, 0x87 },
til::color{ 0xFF, 0x00, 0xAF },
til::color{ 0xFF, 0x00, 0xD7 },
til::color{ 0xFF, 0x00, 0xFF },
til::color{ 0xFF, 0x5F, 0x00 },
til::color{ 0xFF, 0x5F, 0x5F },
til::color{ 0xFF, 0x5F, 0x87 },
til::color{ 0xFF, 0x5F, 0xAF },
til::color{ 0xFF, 0x5F, 0xD7 },
til::color{ 0xFF, 0x5F, 0xFF },
til::color{ 0xFF, 0x87, 0x00 },
til::color{ 0xFF, 0x87, 0x5F },
til::color{ 0xFF, 0x87, 0x87 },
til::color{ 0xFF, 0x87, 0xAF },
til::color{ 0xFF, 0x87, 0xD7 },
til::color{ 0xFF, 0x87, 0xFF },
til::color{ 0xFF, 0xAF, 0x00 },
til::color{ 0xFF, 0xAF, 0x5F },
til::color{ 0xFF, 0xAF, 0x87 },
til::color{ 0xFF, 0xAF, 0xAF },
til::color{ 0xFF, 0xAF, 0xD7 },
til::color{ 0xFF, 0xAF, 0xFF },
til::color{ 0xFF, 0xD7, 0x00 },
til::color{ 0xFF, 0xD7, 0x5F },
til::color{ 0xFF, 0xD7, 0x87 },
til::color{ 0xFF, 0xD7, 0xAF },
til::color{ 0xFF, 0xD7, 0xD7 },
til::color{ 0xFF, 0xD7, 0xFF },
til::color{ 0xFF, 0xFF, 0x00 },
til::color{ 0xFF, 0xFF, 0x5F },
til::color{ 0xFF, 0xFF, 0x87 },
til::color{ 0xFF, 0xFF, 0xAF },
til::color{ 0xFF, 0xFF, 0xD7 },
til::color{ 0xFF, 0xFF, 0xFF },
til::color{ 0x08, 0x08, 0x08 },
til::color{ 0x12, 0x12, 0x12 },
til::color{ 0x1C, 0x1C, 0x1C },
til::color{ 0x26, 0x26, 0x26 },
til::color{ 0x30, 0x30, 0x30 },
til::color{ 0x3A, 0x3A, 0x3A },
til::color{ 0x44, 0x44, 0x44 },
til::color{ 0x4E, 0x4E, 0x4E },
til::color{ 0x58, 0x58, 0x58 },
til::color{ 0x62, 0x62, 0x62 },
til::color{ 0x6C, 0x6C, 0x6C },
til::color{ 0x76, 0x76, 0x76 },
til::color{ 0x80, 0x80, 0x80 },
til::color{ 0x8A, 0x8A, 0x8A },
til::color{ 0x94, 0x94, 0x94 },
til::color{ 0x9E, 0x9E, 0x9E },
til::color{ 0xA8, 0xA8, 0xA8 },
til::color{ 0xB2, 0xB2, 0xB2 },
til::color{ 0xBC, 0xBC, 0xBC },
til::color{ 0xC6, 0xC6, 0xC6 },
til::color{ 0xD0, 0xD0, 0xD0 },
til::color{ 0xDA, 0xDA, 0xDA },
til::color{ 0xE4, 0xE4, 0xE4 },
til::color{ 0xEE, 0xEE, 0xEE },
};
// Routine Description:
// - Determines if a character is a valid number character, 0-9.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isNumber(const wchar_t wch) noexcept
{
return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39
}
// Function Description:
// - Creates a String representation of a guid, in the format
@ -377,6 +111,317 @@ til::color Utils::ColorFromHexString(const std::string_view str)
return til::color{ r, g, b };
}
// Routine Description:
// - Given a color string, attempts to parse the color.
// The color are specified by name or RGB specification as per XParseColor.
// Arguments:
// - string - The string containing the color spec string to parse.
// Return Value:
// - An optional color which contains value if a color was successfully parsed
std::optional<til::color> Utils::ColorFromXTermColor(const std::wstring_view string) noexcept
{
auto color = ColorFromXParseColorSpec(string);
if (!color.has_value())
{
// Try again, but use the app color name parser
color = ColorFromXOrgAppColorName(string);
}
return color;
}
// Routine Description:
// - Given a color spec string, attempts to parse the color that's encoded.
//
// Based on the XParseColor documentation, the supported specs currently are the following:
// spec1: a color in the following format:
// "rgb:<red>/<green>/<blue>"
// spec2: a color in the following format:
// "#<red><green><blue>"
//
// In both specs, <color> is a value contains up to 4 hex digits, upper or lower case.
// Arguments:
// - string - The string containing the color spec string to parse.
// Return Value:
// - An optional color which contains value if a color was successfully parsed
std::optional<til::color> Utils::ColorFromXParseColorSpec(const std::wstring_view string) noexcept
try
{
bool foundXParseColorSpec = false;
bool foundValidColorSpec = false;
bool isSharpSignFormat = false;
size_t rgbHexDigitCount = 0;
std::array<unsigned int, 3> colorValues = { 0 };
std::array<unsigned int, 3> parameterValues = { 0 };
const auto stringSize = string.size();
// First we look for "rgb:"
// Other colorspaces are theoretically possible, but we don't support them.
auto curr = string.cbegin();
if (stringSize > 4)
{
auto prefix = std::wstring(string.substr(0, 4));
// The "rgb:" indicator should be case insensitive. To prevent possible issues under
// different locales, transform only ASCII range latin characters.
std::transform(prefix.begin(), prefix.end(), prefix.begin(), [](const auto x) {
return x >= L'A' && x <= L'Z' ? static_cast<wchar_t>(std::towlower(x)) : x;
});
if (prefix.compare(L"rgb:") == 0)
{
// If all the components have the same digit count, we can have one of the following formats:
// 9 "rgb:h/h/h"
// 12 "rgb:hh/hh/hh"
// 15 "rgb:hhh/hhh/hhh"
// 18 "rgb:hhhh/hhhh/hhhh"
// Note that the component sizes aren't required to be the same.
// Anything in between is also valid, e.g. "rgb:h/hh/h" and "rgb:h/hh/hhh".
// Any fewer cannot be valid, and any more will be too many. Return early in this case.
if (stringSize < 9 || stringSize > 18)
{
return std::nullopt;
}
foundXParseColorSpec = true;
std::advance(curr, 4);
}
}
// Try the sharp sign format.
if (!foundXParseColorSpec && stringSize > 1)
{
if (til::at(string, 0) == L'#')
{
// We can have one of the following formats:
// 4 "#hhh"
// 7 "#hhhhhh"
// 10 "#hhhhhhhhh"
// 13 "#hhhhhhhhhhhh"
// Any other cases will be invalid. Return early in this case.
if (!(stringSize == 4 || stringSize == 7 || stringSize == 10 || stringSize == 13))
{
return std::nullopt;
}
isSharpSignFormat = true;
foundXParseColorSpec = true;
rgbHexDigitCount = (stringSize - 1) / 3;
std::advance(curr, 1);
}
}
// No valid spec is found. Return early.
if (!foundXParseColorSpec)
{
return std::nullopt;
}
// Try to parse the actual color value of each component.
for (size_t component = 0; component < 3; component++)
{
bool foundColor = false;
auto& parameterValue = til::at(parameterValues, component);
// For "sharp sign" format, the rgbHexDigitCount is known.
// For "rgb:" format, colorspecs are up to hhhh/hhhh/hhhh, for 1-4 h's
const auto iteration = isSharpSignFormat ? rgbHexDigitCount : 4;
for (size_t i = 0; i < iteration && curr < string.cend(); i++)
{
const wchar_t wch = *curr++;
parameterValue *= 16;
unsigned int intVal = 0;
const auto ret = HexToUint(wch, intVal);
if (!ret)
{
// Encountered something weird oh no
return std::nullopt;
}
parameterValue += intVal;
if (isSharpSignFormat)
{
// If we get this far, any number can be seen as a valid part
// of this component.
foundColor = true;
if (i >= rgbHexDigitCount)
{
// Successfully parsed this component. Start the next one.
break;
}
}
else
{
// Record the hex digit count of the current component.
rgbHexDigitCount = i + 1;
// If this is the first 2 component...
if (component < 2 && curr < string.cend() && *curr == L'/')
{
// ...and we have successfully parsed this component, we need
// to skip the delimiter before starting the next one.
curr++;
foundColor = true;
break;
}
// Or we have reached the end of the string...
else if (curr >= string.cend())
{
// ...meaning that this is the last component. We're not going to
// see any delimiter. We can just break out.
foundColor = true;
break;
}
}
}
if (!foundColor)
{
// Indicates there was some error parsing color.
return std::nullopt;
}
// Calculate the actual color value based on the hex digit count.
auto& colorValue = til::at(colorValues, component);
const auto scaleMultiplier = isSharpSignFormat ? 0x10 : 0x11;
const auto scaleDivisor = scaleMultiplier << 8 >> 4 * (4 - rgbHexDigitCount);
colorValue = parameterValue * scaleMultiplier / scaleDivisor;
}
if (curr >= string.cend())
{
// We're at the end of the string and we have successfully parsed the color.
foundValidColorSpec = true;
}
// Only if we find a valid colorspec can we pass it out successfully.
if (foundValidColorSpec)
{
return til::color(LOBYTE(til::at(colorValues, 0)),
LOBYTE(til::at(colorValues, 1)),
LOBYTE(til::at(colorValues, 2)));
}
return std::nullopt;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return std::nullopt;
}
// Routine Description:
// - Converts a hex character to its equivalent integer value.
// Arguments:
// - wch - Character to convert.
// - value - receives the int value of the char
// Return Value:
// - true iff the character is a hex character.
bool Utils::HexToUint(const wchar_t wch,
unsigned int& value) noexcept
{
value = 0;
bool success = false;
if (wch >= L'0' && wch <= L'9')
{
value = wch - L'0';
success = true;
}
else if (wch >= L'A' && wch <= L'F')
{
value = (wch - L'A') + 10;
success = true;
}
else if (wch >= L'a' && wch <= L'f')
{
value = (wch - L'a') + 10;
success = true;
}
return success;
}
// Routine Description:
// - Converts a number string to its equivalent unsigned integer value.
// Arguments:
// - wstr - String to convert.
// - value - receives the int value of the string
// Return Value:
// - true iff the string is a unsigned integer string.
bool Utils::StringToUint(const std::wstring_view wstr,
unsigned int& value)
{
if (wstr.size() < 1)
{
return false;
}
unsigned int result = 0;
size_t current = 0;
while (current < wstr.size())
{
const wchar_t wch = wstr.at(current);
if (_isNumber(wch))
{
result *= 10;
result += wch - L'0';
++current;
}
else
{
return false;
}
}
value = result;
return true;
}
// Routine Description:
// - Split a string into different parts using the delimiter provided.
// Arguments:
// - wstr - String to split.
// - delimiter - delimiter to use.
// Return Value:
// - a vector containing the result string parts.
std::vector<std::wstring_view> Utils::SplitString(const std::wstring_view wstr,
const wchar_t delimiter)
{
std::vector<std::wstring_view> result;
size_t current = 0;
while (current < wstr.size())
{
const auto nextDelimiter = wstr.find(delimiter, current);
if (nextDelimiter == std::wstring::npos)
{
result.push_back(wstr.substr(current));
break;
}
else
{
const auto length = nextDelimiter - current;
result.push_back(wstr.substr(current, length));
// Skip this part and the delimiter. Start the next one
current += length + 1;
// The next index is larger than string size, which means the string
// is in the format of "part1;part2;" (assuming use ';' as delimiter).
// Add the last part which is an empty string.
if (current >= wstr.size())
{
result.push_back(L"");
}
}
}
return result;
}
// Routine Description:
// - Shorthand check if a handle value is null or invalid.
// Arguments:
@ -388,63 +433,6 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept
return handle != nullptr && handle != INVALID_HANDLE_VALUE;
}
// Function Description:
// - Fill the first 16 entries of a given color table with the Campbell color
// scheme, in the ANSI/VT RGB order.
// Arguments:
// - table: a color table with at least 16 entries
// Return Value:
// - <none>, throws if the table has less that 16 entries
void Utils::InitializeCampbellColorTable(const gsl::span<COLORREF> table)
{
THROW_HR_IF(E_INVALIDARG, table.size() < 16);
std::copy(campbellColorTable.begin(), campbellColorTable.end(), table.begin());
}
// Function Description:
// - Fill the first 16 entries of a given color table with the Campbell color
// scheme, in the Windows BGR order.
// Arguments:
// - table: a color table with at least 16 entries
// Return Value:
// - <none>, throws if the table has less that 16 entries
void Utils::InitializeCampbellColorTableForConhost(const gsl::span<COLORREF> table)
{
THROW_HR_IF(E_INVALIDARG, table.size() < 16);
InitializeCampbellColorTable(table);
SwapANSIColorOrderForConhost(table);
}
// Function Description:
// - modifies in-place the given color table from ANSI (RGB) order to Console order (BRG).
// Arguments:
// - table: a color table with at least 16 entries
// Return Value:
// - <none>, throws if the table has less that 16 entries
void Utils::SwapANSIColorOrderForConhost(const gsl::span<COLORREF> table)
{
THROW_HR_IF(E_INVALIDARG, table.size() < 16);
std::swap(til::at(table, 1), til::at(table, 4));
std::swap(til::at(table, 3), til::at(table, 6));
std::swap(til::at(table, 9), til::at(table, 12));
std::swap(til::at(table, 11), til::at(table, 14));
}
// Function Description:
// - Fill the first 255 entries of a given color table with the default values
// of a full 256-color table
// Arguments:
// - table: a color table with at least 256 entries
// Return Value:
// - <none>, throws if the table has less that 256 entries
void Utils::Initialize256ColorTable(const gsl::span<COLORREF> table)
{
THROW_HR_IF(E_INVALIDARG, table.size() < 256);
std::copy(standardXterm256ColorTable.begin(), standardXterm256ColorTable.end(), table.begin());
}
// Function Description:
// - Generate a Version 5 UUID (specified in RFC4122 4.3)
// v5 UUIDs are stable given the same namespace and "name".