mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-17 15:36:35 +00:00
Compare commits
13 Commits
dev/duhowe
...
dev/cazamo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dc9bb1558 | ||
|
|
45e93cac42 | ||
|
|
9bf994dd20 | ||
|
|
a7380638a8 | ||
|
|
8f4fdf9751 | ||
|
|
33a80191c1 | ||
|
|
84ae7adec6 | ||
|
|
a6ebdd3d4a | ||
|
|
031998bfc3 | ||
|
|
41e08a68bd | ||
|
|
81170aff78 | ||
|
|
c90ace8326 | ||
|
|
fd30d00304 |
@@ -350,7 +350,7 @@ void ROW::_init() noexcept
|
||||
std::iota(_charOffsets.begin(), _charOffsets.end(), uint16_t{ 0 });
|
||||
#endif
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
void ROW::CopyFrom(const ROW& source)
|
||||
|
||||
@@ -672,9 +672,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Dieser Linktyp wird derzeit nicht unterstützt:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Abbrechen</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Dieser Link kann zu einem unsicheren Speicherort führen. Links können ihren Computer und Ihre Daten beschädigen. Klicken Sie zum Schutz Des Computers nur auf Links aus vertrauenswürdigen Quellen.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Trotzdem öffnen</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Einstellungen</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>This link type is currently not supported:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>This link may lead to an unsafe location. Hyperlinks can be harmful to your computer and data. To protect your computer, only click links from trusted sources.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Open anyway</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Este tipo de vínculo no se admite actualmente:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancelar</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Este vínculo puede dar lugar a una ubicación no segura. Los hipervínculos pueden ser perjudiciales para el equipo y los datos. Para proteger el equipo, haga clic solo en vínculos de orígenes de confianza.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Abrir de todas formas</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Configuración</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Ce type de lien n’est actuellement pas pris en charge :</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Annuler</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Ce lien peut entraîner un emplacement non sécurisé. Les liens hypertexte peuvent endommager votre ordinateur et vos données. Pour protéger votre ordinateur, cliquez uniquement sur des liens provenant de sources fiables.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Ouvrir quand même</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Paramètres</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Questo tipo di collegamento non è al momento supportato:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Annulla</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Questo collegamento potrebbe causare un percorso non sicuro. I collegamenti ipertestuali possono essere dannosi per il computer e i dati. Per proteggere il computer, fare clic solo su collegamenti da origini attendibili.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Apri comunque</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Impostazioni</value>
|
||||
</data>
|
||||
|
||||
@@ -670,9 +670,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>このリンクの種類は現在サポートされていません:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>キャンセル</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>このリンクは安全でない可能性があります。ハイパーリンクは、コンピューターやデータに問題を起こす可能性があります。コンピューターを保護するには、信頼できるソースからのリンクのみをクリックしてください。</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>開く</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>이 링크 형식은 현재 지원되지 않습니다.</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>취소</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>이 링크로 인해 안전하지 않은 위치가 발생할 수 있습니다. 하이퍼링크는 컴퓨터와 데이터를 손상시킬 수 있습니다. 컴퓨터를 보호하려면 신뢰할 수 있는 원본의 링크만 클릭하세요.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>열기</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>설정</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Não há suporte para este tipo de link no momento:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancelar</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Este link pode levar a um local não seguro. Hiperlinks podem ser prejudiciais ao computador e aos dados. Para proteger o computador, clique somente em links de fontes confiáveis.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Abrir mesmo assim</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Configurações</value>
|
||||
</data>
|
||||
|
||||
@@ -669,8 +669,14 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Ťђïś łϊηќ ŧурē ιş çũґѓзⁿτľÿ ñστ şΰρρоŕŧĕđ: !!! !!! !!! !!! </value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Сąñс℮ł !</value>
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Çдπсёľ !</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Ŧђīś ℓîŋќ мαў ľêãδ τб áń úʼnšàƒé ℓоćάŧίоñ. Ĥўрзŗℓĭŋķѕ çâⁿ ъέ ђąřмƒúļ τό ўôця ċómφύŧèґ аňδ ðáťǻ. Ţб ρгøťėçŧ ўòύг ςömφùţĕŕ, ŏŋľỳ čℓΐςķ łίŋκѕ ƒřöм ťŗμѕŧєđ śόυяčêś. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Őρέй ǻпŷŵãγ !!!</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Śëţťĩпğś !!</value>
|
||||
|
||||
@@ -669,8 +669,14 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Ťђïś łϊηќ ŧурē ιş çũґѓзⁿτľÿ ñστ şΰρρоŕŧĕđ: !!! !!! !!! !!! </value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Сąñс℮ł !</value>
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Çдπсёľ !</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Ŧђīś ℓîŋќ мαў ľêãδ τб áń úʼnšàƒé ℓоćάŧίоñ. Ĥўрзŗℓĭŋķѕ çâⁿ ъέ ђąřмƒúļ τό ўôця ċómφύŧèґ аňδ ðáťǻ. Ţб ρгøťėçŧ ўòύг ςömφùţĕŕ, ŏŋľỳ čℓΐςķ łίŋκѕ ƒřöм ťŗμѕŧєđ śόυяčêś. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Őρέй ǻпŷŵãγ !!!</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Śëţťĩпğś !!</value>
|
||||
|
||||
@@ -669,8 +669,14 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Ťђïś łϊηќ ŧурē ιş çũґѓзⁿτľÿ ñστ şΰρρоŕŧĕđ: !!! !!! !!! !!! </value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Сąñс℮ł !</value>
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Çдπсёľ !</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Ŧђīś ℓîŋќ мαў ľêãδ τб áń úʼnšàƒé ℓоćάŧίоñ. Ĥўрзŗℓĭŋķѕ çâⁿ ъέ ђąřмƒúļ τό ўôця ċómφύŧèґ аňδ ðáťǻ. Ţб ρгøťėçŧ ўòύг ςömφùţĕŕ, ŏŋľỳ čℓΐςķ łίŋκѕ ƒřöм ťŗμѕŧєđ śόυяčêś. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Őρέй ǻпŷŵãγ !!!</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Śëţťĩпğś !!</value>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>Этот тип связи в настоящее время не поддерживается:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Отмена</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>Эта ссылка может привести к небезопасному расположению. Гиперссылки могут нанести вред компьютеру и данным. Чтобы защитить компьютер, щелкните ссылки только из надежных источников.</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>Все равно открыть</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>Параметры</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>当前不支持此链接类型:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>取消</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>此链接可能会导致不安全的位置。超链接可能对你的计算机和数据有害。若要保护你的计算机,请仅单击来自受信任源的链接。</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>仍然打开</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
|
||||
@@ -669,9 +669,15 @@
|
||||
<data name="UnsupportedSchemeText" xml:space="preserve">
|
||||
<value>目前不支援此連結類型:</value>
|
||||
</data>
|
||||
<data name="CouldNotOpenUriDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<data name="UriErrorDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>取消</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmText" xml:space="preserve">
|
||||
<value>此連結可能通往不安全地點。超連結可能對你的電腦和資料造成傷害。為了保護你的電腦,只點擊來自可信來源的連結。</value>
|
||||
</data>
|
||||
<data name="UnsafeUrlConfirmAllowAction" xml:space="preserve">
|
||||
<value>一律開啟</value>
|
||||
</data>
|
||||
<data name="SettingsTab" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
|
||||
@@ -3085,18 +3085,42 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
void TerminalPage::_OpenHyperlinkHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs)
|
||||
safe_void_coroutine TerminalPage::_OpenHyperlinkHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto parsed = winrt::Windows::Foundation::Uri(eventArgs.Uri());
|
||||
auto uriString{ eventArgs.Uri() };
|
||||
auto parsed = winrt::Windows::Foundation::Uri(uriString);
|
||||
if (_IsUriSupported(parsed))
|
||||
{
|
||||
ShellExecute(nullptr, L"open", eventArgs.Uri().c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
bool shouldLaunch{ _IsUriConsideredSomewhatSafe(parsed) };
|
||||
|
||||
if (!shouldLaunch)
|
||||
{
|
||||
if (auto presenter{ _dialogPresenter.get() })
|
||||
{
|
||||
// FindName needs to be called first to actually load the xaml object
|
||||
auto unopenedUriDialog = FindName(L"UriErrorDialog").try_as<WUX::Controls::ContentDialog>();
|
||||
|
||||
// Insert the reason and the URI
|
||||
unopenedUriDialog.SecondaryButtonText(RS_(L"UnsafeUrlConfirmAllowAction"));
|
||||
CouldNotOpenUriReason().Text(RS_(L"UnsafeUrlConfirmText"));
|
||||
UnopenedUri().Text(uriString);
|
||||
|
||||
// Show the dialog
|
||||
auto result = co_await presenter.ShowDialog(unopenedUriDialog);
|
||||
shouldLaunch = result == ContentDialogResult::Secondary;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldLaunch)
|
||||
{
|
||||
ShellExecuteW(nullptr, L"open", uriString.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ShowCouldNotOpenDialog(RS_(L"UnsupportedSchemeText"), eventArgs.Uri());
|
||||
_ShowCouldNotOpenDialog(RS_(L"UnsupportedSchemeText"), uriString);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@@ -3116,9 +3140,10 @@ namespace winrt::TerminalApp::implementation
|
||||
if (auto presenter{ _dialogPresenter.get() })
|
||||
{
|
||||
// FindName needs to be called first to actually load the xaml object
|
||||
auto unopenedUriDialog = FindName(L"CouldNotOpenUriDialog").try_as<WUX::Controls::ContentDialog>();
|
||||
auto unopenedUriDialog = FindName(L"UriErrorDialog").try_as<WUX::Controls::ContentDialog>();
|
||||
|
||||
// Insert the reason and the URI
|
||||
unopenedUriDialog.SecondaryButtonText({});
|
||||
CouldNotOpenUriReason().Text(reason);
|
||||
UnopenedUri().Text(uri);
|
||||
|
||||
@@ -3171,6 +3196,30 @@ namespace winrt::TerminalApp::implementation
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TerminalPage::_IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri)
|
||||
{
|
||||
if (parsedUri.SchemeName() == L"http" || parsedUri.SchemeName() == L"https")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (parsedUri.SchemeName() == L"file")
|
||||
{
|
||||
static const auto pathext{ wil::TryGetEnvironmentVariableW<std::wstring>(L"PATHEXT") };
|
||||
const auto filename = parsedUri.Path();
|
||||
for (const auto& e : til::split_iterator{ std::wstring_view{ pathext }, L';' })
|
||||
{
|
||||
if (til::ends_with_insensitive_ascii(filename, e))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Important! Don't take this eventArgs by reference, we need to extend the
|
||||
// lifetime of it to the other side of the co_await!
|
||||
safe_void_coroutine TerminalPage::_ControlNoticeRaisedHandler(const IInspectable /*sender*/,
|
||||
|
||||
@@ -422,8 +422,9 @@ namespace winrt::TerminalApp::implementation
|
||||
safe_void_coroutine _PasteFromClipboardHandler(const IInspectable sender,
|
||||
const Microsoft::Terminal::Control::PasteFromClipboardEventArgs eventArgs);
|
||||
|
||||
void _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs);
|
||||
bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
|
||||
safe_void_coroutine _OpenHyperlinkHandler(const IInspectable sender, const Microsoft::Terminal::Control::OpenHyperlinkEventArgs eventArgs);
|
||||
static bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
|
||||
static bool _IsUriConsideredSomewhatSafe(const winrt::Windows::Foundation::Uri& parsedUri);
|
||||
|
||||
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
|
||||
bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats);
|
||||
|
||||
@@ -144,17 +144,17 @@
|
||||
</TextBlock>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog x:Name="CouldNotOpenUriDialog"
|
||||
x:Uid="CouldNotOpenUriDialog"
|
||||
<ContentDialog x:Name="UriErrorDialog"
|
||||
x:Uid="UriErrorDialog"
|
||||
Grid.Row="2"
|
||||
x:Load="False"
|
||||
DefaultButton="Primary">
|
||||
DefaultButton="Close">
|
||||
<TextBlock IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<TextBlock.ContextFlyout>
|
||||
<mtu:TextMenuFlyout />
|
||||
</TextBlock.ContextFlyout>
|
||||
<Run x:Name="CouldNotOpenUriReason" /> <LineBreak />
|
||||
<Run x:Name="CouldNotOpenUriReason" /> <LineBreak /> <LineBreak />
|
||||
<Run x:Name="UnopenedUri"
|
||||
FontFamily="Cascadia Mono" />
|
||||
</TextBlock>
|
||||
|
||||
@@ -1935,8 +1935,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - args: event data
|
||||
void TermControl::_TappedHandler(const IInspectable& /*sender*/, const TappedRoutedEventArgs& e)
|
||||
{
|
||||
Focus(FocusState::Pointer);
|
||||
|
||||
if (e.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch)
|
||||
{
|
||||
// Normally TSF would be responsible for showing the touch keyboard, but it's buggy for us:
|
||||
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>Tippen, um Symbole zu filtern</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Trennzeichen per Drag &amp; Drop</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Dieser Text wird zwischen den Pfaden mehrerer in das Terminal gezogener Dateien eingefügt.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2610,19 +2610,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>Escriba para filtrar iconos</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Delimitador de arrastrar y colocar</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Este texto se insertará entre las rutas de acceso de varios archivos colocados en el terminal.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C :\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C :\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>Taper pour filtrer les icônes</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Glisser-déplacer le délimiteur</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Ce texte sera inséré entre les chemins d’accès de plusieurs fichiers déposés dans le terminal.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>Digita per filtrare icone</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Trascina e rilascia il delimitatore</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Questo testo verrà inserito tra i percorsi di più file trascinati nel terminale.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>入力してアイコンをフィルター処理します</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>ドラッグ アンド ドロップ区切り記号</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>このテキストは、ターミナルにドロップされた複数のファイルのパスの間に挿入されます。</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL(C:\ -> /mnt/c)</value>
|
||||
<value>WSL(C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin(C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin(C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2(C:\ -> /c)</value>
|
||||
<value>MSYS2(C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW(C:\ -> C:/)</value>
|
||||
<value>MinGW(C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>입력하여 아이콘 필터링</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>끌어서 놓기 구분 기호</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>이 텍스트는 터미널에 놓인 여러 파일의 경로 사이에 삽입됩니다.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>Digite para filtrar ícones</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Delimitador de Arrastar e soltar</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Este texto será inserido entre os caminhos de vários arquivos descartados no terminal.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2610,19 +2610,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c) !!! !!!</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c) !!! !!!</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c) !!! !!! !!</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c) !!! !!! !!</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c) !!! !!</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c) !!! !!</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/) !!! !!</value>
|
||||
<value>MinGW (C:\ 🡒 C:/) !!! !!</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2748,4 +2748,12 @@
|
||||
<value>Тÿρě ţθ ƒíŀŧēŗ īçōйš !!! !!!</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Đґâġ ąńð δŗορ ďèŀιмïţ℮я !!! !!! </value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Тĥїś ťэхť ẃĭŀł вё îⁿŝέŗŧеď вēťщ℮ěπ τĥę ρªτħѕ óƒ мџĺţīрℓé ƒĭļèś đяǿρрεδ ιйţθ ţħê ţèřмĭлªŀ. !!! !!! !!! !!! !!! !!! !!! !!! !!!</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2610,19 +2610,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c) !!! !!!</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c) !!! !!!</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c) !!! !!! !!</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c) !!! !!! !!</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c) !!! !!</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c) !!! !!</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/) !!! !!</value>
|
||||
<value>MinGW (C:\ 🡒 C:/) !!! !!</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2748,4 +2748,12 @@
|
||||
<value>Тÿρě ţθ ƒíŀŧēŗ īçōйš !!! !!!</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Đґâġ ąńð δŗορ ďèŀιмïţ℮я !!! !!! </value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Тĥїś ťэхť ẃĭŀł вё îⁿŝέŗŧеď вēťщ℮ěπ τĥę ρªτħѕ óƒ мџĺţīрℓé ƒĭļèś đяǿρрεδ ιйţθ ţħê ţèřмĭлªŀ. !!! !!! !!! !!! !!! !!! !!! !!! !!!</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2610,19 +2610,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c) !!! !!!</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c) !!! !!!</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c) !!! !!! !!</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c) !!! !!! !!</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c) !!! !!</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c) !!! !!</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/) !!! !!</value>
|
||||
<value>MinGW (C:\ 🡒 C:/) !!! !!</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2748,4 +2748,12 @@
|
||||
<value>Тÿρě ţθ ƒíŀŧēŗ īçōйš !!! !!!</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Đґâġ ąńð δŗορ ďèŀιмïţ℮я !!! !!! </value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Тĥїś ťэхť ẃĭŀł вё îⁿŝέŗŧеď вēťщ℮ěπ τĥę ρªτħѕ óƒ мџĺţīрℓé ƒĭļèś đяǿρрεδ ιйţθ ţħê ţèřмĭлªŀ. !!! !!! !!! !!! !!! !!! !!! !!! !!!</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>Введите текст для фильтрации значков</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>Перетащите разделитель</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>Этот текст будет вставлен между путями нескольких файлов, перетащенных в терминал.</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>键入以筛选图标</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>拖放分隔符</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>此文本将在放置到终端的多个文件的路径之间插入。</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2606,19 +2606,19 @@
|
||||
<comment>An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleWsl.Content" xml:space="preserve">
|
||||
<value>WSL (C:\ -> /mnt/c)</value>
|
||||
<value>WSL (C:\ 🡒 /mnt/c)</value>
|
||||
<comment>{Locked="WSL","C:\","/mnt/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleCygwin.Content" xml:space="preserve">
|
||||
<value>Cygwin (C:\ -> /cygdrive/c)</value>
|
||||
<value>Cygwin (C:\ 🡒 /cygdrive/c)</value>
|
||||
<comment>{Locked="Cygwin","C:\","/cygdrive/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMsys2.Content" xml:space="preserve">
|
||||
<value>MSYS2 (C:\ -> /c)</value>
|
||||
<value>MSYS2 (C:\ 🡒 /c)</value>
|
||||
<comment>{Locked="MSYS2","C:\","/c"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_PathTranslationStyleMinGW.Content" xml:space="preserve">
|
||||
<value>MinGW (C:\ -> C:/)</value>
|
||||
<value>MinGW (C:\ 🡒 C:/)</value>
|
||||
<comment>{Locked="MinGW","C:\","C:/"} An option to choose from for the "path translation" setting.</comment>
|
||||
</data>
|
||||
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
|
||||
@@ -2744,4 +2744,12 @@
|
||||
<value>輸入以篩選圖示</value>
|
||||
<comment>Placeholder text for a text box to filter and select an icon.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.Header" xml:space="preserve">
|
||||
<value>拖放分隔符號</value>
|
||||
<comment>Header for a control to set the delimiter used when dragging multiple files into the terminal.</comment>
|
||||
</data>
|
||||
<data name="Profile_DragDropDelimiter.HelpText" xml:space="preserve">
|
||||
<value>這些文字會插入多個丟入終端機的檔案路徑之間。</value>
|
||||
<comment>A description for what the "drag drop delimiter" setting does.</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -31,19 +31,18 @@ AppearanceConfig::AppearanceConfig(winrt::weak_ref<Model::Profile> sourceProfile
|
||||
winrt::com_ptr<AppearanceConfig> AppearanceConfig::CopyAppearance(const AppearanceConfig* source, winrt::weak_ref<Model::Profile> sourceProfile)
|
||||
{
|
||||
auto appearance{ winrt::make_self<AppearanceConfig>(std::move(sourceProfile)) };
|
||||
appearance->_Foreground = source->_Foreground;
|
||||
appearance->_Background = source->_Background;
|
||||
appearance->_SelectionBackground = source->_SelectionBackground;
|
||||
appearance->_CursorColor = source->_CursorColor;
|
||||
appearance->_Opacity = source->_Opacity;
|
||||
|
||||
appearance->_DarkColorSchemeName = source->_DarkColorSchemeName;
|
||||
appearance->_LightColorSchemeName = source->_LightColorSchemeName;
|
||||
appearance->_json = source->_json;
|
||||
|
||||
#define APPEARANCE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
appearance->_##name = source->_##name;
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_COPY)
|
||||
#undef APPEARANCE_SETTINGS_COPY
|
||||
// JSON-backed settings (Foreground, Background, SelectionBackground, CursorColor,
|
||||
// Opacity, DarkColorSchemeName, LightColorSchemeName, MTSM settings) all live in
|
||||
// _json, which is already deep-copied above.
|
||||
|
||||
// Complex/mutable settings — backing fields for resolution lifecycle.
|
||||
// _json (copied above) is the source of truth; backing fields hold resolved runtime state.
|
||||
appearance->_PixelShaderPath = source->_PixelShaderPath;
|
||||
appearance->_PixelShaderImagePath = source->_PixelShaderImagePath;
|
||||
appearance->_BackgroundImagePath = source->_BackgroundImagePath;
|
||||
|
||||
return appearance;
|
||||
}
|
||||
@@ -52,33 +51,126 @@ Json::Value AppearanceConfig::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, ForegroundKey, _Foreground);
|
||||
JsonUtils::SetValueForKey(json, BackgroundKey, _Background);
|
||||
JsonUtils::SetValueForKey(json, SelectionBackgroundKey, _SelectionBackground);
|
||||
JsonUtils::SetValueForKey(json, CursorColorKey, _CursorColor);
|
||||
JsonUtils::SetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter<float, IntAsFloatPercentConversionTrait>{});
|
||||
if (HasDarkColorSchemeName() || HasLightColorSchemeName())
|
||||
// Nullable color settings: key presence matters (explicit null is valid)
|
||||
for (const auto& key : { ForegroundKey, BackgroundKey, SelectionBackgroundKey, CursorColorKey })
|
||||
{
|
||||
// check if the setting is coming from the UI, if so grab the ColorSchemeName until the settings UI is fixed.
|
||||
if (_LightColorSchemeName != _DarkColorSchemeName)
|
||||
{
|
||||
JsonUtils::SetValueForKey(json["colorScheme"], "dark", _DarkColorSchemeName);
|
||||
JsonUtils::SetValueForKey(json["colorScheme"], "light", _LightColorSchemeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, "colorScheme", _DarkColorSchemeName);
|
||||
}
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, key);
|
||||
}
|
||||
|
||||
// Opacity: copy from _json (may be int or float — preserves original form)
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, OpacityKey);
|
||||
|
||||
// ColorScheme: ConversionTrait<ColorSchemeReference> handles string<->object form.
|
||||
// _json already has the correct serialized form from SetValueForKey.
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, ColorSchemeKey);
|
||||
|
||||
// MTSM appearance settings: copy from _json (the source of truth)
|
||||
#define APPEARANCE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_TO_JSON)
|
||||
#undef APPEARANCE_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings — read from _json (source of truth), not backing fields
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "experimental.pixelShaderPath");
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "experimental.pixelShaderImagePath");
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "backgroundImage");
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool AppearanceConfig::HasSetting(AppearanceSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _APPEARANCE_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_APPEARANCE_SETTINGS(_APPEARANCE_HAS_SETTING)
|
||||
#undef _APPEARANCE_HAS_SETTING
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
return HasForeground();
|
||||
case AppearanceSettingKey::_Background:
|
||||
return HasBackground();
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
return HasSelectionBackground();
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
return HasCursorColor();
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
return HasOpacity();
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
return HasDarkColorSchemeName();
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
return HasLightColorSchemeName();
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
return HasPixelShaderPath();
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
return HasPixelShaderImagePath();
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
return HasBackgroundImagePath();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AppearanceConfig::ClearSetting(AppearanceSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _APPEARANCE_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_APPEARANCE_SETTINGS(_APPEARANCE_CLEAR_SETTING)
|
||||
#undef _APPEARANCE_CLEAR_SETTING
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
ClearForeground();
|
||||
break;
|
||||
case AppearanceSettingKey::_Background:
|
||||
ClearBackground();
|
||||
break;
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
ClearSelectionBackground();
|
||||
break;
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
ClearCursorColor();
|
||||
break;
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
ClearOpacity();
|
||||
break;
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
ClearDarkColorSchemeName();
|
||||
break;
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
ClearLightColorSchemeName();
|
||||
break;
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
ClearPixelShaderPath();
|
||||
break;
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
ClearPixelShaderImagePath();
|
||||
break;
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
ClearBackgroundImagePath();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AppearanceSettingKey> AppearanceConfig::CurrentSettings() const
|
||||
{
|
||||
std::vector<AppearanceSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(AppearanceSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<AppearanceSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Layer values from the given json object on top of the existing properties
|
||||
// of this object. For any keys we're expecting to be able to parse in the
|
||||
@@ -92,45 +184,50 @@ Json::Value AppearanceConfig::ToJson() const
|
||||
// - json: an object which should be a partial serialization of an AppearanceConfig object.
|
||||
void AppearanceConfig::LayerJson(const Json::Value& json)
|
||||
{
|
||||
JsonUtils::GetValueForKey(json, ForegroundKey, _Foreground);
|
||||
_logSettingIfSet(ForegroundKey, _Foreground.has_value());
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
// AppearanceConfig receives the full profile JSON; we store all keys and
|
||||
// read only appearance-relevant ones from it.
|
||||
JsonUtils::MergeJsonKeys(json, _json);
|
||||
|
||||
JsonUtils::GetValueForKey(json, BackgroundKey, _Background);
|
||||
_logSettingIfSet(BackgroundKey, _Background.has_value());
|
||||
// Nullable color settings are now JSON-backed. Log which were set.
|
||||
_logSettingIfSet(ForegroundKey, HasForeground());
|
||||
_logSettingIfSet(BackgroundKey, HasBackground());
|
||||
_logSettingIfSet(SelectionBackgroundKey, HasSelectionBackground());
|
||||
_logSettingIfSet(CursorColorKey, HasCursorColor());
|
||||
|
||||
JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _SelectionBackground);
|
||||
_logSettingIfSet(SelectionBackgroundKey, _SelectionBackground.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, CursorColorKey, _CursorColor);
|
||||
_logSettingIfSet(CursorColorKey, _CursorColor.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyAcrylicTransparencyKey, _Opacity);
|
||||
JsonUtils::GetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter<float, IntAsFloatPercentConversionTrait>{});
|
||||
_logSettingIfSet(OpacityKey, _Opacity.has_value());
|
||||
|
||||
if (json["colorScheme"].isString())
|
||||
// Normalize legacy opacity key into canonical _json key
|
||||
if (json.isMember(JsonKey(LegacyAcrylicTransparencyKey)))
|
||||
{
|
||||
// to make the UI happy, set ColorSchemeName.
|
||||
JsonUtils::GetValueForKey(json, ColorSchemeKey, _DarkColorSchemeName);
|
||||
_LightColorSchemeName = _DarkColorSchemeName;
|
||||
_logSettingSet(ColorSchemeKey);
|
||||
_json[JsonKey(OpacityKey)] = json[JsonKey(LegacyAcrylicTransparencyKey)];
|
||||
}
|
||||
else if (json["colorScheme"].isObject())
|
||||
// Normalize integer percent to float (e.g. 50 → 0.5)
|
||||
if (_json.isMember(JsonKey(OpacityKey)) && _json[JsonKey(OpacityKey)].isInt())
|
||||
{
|
||||
// to make the UI happy, set ColorSchemeName to whatever the dark value is.
|
||||
JsonUtils::GetValueForKey(json["colorScheme"], "dark", _DarkColorSchemeName);
|
||||
JsonUtils::GetValueForKey(json["colorScheme"], "light", _LightColorSchemeName);
|
||||
|
||||
_logSettingSet("colorScheme.dark");
|
||||
_logSettingSet("colorScheme.light");
|
||||
_json[JsonKey(OpacityKey)] = _json[JsonKey(OpacityKey)].asInt() / 100.0f;
|
||||
}
|
||||
_logSettingIfSet(OpacityKey, HasOpacity());
|
||||
|
||||
// ColorScheme: ConversionTrait<ColorSchemeReference> handles string↔object normalization.
|
||||
// The raw JSON is stored in _json; the trait interprets it on read.
|
||||
_logSettingIfSet(ColorSchemeKey, HasColorSchemeRef());
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define APPEARANCE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_LAYER_JSON)
|
||||
#undef APPEARANCE_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings — backing fields populated from _json for resolution lifecycle.
|
||||
// _json is the source of truth for serialization; backing fields are for resolution lifecycle.
|
||||
JsonUtils::GetValueForKey(json, "experimental.pixelShaderPath", _PixelShaderPath);
|
||||
_logSettingIfSet("experimental.pixelShaderPath", _PixelShaderPath.has_value());
|
||||
JsonUtils::GetValueForKey(json, "experimental.pixelShaderImagePath", _PixelShaderImagePath);
|
||||
_logSettingIfSet("experimental.pixelShaderImagePath", _PixelShaderImagePath.has_value());
|
||||
JsonUtils::GetValueForKey(json, "backgroundImage", _BackgroundImagePath);
|
||||
_logSettingIfSet("backgroundImage", _BackgroundImagePath.has_value());
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile AppearanceConfig::SourceProfile()
|
||||
|
||||
@@ -18,11 +18,80 @@ Author(s):
|
||||
|
||||
#include "AppearanceConfig.g.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
#include <DefaultSettings.h>
|
||||
|
||||
// ColorSchemeReference: a pair of color scheme names for dark and light themes.
|
||||
// Represents the polymorphic "colorScheme" JSON key, which can be either:
|
||||
// "colorScheme": "Campbell" → { dark: "Campbell", light: "Campbell" }
|
||||
// "colorScheme": { "dark": "X", "light": "Y" } → { dark: "X", light: "Y" }
|
||||
struct ColorSchemeReference
|
||||
{
|
||||
winrt::hstring dark{ L"Campbell" };
|
||||
winrt::hstring light{ L"Campbell" };
|
||||
|
||||
static ColorSchemeReference Default() { return {}; }
|
||||
bool operator==(const ColorSchemeReference&) const = default;
|
||||
};
|
||||
|
||||
namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
{
|
||||
template<>
|
||||
struct ConversionTrait<ColorSchemeReference>
|
||||
{
|
||||
ColorSchemeReference FromJson(const Json::Value& json)
|
||||
{
|
||||
ColorSchemeReference result;
|
||||
if (json.isString())
|
||||
{
|
||||
// Simple form: both dark and light use the same scheme
|
||||
const auto name = winrt::hstring{ til::u8u16(json.asString()) };
|
||||
result.dark = name;
|
||||
result.light = name;
|
||||
}
|
||||
else if (json.isObject())
|
||||
{
|
||||
// Structured form: { "dark": "...", "light": "..." }
|
||||
if (json.isMember("dark"))
|
||||
{
|
||||
result.dark = winrt::hstring{ til::u8u16(json["dark"].asString()) };
|
||||
}
|
||||
if (json.isMember("light"))
|
||||
{
|
||||
result.light = winrt::hstring{ til::u8u16(json["light"].asString()) };
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json) const
|
||||
{
|
||||
return json.isString() || json.isObject();
|
||||
}
|
||||
|
||||
Json::Value ToJson(const ColorSchemeReference& val)
|
||||
{
|
||||
// Collapse to string when dark == light
|
||||
if (val.dark == val.light)
|
||||
{
|
||||
return til::u16u8(val.dark);
|
||||
}
|
||||
Json::Value obj{ Json::ValueType::objectValue };
|
||||
obj["dark"] = til::u16u8(val.dark);
|
||||
obj["light"] = til::u16u8(val.light);
|
||||
return obj;
|
||||
}
|
||||
|
||||
std::string TypeDescription() const
|
||||
{
|
||||
return "color scheme name (string or {dark, light})";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct AppearanceConfig : AppearanceConfigT<AppearanceConfig, IMediaResourceContainer>, IInheritable<AppearanceConfig>
|
||||
@@ -38,22 +107,65 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void ResolveMediaResources(const Model::MediaResourceResolver& resolver);
|
||||
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Foreground, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Background, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, SelectionBackground, nullptr);
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, CursorColor, nullptr);
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, float, Opacity, 1.0f);
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(AppearanceSettingKey key) const;
|
||||
void ClearSetting(AppearanceSettingKey key);
|
||||
std::vector<AppearanceSettingKey> CurrentSettings() const;
|
||||
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, DarkColorSchemeName, L"Campbell");
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, hstring, LightColorSchemeName, L"Campbell");
|
||||
// Nullable color settings (JSON-backed)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Foreground, "foreground", nullptr)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, Background, "background", nullptr)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, SelectionBackground, "selectionBackground", nullptr)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::IAppearanceConfig, Microsoft::Terminal::Core::Color, CursorColor, "cursorColor", nullptr)
|
||||
|
||||
// Opacity: JSON-backed with normalization (int percent → float in LayerJson)
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, float, Opacity, "opacity", 1.0f)
|
||||
|
||||
// ColorScheme: JSON-backed via INHERITABLE_SETTING with ColorSchemeReference struct.
|
||||
// The polymorphic JSON key ("colorScheme" as string or object) is handled by
|
||||
// ConversionTrait<ColorSchemeReference>. The macro provides Has/Clear/OverrideSource.
|
||||
// Thin wrappers expose the IDL-required DarkColorSchemeName / LightColorSchemeName.
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, ColorSchemeReference, ColorSchemeRef, "colorScheme", ColorSchemeReference::Default())
|
||||
public:
|
||||
hstring DarkColorSchemeName() const { return ColorSchemeRef().dark; }
|
||||
void DarkColorSchemeName(const hstring& value)
|
||||
{
|
||||
auto ref = ColorSchemeRef();
|
||||
ref.dark = value;
|
||||
ColorSchemeRef(ref);
|
||||
}
|
||||
bool HasDarkColorSchemeName() const { return HasColorSchemeRef(); }
|
||||
void ClearDarkColorSchemeName() { ClearColorSchemeRef(); }
|
||||
Model::IAppearanceConfig DarkColorSchemeNameOverrideSource() { return ColorSchemeRefOverrideSource(); }
|
||||
|
||||
hstring LightColorSchemeName() const { return ColorSchemeRef().light; }
|
||||
void LightColorSchemeName(const hstring& value)
|
||||
{
|
||||
auto ref = ColorSchemeRef();
|
||||
ref.light = value;
|
||||
ColorSchemeRef(ref);
|
||||
}
|
||||
bool HasLightColorSchemeName() const { return HasColorSchemeRef(); }
|
||||
void ClearLightColorSchemeName() { ClearColorSchemeRef(); }
|
||||
Model::IAppearanceConfig LightColorSchemeNameOverrideSource() { return ColorSchemeRefOverrideSource(); }
|
||||
|
||||
#define APPEARANCE_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, type, name, ##__VA_ARGS__)
|
||||
INHERITABLE_SETTING(Model::IAppearanceConfig, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_APPEARANCE_SETTINGS(APPEARANCE_SETTINGS_INITIALIZE)
|
||||
#undef APPEARANCE_SETTINGS_INITIALIZE
|
||||
|
||||
// IMediaResource settings with backing fields for resolution lifecycle.
|
||||
// See IInheritable.h for the INHERITABLE_MEDIA_RESOURCE_SETTING macro definition.
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::IAppearanceConfig, PixelShaderPath, "experimental.pixelShaderPath", implementation::MediaResource::Empty())
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::IAppearanceConfig, PixelShaderImagePath, "experimental.pixelShaderImagePath", implementation::MediaResource::Empty())
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::IAppearanceConfig, BackgroundImagePath, "backgroundImage", implementation::MediaResource::Empty())
|
||||
|
||||
private:
|
||||
winrt::weak_ref<Profile> _sourceProfile;
|
||||
|
||||
// Raw JSON for this layer (appearance-relevant keys only).
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
|
||||
@@ -12,6 +12,8 @@ using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view FontInfoKey{ "font" };
|
||||
static constexpr std::string_view FontAxesKey{ "axes" };
|
||||
static constexpr std::string_view FontFeaturesKey{ "features" };
|
||||
static constexpr std::string_view LegacyFontFaceKey{ "fontFace" };
|
||||
static constexpr std::string_view LegacyFontSizeKey{ "fontSize" };
|
||||
static constexpr std::string_view LegacyFontWeightKey{ "fontWeight" };
|
||||
@@ -25,29 +27,11 @@ winrt::com_ptr<FontConfig> FontConfig::CopyFontInfo(const FontConfig* source, wi
|
||||
{
|
||||
auto fontInfo{ winrt::make_self<FontConfig>(std::move(sourceProfile)) };
|
||||
|
||||
#define FONT_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
fontInfo->_##name = source->_##name;
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_COPY)
|
||||
#undef FONT_SETTINGS_COPY
|
||||
fontInfo->_json = source->_json;
|
||||
|
||||
// We cannot simply copy the font axes and features with `fontInfo->_FontAxes = source->_FontAxes;`
|
||||
// since that'll just create a reference; we have to manually copy the values.
|
||||
static constexpr auto cloneFontMap = [](const IFontFeatureMap& map) {
|
||||
std::map<winrt::hstring, float> fontAxes;
|
||||
for (const auto& [k, v] : map)
|
||||
{
|
||||
fontAxes.emplace(k, v);
|
||||
}
|
||||
return winrt::single_threaded_map(std::move(fontAxes));
|
||||
};
|
||||
if (source->_FontAxes)
|
||||
{
|
||||
fontInfo->_FontAxes = cloneFontMap(*source->_FontAxes);
|
||||
}
|
||||
if (source->_FontFeatures)
|
||||
{
|
||||
fontInfo->_FontFeatures = cloneFontMap(*source->_FontFeatures);
|
||||
}
|
||||
// FontAxes and FontFeatures are now JSON-backed — handled by _json copy above.
|
||||
// No manual deep-copy needed; the JSON-backed getter returns a fresh deserialized
|
||||
// collection each time.
|
||||
|
||||
return fontInfo;
|
||||
}
|
||||
@@ -56,14 +40,58 @@ Json::Value FontConfig::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
// MTSM font settings: copy from _json (the source of truth)
|
||||
#define FONT_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_TO_JSON)
|
||||
#undef FONT_SETTINGS_TO_JSON
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool FontConfig::HasSetting(FontSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _FONT_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_FONT_SETTINGS(_FONT_HAS_SETTING)
|
||||
#undef _FONT_HAS_SETTING
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FontConfig::ClearSetting(FontSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _FONT_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_FONT_SETTINGS(_FONT_CLEAR_SETTING)
|
||||
#undef _FONT_CLEAR_SETTING
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<FontSettingKey> FontConfig::CurrentSettings() const
|
||||
{
|
||||
std::vector<FontSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(FontSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<FontSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Layer values from the given json object on top of the existing properties
|
||||
// of this object. For any keys we're expecting to be able to parse in the
|
||||
@@ -83,25 +111,38 @@ void FontConfig::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// A font object is defined, use that
|
||||
const auto fontInfoJson = json[JsonKey(FontInfoKey)];
|
||||
#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(fontInfoJson, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
|
||||
// Merge the font sub-object into stored _json (font-object shape).
|
||||
JsonUtils::MergeJsonKeys(fontInfoJson, _json);
|
||||
|
||||
// MTSM font settings are now JSON-backed (including FontAxes/FontFeatures).
|
||||
// Values are already in _json. We only need to log which settings were set.
|
||||
#define FONT_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
_logSettingIfSet(jsonKey, fontInfoJson.isMember(jsonKey) && !fontInfoJson[jsonKey].isNull());
|
||||
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_LAYER_JSON)
|
||||
#undef FONT_SETTINGS_LAYER_JSON
|
||||
}
|
||||
else
|
||||
{
|
||||
// No font object is defined
|
||||
// No font object is defined — normalize legacy flat keys into font-object shape.
|
||||
if (json.isMember(JsonKey(LegacyFontFaceKey)))
|
||||
{
|
||||
_json["face"] = json[JsonKey(LegacyFontFaceKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyFontSizeKey)))
|
||||
{
|
||||
_json["size"] = json[JsonKey(LegacyFontSizeKey)];
|
||||
}
|
||||
if (json.isMember(JsonKey(LegacyFontWeightKey)))
|
||||
{
|
||||
_json["weight"] = json[JsonKey(LegacyFontWeightKey)];
|
||||
}
|
||||
|
||||
// Log settings as if they were a part of the font object
|
||||
JsonUtils::GetValueForKey(json, LegacyFontFaceKey, _FontFace);
|
||||
_logSettingIfSet("face", _FontFace.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyFontSizeKey, _FontSize);
|
||||
_logSettingIfSet("size", _FontSize.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, LegacyFontWeightKey, _FontWeight);
|
||||
_logSettingIfSet("weight", _FontWeight.has_value());
|
||||
_logSettingIfSet("face", json.isMember(JsonKey(LegacyFontFaceKey)));
|
||||
_logSettingIfSet("size", json.isMember(JsonKey(LegacyFontSizeKey)));
|
||||
_logSettingIfSet("weight", json.isMember(JsonKey(LegacyFontWeightKey)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +153,16 @@ winrt::Microsoft::Terminal::Settings::Model::Profile FontConfig::SourceProfile()
|
||||
|
||||
void FontConfig::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "axes" && _FontAxes.has_value())
|
||||
if (setting == FontAxesKey && HasFontAxes())
|
||||
{
|
||||
for (const auto& [mapKey, _] : _FontAxes.value())
|
||||
for (const auto& [mapKey, _] : FontAxes())
|
||||
{
|
||||
_changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey)));
|
||||
}
|
||||
}
|
||||
else if (setting == "features" && _FontFeatures.has_value())
|
||||
else if (setting == FontFeaturesKey && HasFontFeatures())
|
||||
{
|
||||
for (const auto& [mapKey, _] : _FontFeatures.value())
|
||||
for (const auto& [mapKey, _] : FontFeatures())
|
||||
{
|
||||
_changeLog.emplace(fmt::format(FMT_COMPILE("{}.{}"), setting, til::u16u8(mapKey)));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ Author(s):
|
||||
#include "pch.h"
|
||||
#include "FontConfig.g.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "IInheritable.h"
|
||||
#include <DefaultSettings.h>
|
||||
@@ -39,13 +40,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
Model::Profile SourceProfile();
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(FontSettingKey key) const;
|
||||
void ClearSetting(FontSettingKey key);
|
||||
std::vector<FontSettingKey> CurrentSettings() const;
|
||||
|
||||
#define FONT_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING(Model::FontConfig, type, name, ##__VA_ARGS__)
|
||||
INHERITABLE_SETTING(Model::FontConfig, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_FONT_SETTINGS(FONT_SETTINGS_INITIALIZE)
|
||||
#undef FONT_SETTINGS_INITIALIZE
|
||||
|
||||
private:
|
||||
winrt::weak_ref<Profile> _sourceProfile;
|
||||
|
||||
// Raw JSON for this layer (font sub-object shape).
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
|
||||
@@ -59,16 +59,13 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
{
|
||||
auto globals{ winrt::make_self<GlobalAppSettings>() };
|
||||
|
||||
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
|
||||
|
||||
globals->_defaultProfile = _defaultProfile;
|
||||
globals->_actionMap = _actionMap->Copy();
|
||||
globals->_keybindingsWarnings = _keybindingsWarnings;
|
||||
globals->_json = _json;
|
||||
|
||||
#define GLOBAL_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
globals->_##name = _##name;
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_COPY)
|
||||
#undef GLOBAL_SETTINGS_COPY
|
||||
// JSON-backed settings (UnparsedDefaultProfile, MTSM settings) all live in _json,
|
||||
// which is already deep-copied above. No per-setting copy needed.
|
||||
|
||||
if (_colorSchemes)
|
||||
{
|
||||
@@ -86,6 +83,9 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_themes.Insert(kv.Key(), *themeImpl->Copy());
|
||||
}
|
||||
}
|
||||
|
||||
// Complex/mutable settings — backing fields for mutation lifecycle.
|
||||
// _json (copied above) is the source of truth; backing fields hold runtime state.
|
||||
if (_NewTabMenu)
|
||||
{
|
||||
globals->_NewTabMenu = winrt::single_threaded_vector<Model::NewTabMenuEntry>();
|
||||
@@ -94,14 +94,6 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_NewTabMenu->Append(get_self<NewTabMenuEntry>(entry)->Copy());
|
||||
}
|
||||
}
|
||||
if (_DisabledProfileSources)
|
||||
{
|
||||
globals->_DisabledProfileSources = winrt::single_threaded_vector<hstring>();
|
||||
for (const auto& src : *_DisabledProfileSources)
|
||||
{
|
||||
globals->_DisabledProfileSources->Append(src);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
@@ -120,7 +112,7 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
|
||||
void GlobalAppSettings::DefaultProfile(const winrt::guid& defaultProfile) noexcept
|
||||
{
|
||||
_defaultProfile = defaultProfile;
|
||||
_UnparsedDefaultProfile = Utils::GuidToString(defaultProfile);
|
||||
UnparsedDefaultProfile(hstring{ Utils::GuidToString(defaultProfile) });
|
||||
}
|
||||
|
||||
winrt::guid GlobalAppSettings::DefaultProfile() const
|
||||
@@ -150,25 +142,46 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::FromJson(const Json::Value&
|
||||
|
||||
void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origin)
|
||||
{
|
||||
JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
JsonUtils::MergeJsonKeys(json, _json);
|
||||
|
||||
// UnparsedDefaultProfile is now JSON-backed (in _json["defaultProfile"]).
|
||||
// No backing field to populate.
|
||||
|
||||
// GH#8076 - when adding enum values to this key, we also changed it from
|
||||
// "useTabSwitcher" to "tabSwitcherMode". Continue supporting
|
||||
// "useTabSwitcher", but prefer "tabSwitcherMode"
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyUseTabSwitcherModeKey, _TabSwitcherMode) || _fixupsAppliedDuringLoad;
|
||||
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyInputServiceWarningKey, _InputServiceWarning) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutLargePasteKey, _WarnAboutLargePaste) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste) || _fixupsAppliedDuringLoad;
|
||||
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyConfirmCloseAllTabsKey, _ConfirmCloseAllTabs) || _fixupsAppliedDuringLoad;
|
||||
// Normalize legacy keys into canonical _json keys for JSON-backed getters.
|
||||
static constexpr std::pair<std::string_view, std::string_view> legacyKeyMappings[] = {
|
||||
{ LegacyUseTabSwitcherModeKey, "tabSwitcherMode" },
|
||||
{ LegacyInputServiceWarningKey, "warning.inputService" },
|
||||
{ LegacyWarnAboutLargePasteKey, "warning.largePaste" },
|
||||
{ LegacyWarnAboutMultiLinePasteKey, "warning.multiLinePaste" },
|
||||
{ LegacyConfirmCloseAllTabsKey, "warning.confirmCloseAllTabs" },
|
||||
};
|
||||
for (const auto& [legacyKey, canonicalKey] : legacyKeyMappings)
|
||||
{
|
||||
if (json.isMember(JsonKey(legacyKey)))
|
||||
{
|
||||
_fixupsAppliedDuringLoad = true;
|
||||
_json[JsonKey(canonicalKey)] = json[JsonKey(legacyKey)];
|
||||
}
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define GLOBAL_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_LAYER_JSON)
|
||||
#undef GLOBAL_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings — backing fields populated from _json for runtime use.
|
||||
// _json is the source of truth for serialization; backing fields are for mutation lifecycle.
|
||||
JsonUtils::GetValueForKey(json, "newTabMenu", _NewTabMenu);
|
||||
_logSettingIfSet("newTabMenu", _NewTabMenu.has_value());
|
||||
|
||||
// GH#11975 We only want to allow sensible values and prevent crashes, so we are clamping those values
|
||||
// We only want to assign if the value did change through clamping,
|
||||
// otherwise we could end up setting defaults that get persisted
|
||||
@@ -304,47 +317,105 @@ Json::Value GlobalAppSettings::ToJson()
|
||||
// These experimental options should be removed from the settings file if they're at their default value.
|
||||
// This prevents them from sticking around forever, even if the user was just experimenting with them.
|
||||
// One could consider this a workaround for the settings UI right now not having a "reset to default" button for these.
|
||||
if (_GraphicsAPI == Control::GraphicsAPI::Automatic)
|
||||
if (HasGraphicsAPI() && GraphicsAPI() == Control::GraphicsAPI::Automatic)
|
||||
{
|
||||
_GraphicsAPI.reset();
|
||||
ClearGraphicsAPI();
|
||||
}
|
||||
if (_TextMeasurement == Control::TextMeasurement::Graphemes)
|
||||
if (HasTextMeasurement() && TextMeasurement() == Control::TextMeasurement::Graphemes)
|
||||
{
|
||||
_TextMeasurement.reset();
|
||||
ClearTextMeasurement();
|
||||
}
|
||||
if (_AmbiguousWidth == Control::AmbiguousWidth::Narrow)
|
||||
if (HasAmbiguousWidth() && AmbiguousWidth() == Control::AmbiguousWidth::Narrow)
|
||||
{
|
||||
_AmbiguousWidth.reset();
|
||||
ClearAmbiguousWidth();
|
||||
}
|
||||
if (_DefaultInputScope == Control::DefaultInputScope::Default)
|
||||
if (HasDefaultInputScope() && DefaultInputScope() == Control::DefaultInputScope::Default)
|
||||
{
|
||||
_DefaultInputScope.reset();
|
||||
ClearDefaultInputScope();
|
||||
}
|
||||
|
||||
if (_DisablePartialInvalidation == false)
|
||||
if (HasDisablePartialInvalidation() && DisablePartialInvalidation() == false)
|
||||
{
|
||||
_DisablePartialInvalidation.reset();
|
||||
ClearDisablePartialInvalidation();
|
||||
}
|
||||
if (_SoftwareRendering == false)
|
||||
if (HasSoftwareRendering() && SoftwareRendering() == false)
|
||||
{
|
||||
_SoftwareRendering.reset();
|
||||
ClearSoftwareRendering();
|
||||
}
|
||||
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile);
|
||||
// DefaultProfile: copy from _json
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, DefaultProfileKey);
|
||||
|
||||
// MTSM global settings: copy from _json (the source of truth)
|
||||
#define GLOBAL_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_TO_JSON)
|
||||
#undef GLOBAL_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings — read from _json (source of truth), not backing fields
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "newTabMenu");
|
||||
|
||||
json[JsonKey(ActionsKey)] = _actionMap->ToJson();
|
||||
json[JsonKey(KeybindingsKey)] = _actionMap->KeyBindingsToJson();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::HasSetting(GlobalSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _GLOBAL_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_GLOBAL_SETTINGS(_GLOBAL_HAS_SETTING)
|
||||
#undef _GLOBAL_HAS_SETTING
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
return HasUnparsedDefaultProfile();
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
return HasNewTabMenu();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalAppSettings::ClearSetting(GlobalSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _GLOBAL_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_GLOBAL_SETTINGS(_GLOBAL_CLEAR_SETTING)
|
||||
#undef _GLOBAL_CLEAR_SETTING
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
ClearUnparsedDefaultProfile();
|
||||
break;
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
ClearNewTabMenu();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GlobalSettingKey> GlobalAppSettings::CurrentSettings() const
|
||||
{
|
||||
std::vector<GlobalSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(GlobalSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<GlobalSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::FixupsAppliedDuringLoad()
|
||||
{
|
||||
return _fixupsAppliedDuringLoad || _actionMap->FixupsAppliedDuringLoad();
|
||||
@@ -394,9 +465,9 @@ bool GlobalAppSettings::ShouldUsePersistedLayout() const
|
||||
void GlobalAppSettings::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
_actionMap->ResolveMediaResourcesWithBasePath(SourceBasePath, resolver);
|
||||
if (_NewTabMenu)
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
if (const auto resolvable{ entry.try_as<IPathlessMediaResourceContainer>() })
|
||||
{
|
||||
@@ -414,11 +485,12 @@ void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "theme")
|
||||
{
|
||||
if (_Theme.has_value())
|
||||
if (HasTheme())
|
||||
{
|
||||
const auto theme = Theme();
|
||||
// ThemePair always has a Dark/Light value,
|
||||
// so we need to check if they were explicitly set
|
||||
if (_Theme->DarkName() == _Theme->LightName())
|
||||
if (theme.DarkName() == theme.LightName())
|
||||
{
|
||||
_changeLog.emplace(setting);
|
||||
}
|
||||
@@ -431,9 +503,9 @@ void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
}
|
||||
else if (setting == "newTabMenu")
|
||||
{
|
||||
if (_NewTabMenu.has_value())
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
std::string entryType;
|
||||
switch (entry.Type())
|
||||
@@ -476,7 +548,7 @@ void GlobalAppSettings::UpdateCommandID(const Model::Command& cmd, winrt::hstrin
|
||||
_actionMap->UpdateCommandID(cmd, newID);
|
||||
// newID might have been empty when this function was called, if so actionMap would have generated a new ID, use that
|
||||
newID = cmd.ID();
|
||||
if (_NewTabMenu)
|
||||
if (HasNewTabMenu())
|
||||
{
|
||||
// Recursive lambda function to look through all the new tab menu entries and update IDs accordingly
|
||||
std::function<void(const Model::NewTabMenuEntry&)> recursiveEntryIdUpdate;
|
||||
@@ -503,7 +575,7 @@ void GlobalAppSettings::UpdateCommandID(const Model::Command& cmd, winrt::hstrin
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
for (const auto& entry : NewTabMenu())
|
||||
{
|
||||
recursiveEntryIdUpdate(entry);
|
||||
}
|
||||
@@ -515,8 +587,8 @@ void GlobalAppSettings::_logSettingIfSet(const std::string_view& setting, const
|
||||
if (isSet)
|
||||
{
|
||||
// Exclude some false positives from userDefaults.json
|
||||
const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && _CopyFormatting.has_value() && _CopyFormatting.value() == static_cast<Control::CopyFormat>(0);
|
||||
const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && _NewTabMenu.has_value() && _NewTabMenu->Size() == 1 && _NewTabMenu->GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles;
|
||||
const bool settingCopyFormattingToDefault = til::equals_insensitive_ascii(setting, "copyFormatting") && HasCopyFormatting() && CopyFormatting() == static_cast<Control::CopyFormat>(0);
|
||||
const bool settingNTMToDefault = til::equals_insensitive_ascii(setting, "newTabMenu") && HasNewTabMenu() && NewTabMenu().Size() == 1 && NewTabMenu().GetAt(0).Type() == NewTabMenuEntryType::RemainingProfiles;
|
||||
if (!settingCopyFormattingToDefault && !settingNTMToDefault)
|
||||
{
|
||||
_logSettingSet(setting);
|
||||
|
||||
@@ -18,6 +18,7 @@ Author(s):
|
||||
#include "GlobalAppSettings.g.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "ActionMap.h"
|
||||
#include "Command.h"
|
||||
@@ -56,6 +57,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Json::Value ToJson();
|
||||
bool FixupsAppliedDuringLoad();
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(GlobalSettingKey key) const;
|
||||
void ClearSetting(GlobalSettingKey key);
|
||||
std::vector<GlobalSettingKey> CurrentSettings() const;
|
||||
|
||||
const std::vector<SettingsLoadWarnings>& KeybindingsWarnings() const;
|
||||
|
||||
// This DefaultProfile() setter is called by CascadiaSettings,
|
||||
@@ -80,13 +86,28 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
winrt::hstring SourceBasePath;
|
||||
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, "defaultProfile", L"");
|
||||
|
||||
#define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING_WITH_LOGGING(Model::GlobalAppSettings, type, name, jsonKey, ##__VA_ARGS__)
|
||||
MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_INITIALIZE)
|
||||
#undef GLOBAL_SETTINGS_INITIALIZE
|
||||
|
||||
// NewTabMenu: mutable with backing field for editor in-place mutation.
|
||||
// _json is the source of truth for serialization. Setter dual-writes to both.
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(Model::GlobalAppSettings, std::optional<winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>>, NewTabMenu, winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }))
|
||||
public:
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> NewTabMenu() const
|
||||
{
|
||||
const auto val{ _getNewTabMenuImpl() };
|
||||
return val ? *val : winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} });
|
||||
}
|
||||
void NewTabMenu(const winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>& value)
|
||||
{
|
||||
_NewTabMenu = value;
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(_json, "newTabMenu", value);
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef NDEBUG
|
||||
static constexpr bool debugFeaturesDefault{ false };
|
||||
@@ -94,6 +115,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static constexpr bool debugFeaturesDefault{ true };
|
||||
#endif
|
||||
|
||||
// Raw JSON for this layer. Populated by LayerJson(), will become the
|
||||
// source of truth for settings once the JSON-backed refactor is complete.
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
winrt::guid _defaultProfile{};
|
||||
bool _fixupsAppliedDuringLoad{ false };
|
||||
bool _legacyReloadEnvironmentVariables{ true };
|
||||
|
||||
@@ -14,6 +14,8 @@ Author(s):
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "JsonUtils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
template<typename T>
|
||||
@@ -79,189 +81,554 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
using NullableSetting = std::optional<std::optional<T>>;
|
||||
}
|
||||
|
||||
// Shared implementation between the INHERITABLE_SETTING and INHERITABLE_NULLABLE_SETTING macros.
|
||||
// See below for more details.
|
||||
#define _BASE_INHERITABLE_SETTING(projectedType, storageType, name, ...) \
|
||||
public: \
|
||||
/* Returns true if the user explicitly set the value, false otherwise*/ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _##name.has_value(); \
|
||||
} \
|
||||
\
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
/*user set value was not set*/ \
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no source was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_##name = std::nullopt; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
storageType _##name{ std::nullopt }; \
|
||||
\
|
||||
storageType _get##name##Impl() const \
|
||||
{ \
|
||||
/*return user set value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return _##name; \
|
||||
} \
|
||||
\
|
||||
/*user set value was not set*/ \
|
||||
/*iterate through parents to find a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto val{ parent->_get##name##Impl() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*user set value was not set*/ \
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), storageType> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return { get_strong(), _##name }; \
|
||||
} \
|
||||
\
|
||||
/*user set value was not set*/ \
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_##name }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
// =============================================================================
|
||||
// Inheritable setting macros
|
||||
// =============================================================================
|
||||
//
|
||||
// These macros implement the standard functions for inheritable settings:
|
||||
// - Has<NAME>(): checks if the user explicitly set a value for this setting
|
||||
// - <NAME>OverrideSource(): returns the object that provides the resolved value
|
||||
// - Getter(): returns the resolved value (this layer --> parent --> default)
|
||||
// - Setter(): sets the value
|
||||
// - Clear<NAME>(): clears the user set value (reverts to inherited/default)
|
||||
//
|
||||
// There are two storage strategies:
|
||||
//
|
||||
// 1. JSON-backed (preferred): All state lives in _json (a Json::Value member).
|
||||
// Use these for any setting with a ConversionTrait. The owning class must
|
||||
// declare a `Json::Value _json{ Json::ValueType::objectValue };` member.
|
||||
//
|
||||
// 2. Mutable backing-field (exception): State lives in a std::optional<T> member.
|
||||
// Use these only for types that callers mutate in place after retrieval, such
|
||||
// as IMediaResource (resolved via ResolveMediaResources), IVector, and IMap.
|
||||
// A JSON-backed getter would deserialize a fresh copy on each call, losing
|
||||
// those in-place mutations.
|
||||
//
|
||||
// When adding a new setting, use the JSON-backed macros unless the type requires
|
||||
// in-place mutation. Existing backing-field settings should be migrated to
|
||||
// JSON-backed as their mutation patterns are resolved.
|
||||
//
|
||||
// Available macros (JSON-backed):
|
||||
// INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...)
|
||||
// INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...)
|
||||
// INHERITABLE_NULLABLE_SETTING(projectedType, type, name, jsonKey, ...)
|
||||
// _BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...)
|
||||
//
|
||||
// Available macros (mutable backing-field):
|
||||
// INHERITABLE_MUTABLE_SETTING(projectedType, type, name, ...)
|
||||
// INHERITABLE_NULLABLE_MUTABLE_SETTING(projectedType, type, name, ...)
|
||||
//
|
||||
// =============================================================================
|
||||
|
||||
// =============================================================================
|
||||
// JSON-backed inheritable settings
|
||||
// =============================================================================
|
||||
// No std::optional<T> backing field — _json is the source of truth.
|
||||
// Getters deserialize from _json via ConversionTrait on each call.
|
||||
// Setters serialize to _json via SetValueForKey.
|
||||
// Has/Clear check/modify _json directly.
|
||||
|
||||
// Shared base for JSON-backed inheritable settings.
|
||||
// Provides Has, Clear, OverrideSource, and the private implementation helpers.
|
||||
// Does NOT provide the public getter/setter — use INHERITABLE_SETTING or
|
||||
// INHERITABLE_SETTING_WITH_LOGGING which add those on top of this base.
|
||||
#define _BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
public: \
|
||||
/* Returns true if the user explicitly set the value, false otherwise */ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _json.isMember(jsonKey) && !_json[jsonKey].isNull(); \
|
||||
} \
|
||||
\
|
||||
/* Returns the object that provides the resolved value */ \
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no source was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_json.removeMember(JsonKey(jsonKey)); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
/* Read value from this layer's _json only (no parent walk) */ \
|
||||
std::optional<type> _get##name##FromThisLayer() const \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
type val{ __VA_ARGS__ }; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::GetValueForKey( \
|
||||
_json, jsonKey, val); \
|
||||
return val; \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Resolve the value: this layer --> parents --> nullopt */ \
|
||||
std::optional<type> _get##name##Impl() const \
|
||||
{ \
|
||||
/*return value from this layer*/ \
|
||||
if (auto val{ _get##name##FromThisLayer() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto val{ parent->_get##name##Impl() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Find the object that provides the resolved value */ \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Find the override source and the value it provides */ \
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), std::optional<type>> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return { get_strong(), _get##name##FromThisLayer() }; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_get##name##FromThisLayer() }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// Use INHERITABLE_SETTING and INHERITABLE_NULLABLE_SETTING to implement
|
||||
// standard functions for inheritable settings. This is similar to the WINRT_PROPERTY macro,
|
||||
// except...
|
||||
// - Has<NAME>(): checks if the user explicitly set a value for this setting
|
||||
// - <NAME>OverrideSource(): return the object that provides the resolved value
|
||||
// - Getter(): return the resolved value
|
||||
// - Setter(): set the value directly
|
||||
// - Clear(): clear the user set value
|
||||
// - the setting is saved as an optional, where nullopt means
|
||||
// that we must inherit the value from our parent
|
||||
#define INHERITABLE_SETTING(projectedType, type, name, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, std::optional<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: user set value --> inherited value --> system set value */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
_##name = value; \
|
||||
// JSON-backed inheritable setting.
|
||||
// Getter returns the resolved value: this layer --> inherited --> default.
|
||||
// Setter writes the value to _json.
|
||||
#define INHERITABLE_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, __VA_ARGS__) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value in _json */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
#define INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, std::optional<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: user set value --> inherited value --> system set value */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
if (!_##name.has_value() || _##name.value() != value) \
|
||||
{ \
|
||||
_logSettingSet(jsonKey); \
|
||||
} \
|
||||
_##name = value; \
|
||||
// JSON-backed inheritable setting with change logging.
|
||||
// Same as INHERITABLE_SETTING, but the setter also calls _logSettingSet(jsonKey)
|
||||
// when the value actually changes (for settings change telemetry).
|
||||
#define INHERITABLE_SETTING_WITH_LOGGING(projectedType, type, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, type, name, jsonKey, __VA_ARGS__) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value, log the change, and write to _json */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
const auto existingVal{ _get##name##FromThisLayer() }; \
|
||||
if (!existingVal.has_value() || existingVal.value() != value) \
|
||||
{ \
|
||||
_logSettingSet(jsonKey); \
|
||||
} \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
// This macro is similar to the one above, but is reserved for optional settings
|
||||
// like Profile.Foreground (where null is interpreted
|
||||
// as an acceptable value, rather than "inherit")
|
||||
// "type" is exposed as an IReference
|
||||
#define INHERITABLE_NULLABLE_SETTING(projectedType, type, name, ...) \
|
||||
_BASE_INHERITABLE_SETTING(projectedType, NullableSetting<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: user set value --> inherited value --> system set value */ \
|
||||
winrt::Windows::Foundation::IReference<type> name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
if (val) \
|
||||
{ \
|
||||
if (*val) \
|
||||
{ \
|
||||
return **val; \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
return winrt::Windows::Foundation::IReference<type>{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value */ \
|
||||
void name(const winrt::Windows::Foundation::IReference<type>& value) \
|
||||
{ \
|
||||
if (value) /*set value is different*/ \
|
||||
{ \
|
||||
_##name = std::optional<type>{ value.Value() }; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* note we're setting the _inner_ value */ \
|
||||
_##name = std::optional<type>{ std::nullopt }; \
|
||||
} \
|
||||
// JSON-backed nullable inheritable setting.
|
||||
// For settings where null is a valid explicit value (e.g., Foreground color).
|
||||
// Key absent → inherit from parent
|
||||
// Key present + null → explicitly "no value" (getter returns nullptr)
|
||||
// Key present + value → has a value
|
||||
// Getter returns IReference<T>.
|
||||
#define INHERITABLE_NULLABLE_SETTING(projectedType, type, name, jsonKey, ...) \
|
||||
public: \
|
||||
/* Key presence means explicitly set (even if value is null) */ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _json.isMember(jsonKey); \
|
||||
} \
|
||||
\
|
||||
/* Returns the object that provides the resolved value */ \
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value (remove key entirely → inherit from parent) */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_json.removeMember(JsonKey(jsonKey)); \
|
||||
} \
|
||||
\
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
winrt::Windows::Foundation::IReference<type> name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
if (val) \
|
||||
{ \
|
||||
if (*val) \
|
||||
{ \
|
||||
return **val; \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
return winrt::Windows::Foundation::IReference<type>{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value in _json */ \
|
||||
void name(const winrt::Windows::Foundation::IReference<type>& value) \
|
||||
{ \
|
||||
if (value) \
|
||||
{ \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey( \
|
||||
_json, jsonKey, value.Value()); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* explicitly set to null (not the same as clearing) */ \
|
||||
_json[JsonKey(jsonKey)] = Json::nullValue; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
/* Read nullable value from this layer's _json only (no parent walk) */ \
|
||||
/* Returns: nullopt = not set. optional{nullopt} = explicitly null. */ \
|
||||
/* optional{optional{val}} = has value. */ \
|
||||
NullableSetting<type> _get##name##FromThisLayer() const \
|
||||
{ \
|
||||
if (_json.isMember(jsonKey)) \
|
||||
{ \
|
||||
if (_json[jsonKey].isNull()) \
|
||||
{ \
|
||||
return std::optional<type>{ std::nullopt }; \
|
||||
} \
|
||||
type val{}; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::GetValueForKey( \
|
||||
_json, jsonKey, val); \
|
||||
return std::optional<type>{ val }; \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Resolve the nullable value: this layer --> parents --> nullopt */ \
|
||||
NullableSetting<type> _get##name##Impl() const \
|
||||
{ \
|
||||
if (auto val{ _get##name##FromThisLayer() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto val{ parent->_get##name##Impl() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
} \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Find the object that provides the resolved value */ \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
} \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Find the override source and the value it provides */ \
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), NullableSetting<type>> \
|
||||
{ \
|
||||
if (Has##name()) \
|
||||
{ \
|
||||
return { get_strong(), _get##name##FromThisLayer() }; \
|
||||
} \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_get##name##FromThisLayer() }; \
|
||||
} \
|
||||
} \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Mutable backing-field inheritable settings
|
||||
// =============================================================================
|
||||
// These use a std::optional<T> backing field instead of _json.
|
||||
//
|
||||
// Use these ONLY for types that callers mutate in place after retrieval.
|
||||
// A JSON-backed getter would deserialize a fresh copy on each call, so
|
||||
// in-place mutations (e.g. ResolveMediaResources) would be lost.
|
||||
//
|
||||
// Current uses:
|
||||
// Profile: Icon, EnvironmentVariables, BellSound, UnfocusedAppearance
|
||||
// GlobalAppSettings: DisabledProfileSources, NewTabMenu
|
||||
// AppearanceConfig: PixelShaderPath, PixelShaderImagePath, BackgroundImagePath,
|
||||
// DarkColorSchemeName, LightColorSchemeName
|
||||
// FontConfig: FontAxes, FontFeatures
|
||||
|
||||
// Shared base for backing-field inheritable settings.
|
||||
// Provides Has, Clear, OverrideSource, and the private implementation helpers.
|
||||
#define _BASE_INHERITABLE_MUTABLE_SETTING(projectedType, storageType, name, ...) \
|
||||
public: \
|
||||
/* Returns true if the user explicitly set the value, false otherwise */ \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return _##name.has_value(); \
|
||||
} \
|
||||
\
|
||||
/* Returns the object that provides the resolved value */ \
|
||||
projectedType name##OverrideSource() \
|
||||
{ \
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no source was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Clear the user set value */ \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
_##name = std::nullopt; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
storageType _##name{ std::nullopt }; \
|
||||
\
|
||||
/* Resolve the value: this layer --> parents --> nullopt */ \
|
||||
storageType _get##name##Impl() const \
|
||||
{ \
|
||||
/*return user set value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return _##name; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto val{ parent->_get##name##Impl() }) \
|
||||
{ \
|
||||
return val; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
/* Find the object that provides the resolved value */ \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
/* Find the override source and the value it provides */ \
|
||||
auto _get##name##OverrideSourceAndValueImpl() \
|
||||
->std::pair<decltype(get_strong()), storageType> \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return { get_strong(), _##name }; \
|
||||
} \
|
||||
\
|
||||
/*iterate through parents to find one with a value*/ \
|
||||
for (const auto& parent : _parents) \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return { source, source->_##name }; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/*no value was found*/ \
|
||||
return { nullptr, std::nullopt }; \
|
||||
}
|
||||
|
||||
// Mutable backing-field setting.
|
||||
// Getter returns the resolved value: this layer --> inherited --> default.
|
||||
// Setter writes to the std::optional<T> backing field.
|
||||
#define INHERITABLE_MUTABLE_SETTING(projectedType, type, name, ...) \
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(projectedType, std::optional<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
type name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
return val ? *val : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value */ \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
_##name = value; \
|
||||
}
|
||||
|
||||
// Mutable nullable backing-field setting.
|
||||
// Same as INHERITABLE_MUTABLE_SETTING, but for optional settings where null is
|
||||
// a valid explicit value. Getter returns IReference<T>.
|
||||
#define INHERITABLE_NULLABLE_MUTABLE_SETTING(projectedType, type, name, ...) \
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(projectedType, NullableSetting<type>, name, ...) \
|
||||
public: \
|
||||
/* Returns the resolved value for this setting */ \
|
||||
/* fallback: this layer --> inherited value --> default */ \
|
||||
winrt::Windows::Foundation::IReference<type> name() const \
|
||||
{ \
|
||||
const auto val{ _get##name##Impl() }; \
|
||||
if (val) \
|
||||
{ \
|
||||
if (*val) \
|
||||
{ \
|
||||
return **val; \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
return winrt::Windows::Foundation::IReference<type>{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Overwrite the user set value */ \
|
||||
void name(const winrt::Windows::Foundation::IReference<type>& value) \
|
||||
{ \
|
||||
if (value) \
|
||||
{ \
|
||||
_##name = std::optional<type>{ value.Value() }; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
/* explicitly set to null (not the same as clearing) */ \
|
||||
_##name = std::optional<type>{ std::nullopt }; \
|
||||
} \
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// IMediaResource settings with backing fields for resolution lifecycle.
|
||||
// =============================================================================
|
||||
//
|
||||
// Use for settings of type IMediaResource that need in-place resolution
|
||||
// (e.g., Icon, PixelShaderPath, BackgroundImagePath). The backing field
|
||||
// holds the runtime-resolved object. _json is the source of truth for
|
||||
// serialization. The setter dual-writes to both.
|
||||
//
|
||||
// Parameters:
|
||||
// projectedType - the WinRT projected type (e.g., Model::Profile)
|
||||
// name - the setting name (e.g., Icon)
|
||||
// jsonKey - the JSON key (e.g., "icon")
|
||||
// ... - default value (e.g., implementation::MediaResource::Empty())
|
||||
//
|
||||
#define INHERITABLE_MEDIA_RESOURCE_SETTING(projectedType, name, jsonKey, ...) \
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(projectedType, std::optional<IMediaResource>, name, \
|
||||
implementation::MediaResource::Empty()) \
|
||||
public: \
|
||||
/* Returns the resolved value: this layer --> inherited --> default */ \
|
||||
IMediaResource name() const \
|
||||
{ \
|
||||
const auto v{ _get##name##Impl() }; \
|
||||
return v ? *v : IMediaResource{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
/* Dual-write: backing field (for resolution) + _json (for serialization) */ \
|
||||
void name(const IMediaResource& value) \
|
||||
{ \
|
||||
_##name = value; \
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(_json, jsonKey, value); \
|
||||
}
|
||||
|
||||
@@ -250,6 +250,28 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
SetValueForKey(json, key, target, ConversionTrait<std::decay_t<T>>{});
|
||||
}
|
||||
|
||||
// CopyKeyIfPresent: copies a key's raw Json::Value from source to target
|
||||
// if it exists in source. No type conversion — just raw JSON copy.
|
||||
// Used in ToJson() to copy JSON-backed settings from _json to output.
|
||||
inline void CopyKeyIfPresent(const Json::Value& source, Json::Value& target, const std::string_view key)
|
||||
{
|
||||
if (source.isMember(JsonKey(key)))
|
||||
{
|
||||
target[JsonKey(key)] = source[JsonKey(key)];
|
||||
}
|
||||
}
|
||||
|
||||
// MergeJsonKeys: copies all keys from source into target (key-wise merge).
|
||||
// Existing keys in target are overwritten by source values.
|
||||
// Used in LayerJson() to merge incoming JSON into stored _json.
|
||||
inline void MergeJsonKeys(const Json::Value& source, Json::Value& target)
|
||||
{
|
||||
for (const auto& key : source.getMemberNames())
|
||||
{
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<std::string>
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ Module Name:
|
||||
Abstract:
|
||||
- Contains most of the settings within Terminal Settings Model (global, profile, font, appearance)
|
||||
- To add a new setting to any one of those classes, simply add it to the respective list below, following the macro format
|
||||
- Also defines SettingKey enums for generic setting access
|
||||
|
||||
Author(s):
|
||||
- Pankaj Bhojwani - October 2021
|
||||
@@ -62,16 +63,15 @@ Author(s):
|
||||
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
|
||||
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
|
||||
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
|
||||
X(bool, ShowAdminShield, "showAdminShield", true) \
|
||||
X(bool, TrimPaste, "trimPaste", true) \
|
||||
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
|
||||
X(bool, EnableShellCompletionMenu, "experimental.enableShellCompletionMenu", false) \
|
||||
X(bool, EnableUnfocusedAcrylic, "compatibility.enableUnfocusedAcrylic", true) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
|
||||
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
|
||||
X(hstring, SearchWebDefaultQueryUrl, "searchWebDefaultQueryUrl", L"https://www.bing.com/search?q=%22%s%22") \
|
||||
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false)
|
||||
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr)
|
||||
|
||||
// Also add these settings to:
|
||||
// * Profile.idl
|
||||
@@ -88,15 +88,12 @@ Author(s):
|
||||
X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \
|
||||
X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \
|
||||
X(hstring, StartingDirectory, "startingDirectory") \
|
||||
X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \
|
||||
X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \
|
||||
X(guid, ConnectionType, "connectionType") \
|
||||
X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \
|
||||
X(hstring, TabTitle, "tabTitle") \
|
||||
X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \
|
||||
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \
|
||||
X(bool, RightClickContextMenu, "rightClickContextMenu", false) \
|
||||
X(Windows::Foundation::Collections::IVector<IMediaResource>, BellSound, "bellSound", nullptr) \
|
||||
X(bool, Elevate, "elevate", false) \
|
||||
X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \
|
||||
X(bool, ShowMarks, "showMarksOnScrollbar", false) \
|
||||
@@ -109,7 +106,8 @@ Author(s):
|
||||
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
|
||||
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \
|
||||
X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \
|
||||
X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None)
|
||||
X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \
|
||||
X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr)
|
||||
|
||||
// Intentionally omitted Profile settings:
|
||||
// * Name
|
||||
@@ -124,12 +122,12 @@ Author(s):
|
||||
X(hstring, FontFace, "face", DEFAULT_FONT_FACE) \
|
||||
X(float, FontSize, "size", DEFAULT_FONT_SIZE) \
|
||||
X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \
|
||||
X(IFontAxesMap, FontAxes, "axes") \
|
||||
X(IFontFeatureMap, FontFeatures, "features") \
|
||||
X(bool, EnableBuiltinGlyphs, "builtinGlyphs", true) \
|
||||
X(bool, EnableColorGlyphs, "colorGlyphs", true) \
|
||||
X(winrt::hstring, CellWidth, "cellWidth") \
|
||||
X(winrt::hstring, CellHeight, "cellHeight")
|
||||
X(winrt::hstring, CellHeight, "cellHeight") \
|
||||
X(IFontAxesMap, FontAxes, "axes") \
|
||||
X(IFontFeatureMap, FontFeatures, "features")
|
||||
|
||||
#define MTSM_APPEARANCE_SETTINGS(X) \
|
||||
X(Core::CursorStyle, CursorShape, "cursorShape", Core::CursorStyle::Bar) \
|
||||
@@ -137,10 +135,7 @@ Author(s):
|
||||
X(float, BackgroundImageOpacity, "backgroundImageOpacity", 1.0f) \
|
||||
X(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, "backgroundImageStretchMode", winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill) \
|
||||
X(bool, RetroTerminalEffect, "experimental.retroTerminalEffect", false) \
|
||||
X(IMediaResource, PixelShaderPath, "experimental.pixelShaderPath", implementation::MediaResource::Empty()) \
|
||||
X(IMediaResource, PixelShaderImagePath, "experimental.pixelShaderImagePath", implementation::MediaResource::Empty()) \
|
||||
X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \
|
||||
X(IMediaResource, BackgroundImagePath, "backgroundImage", implementation::MediaResource::Empty()) \
|
||||
X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \
|
||||
X(Core::AdjustTextMode, AdjustIndistinguishableColors, "adjustIndistinguishableColors", Core::AdjustTextMode::Automatic) \
|
||||
X(bool, UseAcrylic, "useAcrylic", false)
|
||||
@@ -174,3 +169,163 @@ Author(s):
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedBackground, "unfocusedBackground", nullptr) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::IconStyle, IconStyle, "iconStyle", winrt::Microsoft::Terminal::Settings::Model::IconStyle::Default) \
|
||||
X(winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility, ShowCloseButton, "showCloseButton", winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always)
|
||||
|
||||
// SettingKey enums: provide a generic way to reference settings by key.
|
||||
// Generated from the MTSM macros above. Also includes special-cased settings
|
||||
// that are not part of the macro lists (prefixed with _ to avoid name collisions).
|
||||
// SETTINGS_SIZE is a sentinel value that must remain last in each enum.
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
#define _MTSM_ENUM_VALUE(type, name, jsonKey, ...) name,
|
||||
|
||||
enum class ProfileSettingKey : int
|
||||
{
|
||||
MTSM_PROFILE_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased profile settings (not in MTSM_PROFILE_SETTINGS due to
|
||||
// custom JSON parsing, but included here for completeness)
|
||||
_Name,
|
||||
_Guid,
|
||||
_Source,
|
||||
_Hidden,
|
||||
_Padding,
|
||||
_TabColor,
|
||||
// Complex/mutable settings with backing fields
|
||||
_Icon,
|
||||
_BellSound,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class GlobalSettingKey : int
|
||||
{
|
||||
MTSM_GLOBAL_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased global settings
|
||||
_UnparsedDefaultProfile,
|
||||
// Complex/mutable settings with backing fields
|
||||
_NewTabMenu,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class FontSettingKey : int
|
||||
{
|
||||
MTSM_FONT_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
enum class AppearanceSettingKey : int
|
||||
{
|
||||
MTSM_APPEARANCE_SETTINGS(_MTSM_ENUM_VALUE)
|
||||
// Special-cased appearance settings
|
||||
_Foreground,
|
||||
_Background,
|
||||
_SelectionBackground,
|
||||
_CursorColor,
|
||||
_Opacity,
|
||||
_DarkColorSchemeName,
|
||||
_LightColorSchemeName,
|
||||
// Complex/mutable settings with backing fields
|
||||
_PixelShaderPath,
|
||||
_PixelShaderImagePath,
|
||||
_BackgroundImagePath,
|
||||
SETTINGS_SIZE
|
||||
};
|
||||
|
||||
#undef _MTSM_ENUM_VALUE
|
||||
|
||||
// JSON key lookup: returns the JSON key string for a given SettingKey.
|
||||
// Generated from the same macros to maintain a single source of truth.
|
||||
constexpr std::string_view JsonKeyForSetting(ProfileSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_PROFILE_SETTINGS(_MTSM_KEY_CASE)
|
||||
case ProfileSettingKey::_Name:
|
||||
return "name";
|
||||
case ProfileSettingKey::_Guid:
|
||||
return "guid";
|
||||
case ProfileSettingKey::_Source:
|
||||
return "source";
|
||||
case ProfileSettingKey::_Hidden:
|
||||
return "hidden";
|
||||
case ProfileSettingKey::_Padding:
|
||||
return "padding";
|
||||
case ProfileSettingKey::_TabColor:
|
||||
return "tabColor";
|
||||
case ProfileSettingKey::_Icon:
|
||||
return "icon";
|
||||
case ProfileSettingKey::_BellSound:
|
||||
return "bellSound";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(GlobalSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case GlobalSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_GLOBAL_SETTINGS(_MTSM_KEY_CASE)
|
||||
case GlobalSettingKey::_UnparsedDefaultProfile:
|
||||
return "defaultProfile";
|
||||
case GlobalSettingKey::_NewTabMenu:
|
||||
return "newTabMenu";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(FontSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case FontSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_FONT_SETTINGS(_MTSM_KEY_CASE)
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
|
||||
constexpr std::string_view JsonKeyForSetting(AppearanceSettingKey key)
|
||||
{
|
||||
#define _MTSM_KEY_CASE(type, name, jsonKey, ...) \
|
||||
case AppearanceSettingKey::name: \
|
||||
return jsonKey;
|
||||
switch (key)
|
||||
{
|
||||
MTSM_APPEARANCE_SETTINGS(_MTSM_KEY_CASE)
|
||||
case AppearanceSettingKey::_Foreground:
|
||||
return "foreground";
|
||||
case AppearanceSettingKey::_Background:
|
||||
return "background";
|
||||
case AppearanceSettingKey::_SelectionBackground:
|
||||
return "selectionBackground";
|
||||
case AppearanceSettingKey::_CursorColor:
|
||||
return "cursorColor";
|
||||
case AppearanceSettingKey::_Opacity:
|
||||
return "opacity";
|
||||
case AppearanceSettingKey::_DarkColorSchemeName:
|
||||
return "colorScheme";
|
||||
case AppearanceSettingKey::_LightColorSchemeName:
|
||||
return "colorScheme";
|
||||
case AppearanceSettingKey::_PixelShaderPath:
|
||||
return "experimental.pixelShaderPath";
|
||||
case AppearanceSettingKey::_PixelShaderImagePath:
|
||||
return "experimental.pixelShaderImagePath";
|
||||
case AppearanceSettingKey::_BackgroundImagePath:
|
||||
return "backgroundImage";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
#undef _MTSM_KEY_CASE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ static constexpr std::string_view LegacyAutoMarkPromptsKey{ "experimental.autoMa
|
||||
static constexpr std::string_view LegacyShowMarksKey{ "experimental.showMarksOnScrollbar" };
|
||||
static constexpr std::string_view LegacyRightClickContextMenuKey{ "experimental.rightClickContextMenu" };
|
||||
|
||||
Profile::Profile(guid guid) noexcept :
|
||||
_Guid(guid)
|
||||
Profile::Profile(guid guid)
|
||||
{
|
||||
Guid(guid);
|
||||
}
|
||||
|
||||
void Profile::CreateUnfocusedAppearance()
|
||||
@@ -105,25 +105,23 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
|
||||
profile->_Deleted = _Deleted;
|
||||
profile->_Orphaned = _Orphaned;
|
||||
profile->_Updates = _Updates;
|
||||
profile->_Guid = _Guid;
|
||||
profile->_Name = _Name;
|
||||
profile->_Source = _Source;
|
||||
profile->_Hidden = _Hidden;
|
||||
profile->_TabColor = _TabColor;
|
||||
profile->_Padding = _Padding;
|
||||
|
||||
profile->_Origin = _Origin;
|
||||
profile->_FontInfo = *fontInfo;
|
||||
profile->_DefaultAppearance = *defaultAppearance;
|
||||
profile->_json = _json;
|
||||
|
||||
#define PROFILE_SETTINGS_COPY(type, name, jsonKey, ...) \
|
||||
profile->_##name = _##name;
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_COPY)
|
||||
#undef PROFILE_SETTINGS_COPY
|
||||
// JSON-backed settings (Name, Source, Hidden, Guid, Padding, TabColor, MTSM settings)
|
||||
// all live in _json, which is already deep-copied above. No per-setting copy needed.
|
||||
|
||||
// Complex/mutable settings — backing fields for resolution lifecycle.
|
||||
// _json (copied above) is the source of truth; backing fields hold resolved runtime state.
|
||||
profile->_Icon = _Icon;
|
||||
|
||||
|
||||
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
|
||||
if (_BellSound)
|
||||
{
|
||||
// BellSound is an IVector<>, so we need to make a new vector pointing at the same objects
|
||||
profile->_BellSound = winrt::single_threaded_vector(wil::to_vector(*_BellSound));
|
||||
}
|
||||
|
||||
@@ -169,6 +167,10 @@ winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Prof
|
||||
// <none>
|
||||
void Profile::LayerJson(const Json::Value& json)
|
||||
{
|
||||
// Merge incoming JSON keys into stored _json (key-wise, not replacement).
|
||||
// This preserves keys from earlier LayerJson calls that aren't in the new JSON.
|
||||
JsonUtils::MergeJsonKeys(json, _json);
|
||||
|
||||
// Appearance Settings
|
||||
auto defaultAppearanceImpl = winrt::get_self<implementation::AppearanceConfig>(_DefaultAppearance);
|
||||
defaultAppearanceImpl->LayerJson(json);
|
||||
@@ -177,37 +179,53 @@ void Profile::LayerJson(const Json::Value& json)
|
||||
auto fontInfoImpl = winrt::get_self<implementation::FontConfig>(_FontInfo);
|
||||
fontInfoImpl->LayerJson(json);
|
||||
|
||||
// Profile-specific Settings
|
||||
JsonUtils::GetValueForKey(json, NameKey, _Name);
|
||||
// Profile-specific settings: now JSON-backed. Values are already in _json
|
||||
// from the merge step above. Only need to handle non-JSON fields and logging.
|
||||
JsonUtils::GetValueForKey(json, UpdatesKey, _Updates);
|
||||
JsonUtils::GetValueForKey(json, GuidKey, _Guid);
|
||||
|
||||
// Normalize Padding: allow integer → string coercion (e.g. "padding": 8 → "8")
|
||||
if (_json.isMember(JsonKey(PaddingKey)) && _json[JsonKey(PaddingKey)].isInt())
|
||||
{
|
||||
_json[JsonKey(PaddingKey)] = std::to_string(_json[JsonKey(PaddingKey)].asInt());
|
||||
}
|
||||
|
||||
// Make sure Source is before Hidden! We use that to exclude false positives from the settings logger!
|
||||
JsonUtils::GetValueForKey(json, SourceKey, _Source);
|
||||
JsonUtils::GetValueForKey(json, HiddenKey, _Hidden);
|
||||
_logSettingIfSet(HiddenKey, _Hidden.has_value());
|
||||
|
||||
// Padding was never specified as an integer, but it was a common working mistake.
|
||||
// Allow it to be permissive.
|
||||
JsonUtils::GetValueForKey(json, PaddingKey, _Padding, JsonUtils::OptionalConverter<hstring, JsonUtils::PermissiveStringConverter<std::wstring>>{});
|
||||
_logSettingIfSet(PaddingKey, _Padding.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, TabColorKey, _TabColor);
|
||||
_logSettingIfSet(TabColorKey, _TabColor.has_value());
|
||||
_logSettingIfSet(HiddenKey, HasHidden());
|
||||
_logSettingIfSet(PaddingKey, HasPadding());
|
||||
_logSettingIfSet(TabColorKey, HasTabColor());
|
||||
|
||||
// Try to load some legacy keys, to migrate them.
|
||||
// Done _before_ the MTSM_PROFILE_SETTINGS, which have the updated keys.
|
||||
JsonUtils::GetValueForKey(json, LegacyShowMarksKey, _ShowMarks);
|
||||
JsonUtils::GetValueForKey(json, LegacyAutoMarkPromptsKey, _AutoMarkPrompts);
|
||||
JsonUtils::GetValueForKey(json, LegacyRightClickContextMenuKey, _RightClickContextMenu);
|
||||
// Normalize legacy keys into canonical _json keys so JSON-backed getters find them.
|
||||
// Done _before_ the MTSM_PROFILE_SETTINGS logging, which have the updated keys.
|
||||
static constexpr std::pair<std::string_view, std::string_view> legacyKeyMappings[] = {
|
||||
{ LegacyShowMarksKey, "showMarksOnScrollbar" },
|
||||
{ LegacyAutoMarkPromptsKey, "autoMarkPrompts" },
|
||||
{ LegacyRightClickContextMenuKey, "rightClickContextMenu" },
|
||||
};
|
||||
for (const auto& [legacyKey, canonicalKey] : legacyKeyMappings)
|
||||
{
|
||||
if (json.isMember(JsonKey(legacyKey)))
|
||||
{
|
||||
_json[JsonKey(canonicalKey)] = json[JsonKey(legacyKey)];
|
||||
}
|
||||
}
|
||||
|
||||
// MTSM settings are now JSON-backed (no backing fields).
|
||||
// Values are already in _json from the merge step above.
|
||||
// We only need to log which settings were set in this layer.
|
||||
#define PROFILE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
|
||||
_logSettingIfSet(jsonKey, _##name.has_value());
|
||||
_logSettingIfSet(jsonKey, json.isMember(jsonKey) && !json[jsonKey].isNull());
|
||||
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_LAYER_JSON)
|
||||
#undef PROFILE_SETTINGS_LAYER_JSON
|
||||
|
||||
// Complex/mutable settings — backing fields populated from _json for runtime resolution.
|
||||
// _json is the source of truth for serialization; backing fields are for resolution lifecycle.
|
||||
JsonUtils::GetValueForKey(json, "icon", _Icon);
|
||||
_logSettingIfSet("icon", _Icon.has_value());
|
||||
JsonUtils::GetValueForKey(json, "bellSound", _BellSound);
|
||||
_logSettingIfSet("bellSound", _BellSound.has_value());
|
||||
|
||||
if (json.isMember(JsonKey(UnfocusedAppearanceKey)))
|
||||
{
|
||||
auto unfocusedAppearance{ winrt::make_self<implementation::AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
@@ -332,25 +350,44 @@ Json::Value Profile::ToJson() const
|
||||
// GH #9962:
|
||||
// If the settings.json was missing, when we load the dynamic profiles, they are completely empty.
|
||||
// This caused us to serialize empty profiles "{}" on accident.
|
||||
const auto writeBasicSettings{ !Source().empty() };
|
||||
const auto writeBasicSettings{ HasSource() };
|
||||
|
||||
// Profile-specific Settings
|
||||
JsonUtils::SetValueForKey(json, NameKey, writeBasicSettings ? Name() : _Name);
|
||||
JsonUtils::SetValueForKey(json, GuidKey, writeBasicSettings ? Guid() : _Guid);
|
||||
JsonUtils::SetValueForKey(json, HiddenKey, writeBasicSettings ? Hidden() : _Hidden);
|
||||
JsonUtils::SetValueForKey(json, SourceKey, writeBasicSettings ? Source() : _Source);
|
||||
// Profile-specific Settings (JSON-backed)
|
||||
if (writeBasicSettings)
|
||||
{
|
||||
// Dynamic profiles: write resolved values so defaults are materialized
|
||||
JsonUtils::SetValueForKey(json, NameKey, Name());
|
||||
JsonUtils::SetValueForKey(json, GuidKey, Guid());
|
||||
JsonUtils::SetValueForKey(json, HiddenKey, Hidden());
|
||||
JsonUtils::SetValueForKey(json, SourceKey, Source());
|
||||
}
|
||||
else
|
||||
{
|
||||
// User-defined profiles: copy local layer from _json
|
||||
for (const auto& key : { NameKey, GuidKey, HiddenKey, SourceKey })
|
||||
{
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, key);
|
||||
}
|
||||
}
|
||||
|
||||
// PermissiveStringConverter is unnecessary for serialization
|
||||
JsonUtils::SetValueForKey(json, PaddingKey, _Padding);
|
||||
// Padding: copy from _json
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, PaddingKey);
|
||||
|
||||
JsonUtils::SetValueForKey(json, TabColorKey, _TabColor);
|
||||
// TabColor: nullable — key presence matters (explicit null is valid)
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, TabColorKey);
|
||||
|
||||
// MTSM profile settings: copy from _json (the source of truth)
|
||||
#define PROFILE_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
|
||||
JsonUtils::SetValueForKey(json, jsonKey, _##name);
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, jsonKey);
|
||||
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_TO_JSON)
|
||||
#undef PROFILE_SETTINGS_TO_JSON
|
||||
|
||||
// Complex/mutable settings with backing fields (runtime resolution)
|
||||
// Read from _json (source of truth), not from backing fields
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "icon");
|
||||
JsonUtils::CopyKeyIfPresent(_json, json, "bellSound");
|
||||
|
||||
if (auto fontJSON = winrt::get_self<FontConfig>(_FontInfo)->ToJson(); !fontJSON.empty())
|
||||
{
|
||||
json[JsonKey(FontInfoKey)] = std::move(fontJSON);
|
||||
@@ -364,6 +401,89 @@ Json::Value Profile::ToJson() const
|
||||
return json;
|
||||
}
|
||||
|
||||
bool Profile::HasSetting(ProfileSettingKey key) const
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _PROFILE_HAS_SETTING(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
return Has##name();
|
||||
MTSM_PROFILE_SETTINGS(_PROFILE_HAS_SETTING)
|
||||
#undef _PROFILE_HAS_SETTING
|
||||
case ProfileSettingKey::_Name:
|
||||
return HasName();
|
||||
case ProfileSettingKey::_Guid:
|
||||
return HasGuid();
|
||||
case ProfileSettingKey::_Source:
|
||||
return HasSource();
|
||||
case ProfileSettingKey::_Hidden:
|
||||
return HasHidden();
|
||||
case ProfileSettingKey::_Padding:
|
||||
return HasPadding();
|
||||
case ProfileSettingKey::_TabColor:
|
||||
return HasTabColor();
|
||||
case ProfileSettingKey::_Icon:
|
||||
return HasIcon();
|
||||
case ProfileSettingKey::_BellSound:
|
||||
return HasBellSound();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::ClearSetting(ProfileSettingKey key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
#define _PROFILE_CLEAR_SETTING(type, name, jsonKey, ...) \
|
||||
case ProfileSettingKey::name: \
|
||||
Clear##name(); \
|
||||
break;
|
||||
MTSM_PROFILE_SETTINGS(_PROFILE_CLEAR_SETTING)
|
||||
#undef _PROFILE_CLEAR_SETTING
|
||||
case ProfileSettingKey::_Name:
|
||||
ClearName();
|
||||
break;
|
||||
case ProfileSettingKey::_Guid:
|
||||
ClearGuid();
|
||||
break;
|
||||
case ProfileSettingKey::_Source:
|
||||
ClearSource();
|
||||
break;
|
||||
case ProfileSettingKey::_Hidden:
|
||||
ClearHidden();
|
||||
break;
|
||||
case ProfileSettingKey::_Padding:
|
||||
ClearPadding();
|
||||
break;
|
||||
case ProfileSettingKey::_TabColor:
|
||||
ClearTabColor();
|
||||
break;
|
||||
case ProfileSettingKey::_Icon:
|
||||
ClearIcon();
|
||||
break;
|
||||
case ProfileSettingKey::_BellSound:
|
||||
ClearBellSound();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ProfileSettingKey> Profile::CurrentSettings() const
|
||||
{
|
||||
std::vector<ProfileSettingKey> result;
|
||||
for (auto i = 0; i < static_cast<int>(ProfileSettingKey::SETTINGS_SIZE); i++)
|
||||
{
|
||||
const auto key = static_cast<ProfileSettingKey>(i);
|
||||
if (HasSetting(key))
|
||||
{
|
||||
result.push_back(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Given a commandLine like the following:
|
||||
// * "C:\WINDOWS\System32\cmd.exe"
|
||||
// * "pwsh -WorkingDirectory ~"
|
||||
@@ -502,13 +622,13 @@ void Profile::_logSettingIfSet(const std::string_view& setting, const bool isSet
|
||||
|
||||
// Exclude some false positives from userDefaults.json
|
||||
// NOTE: we can't use the OriginTag here because it hasn't been set yet!
|
||||
const bool isWinPow = _Guid.has_value() && *_Guid == DEFAULT_WINDOWS_POWERSHELL_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Windows PowerShell");
|
||||
const bool isCmd = _Guid.has_value() && *_Guid == DEFAULT_COMMAND_PROMPT_GUID; //_Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Command Prompt");
|
||||
const bool isACS = _Name.has_value() && til::equals_insensitive_ascii(*_Name, L"Azure Cloud Shell");
|
||||
const bool isWTDynamicProfile = _Source.has_value() && til::starts_with(*_Source, L"Windows.Terminal");
|
||||
const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && _Hidden.has_value() && _Hidden == false;
|
||||
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
||||
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && _Commandline.has_value() && til::equals_insensitive_ascii(*_Commandline, L"%SystemRoot%\\System32\\cmd.exe");
|
||||
const bool isWinPow = HasGuid() && Guid() == DEFAULT_WINDOWS_POWERSHELL_GUID;
|
||||
const bool isCmd = HasGuid() && Guid() == DEFAULT_COMMAND_PROMPT_GUID;
|
||||
const bool isACS = HasName() && til::equals_insensitive_ascii(Name(), L"Azure Cloud Shell");
|
||||
const bool isWTDynamicProfile = HasSource() && til::starts_with(Source(), L"Windows.Terminal");
|
||||
const bool settingHiddenToFalse = til::equals_insensitive_ascii(setting, HiddenKey) && HasHidden() && Hidden() == false;
|
||||
const bool settingCommandlineToWinPow = til::equals_insensitive_ascii(setting, "commandline") && HasCommandline() && til::equals_insensitive_ascii(Commandline(), L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe");
|
||||
const bool settingCommandlineToCmd = til::equals_insensitive_ascii(setting, "commandline") && HasCommandline() && til::equals_insensitive_ascii(Commandline(), L"%SystemRoot%\\System32\\cmd.exe");
|
||||
// clang-format off
|
||||
if (!(isWinPow && (settingHiddenToFalse || settingCommandlineToWinPow))
|
||||
&& !(isCmd && (settingHiddenToFalse || settingCommandlineToCmd))
|
||||
@@ -541,7 +661,7 @@ void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver
|
||||
auto newIcon{ MediaResource::FromString(icon->Path()) };
|
||||
const std::wstring cmdline{ NormalizeCommandLine(iconSource->Commandline().c_str()) };
|
||||
newIcon.Resolve(cmdline.c_str() /* c_str: give hstring a chance to find the null terminator */);
|
||||
iconSource->_Icon = std::move(newIcon);
|
||||
iconSource->Icon(std::move(newIcon));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ Author(s):
|
||||
#include "MTSMSettings.h"
|
||||
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include <DefaultSettings.h>
|
||||
#include "MediaResourceSupport.h"
|
||||
#include "AppearanceConfig.h"
|
||||
@@ -80,7 +81,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
public:
|
||||
Profile() noexcept = default;
|
||||
Profile(guid guid) noexcept;
|
||||
// Not noexcept: Guid(guid) writes to _json, which allocates.
|
||||
// The old backing-field constructor was noexcept (POD copy into std::optional),
|
||||
// but JSON-backed storage requires heap allocation.
|
||||
Profile(guid guid);
|
||||
|
||||
void CreateUnfocusedAppearance();
|
||||
void DeleteUnfocusedAppearance();
|
||||
@@ -98,6 +102,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void LayerJson(const Json::Value& json);
|
||||
Json::Value ToJson() const;
|
||||
|
||||
// Generic setting access via SettingKey
|
||||
bool HasSetting(ProfileSettingKey key) const;
|
||||
void ClearSetting(ProfileSettingKey key);
|
||||
std::vector<ProfileSettingKey> CurrentSettings() const;
|
||||
|
||||
hstring EvaluatedStartingDirectory() const;
|
||||
|
||||
Model::IAppearanceConfig DefaultAppearance();
|
||||
@@ -122,16 +131,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
WINRT_PROPERTY(OriginTag, Origin, OriginTag::None);
|
||||
WINRT_PROPERTY(guid, Updates);
|
||||
|
||||
// Nullable/optional settings
|
||||
INHERITABLE_NULLABLE_SETTING(Model::Profile, Microsoft::Terminal::Core::Color, TabColor, nullptr);
|
||||
INHERITABLE_SETTING(Model::Profile, Model::IAppearanceConfig, UnfocusedAppearance, nullptr);
|
||||
// Nullable/optional settings (JSON-backed)
|
||||
INHERITABLE_NULLABLE_SETTING(Model::Profile, Microsoft::Terminal::Core::Color, TabColor, "tabColor", nullptr)
|
||||
INHERITABLE_MUTABLE_SETTING(Model::Profile, Model::IAppearanceConfig, UnfocusedAppearance, nullptr);
|
||||
|
||||
// Settings that cannot be put in the macro because of how they are handled in ToJson/LayerJson
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Name, L"Default");
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Source);
|
||||
INHERITABLE_SETTING(Model::Profile, bool, Hidden, false);
|
||||
INHERITABLE_SETTING(Model::Profile, guid, Guid, _GenerateGuidForProfile(Name(), Source()));
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Padding, DEFAULT_PADDING);
|
||||
// Settings that are JSON-backed but need custom handling in ToJson/LayerJson
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Name, "name", L"Default")
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Source, "source")
|
||||
INHERITABLE_SETTING(Model::Profile, bool, Hidden, "hidden", false)
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Padding, "padding", DEFAULT_PADDING)
|
||||
|
||||
// Guid: hand-written JSON-backed (dynamic default)
|
||||
_BASE_INHERITABLE_SETTING(Model::Profile, guid, Guid, "guid")
|
||||
public:
|
||||
guid Guid() const
|
||||
{
|
||||
const auto val{ _getGuidImpl() };
|
||||
return val ? *val : _GenerateGuidForProfile(Name(), Source());
|
||||
}
|
||||
void Guid(const guid& value)
|
||||
{
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(
|
||||
_json, "guid", value);
|
||||
}
|
||||
|
||||
winrt::hstring SourceBasePath;
|
||||
|
||||
@@ -141,10 +163,33 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_INITIALIZE)
|
||||
#undef PROFILE_SETTINGS_INITIALIZE
|
||||
|
||||
// IMediaResource settings with backing fields for resolution lifecycle.
|
||||
// _json is the source of truth for serialization. Setters dual-write to both
|
||||
// the backing field (for runtime resolution) and _json (for auto-save).
|
||||
INHERITABLE_MEDIA_RESOURCE_SETTING(Model::Profile, Icon, "icon", implementation::MediaResource::FromString(L"\uE756"))
|
||||
|
||||
// BellSound: IVector<IMediaResource> with backing field + dual-write setter.
|
||||
_BASE_INHERITABLE_MUTABLE_SETTING(Model::Profile, std::optional<Windows::Foundation::Collections::IVector<IMediaResource>>, BellSound, nullptr)
|
||||
public:
|
||||
Windows::Foundation::Collections::IVector<IMediaResource> BellSound() const
|
||||
{
|
||||
const auto val{ _getBellSoundImpl() };
|
||||
return val ? *val : nullptr;
|
||||
}
|
||||
void BellSound(const Windows::Foundation::Collections::IVector<IMediaResource>& value)
|
||||
{
|
||||
_BellSound = value;
|
||||
::Microsoft::Terminal::Settings::Model::JsonUtils::SetValueForKey(_json, "bellSound", value);
|
||||
}
|
||||
|
||||
private:
|
||||
Model::IAppearanceConfig _DefaultAppearance{ winrt::make<AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
Model::FontConfig _FontInfo{ winrt::make<FontConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
|
||||
// Raw JSON for this layer. Populated by LayerJson(), will become the
|
||||
// source of truth for settings once the JSON-backed refactor is complete.
|
||||
Json::Value _json{ Json::ValueType::objectValue };
|
||||
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
|
||||
|
||||
@@ -105,9 +105,10 @@ namespace SettingsModelUnitTests
|
||||
TEST_METHOD(RealResolverUrlCases);
|
||||
TEST_METHOD(RealResolverUNCCases);
|
||||
|
||||
static constexpr std::wstring_view pingCommandline{ LR"(C:\Windows\System32\PING.EXE)" }; // Normalized by Profile (this is the casing that Windows stores on disk)
|
||||
static constexpr std::wstring_view overrideCommandline{ LR"(C:\Windows\System32\cscript.exe)" };
|
||||
static constexpr std::wstring_view cmdCommandline{ LR"(C:\Windows\System32\cmd.exe)" }; // The default commandline for a profile
|
||||
// These are normalized by NormalizeCommandLine, which resolves to the on-disk casing.
|
||||
// They are used in test cases where media paths fall back to profile command lines.
|
||||
static inline std::wstring overrideCommandline;
|
||||
static inline std::wstring cmdCommandline;
|
||||
static constexpr std::wstring_view fragmentBasePath1{ LR"(C:\Windows\Media)" };
|
||||
|
||||
private:
|
||||
@@ -218,6 +219,9 @@ namespace SettingsModelUnitTests
|
||||
// Some of our tests use paths under system32. Just don't redirect them.
|
||||
Wow64DisableWow64FsRedirection(&redirectionFlag);
|
||||
#endif
|
||||
// Normalize these AFTER the call above so that we get the correctly redirected paths.
|
||||
overrideCommandline = implementation::Profile::NormalizeCommandLine(LR"(C:\Windows\System32\cscript.exe)");
|
||||
cmdCommandline = implementation::Profile::NormalizeCommandLine(LR"(C:\Windows\System32\cmd.exe)");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,14 @@ namespace SettingsModelUnitTests
|
||||
TEST_METHOD(TestGenGuidsForProfiles);
|
||||
TEST_METHOD(TestCorrectOldDefaultShellPaths);
|
||||
TEST_METHOD(ProfileDefaultsProhibitedSettings);
|
||||
|
||||
TEST_METHOD(JsonSyncOnSetAndClear);
|
||||
TEST_METHOD(SettingKeyEnumAndJsonKeyLookup);
|
||||
TEST_METHOD(GenericHasAndClearMatchTypedAPIs);
|
||||
TEST_METHOD(CurrentSettingsReturnsCorrectKeys);
|
||||
TEST_METHOD(SpecialCasedSettingsJsonBacked);
|
||||
TEST_METHOD(NullableSettingJsonBacked);
|
||||
TEST_METHOD(ColorSchemeJsonBacked);
|
||||
};
|
||||
|
||||
void ProfileTests::ProfileGeneratesGuid()
|
||||
@@ -532,4 +540,424 @@ namespace SettingsModelUnitTests
|
||||
VERIFY_ARE_NOT_EQUAL(L"Default Profile Source", allProfiles.GetAt(2).Source());
|
||||
VERIFY_ARE_NOT_EQUAL(L"foo.exe", allProfiles.GetAt(2).Commandline());
|
||||
}
|
||||
|
||||
void ProfileTests::JsonSyncOnSetAndClear()
|
||||
{
|
||||
// Verify that setting a value via the typed setter updates the internal
|
||||
// _json, and clearing it removes the key from _json.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial value
|
||||
VERIFY_ARE_EQUAL(1000, profile.HistorySize());
|
||||
VERIFY_IS_TRUE(profile.HasHistorySize());
|
||||
|
||||
// Modify setting; _json should be updated
|
||||
profile.HistorySize(5000);
|
||||
VERIFY_ARE_EQUAL(5000, profile.HistorySize());
|
||||
|
||||
// Verify ToJson reflects the change
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_ARE_EQUAL(5000, json["historySize"].asInt());
|
||||
|
||||
// Clear setting; should fall back to default
|
||||
profile.ClearHistorySize();
|
||||
VERIFY_IS_FALSE(profile.HasHistorySize());
|
||||
// Should now inherit or use default (9001 is the DEFAULT_HISTORY_SIZE)
|
||||
VERIFY_ARE_EQUAL(DEFAULT_HISTORY_SIZE, profile.HistorySize());
|
||||
|
||||
// ToJson should no longer have historySize
|
||||
const auto json2 = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json2.isMember("historySize"));
|
||||
}
|
||||
|
||||
void ProfileTests::SettingKeyEnumAndJsonKeyLookup()
|
||||
{
|
||||
// Verify that the SettingKey enums map to correct JSON keys.
|
||||
using namespace implementation;
|
||||
|
||||
// Spot-check a few profile setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "historySize" }, JsonKeyForSetting(ProfileSettingKey::HistorySize));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "snapOnInput" }, JsonKeyForSetting(ProfileSettingKey::SnapOnInput));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "commandline" }, JsonKeyForSetting(ProfileSettingKey::Commandline));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "tabTitle" }, JsonKeyForSetting(ProfileSettingKey::TabTitle));
|
||||
|
||||
// Special-cased settings
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "name" }, JsonKeyForSetting(ProfileSettingKey::_Name));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "guid" }, JsonKeyForSetting(ProfileSettingKey::_Guid));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "hidden" }, JsonKeyForSetting(ProfileSettingKey::_Hidden));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "padding" }, JsonKeyForSetting(ProfileSettingKey::_Padding));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "tabColor" }, JsonKeyForSetting(ProfileSettingKey::_TabColor));
|
||||
|
||||
// Global setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "initialRows" }, JsonKeyForSetting(GlobalSettingKey::InitialRows));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "alwaysOnTop" }, JsonKeyForSetting(GlobalSettingKey::AlwaysOnTop));
|
||||
|
||||
// Font setting keys
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "face" }, JsonKeyForSetting(FontSettingKey::FontFace));
|
||||
VERIFY_ARE_EQUAL(std::string_view{ "size" }, JsonKeyForSetting(FontSettingKey::FontSize));
|
||||
|
||||
// SETTINGS_SIZE should be a valid (but large) enum value
|
||||
VERIFY_IS_TRUE(static_cast<int>(ProfileSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(GlobalSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(FontSettingKey::SETTINGS_SIZE) > 0);
|
||||
VERIFY_IS_TRUE(static_cast<int>(AppearanceSettingKey::SETTINGS_SIZE) > 0);
|
||||
}
|
||||
|
||||
void ProfileTests::GenericHasAndClearMatchTypedAPIs()
|
||||
{
|
||||
// Verify that HasSetting(key) and ClearSetting(key) match the
|
||||
// typed HasXxx() and ClearXxx() methods.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000,
|
||||
"snapOnInput": false
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// HasSetting should match HasXxx for set values
|
||||
VERIFY_ARE_EQUAL(profile.HasHistorySize(), profileImpl->HasSetting(implementation::ProfileSettingKey::HistorySize));
|
||||
VERIFY_ARE_EQUAL(profile.HasSnapOnInput(), profileImpl->HasSetting(implementation::ProfileSettingKey::SnapOnInput));
|
||||
|
||||
// HasSetting should match HasXxx for unset values
|
||||
VERIFY_ARE_EQUAL(profile.HasTabTitle(), profileImpl->HasSetting(implementation::ProfileSettingKey::TabTitle));
|
||||
VERIFY_IS_FALSE(profileImpl->HasSetting(implementation::ProfileSettingKey::TabTitle));
|
||||
|
||||
// ClearSetting should behave like ClearXxx
|
||||
profileImpl->ClearSetting(implementation::ProfileSettingKey::HistorySize);
|
||||
VERIFY_IS_FALSE(profile.HasHistorySize());
|
||||
VERIFY_IS_FALSE(profileImpl->HasSetting(implementation::ProfileSettingKey::HistorySize));
|
||||
}
|
||||
|
||||
void ProfileTests::CurrentSettingsReturnsCorrectKeys()
|
||||
{
|
||||
// Verify that CurrentSettings() returns the keys that are explicitly
|
||||
// set at the current layer.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1000,
|
||||
"snapOnInput": false,
|
||||
"tabTitle": "MyTab"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
const auto currentKeys = profileImpl->CurrentSettings();
|
||||
|
||||
// historySize, snapOnInput, and tabTitle should be in the list
|
||||
auto hasHistorySize = false;
|
||||
auto hasSnapOnInput = false;
|
||||
auto hasTabTitle = false;
|
||||
for (const auto& key : currentKeys)
|
||||
{
|
||||
if (key == implementation::ProfileSettingKey::HistorySize)
|
||||
{
|
||||
hasHistorySize = true;
|
||||
}
|
||||
if (key == implementation::ProfileSettingKey::SnapOnInput)
|
||||
{
|
||||
hasSnapOnInput = true;
|
||||
}
|
||||
if (key == implementation::ProfileSettingKey::TabTitle)
|
||||
{
|
||||
hasTabTitle = true;
|
||||
}
|
||||
}
|
||||
VERIFY_IS_TRUE(hasHistorySize, L"historySize should be in CurrentSettings");
|
||||
VERIFY_IS_TRUE(hasSnapOnInput, L"snapOnInput should be in CurrentSettings");
|
||||
VERIFY_IS_TRUE(hasTabTitle, L"tabTitle should be in CurrentSettings");
|
||||
|
||||
// Clear one and verify it's removed
|
||||
profileImpl->ClearSetting(implementation::ProfileSettingKey::TabTitle);
|
||||
const auto updatedKeys = profileImpl->CurrentSettings();
|
||||
auto hasTabTitleAfterClear = false;
|
||||
for (const auto& key : updatedKeys)
|
||||
{
|
||||
if (key == implementation::ProfileSettingKey::TabTitle)
|
||||
{
|
||||
hasTabTitleAfterClear = true;
|
||||
}
|
||||
}
|
||||
VERIFY_IS_FALSE(hasTabTitleAfterClear, L"tabTitle should NOT be in CurrentSettings after clear");
|
||||
}
|
||||
|
||||
void ProfileTests::SpecialCasedSettingsJsonBacked()
|
||||
{
|
||||
// Verify that special-cased settings (Name, Guid, Hidden, Padding)
|
||||
// are now JSON-backed: setters write to _json, Has/Clear work correctly.
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "TestProfile",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"hidden": true,
|
||||
"padding": "12"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial values loaded from JSON
|
||||
VERIFY_ARE_EQUAL(L"TestProfile", profile.Name());
|
||||
VERIFY_IS_TRUE(profile.HasName());
|
||||
VERIFY_IS_TRUE(profile.Hidden());
|
||||
VERIFY_IS_TRUE(profile.HasHidden());
|
||||
VERIFY_ARE_EQUAL(L"12", profile.Padding());
|
||||
VERIFY_IS_TRUE(profile.HasPadding());
|
||||
|
||||
// Modify Name via setter — should update _json
|
||||
profile.Name(L"NewName");
|
||||
VERIFY_ARE_EQUAL(L"NewName", profile.Name());
|
||||
const auto json1 = profileImpl->ToJson();
|
||||
VERIFY_ARE_EQUAL(L"NewName", til::u8u16(json1["name"].asString()));
|
||||
|
||||
// Clear Name — should fall back to default
|
||||
profile.ClearName();
|
||||
VERIFY_IS_FALSE(profile.HasName());
|
||||
VERIFY_ARE_EQUAL(L"Default", profile.Name());
|
||||
|
||||
// Modify Hidden via setter
|
||||
profile.Hidden(false);
|
||||
VERIFY_ARE_EQUAL(false, profile.Hidden());
|
||||
VERIFY_IS_TRUE(profile.HasHidden());
|
||||
|
||||
// Clear Hidden — should fall back to default (false)
|
||||
profile.ClearHidden();
|
||||
VERIFY_IS_FALSE(profile.HasHidden());
|
||||
VERIFY_ARE_EQUAL(false, profile.Hidden());
|
||||
|
||||
// Modify Padding — should write to _json
|
||||
profile.Padding(L"24");
|
||||
VERIFY_ARE_EQUAL(L"24", profile.Padding());
|
||||
|
||||
// Test Guid setter
|
||||
static constexpr winrt::guid testGuid{ 0x11111111, 0x2222, 0x3333, { 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x77 } };
|
||||
profile.Guid(testGuid);
|
||||
VERIFY_ARE_EQUAL(testGuid, profile.Guid());
|
||||
}
|
||||
|
||||
void ProfileTests::NullableSettingJsonBacked()
|
||||
{
|
||||
// Verify that nullable settings (TabColor) are JSON-backed.
|
||||
// Null is a valid explicit value meaning "no color".
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"tabColor": "#FF0000"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
|
||||
// Verify initial value
|
||||
VERIFY_IS_TRUE(profile.HasTabColor());
|
||||
VERIFY_IS_NOT_NULL(profile.TabColor());
|
||||
|
||||
// Set to null explicitly (means "no color")
|
||||
profile.TabColor(nullptr);
|
||||
VERIFY_IS_TRUE(profile.HasTabColor()); // still "set" — explicitly null
|
||||
VERIFY_IS_NULL(profile.TabColor());
|
||||
|
||||
// Clear — should inherit from parent (which has no tabColor)
|
||||
profile.ClearTabColor();
|
||||
VERIFY_IS_FALSE(profile.HasTabColor());
|
||||
|
||||
// ToJson should not have tabColor after clearing
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json.isMember("tabColor"));
|
||||
}
|
||||
|
||||
void ProfileTests::ColorSchemeJsonBacked()
|
||||
{
|
||||
// Verify that DarkColorSchemeName/LightColorSchemeName are JSON-backed,
|
||||
// including round-trip through LayerJson/ToJson, parent inheritance,
|
||||
// and the polymorphic colorScheme key (string vs object forms).
|
||||
|
||||
// Case 1: string form input — sets both dark and light to the same scheme
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": "One Half Dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
// Both dark and light should be set to the same value
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", appearance.LightColorSchemeName());
|
||||
VERIFY_IS_TRUE(appearanceImpl->HasDarkColorSchemeName());
|
||||
VERIFY_IS_TRUE(appearanceImpl->HasLightColorSchemeName());
|
||||
|
||||
// ToJson should collapse to a string since dark == light
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json.isMember("colorScheme"));
|
||||
VERIFY_IS_TRUE(json["colorScheme"].isString());
|
||||
VERIFY_ARE_EQUAL("One Half Dark", json["colorScheme"].asString());
|
||||
}
|
||||
|
||||
// Case 2: object form input — dark and light are different
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": {
|
||||
"dark": "One Half Dark",
|
||||
"light": "One Half Light"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
// Each should return its own value
|
||||
VERIFY_ARE_EQUAL(L"One Half Dark", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"One Half Light", appearance.LightColorSchemeName());
|
||||
|
||||
// ToJson should produce an object since dark != light
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json.isMember("colorScheme"));
|
||||
VERIFY_IS_TRUE(json["colorScheme"].isObject());
|
||||
VERIFY_ARE_EQUAL("One Half Dark", json["colorScheme"]["dark"].asString());
|
||||
VERIFY_ARE_EQUAL("One Half Light", json["colorScheme"]["light"].asString());
|
||||
}
|
||||
|
||||
// Case 3: setter updates — setting dark should preserve light, and vice versa
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": "Campbell"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
// Change dark only — light should stay "Campbell"
|
||||
appearance.DarkColorSchemeName(L"Tango Dark");
|
||||
VERIFY_ARE_EQUAL(L"Tango Dark", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", appearance.LightColorSchemeName());
|
||||
|
||||
// ToJson should produce an object since dark != light
|
||||
const auto json1 = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json1["colorScheme"].isObject());
|
||||
VERIFY_ARE_EQUAL("Tango Dark", json1["colorScheme"]["dark"].asString());
|
||||
VERIFY_ARE_EQUAL("Campbell", json1["colorScheme"]["light"].asString());
|
||||
|
||||
// Change light to match dark — should collapse to string on ToJson
|
||||
appearance.LightColorSchemeName(L"Tango Dark");
|
||||
VERIFY_ARE_EQUAL(L"Tango Dark", appearance.LightColorSchemeName());
|
||||
const auto json2 = profileImpl->ToJson();
|
||||
VERIFY_IS_TRUE(json2["colorScheme"].isString());
|
||||
VERIFY_ARE_EQUAL("Tango Dark", json2["colorScheme"].asString());
|
||||
}
|
||||
|
||||
// Case 4: clear — removes colorScheme entirely, falls back to default "Campbell"
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"({
|
||||
"profiles": {
|
||||
"list": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"colorScheme": "One Half Dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
})" };
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(settingsJson);
|
||||
const auto profile = settings->AllProfiles().GetAt(0);
|
||||
const auto profileImpl = winrt::get_self<implementation::Profile>(profile);
|
||||
const auto appearance = profile.DefaultAppearance();
|
||||
const auto appearanceImpl = winrt::get_self<implementation::AppearanceConfig>(appearance);
|
||||
|
||||
VERIFY_IS_TRUE(appearanceImpl->HasDarkColorSchemeName());
|
||||
|
||||
// Clear removes colorScheme from this layer
|
||||
appearanceImpl->ClearDarkColorSchemeName();
|
||||
VERIFY_IS_FALSE(appearanceImpl->HasDarkColorSchemeName());
|
||||
VERIFY_IS_FALSE(appearanceImpl->HasLightColorSchemeName());
|
||||
|
||||
// Falls back to default "Campbell"
|
||||
VERIFY_ARE_EQUAL(L"Campbell", appearance.DarkColorSchemeName());
|
||||
VERIFY_ARE_EQUAL(L"Campbell", appearance.LightColorSchemeName());
|
||||
|
||||
// ToJson should not have colorScheme
|
||||
const auto json = profileImpl->ToJson();
|
||||
VERIFY_IS_FALSE(json.isMember("colorScheme"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ void FontTests::TestCurrentFontAPIsInvalid()
|
||||
}
|
||||
else
|
||||
{
|
||||
hConsoleOutput = (HANDLE)dwConsoleOutput;
|
||||
hConsoleOutput = ULongToHandle(dwConsoleOutput);
|
||||
}
|
||||
|
||||
if (strOperation == L"Get")
|
||||
@@ -107,7 +107,7 @@ void FontTests::TestGetFontSizeInvalid()
|
||||
// Need to make sure that last error is cleared so that we can verify that lasterror was set by GetConsoleFontSize
|
||||
SetLastError(0);
|
||||
|
||||
auto coordFontSize = OneCoreDelay::GetConsoleFontSize((HANDLE)dwConsoleOutput, 0);
|
||||
auto coordFontSize = OneCoreDelay::GetConsoleFontSize(ULongToHandle(dwConsoleOutput), 0);
|
||||
VERIFY_ARE_EQUAL(coordFontSize, c_coordZero, L"Ensure (0,0) coord returned to indicate failure");
|
||||
VERIFY_ARE_EQUAL(GetLastError(), (DWORD)ERROR_INVALID_HANDLE, L"Ensure last error was set appropriately");
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ void ConhostInternalGetSet::UnknownSequence() noexcept
|
||||
// us from using a more conservative solution (e.g. always fetching the cursor position).
|
||||
if (gci.IsInVtIoMode())
|
||||
{
|
||||
gci.GetActiveOutputBuffer().SetConptyCursorPositionMayBeWrong();
|
||||
gci.GetActiveOutputBuffer().GetActiveBuffer().SetConptyCursorPositionMayBeWrong();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
constexpr bool ends_with_insensitive_ascii(const std::basic_string_view<T, Traits>& str, const std::basic_string_view<T, Traits>& suffix) noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
return str.size() >= suffix.size() && equals_insensitive_ascii<>({ str.data() - suffix.size(), suffix.size() }, suffix);
|
||||
return str.size() >= suffix.size() && equals_insensitive_ascii<>({ str.data() + str.size() - suffix.size(), suffix.size() }, suffix);
|
||||
}
|
||||
|
||||
constexpr bool ends_with_insensitive_ascii(const std::string_view& str, const std::string_view& prefix) noexcept
|
||||
@@ -251,7 +251,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
|
||||
constexpr bool ends_with_insensitive_ascii(const std::wstring_view& str, const std::wstring_view& prefix) noexcept
|
||||
{
|
||||
return ends_with<>(str, prefix);
|
||||
return ends_with_insensitive_ascii<>(str, prefix);
|
||||
}
|
||||
|
||||
template<typename T, typename Traits>
|
||||
|
||||
@@ -6,8 +6,3 @@
|
||||
|
||||
TARGETNAME = ConInteractivityOneCoreLib
|
||||
TARGETTYPE = LIBRARY
|
||||
|
||||
# VSTS 14847240: Locally suppress individual -Wv:17 compiler warnings.
|
||||
# For more information, visit https://osgwiki.com/wiki/Windows_C%2B%2B_Toolset_Status.
|
||||
USER_C_FLAGS=$(USER_C_FLAGS) /wd4302 # 'conversion': truncation from 'type1' to 'type2'
|
||||
USER_C_FLAGS=$(USER_C_FLAGS) /wd4311 # 'variable': pointer truncation from 'type 1' to 'type 2'
|
||||
|
||||
@@ -119,12 +119,15 @@ DWORD WINAPI Renderer::s_renderThread(void* param) noexcept
|
||||
|
||||
DWORD Renderer::_renderThread() noexcept
|
||||
{
|
||||
while (true)
|
||||
while (_threadKeepRunning.load(std::memory_order_relaxed))
|
||||
{
|
||||
_enable.wait();
|
||||
_waitUntilCanRender();
|
||||
_waitUntilTimerOrRedraw();
|
||||
|
||||
// We just completed what could have been a long wait;
|
||||
// eagerly check again to prevent rendering if we don't
|
||||
// need to.
|
||||
if (!_threadKeepRunning.load(std::memory_order_relaxed))
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -595,9 +595,12 @@ constexpr T saturate(auto val)
|
||||
RETURN_IF_FAILED(pObjectHandle->GetScreenBuffer(GENERIC_READ, &pObj));
|
||||
|
||||
// See ConptyCursorPositionMayBeWrong() for details.
|
||||
if (pObj->ConptyCursorPositionMayBeWrong())
|
||||
auto& activeBuffer = pObj->GetActiveBuffer();
|
||||
// GetConsoleScreenBufferInfoExImpl uses GetActiveBuffer internally, but
|
||||
// under the console lock.
|
||||
if (activeBuffer.ConptyCursorPositionMayBeWrong())
|
||||
{
|
||||
pObj->WaitForConptyCursorPositionToBeSynchronized();
|
||||
activeBuffer.WaitForConptyCursorPositionToBeSynchronized();
|
||||
}
|
||||
|
||||
m->_pApiRoutines->GetConsoleScreenBufferInfoExImpl(*pObj, ex);
|
||||
|
||||
@@ -166,7 +166,7 @@ void InteractDispatch::MoveCursor(const VTInt row, const VTInt col)
|
||||
|
||||
// Unblock any callers inside SCREEN_INFORMATION::WaitForConptyCursorPositionToBeSynchronized().
|
||||
// The cursor position has now been updated to the terminal's.
|
||||
info.ResetConptyCursorPositionMayBeWrong();
|
||||
info.GetActiveBuffer().ResetConptyCursorPositionMayBeWrong();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
||||
@@ -31,6 +31,7 @@ Enum AssetType {
|
||||
ApplicationBundle
|
||||
PreinstallKit
|
||||
GroupPolicy
|
||||
NugetPackage
|
||||
Zip
|
||||
}
|
||||
|
||||
@@ -87,6 +88,9 @@ Class Asset {
|
||||
} ElseIf (".zip" -eq $local:ext -and $local:filename -like 'GroupPolicy*') {
|
||||
$this.Type = [AssetType]::GroupPolicy
|
||||
$this.Architecture = "all"
|
||||
} ElseIf (".nupkg" -eq $local:ext) {
|
||||
$this.Type = [AssetType]::NugetPackage
|
||||
$this.Architecture = "all"
|
||||
} ElseIf (".zip" -eq $local:ext) {
|
||||
$this.Type = [AssetType]::Zip
|
||||
} ElseIf (".msixbundle" -eq $local:ext) {
|
||||
@@ -108,6 +112,13 @@ Class Asset {
|
||||
Write-Verbose "Parsing AppxManifest.xml"
|
||||
$local:Manifest = [xml](Get-Content (Join-Path $local:directory AppxManifest.xml))
|
||||
$this.ParseManifest($local:Manifest)
|
||||
} ElseIf ($this.Type -Eq [AssetType]::NugetPackage) {
|
||||
Write-Verbose "Cracking nuget package $($local:filename)"
|
||||
& $script:tar -x -f $local:bundlePath -C $local:directory *.nuspec
|
||||
$local:nuspec = Get-ChildItem $local:directory *.nuspec -Recurse | Select-Object -First 1
|
||||
Write-Verbose "Parsing $($local:nuspec.Name)"
|
||||
$local:Manifest = [xml](Get-Content $local:nuspec)
|
||||
$this.ParseNuspec($local:Manifest)
|
||||
} Else {
|
||||
If ($this.Type -Ne [AssetType]::GroupPolicy) {
|
||||
& $script:tar -x -f $this.Path -C $local:directory --strip-components=1 '*/wt.exe'
|
||||
@@ -135,6 +146,12 @@ Class Asset {
|
||||
$this.Version = $Manifest.Package.Identity.Version
|
||||
}
|
||||
|
||||
[void]ParseNuspec([xml]$Nuspec) {
|
||||
$this.Name = $Nuspec.package.metadata.id
|
||||
$this.Version = ($Nuspec.package.metadata.version -split '-')[0]
|
||||
$this.ExpandedVersion = $Nuspec.package.metadata.version
|
||||
}
|
||||
|
||||
[void]ParseFilename([string]$filename) {
|
||||
$parts = [IO.Path]::GetFileNameWithoutExtension($filename).Split("_")
|
||||
$this.Name = $parts[0]
|
||||
@@ -160,6 +177,9 @@ Class Asset {
|
||||
GroupPolicy {
|
||||
"{0}_{1}.zip" -f ($this.Name, $this.Version)
|
||||
}
|
||||
NugetPackage {
|
||||
"{0}.{1}.nupkg" -f ($this.Name, $this.ExpandedVersion)
|
||||
}
|
||||
Default {
|
||||
Throw "Unknown type $($_.Type)"
|
||||
}
|
||||
@@ -253,15 +273,11 @@ Function New-ReleaseBody([Release]$Release) {
|
||||
If (-Not [String]::IsNullOrEmpty($zipAssetVersion)) {
|
||||
$body += "_Binary files inside the unpackaged distribution archive bear the version number ``$zipAssetVersion``._`n`n"
|
||||
}
|
||||
$body += "### Asset Hashes`n`n";
|
||||
ForEach($a in $Release.Assets) {
|
||||
$body += "- {0}`n - SHA256 ``{1}```n" -f ($a.IdealFilename(), (Get-FileHash $a.Path -Algorithm SHA256 | Select-Object -Expand Hash))
|
||||
}
|
||||
Return $body
|
||||
}
|
||||
|
||||
# Collect Assets from $Directory, figure out what those assets are
|
||||
$Assets = Get-ChildItem $Directory -Recurse -Include *.msixbundle, *.zip | ForEach-Object {
|
||||
$Assets = Get-ChildItem $Directory -Recurse -Include *.msixbundle, *.zip, *.nupkg -Exclude *.Wpf.*,*.symbols.nupkg | ForEach-Object {
|
||||
[Asset]::CreateFromFile($_.FullName)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user