mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Rewrite media resource handling (relative path icons, web URLs) (#19143)
This pull request broadly rewrites how we handle all media resources in the Terminal settings model. ## What is a media resource? A media resource is any JSON property that refers to a file on disk, including: - `icon` on profile - `backgroundImage` on profile (appearance) - `pixelShaderPath` and `pixelShaderImagePath` on profile (appearance) - `icon` on command and the new tab menu entries The last two bullet points were newly discovered during the course of this work. ## Description of Changes In every place the settings model used to store a string for a media path, it now stores an `IMediaResource`. A media resource must be _resolved_ before it's used. When resolved, it can report whether it is `Ok` (found, valid) and what the final normalized path was. This allows the settings model to apply some new behaviors. One of those new behaviors is resolving media paths _relative to the JSON file that referred to them._ This means fragments and user settings can now contain _local_ images, pixel shaders and more and refer to them by filename. Relative path support requires us to track the path from which every media resource "container" was read[^2]. For "big" objects like Profile, we track it directly in the object and for each layer. This means that fragments **updating** a profile pass their relative base path into the mix. For some of the entries such as those in `newTabMenu`, we just wing it (#19191). For everything that is recursively owned by a parent that has a path (say each Command inside an ActionMap), we pass it in from the parent during media resolution. During resolution, we now track _exactly which layer_ an icon, background image, or pixel shader path came from and read the "base path" from only that layer. The base path is not inherited. Another new behavior is in the handling of web and other URLs. Canonical and a few other WSL distributors had to resort to web URLs for icons because we did not support loading them from the package. Julia tried to use `ms-appx://JuliaPackageNameHere/path/to/icon` for the same reason. Neither was intended, and of the two the second _should_ have worked but never could[^1]. For both `http(s?)` URLs and `ms-appx://` URLs which specify a package name, we now strip everything except the filename. As an example... If my fragment specifies `https://example.net/assets/foo.ico`, and my fragment was loaded from `C:\Fragments`, Terminal will look *only* at `C:\Fragments\foo.ico`. This works today for Julia (they put their icon in the fragment folder hoping that one day we would support this.) It will require some work from existing WSL distributors. I'm told that this is similar to how XML schema documents work. Now, icons are special. They support _Emoji_ and _Segoe Icons_. This PR adds an early pass to avoid resolving anything that looks like an emoji. This PR intentionally expands the heuristic definition of an emoji. It used to only cover 1-2 code unit emoji, which prevented the use of any emoji more complicated than "man in business suite levitating." An icon path will now be considered an emoji or symbol icon if it is composed of a single grapheme cluster (as measured by ICU.) This is not perfect, as it errs on the side of allowing too many things... but each of those things is technically a single grapheme cluster and is a perfectly legal FontIcon ;) Profile icons are _even more special_ than icons. They have an additional fallback behavior which we had to preserve. When a profile icon fails validation, or is expressly set to `null`, we fall back to the EXE specified in the command line. Because we do this fallback during resolution, _and the icon may be inherited by any higher profile,_ we can only resolve it against the commandline at the same level as the failed or nulled icon. Therefore, if you specify `icon: null` in your `defaults` profile, it will only ever resolve to `cmd.exe` for any profile that inherits it (unless you change `defaults.commandline`). This change expands support for the magic keywords `desktopWallpaper` and `none` to all media paths (yes, even `pixelShaderPath`... but also, `pixelShaderImagePath`!) It also expands support for _environment variables_ to all of those places. Yes, we had like forty different handlers for different types of string path. They are now uniform. ## Resource Validation Media resources which are not found are "rejected". If a rejected resource lives in _user_ settings, we will generate a warning and display it. In the future, we could detect this in the Settings UI and display a warning inline. ## Surprises I learned that `Windows.Foundation.Uri` parses file paths into `file://` URIs, but does not offer you a way to get the original file path back out. If you pass `C:\hello world`, _`Uri.Path`_ will return `/C:/hello%20world`. I kid you not. As a workaround, we bail out of URL handling if the `:` is too close to the start (indicating an absolute file path). ## Testing I added a narow test hook in the media resource resolver, which is removed completely by link-time code generation. It is a real joy. The test cases are all new and hopefully comprehensive. Closes #19075 Closes #16295 Closes #10359 (except it doesn't support fonts) Supersedes #16949 somewhat (`WT_SETTINGS_DIR`) Refs #18679 Refs #19215 (future work) Refs #19201 (future work) Refs #19191 (future work) [^1]: Handling a `ms-appx` path requires us to _add their package to our dependency graph_ for the entire duration during which the resource will be used. For us, that could be any time (like opening the command palette for the first time!) [^2]: We don't bother tracking where the defaults came from, because we control everything about them.
This commit is contained in:
parent
a258d7d3df
commit
c0f9a198c6
1
.github/actions/spelling/allow/apis.txt
vendored
1
.github/actions/spelling/allow/apis.txt
vendored
@ -174,6 +174,7 @@ tokeninfo
|
||||
tolower
|
||||
toupper
|
||||
TRACKMOUSEEVENT
|
||||
ubrk
|
||||
UChar
|
||||
UFIELD
|
||||
ULARGE
|
||||
|
||||
1
.github/actions/spelling/allow/microsoft.txt
vendored
1
.github/actions/spelling/allow/microsoft.txt
vendored
@ -34,6 +34,7 @@ issecret
|
||||
libucrt
|
||||
libucrtd
|
||||
LOCKFILE
|
||||
LTCG
|
||||
Lxss
|
||||
makepri
|
||||
microsoft
|
||||
|
||||
1
.github/actions/spelling/allow/names.txt
vendored
1
.github/actions/spelling/allow/names.txt
vendored
@ -28,6 +28,7 @@ jerrysh
|
||||
Kaiyu
|
||||
leonardder
|
||||
lhecker
|
||||
Lovecraft
|
||||
masserano
|
||||
menger
|
||||
migrie
|
||||
|
||||
@ -61,7 +61,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::hstring Icon()
|
||||
{
|
||||
return _Command.IconPath();
|
||||
return _Command.Icon().Resolved();
|
||||
}
|
||||
|
||||
WINRT_PROPERTY(Microsoft::Terminal::Settings::Model::Command, Command, nullptr);
|
||||
|
||||
@ -19,41 +19,6 @@ DEFINE_PROPERTYKEY(PKEY_AppUserModel_DestListLogoUri, 0x9F4C2855, 0x9F79, 0x4B39
|
||||
{ 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 29 \
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - This function guesses whether a string is a file path.
|
||||
static constexpr bool _isProbableFilePath(std::wstring_view path)
|
||||
{
|
||||
// "C:X", "C:\X", "\\?", "\\."
|
||||
// _this function rejects \??\ as a path_
|
||||
if (path.size() >= 3)
|
||||
{
|
||||
const auto firstColon{ path.find(L':') };
|
||||
if (firstColon == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto prefix{ path.substr(0, 2) };
|
||||
return prefix == LR"(//)" || prefix == LR"(\\)";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - DestListLogoUri cannot take paths that are separated by / unless they're URLs.
|
||||
// This function uses std::filesystem to normalize strings that appear to be file
|
||||
// paths to have the "correct" slash direction.
|
||||
static std::wstring _normalizeIconPath(std::wstring_view path)
|
||||
{
|
||||
const auto fullPath{ wil::ExpandEnvironmentStringsW<std::wstring>(path.data()) };
|
||||
if (_isProbableFilePath(fullPath))
|
||||
{
|
||||
std::filesystem::path asPath{ fullPath };
|
||||
return asPath.make_preferred().wstring();
|
||||
}
|
||||
return std::wstring{ fullPath };
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Updates the items of the Jumplist based on the given settings.
|
||||
// Arguments:
|
||||
@ -124,7 +89,7 @@ void Jumplist::_updateProfiles(IObjectCollection* jumplistItems, winrt::Windows:
|
||||
auto args = fmt::format(FMT_COMPILE(L"-p {}"), to_hstring(profile.Guid()));
|
||||
|
||||
// Create the shell link object for the profile
|
||||
const auto normalizedIconPath{ _normalizeIconPath(profile.Icon()) };
|
||||
const auto normalizedIconPath{ profile.Icon().Resolved() };
|
||||
const auto shLink = _createShellLink(profile.Name(), normalizedIconPath, args);
|
||||
THROW_IF_FAILED(jumplistItems->AddObject(shLink.get()));
|
||||
}
|
||||
|
||||
@ -270,14 +270,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Registerkarte kopieren</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Profil mit einem ungültigen "backgroundImage" gefunden. Dieses Profil hat standardmäßig kein Hintergrundbild. Stellen Sie sicher, dass beim Festlegen eines "backgroundImage" der Wert ein gültiger Dateipfad zu einem Bild ist.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Profil mit einem ungültigen "icon" gefunden. Dieses Profil hat standardmäßig kein Symbol. Stellen Sie sicher, dass beim Festlegen eines "icon" der Wert ein gültiger Dateipfad zu einem Bild ist.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Beim Analysieren Ihrer Tastenzuordnungen wurden Warnungen gefunden:</value>
|
||||
</data>
|
||||
|
||||
@ -271,13 +271,9 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Duplicate tab</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Found a profile with an invalid "icon". Defaulting that profile to have no icon. Make sure that when setting an "icon", the value is a valid file path to an image.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
<data name="InvalidMediaResource" xml:space="preserve">
|
||||
<value>One or more resources (such as icon or backgroundImage) specified in your settings could not be found.</value>
|
||||
<comment>{Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Warnings were found while parsing your keybindings:</value>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Duplicar pestaña</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Se encontró un perfil con un "backgroundImage" no válido. Si se predetermina que ese perfil no tiene imagen de fondo. Asegúrese de que al establecer "backgroundImage", el valor sea una ruta de acceso de archivo válida a una imagen.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Se encontró un perfil con un "icon" no válido. Estableciendo ese perfil para no tener icono. Asegúrese de que, al establecer un "icon", el valor es una ruta de acceso de archivo válida a una imagen.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Se encontraron advertencias al analizar los enlaces de teclado:</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Dupliquer l’onglet</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Profil détecté avec une "backgroundImage" non valide. Par défaut, ce profil ne possède pas d’image d’arrière-plan. Assurez-vous que lorsque vous définissez une "backgroundImage", la valeur est un chemin d’accès de fichier valide vers une image.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Profil détecté avec une "icon" non valide. Par défaut, ce profil ne possède pas d’icône. Assurez-vous que lorsque vous définissez une "icon", la valeur est un chemin d’accès de fichier valide vers une image.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Des avertissements ont été détectés lors de l’analyse de vos combinaisons de touches :</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Duplica scheda</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>È stato trovato un profilo con un "backgroundImage" non valido. Impostazione predefinita per il profilo non è disponibile un'immagine di sfondo. Accertarsi che quando si imposta un "backgroundImage", il valore è un percorso di file valido per un'immagine.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Trovato un profilo con "icon" non valida. Impostare il profilo senza icon. Assicurarsi che, quando si imposta una "icon", il valore abbia un percorso file valido per un'immagine.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Sono stati trovati avvisi durante l'analisi delle associazioni di tasti:</value>
|
||||
</data>
|
||||
|
||||
@ -268,14 +268,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>タブを複製する</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>無効な "backgroundImage" を持つプロファイルが見つかりました。既定では、そのプロファイルに背景画像はありません。"backgroundImage" を設定するときに、値が画像への有効なファイル パスとなっていることをご確認ください。</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>無効な "icon" を持つプロファイルが見つかりました。既定では、そのプロファイルにアイコンはありません。"icon" を設定するときに、値が画像への有効なファイル パスとなっていることをご確認ください。</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>キー バインドの解析中に警告が検出されました:</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>탭 복제</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>잘못된 "backgroundImage" 프로필을 찾았습니다. 해당 프로필을 배경 이미지가 없는 기본값으로 설정합니다. "backgroundImage"를 설정할 때 값이 이미지에 대한 유효한 파일 경로인지 확인합니다.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>잘못된 "icon"이 있는 프로필을 발견했습니다. 해당 프로필에 아이콘이 없도록 기본값을 설정합니다. "icon" 설정 시 값이 이미지에 대한 올바른 파일 경로인지 확인합니다.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>키 바인딩 구문을 분석하는 동안 경고를 발견했습니다.</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Duplicar guia</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Foi encontrado um perfil com um "backgroundImage" inválido. O perfil deve ser o padrão para que não haja nenhuma imagem de tela de fundo. Certifique-se de que, ao definir um "backgroundImage", o valor é um caminho de arquivo válido para uma imagem.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Foi encontrado um perfil com um "icon" inválido. Padronize esse perfil para ele não ter ícone. Certifique-se de que, ao definir um "icon", o valor seja um caminho de arquivo válido para uma imagem.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Os avisos foram encontrados durante a análise das suas ligações de teclas:</value>
|
||||
</data>
|
||||
|
||||
@ -271,14 +271,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Ďϋφľіčάтέ τàв !!! </value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!!</value>
|
||||
</data>
|
||||
|
||||
@ -271,14 +271,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Ďϋφľіčάтέ τàв !!! </value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!!</value>
|
||||
</data>
|
||||
|
||||
@ -271,14 +271,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Ďϋφľіčάтέ τàв !!! </value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! </value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!!</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>Дублировать вкладку</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Найден профиль с недопустимым объектом "backgroundImage". По умолчанию для этого профиля не используется фоновое изображение. Убедитесь, что значение, заданное для "backgroundImage", является допустимым путем файла к изображению.</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>Найден профиль с недопустимым объектом "icon". По умолчанию для этого профиля не используется значок. Убедитесь, что значение, заданное для "icon", является допустимым путем файла к изображению.</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>При анализе настраиваемых сочетаний клавиш найдены предупреждения:</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>复制标签页</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>找到一个具有无效 "backgroundImage" 的配置文件。将该配置文件设置为默认设置为不包含背景图像。请确保在设置 "backgroundImage" 时,该值是指向图像的有效文件路径。</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>找到一个带有无效 "icon" 的配置文件。将该配置文件默认为无图标。确保设置 "icon" 时,该值是图像的有效文件路径。</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>分析键绑定时发现警告:</value>
|
||||
</data>
|
||||
|
||||
@ -267,14 +267,6 @@
|
||||
<data name="DuplicateTabText" xml:space="preserve">
|
||||
<value>複製索引標籤</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>找到具有無效 "backgroundImage" 的設定檔。將該設定檔的預設值設為沒有背景影像。請確定設定 "backgroundImage" 時,該值是影像的有效檔案路徑。</value>
|
||||
<comment>{Locked="\"backgroundImage\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidIcon" xml:space="preserve">
|
||||
<value>已發現具有無效 "icon" 的設定檔。將該設定檔預設為無圖示。設定 "icon" 時,請確認值是有效的影像檔案路徑。</value>
|
||||
<comment>{Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized.</comment>
|
||||
</data>
|
||||
<data name="AtLeastOneKeybindingWarning" xml:space="preserve">
|
||||
<value>剖析金鑰繫結時發現警告:</value>
|
||||
</data>
|
||||
|
||||
@ -937,7 +937,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto folderItem = WUX::Controls::MenuFlyoutSubItem{};
|
||||
folderItem.Text(folderEntry.Name());
|
||||
|
||||
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon());
|
||||
auto icon = _CreateNewTabFlyoutIcon(folderEntry.Icon().Resolved());
|
||||
folderItem.Icon(icon);
|
||||
|
||||
for (const auto& folderEntryItem : folderEntryItems)
|
||||
@ -987,7 +987,7 @@ namespace winrt::TerminalApp::implementation
|
||||
break;
|
||||
}
|
||||
|
||||
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex(), profileEntry.Icon());
|
||||
auto profileItem = _CreateNewTabFlyoutProfile(profileEntry.Profile(), profileEntry.ProfileIndex(), profileEntry.Icon().Resolved());
|
||||
items.push_back(profileItem);
|
||||
break;
|
||||
}
|
||||
@ -997,7 +997,7 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto actionId = actionEntry.ActionId();
|
||||
if (_settings.ActionMap().GetActionByID(actionId))
|
||||
{
|
||||
auto actionItem = _CreateNewTabFlyoutAction(actionId, actionEntry.Icon());
|
||||
auto actionItem = _CreateNewTabFlyoutAction(actionId, actionEntry.Icon().Resolved());
|
||||
items.push_back(actionItem);
|
||||
}
|
||||
|
||||
@ -1036,7 +1036,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// If a custom icon path has been specified, set it as the icon for
|
||||
// this flyout item. Otherwise, if an icon is set for this profile, set that icon
|
||||
// for this flyout item.
|
||||
const auto& iconPath = iconPathOverride.empty() ? profile.EvaluatedIcon() : iconPathOverride;
|
||||
const auto& iconPath = iconPathOverride.empty() ? profile.Icon().Resolved() : iconPathOverride;
|
||||
if (!iconPath.empty())
|
||||
{
|
||||
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
|
||||
@ -1122,7 +1122,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// If a custom icon path has been specified, set it as the icon for
|
||||
// this flyout item. Otherwise, if an icon is set for this action, set that icon
|
||||
// for this flyout item.
|
||||
const auto& iconPath = iconPathOverride.empty() ? action.IconPath() : iconPathOverride;
|
||||
const auto& iconPath = iconPathOverride.empty() ? action.Icon().Resolved() : iconPathOverride;
|
||||
if (!iconPath.empty())
|
||||
{
|
||||
const auto icon = _CreateNewTabFlyoutIcon(iconPath);
|
||||
@ -3561,7 +3561,7 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
const auto path = newAppearance.ExpandedBackgroundImagePath();
|
||||
const auto path = newAppearance.BackgroundImagePath().Resolved();
|
||||
if (path.empty())
|
||||
{
|
||||
_tabContent.Background(nullptr);
|
||||
@ -5042,7 +5042,7 @@ namespace winrt::TerminalApp::implementation
|
||||
makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr }, menu);
|
||||
|
||||
const auto focusedProfileName = focusedProfile.Name();
|
||||
const auto focusedProfileIcon = focusedProfile.Icon();
|
||||
const auto focusedProfileIcon = focusedProfile.Icon().Resolved();
|
||||
const auto splitPaneDuplicateText = RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName; // SplitPaneDuplicateText
|
||||
|
||||
const auto splitPaneRightText = RS_(L"SplitPaneRightText");
|
||||
@ -5068,7 +5068,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
const auto profile = activeProfiles.GetAt(profileIndex);
|
||||
const auto profileName = profile.Name();
|
||||
const auto profileIcon = profile.Icon();
|
||||
const auto profileIcon = profile.Icon().Resolved();
|
||||
|
||||
NewTerminalArgs args{};
|
||||
args.Profile(profileName);
|
||||
@ -5103,22 +5103,22 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Down, mruPanes))
|
||||
{
|
||||
makeItem(RS_(L"SwapPaneDownText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Down } }, swapPaneMenu);
|
||||
makeItem(RS_(L"SwapPaneDownText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Down } }, swapPaneMenu);
|
||||
}
|
||||
|
||||
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Right, mruPanes))
|
||||
{
|
||||
makeItem(RS_(L"SwapPaneRightText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Right } }, swapPaneMenu);
|
||||
makeItem(RS_(L"SwapPaneRightText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Right } }, swapPaneMenu);
|
||||
}
|
||||
|
||||
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Up, mruPanes))
|
||||
{
|
||||
makeItem(RS_(L"SwapPaneUpText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Up } }, swapPaneMenu);
|
||||
makeItem(RS_(L"SwapPaneUpText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Up } }, swapPaneMenu);
|
||||
}
|
||||
|
||||
if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Left, mruPanes))
|
||||
{
|
||||
makeItem(RS_(L"SwapPaneLeftText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Left } }, swapPaneMenu);
|
||||
makeItem(RS_(L"SwapPaneLeftText"), neighbor->GetProfile().Icon().Resolved(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Left } }, swapPaneMenu);
|
||||
}
|
||||
|
||||
makeMenuItem(RS_(L"SwapPaneText"), L"\xF1CB", swapPaneMenu, menu);
|
||||
|
||||
@ -82,7 +82,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::hstring TerminalPaneContent::Icon() const
|
||||
{
|
||||
return _profile.EvaluatedIcon();
|
||||
return _profile.Icon().Resolved();
|
||||
}
|
||||
|
||||
Windows::Foundation::IReference<winrt::Windows::UI::Color> TerminalPaneContent::TabColor() const noexcept
|
||||
|
||||
@ -40,8 +40,7 @@ static const std::array settingsLoadWarningsLabels{
|
||||
USES_RESOURCE(L"MissingDefaultProfileText"),
|
||||
USES_RESOURCE(L"DuplicateProfileText"),
|
||||
USES_RESOURCE(L"UnknownColorSchemeText"),
|
||||
USES_RESOURCE(L"InvalidBackgroundImage"),
|
||||
USES_RESOURCE(L"InvalidIcon"),
|
||||
USES_RESOURCE(L"InvalidMediaResource"),
|
||||
USES_RESOURCE(L"AtLeastOneKeybindingWarning"),
|
||||
USES_RESOURCE(L"TooManyKeysForChord"),
|
||||
USES_RESOURCE(L"MissingRequiredParameter"),
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon.Resolved), Mode=OneTime}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind Name}" />
|
||||
|
||||
@ -254,9 +254,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// Cache the original BG image path. If the user clicks "Use desktop
|
||||
// wallpaper", then un-checks it, this is the string we'll restore to
|
||||
// them.
|
||||
if (BackgroundImagePath() != L"desktopWallpaper")
|
||||
if (BackgroundImagePath().Path() != L"desktopWallpaper")
|
||||
{
|
||||
_lastBgImagePath = BackgroundImagePath();
|
||||
_lastBgImagePath = BackgroundImagePath().Path();
|
||||
}
|
||||
}
|
||||
|
||||
@ -913,7 +913,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path)
|
||||
{
|
||||
BackgroundImagePath(path);
|
||||
_appearance.BackgroundImagePath(Model::MediaResourceHelper::FromString(path));
|
||||
_NotifyChanges(L"BackgroundImagePath");
|
||||
}
|
||||
|
||||
hstring AppearanceViewModel::BackgroundImageAlignmentCurrentValue() const
|
||||
@ -956,7 +957,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
hstring AppearanceViewModel::CurrentBackgroundImagePath() const
|
||||
{
|
||||
const auto bgImagePath = BackgroundImagePath();
|
||||
const auto bgImagePath = BackgroundImagePath().Path();
|
||||
if (bgImagePath.empty())
|
||||
{
|
||||
return RS_(L"Appearance_BackgroundImageNone");
|
||||
@ -970,7 +971,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
bool AppearanceViewModel::UseDesktopBGImage() const
|
||||
{
|
||||
return BackgroundImagePath() == L"desktopWallpaper";
|
||||
return BackgroundImagePath().Path() == L"desktopWallpaper";
|
||||
}
|
||||
|
||||
void AppearanceViewModel::UseDesktopBGImage(const bool useDesktop)
|
||||
@ -983,23 +984,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
//
|
||||
// Only stash this value if it's not the special "desktopWallpaper"
|
||||
// value.
|
||||
if (BackgroundImagePath() != L"desktopWallpaper")
|
||||
if (BackgroundImagePath().Path() != L"desktopWallpaper")
|
||||
{
|
||||
_lastBgImagePath = BackgroundImagePath();
|
||||
_lastBgImagePath = BackgroundImagePath().Path();
|
||||
}
|
||||
BackgroundImagePath(L"desktopWallpaper");
|
||||
SetBackgroundImagePath(L"desktopWallpaper");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore the path we had previously cached. This might be the
|
||||
// empty string.
|
||||
BackgroundImagePath(_lastBgImagePath);
|
||||
SetBackgroundImagePath(_lastBgImagePath);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppearanceViewModel::BackgroundImageSettingsVisible() const
|
||||
{
|
||||
return !BackgroundImagePath().empty();
|
||||
return !BackgroundImagePath().Path().empty();
|
||||
}
|
||||
|
||||
void AppearanceViewModel::ClearColorScheme()
|
||||
@ -1424,7 +1425,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
auto file = co_await OpenImagePicker(parentHwnd);
|
||||
if (!file.empty())
|
||||
{
|
||||
Appearance().BackgroundImagePath(file);
|
||||
Appearance().SetBackgroundImagePath(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Boolean, RetroTerminalEffect);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Core.CursorStyle, CursorShape);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(UInt32, CursorHeight);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, BackgroundImagePath);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.IMediaResource, BackgroundImagePath);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, BackgroundImageOpacity);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Xaml.Media.Stretch, BackgroundImageStretchMode);
|
||||
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.ConvergedAlignment, BackgroundImageAlignment);
|
||||
|
||||
@ -542,7 +542,7 @@
|
||||
IsEnabled="{x:Bind mtu:Converters.InvertBoolean(Appearance.UseDesktopBGImage), Mode=OneWay}"
|
||||
IsSpellCheckEnabled="False"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind mtu:Converters.StringOrEmptyIfPlaceholder('desktopWallpaper', Appearance.BackgroundImagePath), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
|
||||
Text="{x:Bind mtu:Converters.StringOrEmptyIfPlaceholder('desktopWallpaper', Appearance.BackgroundImagePath.Path), Mode=TwoWay, BindBack=Appearance.SetBackgroundImagePath}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Uid="Profile_BackgroundImageBrowse"
|
||||
Margin="0,10,10,0"
|
||||
|
||||
@ -217,7 +217,7 @@
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,12,0"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Profile.EvaluatedIcon), Mode=OneWay}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Profile.Icon.Resolved), Mode=OneWay}" />
|
||||
|
||||
<TextBlock Text="{x:Bind Profile.Name, Mode=OneWay}" />
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneTime}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon.Resolved), Mode=OneTime}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind Name}" />
|
||||
|
||||
@ -840,7 +840,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
if (auto menuItem{ weakMenuItem.get() })
|
||||
{
|
||||
const auto& tag{ menuItem.Tag().as<Editor::ProfileViewModel>() };
|
||||
if (args.PropertyName() == L"Icon" || args.PropertyName() == L"EvaluatedIcon")
|
||||
if (args.PropertyName() == L"Icon")
|
||||
{
|
||||
menuItem.Icon(UI::IconPathConverter::IconWUX(tag.EvaluatedIcon()));
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
VerticalAlignment="Center"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(ProfileEntry.Profile.EvaluatedIcon), Mode=OneTime}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(ProfileEntry.Profile.Icon.Resolved), Mode=OneTime}" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{x:Bind ProfileEntry.Profile.Name, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
@ -379,7 +379,7 @@
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" />
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon.Resolved), Mode=OneTime}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind Name}" />
|
||||
|
||||
@ -566,7 +566,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
const auto actionID = _ActionEntry.ActionId();
|
||||
if (const auto& action = _Settings.ActionMap().GetActionByID(actionID))
|
||||
{
|
||||
return action.IconPath();
|
||||
return action.Icon().Resolved();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -132,8 +132,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
bool Inlining() const;
|
||||
void Inlining(bool value);
|
||||
|
||||
hstring Icon() const { return _FolderEntry.Icon().Path(); }
|
||||
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, Name);
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, Icon);
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, AllowEmpty);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel>, Entries);
|
||||
|
||||
@ -83,7 +83,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
FolderEntryViewModel(Microsoft.Terminal.Settings.Model.FolderEntry folderEntry, Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
String Name;
|
||||
String Icon;
|
||||
String Icon { get; };
|
||||
Boolean Inlining;
|
||||
Boolean AllowEmpty;
|
||||
IObservableVector<Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel> Entries;
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include "../WinRTUtils/inc/Utils.h"
|
||||
#include "../../renderer/base/FontCache.h"
|
||||
#include "SegoeFluentIconList.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@ -94,6 +95,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// _DeduceCurrentIconType() ends with a "CurrentIconType" notification
|
||||
// so we don't need to call _UpdateIconPreview() here
|
||||
_DeduceCurrentIconType();
|
||||
// The icon changed; let's re-evaluate it with its new context.
|
||||
_appSettings.ResolveMediaResources();
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentIconType")
|
||||
{
|
||||
@ -109,11 +112,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentBuiltInIcon")
|
||||
{
|
||||
Icon(unbox_value<hstring>(_CurrentBuiltInIcon.as<Editor::EnumEntry>().EnumValue()));
|
||||
IconPath(unbox_value<hstring>(_CurrentBuiltInIcon.as<Editor::EnumEntry>().EnumValue()));
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentEmojiIcon")
|
||||
{
|
||||
Icon(CurrentEmojiIcon());
|
||||
IconPath(CurrentEmojiIcon());
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentBellSounds")
|
||||
{
|
||||
@ -182,17 +185,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void ProfileViewModel::_DeduceCurrentIconType()
|
||||
{
|
||||
const auto& profileIcon = _profile.Icon();
|
||||
const auto profileIcon = IconPath();
|
||||
if (profileIcon == HideIconValue)
|
||||
{
|
||||
_currentIconType = _IconTypes.GetAt(0);
|
||||
}
|
||||
else if (L"\uE700" <= profileIcon && profileIcon <= L"\uF8B3")
|
||||
else if (profileIcon.size() == 1 && (L'\uE700' <= til::at(profileIcon, 0) && til::at(profileIcon, 0) <= L'\uF8B3'))
|
||||
{
|
||||
_currentIconType = _IconTypes.GetAt(1);
|
||||
_DeduceCurrentBuiltInIcon();
|
||||
}
|
||||
else if (profileIcon.size() <= 2)
|
||||
else if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(profileIcon))
|
||||
{
|
||||
// We already did a range check for MDL2 Assets in the previous one,
|
||||
// so if we're out of that range but still short, assume we're an emoji
|
||||
@ -211,7 +214,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
_UpdateBuiltInIcons();
|
||||
}
|
||||
const auto& profileIcon = Icon();
|
||||
const auto profileIcon = IconPath();
|
||||
for (uint32_t i = 0; i < _BuiltInIcons.Size(); i++)
|
||||
{
|
||||
const auto& builtIn = _BuiltInIcons.GetAt(i);
|
||||
@ -282,6 +285,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
Model::TerminalSettings ProfileViewModel::TermSettings() const
|
||||
{
|
||||
// This may look pricey, but it only resolves resources that have not been visited
|
||||
// and the preview update is debounced.
|
||||
_appSettings.ResolveMediaResources();
|
||||
return Model::TerminalSettings::CreateForPreview(_appSettings, _profile);
|
||||
}
|
||||
|
||||
@ -539,7 +545,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
return RS_(L"Profile_IconTypeNone");
|
||||
}
|
||||
return Icon();
|
||||
return IconPath(); // For display as a string
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Controls::IconElement ProfileViewModel::IconPreview() const
|
||||
@ -561,7 +567,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// Stash the current value of Icon. If the user
|
||||
// switches out of then back to IconType::Image, we want
|
||||
// the path that we display in the text box to remain unchanged.
|
||||
_lastIconPath = Icon();
|
||||
_lastIconPath = IconPath();
|
||||
}
|
||||
|
||||
// Set the member here instead of after setting Icon() below!
|
||||
@ -576,7 +582,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
case IconType::None:
|
||||
{
|
||||
_profile.Icon(HideIconValue);
|
||||
IconPath(winrt::hstring{ HideIconValue });
|
||||
break;
|
||||
}
|
||||
case IconType::Image:
|
||||
@ -585,7 +591,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
// Conversely, if we switch to Image,
|
||||
// retrieve that saved value and apply it
|
||||
_profile.Icon(_lastIconPath);
|
||||
IconPath(_lastIconPath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -593,7 +599,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
if (_CurrentBuiltInIcon)
|
||||
{
|
||||
_profile.Icon(unbox_value<hstring>(_CurrentBuiltInIcon.as<Editor::EnumEntry>().EnumValue()));
|
||||
IconPath(unbox_value<hstring>(_CurrentBuiltInIcon.as<Editor::EnumEntry>().EnumValue()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
winrt::hstring EvaluatedIcon() const
|
||||
{
|
||||
return _profile.EvaluatedIcon();
|
||||
return _profile.Icon().Resolved();
|
||||
}
|
||||
Windows::Foundation::IInspectable CurrentIconType() const noexcept
|
||||
{
|
||||
@ -94,6 +94,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
bool UsingBuiltInIcon() const;
|
||||
bool UsingEmojiIcon() const;
|
||||
bool UsingImageIcon() const;
|
||||
winrt::hstring IconPath() const { return _profile.Icon().Path(); }
|
||||
void IconPath(const winrt::hstring& path)
|
||||
{
|
||||
Icon(Model::MediaResourceHelper::FromString(path));
|
||||
_NotifyChanges(L"Icon");
|
||||
}
|
||||
|
||||
// starting directory
|
||||
hstring CurrentStartingDirectoryPreview() const;
|
||||
|
||||
@ -116,6 +116,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Boolean UsingBuiltInIcon { get; };
|
||||
Boolean UsingEmojiIcon { get; };
|
||||
Boolean UsingImageIcon { get; };
|
||||
String IconPath;
|
||||
|
||||
IInspectable CurrentBuiltInIcon;
|
||||
Windows.Foundation.Collections.IVector<IInspectable> BuiltInIcons { get; };
|
||||
@ -132,7 +133,7 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Source);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, ConnectionType);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Hidden);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Icon);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.IMediaResource, Icon);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.CloseOnExitMode, CloseOnExit);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, TabTitle);
|
||||
OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference<Microsoft.Terminal.Core.Color>, TabColor);
|
||||
|
||||
@ -120,7 +120,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
auto file = co_await OpenImagePicker(parentHwnd);
|
||||
if (!file.empty())
|
||||
{
|
||||
_Profile.Icon(file);
|
||||
_Profile.IconPath(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
<!-- Icon -->
|
||||
<local:SettingContainer x:Uid="Profile_Icon"
|
||||
ClearSettingValue="{x:Bind Profile.ClearIcon}"
|
||||
CurrentValueAccessibleName="{x:Bind Profile.Icon, Mode=OneWay}"
|
||||
CurrentValueAccessibleName="{x:Bind Profile.LocalizedIcon, Mode=OneWay}"
|
||||
HasSettingValue="{x:Bind Profile.HasIcon, Mode=OneWay}"
|
||||
SettingOverrideSource="{x:Bind Profile.IconOverrideSource, Mode=OneWay}"
|
||||
Style="{StaticResource ExpanderSettingContainerStyleWithComplexPreview}">
|
||||
@ -174,7 +174,7 @@
|
||||
FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
|
||||
IsSpellCheckEnabled="False"
|
||||
Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind Profile.Icon, Mode=TwoWay}"
|
||||
Text="{x:Bind Profile.IconPath, Mode=TwoWay}"
|
||||
Visibility="{x:Bind Profile.UsingImageIcon, Mode=OneWay}" />
|
||||
<Button x:Uid="Profile_IconBrowse"
|
||||
Grid.Column="2"
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "pch.h"
|
||||
#include "ActionEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "ActionEntry.g.cpp"
|
||||
|
||||
@ -16,7 +17,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
|
||||
ActionEntry::ActionEntry() noexcept :
|
||||
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
|
||||
ActionEntryT<ActionEntry, NewTabMenuEntry, IPathlessMediaResourceContainer>(NewTabMenuEntryType::Action)
|
||||
{
|
||||
}
|
||||
|
||||
@ -25,7 +26,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _icon);
|
||||
|
||||
return json;
|
||||
}
|
||||
@ -35,7 +36,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto entry = winrt::make_self<ActionEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_icon);
|
||||
|
||||
return entry;
|
||||
}
|
||||
@ -44,7 +45,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
auto entry = winrt::make_self<ActionEntry>();
|
||||
entry->_ActionId = _ActionId;
|
||||
entry->_Icon = _Icon;
|
||||
entry->_icon = _icon;
|
||||
return *entry;
|
||||
}
|
||||
|
||||
void ActionEntry::ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (_icon)
|
||||
{
|
||||
// TODO GH#19191 (Hardcoded Origin, since that's the only place it could have come from)
|
||||
ResolveIconMediaResource(OriginTag::User, basePath, _icon, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,10 +16,11 @@ Author(s):
|
||||
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "ActionEntry.g.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct ActionEntry : ActionEntryT<ActionEntry, NewTabMenuEntry>
|
||||
struct ActionEntry : ActionEntryT<ActionEntry, NewTabMenuEntry, IPathlessMediaResourceContainer>
|
||||
{
|
||||
public:
|
||||
ActionEntry() noexcept;
|
||||
@ -29,8 +30,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver) override;
|
||||
|
||||
IMediaResource Icon() const noexcept
|
||||
{
|
||||
return _icon ? _icon : MediaResource::Empty();
|
||||
}
|
||||
|
||||
void Icon(const IMediaResource& val)
|
||||
{
|
||||
_icon = val;
|
||||
}
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, ActionId);
|
||||
WINRT_PROPERTY(winrt::hstring, Icon);
|
||||
|
||||
private:
|
||||
IMediaResource _icon;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +99,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// - have a hash that matches a command in the inbox actions
|
||||
std::erase_if(_ActionMap, [&](const auto& pair) {
|
||||
const auto userCmdImpl{ get_self<Command>(pair.second) };
|
||||
if (userCmdImpl->IDWasGenerated() && !userCmdImpl->HasName() && userCmdImpl->IconPath().empty())
|
||||
if (userCmdImpl->IDWasGenerated() && !userCmdImpl->HasName() && userCmdImpl->Icon().Path().empty())
|
||||
{
|
||||
const auto userActionHash = Hash(userCmdImpl->ActionAndArgs());
|
||||
if (const auto inboxCmd = inboxActions.find(userActionHash); inboxCmd != inboxActions.end())
|
||||
@ -535,9 +535,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
cmdImpl->Name(foundCmdImpl->Name());
|
||||
}
|
||||
if (!foundCmdImpl->IconPath().empty() && cmdImpl->IconPath().empty())
|
||||
if (!foundCmdImpl->Icon().Path().empty() && cmdImpl->Icon().Path().empty())
|
||||
{
|
||||
cmdImpl->IconPath(foundCmdImpl->IconPath());
|
||||
cmdImpl->Icon(foundCmdImpl->Icon());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -809,6 +809,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return _ExpandedCommandsCache;
|
||||
}
|
||||
|
||||
void ActionMap::ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
for (const auto& [_, cmd] : _ActionMap)
|
||||
{
|
||||
winrt::get_self<implementation::Command>(cmd)->ResolveMediaResourcesWithBasePath(basePath, resolver);
|
||||
}
|
||||
|
||||
// Serialize all nested Command objects added in the current layer
|
||||
for (const auto& [_, cmd] : _NestedCommands)
|
||||
{
|
||||
winrt::get_self<implementation::Command>(cmd)->ResolveMediaResourcesWithBasePath(basePath, resolver);
|
||||
}
|
||||
|
||||
// Serialize all iterable Command objects added in the current layer
|
||||
for (const auto& cmd : _IterableCommands)
|
||||
{
|
||||
winrt::get_self<implementation::Command>(cmd)->ResolveMediaResourcesWithBasePath(basePath, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region Snippets
|
||||
std::vector<Model::Command> _filterToSnippets(IMapView<hstring, Model::Command> nameMap,
|
||||
winrt::hstring currentCommandline,
|
||||
|
||||
@ -87,6 +87,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Foundation::Collections::IVector<Model::Command>> FilterToSnippets(winrt::hstring currentCommandline, winrt::hstring currentWorkingDirectory);
|
||||
|
||||
void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver);
|
||||
|
||||
private:
|
||||
Model::Command _GetActionByID(const winrt::hstring& actionID) const;
|
||||
std::optional<winrt::hstring> _GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "Command.idl";
|
||||
import "ISettingsModelObject.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
@ -25,7 +26,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Windows.Foundation.IAsyncOperation<IVector<Command> > FilterToSnippets(String CurrentCommandline, String CurrentWorkingDirectory);
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionMap : IActionMapView
|
||||
runtimeclass ActionMap : IActionMapView
|
||||
{
|
||||
void RebindKeys(Microsoft.Terminal.Control.KeyChord oldKeys, Microsoft.Terminal.Control.KeyChord newKeys);
|
||||
void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys);
|
||||
|
||||
@ -6,9 +6,12 @@
|
||||
#include "AppearanceConfig.g.cpp"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "Profile.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
@ -20,12 +23,12 @@ static constexpr std::string_view LegacyAcrylicTransparencyKey{ "acrylicOpacity"
|
||||
static constexpr std::string_view OpacityKey{ "opacity" };
|
||||
static constexpr std::string_view ColorSchemeKey{ "colorScheme" };
|
||||
|
||||
AppearanceConfig::AppearanceConfig(winrt::weak_ref<Profile> sourceProfile) :
|
||||
AppearanceConfig::AppearanceConfig(winrt::weak_ref<Model::Profile> sourceProfile) :
|
||||
_sourceProfile(std::move(sourceProfile))
|
||||
{
|
||||
}
|
||||
|
||||
winrt::com_ptr<AppearanceConfig> AppearanceConfig::CopyAppearance(const AppearanceConfig* source, winrt::weak_ref<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;
|
||||
@ -135,40 +138,35 @@ winrt::Microsoft::Terminal::Settings::Model::Profile AppearanceConfig::SourcePro
|
||||
return _sourceProfile.get();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns this AppearanceConfig's background image path, if one is set, expanding
|
||||
// any environment variables in the path, if there are any.
|
||||
// - Or if "DesktopWallpaper" is set, then gets the path to the desktops wallpaper.
|
||||
// - This is the same as Profile::ExpandedBackgroundImagePath, but for AppearanceConfig
|
||||
// - NOTE: This is just placeholder for now, eventually the path will no longer be expanded in the settings model
|
||||
// Return Value:
|
||||
// - This profile's expanded background image path / desktops's wallpaper path /the empty string.
|
||||
winrt::hstring AppearanceConfig::ExpandedBackgroundImagePath()
|
||||
std::tuple<winrt::hstring, Model::OriginTag> AppearanceConfig::_getSourceProfileBasePathAndOrigin() const
|
||||
{
|
||||
const auto path{ BackgroundImagePath() };
|
||||
if (path.empty())
|
||||
winrt::hstring sourceBasePath{};
|
||||
OriginTag origin{ OriginTag::None };
|
||||
if (const auto profile{ _sourceProfile.get() })
|
||||
{
|
||||
return path;
|
||||
const auto profileImpl{ winrt::get_self<implementation::Profile>(profile) };
|
||||
sourceBasePath = profileImpl->SourceBasePath;
|
||||
origin = profileImpl->Origin();
|
||||
}
|
||||
// checks if the user would like to copy their desktop wallpaper
|
||||
// if so, replaces the path with the desktop wallpaper's path
|
||||
else if (path == L"desktopWallpaper")
|
||||
{
|
||||
WCHAR desktopWallpaper[MAX_PATH];
|
||||
return { sourceBasePath, origin };
|
||||
}
|
||||
|
||||
// "The returned string will not exceed MAX_PATH characters" as of 2020
|
||||
if (SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, desktopWallpaper, SPIF_UPDATEINIFILE))
|
||||
{
|
||||
return winrt::hstring{ (desktopWallpaper) };
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
else
|
||||
void AppearanceConfig::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (const auto [source, resource] = _getBackgroundImagePathOverrideSourceAndValueImpl(); source && resource && *resource)
|
||||
{
|
||||
return winrt::hstring{ wil::ExpandEnvironmentStringsW<std::wstring>(path.c_str()) };
|
||||
const auto [sourceBasePath, sourceOrigin]{ source->_getSourceProfileBasePathAndOrigin() };
|
||||
ResolveMediaResource(sourceOrigin, sourceBasePath, *resource, resolver);
|
||||
}
|
||||
if (const auto [source, resource]{ _getPixelShaderPathOverrideSourceAndValueImpl() }; source && resource && *resource)
|
||||
{
|
||||
const auto [sourceBasePath, sourceOrigin]{ source->_getSourceProfileBasePathAndOrigin() };
|
||||
ResolveMediaResource(sourceOrigin, sourceBasePath, *resource, resolver);
|
||||
}
|
||||
if (const auto [source, resource]{ _getPixelShaderImagePathOverrideSourceAndValueImpl() }; source && resource && *resource)
|
||||
{
|
||||
const auto [sourceBasePath, sourceOrigin]{ source->_getSourceProfileBasePathAndOrigin() };
|
||||
ResolveMediaResource(sourceOrigin, sourceBasePath, *resource, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,11 +20,12 @@ Author(s):
|
||||
#include "JsonUtils.h"
|
||||
#include "IInheritable.h"
|
||||
#include "MTSMSettings.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
#include <DefaultSettings.h>
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct AppearanceConfig : AppearanceConfigT<AppearanceConfig>, IInheritable<AppearanceConfig>
|
||||
struct AppearanceConfig : AppearanceConfigT<AppearanceConfig, IMediaResourceContainer>, IInheritable<AppearanceConfig>
|
||||
{
|
||||
public:
|
||||
AppearanceConfig(winrt::weak_ref<Profile> sourceProfile);
|
||||
@ -35,7 +36,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
Model::Profile SourceProfile();
|
||||
|
||||
winrt::hstring ExpandedBackgroundImagePath();
|
||||
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);
|
||||
@ -57,5 +58,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
void _logSettingIfSet(const std::string_view& setting, const bool isSet);
|
||||
|
||||
std::tuple<winrt::hstring, Model::OriginTag> _getSourceProfileBasePathAndOrigin() const;
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,6 +5,6 @@ import "IAppearanceConfig.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
[default_interface] runtimeclass AppearanceConfig : IAppearanceConfig {
|
||||
runtimeclass AppearanceConfig : [default] IAppearanceConfig {
|
||||
}
|
||||
}
|
||||
|
||||
@ -488,134 +488,159 @@ void CascadiaSettings::_validateAllSchemesExist()
|
||||
}
|
||||
}
|
||||
|
||||
static bool _validateSingleMediaResource(std::wstring_view resource)
|
||||
extern bool TestHook_CascadiaSettings_ResolveSingleMediaResource(Model::OriginTag origin, std::wstring_view basePath, const Model::IMediaResource& resource);
|
||||
|
||||
static void _resolveSingleMediaResourceInner(Model::OriginTag origin, std::wstring_view basePath, const Model::IMediaResource& resource)
|
||||
{
|
||||
// URI
|
||||
try
|
||||
if (TestHook_CascadiaSettings_ResolveSingleMediaResource(origin, basePath, resource))
|
||||
{
|
||||
winrt::Windows::Foundation::Uri resourceUri{ resource };
|
||||
if (!resourceUri)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (Feature_DisableWebSourceIcons::IsEnabled())
|
||||
{
|
||||
const auto scheme{ resourceUri.SchemeName() };
|
||||
// Only file: URIs and ms-* URIs are permissible. http, https, ftp, gopher, etc. are not.
|
||||
return til::equals_insensitive_ascii(scheme, L"file") || til::starts_with_insensitive_ascii(scheme, L"ms-");
|
||||
}
|
||||
|
||||
return true;
|
||||
// See the implementation in TestHooks.cpp. Link-time Code Generation (LTCG) will delete this entire call
|
||||
// and the test hook function itself.
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
|
||||
auto resourcePath{ resource.Path() };
|
||||
|
||||
if (til::equals_insensitive_ascii(resourcePath, L"desktopWallpaper"))
|
||||
{
|
||||
// fall through
|
||||
WCHAR desktopWallpaper[MAX_PATH];
|
||||
|
||||
// "The returned string will not exceed MAX_PATH characters" as of 2020
|
||||
if (SystemParametersInfoW(SPI_GETDESKWALLPAPER, MAX_PATH, desktopWallpaper, SPIF_UPDATEINIFILE))
|
||||
{
|
||||
resource.Resolve(winrt::hstring{ &desktopWallpaper[0] });
|
||||
}
|
||||
else
|
||||
{
|
||||
resource.Reject();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (til::equals_insensitive_ascii(resourcePath, L"none"))
|
||||
{
|
||||
// Resolve "none" to the OK Empty string.
|
||||
resource.Resolve({});
|
||||
return;
|
||||
}
|
||||
else if (resourcePath.empty())
|
||||
{
|
||||
// Do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
resourcePath = wil::ExpandEnvironmentStringsW<std::wstring>(resourcePath.data());
|
||||
const auto colon{ std::wstring_view{ resourcePath }.find_first_of(L':') };
|
||||
|
||||
// URI (contains a :, but only after a reasonable distance for a schema)
|
||||
if (colon != std::wstring_view::npos && colon >= 4)
|
||||
{
|
||||
try
|
||||
{
|
||||
const winrt::Windows::Foundation::Uri resourceUri{ resourcePath };
|
||||
if (!resourceUri)
|
||||
{
|
||||
resource.Reject();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scheme{ resourceUri.SchemeName() };
|
||||
if (til::starts_with_insensitive_ascii(scheme, L"http") ||
|
||||
(til::equals_insensitive_ascii(scheme, L"ms-appx") && !resourceUri.Domain().empty()))
|
||||
{
|
||||
// http(s) URLs (WSL Distro AppX fragments) and ms-appx://APPLICATION/ (Julia) URLs decay to fragment-relative paths
|
||||
const auto path{ resourceUri.Path() };
|
||||
const std::wstring_view pathView{ path };
|
||||
const std::wstring_view file{ pathView.substr(pathView.find_last_of(L'/') + 1) };
|
||||
|
||||
resourcePath = winrt::hstring{ file };
|
||||
// FALL THROUGH TO TRY FILESYSTEM PATHS
|
||||
}
|
||||
else if (til::equals_insensitive_ascii(scheme, L"file"))
|
||||
{
|
||||
const auto uriPath{ resourceUri.Path() };
|
||||
if (uriPath.size() < 2)
|
||||
{
|
||||
resource.Reject();
|
||||
return;
|
||||
}
|
||||
// Uri mangles file paths to begin with a / (ala /C:/) and escapes special characters such as Space.
|
||||
// Try to un-mangle it.
|
||||
resourcePath = til::safe_slice_abs(winrt::Windows::Foundation::Uri::UnescapeComponent(uriPath), 1, SIZE_T_MAX);
|
||||
// FALL THROUGH TO TRY FILESYSTEM PATHS
|
||||
}
|
||||
else if (!til::starts_with_insensitive_ascii(scheme, L"ms-"))
|
||||
{
|
||||
// Other non-file and non-ms* URLs are disallowed
|
||||
resource.Reject();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other URLs (so, file and ms-*) are permissible.
|
||||
resource.Resolve(resourcePath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
// Not a URI? Try a path.
|
||||
try
|
||||
{
|
||||
std::filesystem::path resourcePath{ resource };
|
||||
return std::filesystem::exists(resourcePath);
|
||||
std::filesystem::path resourceAsFilesystemPath{ std::wstring_view{ resourcePath } };
|
||||
if (!basePath.empty())
|
||||
{
|
||||
resourceAsFilesystemPath = std::filesystem::path{ basePath } / resourceAsFilesystemPath;
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(resourceAsFilesystemPath))
|
||||
{
|
||||
resource.Reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resource.Resolve(winrt::hstring{ resourceAsFilesystemPath.lexically_normal().native() });
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
return false;
|
||||
|
||||
resource.Reject();
|
||||
}
|
||||
|
||||
void CascadiaSettings::_resolveSingleMediaResource(OriginTag origin, std::wstring_view basePath, const Model::IMediaResource& resource)
|
||||
{
|
||||
_resolveSingleMediaResourceInner(origin, basePath, resource);
|
||||
if (!resource.Ok() && (origin == OriginTag::User || origin == OriginTag::ProfilesDefaults))
|
||||
{
|
||||
_foundInvalidUserResources = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ensures that all specified images resources (icons and background images) are valid URIs.
|
||||
// This does not verify that the icon or background image files are encoded as an image.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
// - Appends a SettingsLoadWarnings::InvalidBackgroundImage to our list of warnings if
|
||||
// we find any invalid background images.
|
||||
// - Appends a SettingsLoadWarnings::InvalidIconImage to our list of warnings if
|
||||
// we find any invalid icon images.
|
||||
// - Ensures that all specified images resources (icons and background images) are valid.
|
||||
void CascadiaSettings::_validateMediaResources()
|
||||
{
|
||||
auto warnInvalidBackground{ false };
|
||||
auto warnInvalidIcon{ false };
|
||||
_foundInvalidUserResources = false;
|
||||
|
||||
for (auto profile : _allProfiles)
|
||||
const MediaResourceResolver mediaResourceResolver{ this, &CascadiaSettings::_resolveSingleMediaResource };
|
||||
|
||||
for (const auto& profile : _allProfiles)
|
||||
{
|
||||
if (const auto path = profile.DefaultAppearance().ExpandedBackgroundImagePath(); !path.empty())
|
||||
{
|
||||
if (!_validateSingleMediaResource(path))
|
||||
{
|
||||
if (profile.DefaultAppearance().HasBackgroundImagePath())
|
||||
{
|
||||
// Only warn and delete if the user set this at the top level (do not warn for fragments, just clear it)
|
||||
warnInvalidBackground = true;
|
||||
profile.DefaultAppearance().ClearBackgroundImagePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
// reset background image path (set it to blank as an override for any fragment value)
|
||||
profile.DefaultAppearance().BackgroundImagePath({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.UnfocusedAppearance())
|
||||
{
|
||||
if (const auto path = profile.UnfocusedAppearance().ExpandedBackgroundImagePath(); !path.empty())
|
||||
{
|
||||
if (!_validateSingleMediaResource(path))
|
||||
{
|
||||
if (profile.UnfocusedAppearance().HasBackgroundImagePath())
|
||||
{
|
||||
warnInvalidBackground = true;
|
||||
profile.UnfocusedAppearance().ClearBackgroundImagePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
// reset background image path (set it to blank as an override for any fragment value)
|
||||
profile.UnfocusedAppearance().BackgroundImagePath({});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Anything longer than 2 wchar_t's _isn't_ an emoji or symbol, so treat
|
||||
// it as an invalid path.
|
||||
//
|
||||
// Explicitly just use the Icon here, not the EvaluatedIcon. We don't
|
||||
// want to blow up if we fell back to the commandline and the
|
||||
// commandline _isn't an icon_.
|
||||
// GH #17943: "none" is a special value interpreted as "remove the icon"
|
||||
static constexpr std::wstring_view HideIconValue{ L"none" };
|
||||
if (const auto icon = profile.Icon(); icon.size() > 2 && icon != HideIconValue)
|
||||
{
|
||||
const auto iconPath{ wil::ExpandEnvironmentStringsW<std::wstring>(icon.c_str()) };
|
||||
if (!_validateSingleMediaResource(iconPath))
|
||||
{
|
||||
if (profile.HasIcon())
|
||||
{
|
||||
warnInvalidIcon = true;
|
||||
profile.ClearIcon();
|
||||
}
|
||||
else
|
||||
{
|
||||
profile.Icon({});
|
||||
}
|
||||
}
|
||||
}
|
||||
profile.as<IMediaResourceContainer>()->ResolveMediaResources(mediaResourceResolver);
|
||||
}
|
||||
|
||||
if (warnInvalidBackground)
|
||||
{
|
||||
_warnings.Append(SettingsLoadWarnings::InvalidBackgroundImage);
|
||||
}
|
||||
_globals->ResolveMediaResources(mediaResourceResolver);
|
||||
|
||||
if (warnInvalidIcon)
|
||||
if (_foundInvalidUserResources)
|
||||
{
|
||||
_warnings.Append(SettingsLoadWarnings::InvalidIcon);
|
||||
_warnings.Append(SettingsLoadWarnings::InvalidMediaResource);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void ApplyRuntimeInitialSettings();
|
||||
void MergeInboxIntoUserSettings();
|
||||
void FindFragmentsAndMergeIntoUserSettings(bool generateExtensionPackages);
|
||||
void MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content);
|
||||
void MergeFragmentIntoUserSettings(const winrt::hstring& source, const winrt::hstring& basePath, const std::string_view& content);
|
||||
void FinalizeLayering();
|
||||
bool DisableDeletedProfiles();
|
||||
bool RemapColorSchemeForProfile(const winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Profile>& profile);
|
||||
@ -123,7 +123,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
static const Json::Value& _getJSONValue(const Json::Value& json, const std::string_view& key) noexcept;
|
||||
std::span<const winrt::com_ptr<implementation::Profile>> _getNonUserOriginProfiles() const;
|
||||
void _parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
|
||||
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta);
|
||||
void _parseFragment(const winrt::hstring& source, const winrt::hstring& sourceBasePath, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta);
|
||||
static JsonSettings _parseJson(const std::string_view& content);
|
||||
static winrt::com_ptr<implementation::Profile> _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson);
|
||||
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
|
||||
@ -191,6 +191,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void CurrentDefaultTerminal(const Model::DefaultTerminal& terminal);
|
||||
|
||||
void ExpandCommands();
|
||||
void ResolveMediaResources() { _validateMediaResources(); }
|
||||
|
||||
void LogSettingChanges(bool isJsonLoad) const;
|
||||
|
||||
@ -210,6 +211,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void _validateSettings();
|
||||
void _validateAllSchemesExist();
|
||||
void _resolveSingleMediaResource(OriginTag origin, std::wstring_view basePath, const Model::IMediaResource& resource);
|
||||
void _validateMediaResources();
|
||||
void _validateProfileEnvironmentVariables();
|
||||
void _validateKeybindings() const;
|
||||
@ -233,6 +235,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::SettingsLoadWarnings> _warnings = winrt::single_threaded_vector<Model::SettingsLoadWarnings>();
|
||||
winrt::Windows::Foundation::IReference<Model::SettingsLoadErrors> _loadError;
|
||||
winrt::hstring _deserializationErrorMessage;
|
||||
bool _foundInvalidUserResources{ false };
|
||||
|
||||
// defterm
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<Model::DefaultTerminal> _defaultTerminals{ nullptr };
|
||||
|
||||
@ -14,7 +14,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Machine
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass CascadiaSettings {
|
||||
runtimeclass CascadiaSettings {
|
||||
static CascadiaSettings LoadDefaults();
|
||||
static CascadiaSettings LoadAll();
|
||||
|
||||
@ -64,6 +64,8 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
DefaultTerminal CurrentDefaultTerminal;
|
||||
|
||||
void ExpandCommands();
|
||||
|
||||
void ResolveMediaResources();
|
||||
}
|
||||
|
||||
runtimeclass FragmentProfileEntry
|
||||
|
||||
@ -329,6 +329,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensio
|
||||
ParsedSettings fragmentSettings;
|
||||
|
||||
const auto parseAndLayerFragmentFiles = [&](const std::filesystem::path& path, const winrt::hstring& source, FragmentScope scope) {
|
||||
const winrt::hstring sourceBasePath{ path.native() };
|
||||
for (const auto& fragmentExt : std::filesystem::directory_iterator{ path })
|
||||
{
|
||||
const auto fragExtPath = fragmentExt.path();
|
||||
@ -340,6 +341,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensio
|
||||
if (!content.empty())
|
||||
{
|
||||
_parseFragment(source,
|
||||
sourceBasePath,
|
||||
content,
|
||||
fragmentSettings,
|
||||
generateExtensionPackages ?
|
||||
@ -448,10 +450,10 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings(bool generateExtensio
|
||||
// See FindFragmentsAndMergeIntoUserSettings.
|
||||
// This function does the same, but for a single given JSON blob and source
|
||||
// and at the time of writing is used for unit tests only.
|
||||
void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content)
|
||||
void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, const winrt::hstring& basePath, const std::string_view& content)
|
||||
{
|
||||
ParsedSettings fragmentSettings;
|
||||
_parseFragment(source, content, fragmentSettings, std::nullopt);
|
||||
_parseFragment(source, basePath, content, fragmentSettings, std::nullopt);
|
||||
}
|
||||
|
||||
// Call this method before passing SettingsLoader to the CascadiaSettings constructor.
|
||||
@ -627,7 +629,7 @@ bool SettingsLoader::FixupUserSettings()
|
||||
{
|
||||
for (auto&& icon : iconsToClearFromVisualStudioProfiles)
|
||||
{
|
||||
if (profile->Icon() == icon)
|
||||
if (profile->Icon().Path() == icon)
|
||||
{
|
||||
profile->ClearIcon();
|
||||
fixedUp = true;
|
||||
@ -832,7 +834,7 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source
|
||||
// schemes and profiles. Additionally this function supports profiles which specify an "updates" key.
|
||||
// - fragmentMeta: If set, construct and register FragmentSettings objects. Provides metadata necessary for doing so.
|
||||
// Otherwise, completely skip over that extra work and apply parsed settings to the user settings, if allowed by disabledProfileSources ("_ignoredNamespaces").
|
||||
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta)
|
||||
void SettingsLoader::_parseFragment(const winrt::hstring& source, const winrt::hstring& sourceBasePath, const std::string_view& content, ParsedSettings& settings, const std::optional<ParseFragmentMetadata>& fragmentMeta)
|
||||
{
|
||||
auto json = _parseJson(content);
|
||||
|
||||
@ -880,6 +882,7 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str
|
||||
// parsing - fragments shouldn't be allowed to bind actions to keys
|
||||
// directly. We may want to revisit circa GH#2205
|
||||
settings.globals = winrt::make_self<GlobalAppSettings>();
|
||||
settings.globals->SourceBasePath = sourceBasePath;
|
||||
settings.globals->LayerActionsFrom(json.root, OriginTag::Fragment, false);
|
||||
}
|
||||
}
|
||||
@ -908,6 +911,7 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str
|
||||
auto destinationSet = profile->HasGuid() ? &newProfiles : &modifiedProfiles;
|
||||
if (guid != winrt::guid{})
|
||||
{
|
||||
profile->SourceBasePath = sourceBasePath;
|
||||
if (buildFragmentSettings)
|
||||
{
|
||||
destinationSet->emplace_back(winrt::make<FragmentProfileEntry>(guid, hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) }));
|
||||
@ -1196,6 +1200,14 @@ try
|
||||
|
||||
SettingsLoader loader{ settingsStringView, LoadStringResource(IDR_DEFAULTS) };
|
||||
|
||||
winrt::hstring baseUserSettingsPath{ GetBaseSettingsPath().native() };
|
||||
loader.userSettings.baseLayerProfile->SourceBasePath = baseUserSettingsPath;
|
||||
loader.userSettings.globals->SourceBasePath = baseUserSettingsPath;
|
||||
for (auto&& userProfile : loader.userSettings.profiles)
|
||||
{
|
||||
userProfile->SourceBasePath = baseUserSettingsPath;
|
||||
}
|
||||
|
||||
// Generate dynamic profiles and add them as parents of user profiles.
|
||||
// That way the user profiles will get appropriate defaults from the generators (like icons and such).
|
||||
loader.GenerateProfiles();
|
||||
|
||||
@ -91,7 +91,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
command->_Origin = _Origin;
|
||||
command->_ID = _ID;
|
||||
command->_ActionAndArgs = *get_self<implementation::ActionAndArgs>(_ActionAndArgs)->Copy();
|
||||
command->_iconPath = _iconPath;
|
||||
command->_icon = _icon;
|
||||
command->_IterateOn = _IterateOn;
|
||||
command->_Description = _Description;
|
||||
|
||||
@ -223,21 +223,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
}
|
||||
|
||||
hstring Command::IconPath() const noexcept
|
||||
IMediaResource Command::Icon() const noexcept
|
||||
{
|
||||
if (_iconPath.has_value())
|
||||
{
|
||||
return hstring{ *_iconPath };
|
||||
}
|
||||
return {};
|
||||
return (_icon && *_icon) ? *_icon : MediaResource::Empty();
|
||||
}
|
||||
|
||||
void Command::IconPath(const hstring& val)
|
||||
void Command::Icon(const IMediaResource& val)
|
||||
{
|
||||
if (!_iconPath.has_value() || _iconPath.value() != val)
|
||||
{
|
||||
_iconPath = val;
|
||||
}
|
||||
_icon = val;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@ -297,7 +290,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
result->_nestedCommand = true;
|
||||
}
|
||||
|
||||
JsonUtils::GetValueForKey(json, IconKey, result->_iconPath);
|
||||
JsonUtils::GetValueForKey(json, IconKey, result->_icon);
|
||||
|
||||
// If we're a nested command, we can ignore the current action.
|
||||
if (!nested)
|
||||
@ -347,7 +340,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
JsonUtils::GetValueForKey(json, IDKey, result->_ID);
|
||||
JsonUtils::GetValueForKey(json, DescriptionKey, result->_Description);
|
||||
JsonUtils::GetValueForKey(json, IconKey, result->_iconPath);
|
||||
JsonUtils::GetValueForKey(json, IconKey, result->_icon);
|
||||
JsonUtils::GetValueForKey(json, NameKey, result->_name);
|
||||
|
||||
const auto action{ ShortcutAction::SendInput };
|
||||
@ -426,7 +419,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath);
|
||||
JsonUtils::SetValueForKey(cmdJson, IconKey, _icon);
|
||||
JsonUtils::SetValueForKey(cmdJson, NameKey, _name);
|
||||
if (!_Description.empty())
|
||||
{
|
||||
@ -597,7 +590,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
// - Escape the profile name for JSON appropriately
|
||||
auto escapedProfileName = _escapeForJson(til::u16u8(p.Name()));
|
||||
auto escapedProfileIcon = _escapeForJson(til::u16u8(p.EvaluatedIcon()));
|
||||
// Use the fully resolved icon here so it doesn't have to go around the resolver again
|
||||
auto escapedProfileIcon = _escapeForJson(til::u16u8(p.Icon().Resolved()));
|
||||
auto newJsonString = til::replace_needle_in_haystack(oldJsonString,
|
||||
ProfileNameToken,
|
||||
escapedProfileName);
|
||||
@ -638,6 +632,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return newCommands;
|
||||
}
|
||||
|
||||
void Command::ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (_icon && *_icon)
|
||||
{
|
||||
ResolveIconMediaResource(_Origin, basePath, *_icon, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::Command> Command::ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength)
|
||||
{
|
||||
if (json.empty())
|
||||
@ -686,34 +688,34 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
switch (resultType)
|
||||
{
|
||||
case 1: // History -> 0xe81c History
|
||||
c->_iconPath = L"\ue81c";
|
||||
c->_icon = MediaResource::FromString(L"\ue81c");
|
||||
break;
|
||||
case 2: // Command -> 0xecaa AppIconDefault
|
||||
c->_iconPath = L"\uecaa";
|
||||
c->_icon = MediaResource::FromString(L"\uecaa");
|
||||
break;
|
||||
case 3: // ProviderItem -> 0xe8e4 AlignLeft
|
||||
c->_iconPath = L"\ue8e4";
|
||||
c->_icon = MediaResource::FromString(L"\ue8e4");
|
||||
break;
|
||||
case 4: // ProviderContainer -> 0xe838 FolderOpen
|
||||
c->_iconPath = L"\ue838";
|
||||
c->_icon = MediaResource::FromString(L"\ue838");
|
||||
break;
|
||||
case 5: // Property -> 0xe7c1 Flag
|
||||
c->_iconPath = L"\ue7c1";
|
||||
c->_icon = MediaResource::FromString(L"\ue7c1");
|
||||
break;
|
||||
case 6: // Method -> 0xecaa AppIconDefault
|
||||
c->_iconPath = L"\uecaa";
|
||||
c->_icon = MediaResource::FromString(L"\uecaa");
|
||||
break;
|
||||
case 7: // ParameterName -> 0xe7c1 Flag
|
||||
c->_iconPath = L"\ue7c1";
|
||||
c->_icon = MediaResource::FromString(L"\ue7c1");
|
||||
break;
|
||||
case 8: // ParameterValue -> 0xf000 KnowledgeArticle
|
||||
c->_iconPath = L"\uf000";
|
||||
c->_icon = MediaResource::FromString(L"\uf000");
|
||||
break;
|
||||
case 10: // Namespace -> 0xe943 Code
|
||||
c->_iconPath = L"\ue943";
|
||||
c->_icon = MediaResource::FromString(L"\ue943");
|
||||
break;
|
||||
case 13: // DynamicKeyword -> 0xe945 LightningBolt
|
||||
c->_iconPath = L"\ue945";
|
||||
c->_icon = MediaResource::FromString(L"\ue945");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -779,7 +781,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto command = winrt::make_self<Command>();
|
||||
command->_ActionAndArgs = actionAndArgs;
|
||||
command->_name = CommandNameOrResource{ .name = std::wstring{ line } };
|
||||
command->_iconPath = iconPath;
|
||||
command->_icon = MediaResource::FromString(iconPath);
|
||||
result.push_back(*command);
|
||||
foundCommands[line] = true;
|
||||
}
|
||||
|
||||
@ -83,8 +83,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void GenerateID();
|
||||
bool IDWasGenerated();
|
||||
|
||||
hstring IconPath() const noexcept;
|
||||
void IconPath(const hstring& val);
|
||||
IMediaResource Icon() const noexcept;
|
||||
void Icon(const IMediaResource& val);
|
||||
|
||||
void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver);
|
||||
|
||||
static Windows::Foundation::Collections::IVector<Model::Command> ParsePowerShellMenuComplete(winrt::hstring json, int32_t replaceLength);
|
||||
static Windows::Foundation::Collections::IVector<Model::Command> HistoryToCommands(Windows::Foundation::Collections::IVector<winrt::hstring> history,
|
||||
@ -103,7 +105,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
std::optional<CommandNameOrResource> _name;
|
||||
std::wstring _ID;
|
||||
bool _IDWasGenerated{ false };
|
||||
std::optional<std::wstring> _iconPath;
|
||||
std::optional<IMediaResource> _icon;
|
||||
bool _nestedCommand{ false };
|
||||
|
||||
static std::vector<Model::Command> _expandCommand(Command* const expandable,
|
||||
|
||||
@ -21,7 +21,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
#undef ON_ALL_ACTIONS
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionAndArgs {
|
||||
runtimeclass ActionAndArgs {
|
||||
ActionAndArgs();
|
||||
ActionAndArgs(ShortcutAction action, IActionArgs args);
|
||||
|
||||
@ -32,7 +32,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
ShortcutAction Action;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Command : ISettingsModelObject
|
||||
runtimeclass Command : ISettingsModelObject
|
||||
{
|
||||
Command();
|
||||
|
||||
@ -44,7 +44,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
ActionAndArgs ActionAndArgs { get; };
|
||||
|
||||
String IconPath;
|
||||
IMediaResource Icon;
|
||||
|
||||
Boolean HasNestedCommands { get; };
|
||||
Windows.Foundation.Collections.IMapView<String, Command> NestedCommands { get; };
|
||||
|
||||
@ -25,7 +25,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
|
||||
FolderEntry::FolderEntry(const winrt::hstring& name) noexcept :
|
||||
FolderEntryT<FolderEntry, NewTabMenuEntry>(NewTabMenuEntryType::Folder),
|
||||
FolderEntryT<FolderEntry, NewTabMenuEntry, IPathlessMediaResourceContainer>(NewTabMenuEntryType::Folder),
|
||||
_Name{ name }
|
||||
{
|
||||
}
|
||||
@ -35,7 +35,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _icon);
|
||||
JsonUtils::SetValueForKey(json, EntriesKey, _RawEntries);
|
||||
JsonUtils::SetValueForKey(json, InliningKey, _Inlining);
|
||||
JsonUtils::SetValueForKey(json, AllowEmptyKey, _AllowEmpty);
|
||||
@ -48,7 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto entry = winrt::make_self<FolderEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_icon);
|
||||
JsonUtils::GetValueForKey(json, EntriesKey, entry->_RawEntries);
|
||||
JsonUtils::GetValueForKey(json, InliningKey, entry->_Inlining);
|
||||
JsonUtils::GetValueForKey(json, AllowEmptyKey, entry->_AllowEmpty);
|
||||
@ -127,7 +127,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
auto entry = winrt::make_self<FolderEntry>();
|
||||
entry->_Name = _Name;
|
||||
entry->_Icon = _Icon;
|
||||
entry->_icon = _icon;
|
||||
entry->_Inlining = _Inlining;
|
||||
entry->_AllowEmpty = _AllowEmpty;
|
||||
|
||||
@ -141,4 +141,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
return *entry;
|
||||
}
|
||||
|
||||
void FolderEntry::ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (_icon)
|
||||
{
|
||||
// TODO GH#19191 (Hardcoded Origin, since that's the only place it could have come from)
|
||||
ResolveIconMediaResource(OriginTag::User, basePath, _icon, resolver);
|
||||
}
|
||||
|
||||
if (_RawEntries)
|
||||
{
|
||||
for (const auto& entry : _RawEntries)
|
||||
{
|
||||
if (const auto resolvable{ entry.try_as<IPathlessMediaResourceContainer>() })
|
||||
{
|
||||
resolvable->ResolveMediaResourcesWithBasePath(basePath, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,10 +17,11 @@ Author(s):
|
||||
#include "pch.h"
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "FolderEntry.g.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct FolderEntry : FolderEntryT<FolderEntry, NewTabMenuEntry>
|
||||
struct FolderEntry : FolderEntryT<FolderEntry, NewTabMenuEntry, IPathlessMediaResourceContainer>
|
||||
{
|
||||
public:
|
||||
FolderEntry() noexcept;
|
||||
@ -37,11 +38,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// entries to be rendered to WinRT.
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> Entries() const;
|
||||
|
||||
void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver) override;
|
||||
|
||||
IMediaResource Icon() const noexcept
|
||||
{
|
||||
return _icon ? _icon : MediaResource::Empty();
|
||||
}
|
||||
|
||||
void Icon(const IMediaResource& val)
|
||||
{
|
||||
_icon = val;
|
||||
}
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, Name);
|
||||
WINRT_PROPERTY(winrt::hstring, Icon);
|
||||
WINRT_PROPERTY(FolderEntryInlining, Inlining, FolderEntryInlining::Never);
|
||||
WINRT_PROPERTY(bool, AllowEmpty, false);
|
||||
WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, RawEntries);
|
||||
|
||||
private:
|
||||
IMediaResource _icon;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
#include "GlobalAppSettings.g.cpp"
|
||||
|
||||
#include "MediaResourceSupport.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
@ -378,6 +380,25 @@ bool GlobalAppSettings::ShouldUsePersistedLayout() const
|
||||
return FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout;
|
||||
}
|
||||
|
||||
void GlobalAppSettings::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
_actionMap->ResolveMediaResourcesWithBasePath(SourceBasePath, resolver);
|
||||
if (_NewTabMenu)
|
||||
{
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
{
|
||||
if (const auto resolvable{ entry.try_as<IPathlessMediaResourceContainer>() })
|
||||
{
|
||||
resolvable->ResolveMediaResourcesWithBasePath(SourceBasePath, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& parent : _parents)
|
||||
{
|
||||
parent->ResolveMediaResources(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalAppSettings::_logSettingSet(const std::string_view& setting)
|
||||
{
|
||||
if (setting == "theme")
|
||||
|
||||
@ -75,6 +75,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void LogSettingChanges(std::set<std::string>& changes, const std::string_view& context) const;
|
||||
|
||||
void ResolveMediaResources(const Model::MediaResourceResolver& resolver);
|
||||
|
||||
winrt::hstring SourceBasePath;
|
||||
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
|
||||
|
||||
#define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "Profile.idl";
|
||||
import "ISettingsModelObject.idl";
|
||||
#include "IInheritable.idl.h"
|
||||
|
||||
#define INHERITABLE_APPEARANCE_SETTING(Type, Name) \
|
||||
@ -42,16 +43,15 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
INHERITABLE_APPEARANCE_SETTING(Microsoft.Terminal.Core.CursorStyle, CursorShape);
|
||||
INHERITABLE_APPEARANCE_SETTING(UInt32, CursorHeight);
|
||||
|
||||
INHERITABLE_APPEARANCE_SETTING(String, BackgroundImagePath);
|
||||
String ExpandedBackgroundImagePath { get; };
|
||||
INHERITABLE_APPEARANCE_SETTING(IMediaResource, BackgroundImagePath);
|
||||
|
||||
INHERITABLE_APPEARANCE_SETTING(Single, BackgroundImageOpacity);
|
||||
INHERITABLE_APPEARANCE_SETTING(Windows.UI.Xaml.Media.Stretch, BackgroundImageStretchMode);
|
||||
INHERITABLE_APPEARANCE_SETTING(ConvergedAlignment, BackgroundImageAlignment);
|
||||
|
||||
INHERITABLE_APPEARANCE_SETTING(Boolean, RetroTerminalEffect);
|
||||
INHERITABLE_APPEARANCE_SETTING(String, PixelShaderPath);
|
||||
INHERITABLE_APPEARANCE_SETTING(String, PixelShaderImagePath);
|
||||
INHERITABLE_APPEARANCE_SETTING(IMediaResource, PixelShaderPath);
|
||||
INHERITABLE_APPEARANCE_SETTING(IMediaResource, PixelShaderImagePath);
|
||||
INHERITABLE_APPEARANCE_SETTING(IntenseStyle, IntenseTextStyle);
|
||||
INHERITABLE_APPEARANCE_SETTING(Microsoft.Terminal.Core.AdjustTextMode, AdjustIndistinguishableColors);
|
||||
INHERITABLE_APPEARANCE_SETTING(Single, Opacity);
|
||||
|
||||
@ -97,7 +97,7 @@ public: \
|
||||
{ \
|
||||
if (auto source{ parent->_get##name##OverrideSourceImpl() }) \
|
||||
{ \
|
||||
return source; \
|
||||
return *source; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
@ -136,12 +136,12 @@ private: \
|
||||
return std::nullopt; \
|
||||
} \
|
||||
\
|
||||
projectedType _get##name##OverrideSourceImpl() const \
|
||||
auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \
|
||||
{ \
|
||||
/*we have a value*/ \
|
||||
if (_##name) \
|
||||
{ \
|
||||
return *this; \
|
||||
return get_strong(); \
|
||||
} \
|
||||
\
|
||||
/*user set value was not set*/ \
|
||||
@ -156,6 +156,29 @@ private: \
|
||||
\
|
||||
/*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 }; \
|
||||
}
|
||||
|
||||
// Use INHERITABLE_SETTING and INHERITABLE_NULLABLE_SETTING to implement
|
||||
|
||||
@ -17,4 +17,19 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
interface ISettingsModelObject {
|
||||
OriginTag Origin { get; };
|
||||
}
|
||||
|
||||
interface IMediaResource {
|
||||
String Path { get; };
|
||||
String Resolved { get; };
|
||||
void Resolve(String finalValue);
|
||||
void Reject();
|
||||
Boolean Ok { get; };
|
||||
};
|
||||
|
||||
delegate void MediaResourceResolver(OriginTag origin, String basePath, IMediaResource resource);
|
||||
|
||||
static runtimeclass MediaResourceHelper {
|
||||
static IMediaResource FromString(String s);
|
||||
static IMediaResource Empty();
|
||||
};
|
||||
}
|
||||
|
||||
@ -87,6 +87,7 @@ 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) \
|
||||
@ -133,10 +134,10 @@ 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(hstring, PixelShaderPath, "experimental.pixelShaderPath") \
|
||||
X(hstring, PixelShaderImagePath, "experimental.pixelShaderImagePath") \
|
||||
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(hstring, BackgroundImagePath, "backgroundImage") \
|
||||
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)
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
#include "pch.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
#include "MediaResourceHelper.g.cpp"
|
||||
147
src/cascadia/TerminalSettingsModel/MediaResourceSupport.h
Normal file
147
src/cascadia/TerminalSettingsModel/MediaResourceSupport.h
Normal file
@ -0,0 +1,147 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MediaResourceHelper.g.h"
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
struct
|
||||
__declspec(uuid("6068ee1b-1ea0-4804-993a-42ef0c58d867"))
|
||||
IMediaResourceContainer : public IUnknown
|
||||
{
|
||||
virtual void ResolveMediaResources(const winrt::Microsoft::Terminal::Settings::Model::MediaResourceResolver& resolver) = 0;
|
||||
};
|
||||
|
||||
struct
|
||||
__declspec(uuid("9f11361c-7c8f-45c9-8948-36b66d67eca8"))
|
||||
IPathlessMediaResourceContainer : public IUnknown
|
||||
{
|
||||
virtual void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const winrt::Microsoft::Terminal::Settings::Model::MediaResourceResolver& resolver) = 0;
|
||||
};
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct EmptyMediaResource : winrt::implements<EmptyMediaResource, winrt::Microsoft::Terminal::Settings::Model::IMediaResource, winrt::non_agile, winrt::no_weak_ref, winrt::no_module_lock>
|
||||
{
|
||||
// Micro-optimization: having one empty resource that contains no actual paths saves us a few bytes per object
|
||||
winrt::hstring Path() { return {}; };
|
||||
winrt::hstring Resolved() { return {}; }
|
||||
|
||||
bool Ok() const { return false; }
|
||||
|
||||
void Resolve(const winrt::hstring&)
|
||||
{
|
||||
assert(false); // Somebody tried to resolve the empty media resource
|
||||
}
|
||||
|
||||
void Reject()
|
||||
{
|
||||
assert(false); // Somebody tried to resolve the empty media resource
|
||||
}
|
||||
};
|
||||
|
||||
/* MEDIA RESOURCES
|
||||
*
|
||||
* A media resource is a container for two strings: one pre-validation path and one post-validation path.
|
||||
* It is expected that, before they are used, they are passed through a resolver.
|
||||
*
|
||||
* A resolver may Resolve() a media resource to a path or Reject() it.
|
||||
* - If it is Resolved, the new path is accessible via the Resolved() method.
|
||||
* - If it is Rejected, the Resolved() method will return the empty string.
|
||||
*
|
||||
* A media resource is considered `Ok` if it has been Resolved to a real path.
|
||||
*
|
||||
* As a special case, if it has been neither resolved nor rejected, it will return the pre-validation
|
||||
* path--this is intended to aid its use in places where the risk of using an unresolved media path
|
||||
* is fine.
|
||||
*/
|
||||
struct MediaResource : winrt::implements<MediaResource, winrt::Microsoft::Terminal::Settings::Model::IMediaResource, winrt::non_agile, winrt::no_weak_ref, winrt::no_module_lock>
|
||||
{
|
||||
MediaResource() {}
|
||||
MediaResource(const winrt::hstring& p) :
|
||||
value{ p } {}
|
||||
|
||||
winrt::hstring Path() { return value; };
|
||||
winrt::hstring Resolved() { return resolved ? resolvedValue : value; }
|
||||
|
||||
bool Ok() const { return ok; }
|
||||
|
||||
void Resolve(const winrt::hstring& newPath)
|
||||
{
|
||||
resolvedValue = newPath;
|
||||
ok = true;
|
||||
resolved = true;
|
||||
}
|
||||
|
||||
void Reject()
|
||||
{
|
||||
resolvedValue = {};
|
||||
ok = false;
|
||||
resolved = true;
|
||||
}
|
||||
|
||||
winrt::hstring value{};
|
||||
winrt::hstring resolvedValue{};
|
||||
bool ok{ false }; // Path() was transformed into a final and valid Resolved path
|
||||
bool resolved{ false }; // This resource has been visited by a resolver, regardless of the outcome.
|
||||
|
||||
static IMediaResource Empty()
|
||||
{
|
||||
static IMediaResource emptyResource{ winrt::make<EmptyMediaResource>() };
|
||||
return emptyResource;
|
||||
}
|
||||
|
||||
static IMediaResource FromString(const winrt::hstring& string)
|
||||
{
|
||||
return winrt::make<MediaResource>(string);
|
||||
}
|
||||
};
|
||||
|
||||
_TIL_INLINEPREFIX void ResolveMediaResource(const winrt::Microsoft::Terminal::Settings::Model::OriginTag origin, const winrt::hstring& basePath, const Model::IMediaResource& resource, const winrt::Microsoft::Terminal::Settings::Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
const auto path{ resource.Path() };
|
||||
if (path.empty() || resource.Ok())
|
||||
{
|
||||
// Don't resolve empty resources *or* resources which have already been found.
|
||||
return;
|
||||
}
|
||||
resolver(origin, basePath, resource);
|
||||
}
|
||||
|
||||
_TIL_INLINEPREFIX void ResolveIconMediaResource(const winrt::Microsoft::Terminal::Settings::Model::OriginTag origin, const winrt::hstring& basePath, const Model::IMediaResource& resource, const winrt::Microsoft::Terminal::Settings::Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (const winrt::hstring path{ resource.Path() }; !path.empty())
|
||||
{
|
||||
if (::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(path))
|
||||
{
|
||||
resource.Resolve(path);
|
||||
return;
|
||||
}
|
||||
|
||||
ResolveMediaResource(origin, basePath, resource, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
// This exists to allow external consumers to call this code via WinRT
|
||||
struct MediaResourceHelper
|
||||
{
|
||||
static winrt::Microsoft::Terminal::Settings::Model::IMediaResource FromString(hstring const& s)
|
||||
{
|
||||
return MediaResource::FromString(s);
|
||||
}
|
||||
static winrt::Microsoft::Terminal::Settings::Model::IMediaResource Empty()
|
||||
{
|
||||
return MediaResource::Empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
struct MediaResourceHelper : MediaResourceHelperT<MediaResourceHelper, implementation::MediaResourceHelper>
|
||||
{
|
||||
};
|
||||
}
|
||||
@ -46,6 +46,7 @@
|
||||
<ClInclude Include="MatchProfilesEntry.h">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MediaResourceSupport.h" />
|
||||
<ClInclude Include="VisualStudioGenerator.h" />
|
||||
<ClInclude Include="DefaultTerminal.h">
|
||||
<DependentUpon>DefaultTerminal.idl</DependentUpon>
|
||||
@ -206,11 +207,13 @@
|
||||
<ClCompile Include="MatchProfilesEntry.cpp">
|
||||
<DependentUpon>NewTabMenuEntry.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MediaResourceSupport.cpp" />
|
||||
<ClCompile Include="VsDevCmdGenerator.cpp" />
|
||||
<ClCompile Include="VsDevShellGenerator.cpp" />
|
||||
<ClCompile Include="VsSetupConfiguration.cpp" />
|
||||
<ClCompile Include="WslDistroGenerator.cpp" />
|
||||
<ClCompile Include="SshHostGenerator.cpp" />
|
||||
<ClCompile Include="TestHooks.cpp" />
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
<ClCompile Include="VsSetupConfiguration.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TestHooks.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
|
||||
@ -33,7 +33,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
|
||||
Profile Profile;
|
||||
Int32 ProfileIndex;
|
||||
String Icon;
|
||||
IMediaResource Icon;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ActionEntry : NewTabMenuEntry
|
||||
@ -41,7 +41,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
ActionEntry();
|
||||
|
||||
String ActionId;
|
||||
String Icon;
|
||||
IMediaResource Icon;
|
||||
}
|
||||
|
||||
enum FolderEntryInlining
|
||||
@ -56,7 +56,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
FolderEntry(String name);
|
||||
|
||||
String Name;
|
||||
String Icon;
|
||||
IMediaResource Icon;
|
||||
FolderEntryInlining Inlining;
|
||||
Boolean AllowEmpty;
|
||||
|
||||
|
||||
@ -27,7 +27,6 @@ static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view GuidKey{ "guid" };
|
||||
static constexpr std::string_view SourceKey{ "source" };
|
||||
static constexpr std::string_view HiddenKey{ "hidden" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
|
||||
static constexpr std::string_view FontInfoKey{ "font" };
|
||||
static constexpr std::string_view PaddingKey{ "padding" };
|
||||
@ -112,7 +111,6 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
|
||||
profile->_Hidden = _Hidden;
|
||||
profile->_TabColor = _TabColor;
|
||||
profile->_Padding = _Padding;
|
||||
profile->_Icon = _Icon;
|
||||
|
||||
profile->_Origin = _Origin;
|
||||
profile->_FontInfo = *fontInfo;
|
||||
@ -195,9 +193,6 @@ void Profile::LayerJson(const Json::Value& json)
|
||||
JsonUtils::GetValueForKey(json, HiddenKey, _Hidden);
|
||||
_logSettingIfSet(HiddenKey, _Hidden.has_value());
|
||||
|
||||
JsonUtils::GetValueForKey(json, IconKey, _Icon);
|
||||
_logSettingIfSet(IconKey, _Icon.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>>{});
|
||||
@ -351,11 +346,6 @@ Json::Value Profile::ToJson() const
|
||||
JsonUtils::SetValueForKey(json, HiddenKey, writeBasicSettings ? Hidden() : _Hidden);
|
||||
JsonUtils::SetValueForKey(json, SourceKey, writeBasicSettings ? Source() : _Source);
|
||||
|
||||
// Recall: Icon isn't actually a setting in the MTSM_PROFILE_SETTINGS. We
|
||||
// defined it manually in Profile, so make sure we only serialize the Icon
|
||||
// if the user actually changed it here.
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
|
||||
// PermissiveStringConverter is unnecessary for serialization
|
||||
JsonUtils::SetValueForKey(json, PaddingKey, _Padding);
|
||||
|
||||
@ -380,102 +370,6 @@ Json::Value Profile::ToJson() const
|
||||
return json;
|
||||
}
|
||||
|
||||
// This is the implementation for and INHERITABLE_SETTING, but with one addition
|
||||
// in the setter. We want to make sure to clear out our cached icon, so that we
|
||||
// can re-evaluate it as it changes in the SUI.
|
||||
void Profile::Icon(const winrt::hstring& value)
|
||||
{
|
||||
_evaluatedIcon = std::nullopt;
|
||||
_Icon = value;
|
||||
}
|
||||
winrt::hstring Profile::Icon() const
|
||||
{
|
||||
const auto val{ _getIconImpl() };
|
||||
return val ? *val : hstring{ L"\uE756" };
|
||||
}
|
||||
|
||||
winrt::hstring Profile::EvaluatedIcon()
|
||||
{
|
||||
// We cache the result here, so we don't search the path for the exe every time.
|
||||
if (!_evaluatedIcon.has_value())
|
||||
{
|
||||
_evaluatedIcon = _evaluateIcon();
|
||||
}
|
||||
return *_evaluatedIcon;
|
||||
}
|
||||
|
||||
winrt::hstring Profile::_evaluateIcon() const
|
||||
{
|
||||
// If the profile has an icon, return it.
|
||||
if (!Icon().empty())
|
||||
{
|
||||
return Icon();
|
||||
}
|
||||
|
||||
// Otherwise, use NormalizeCommandLine to find the actual exe name. This
|
||||
// will actually search for the exe, including spaces, in the same way that
|
||||
// CreateProcess does.
|
||||
std::wstring cmdline{ NormalizeCommandLine(Commandline().c_str()) };
|
||||
// NormalizeCommandLine will return a string with embedded nulls after each
|
||||
// arg. We just want the first one.
|
||||
return winrt::hstring{ cmdline.c_str() };
|
||||
}
|
||||
|
||||
bool Profile::HasIcon() const
|
||||
{
|
||||
return _Icon.has_value();
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile Profile::IconOverrideSource()
|
||||
{
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
if (auto source{ parent->_getIconOverrideSourceImpl() })
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Profile::ClearIcon()
|
||||
{
|
||||
_Icon = std::nullopt;
|
||||
_evaluatedIcon = std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<winrt::hstring> Profile::_getIconImpl() const
|
||||
{
|
||||
if (_Icon)
|
||||
{
|
||||
return _Icon;
|
||||
}
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
if (auto val{ parent->_getIconImpl() })
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::Profile Profile::_getIconOverrideSourceImpl() const
|
||||
{
|
||||
if (_Icon)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
if (auto source{ parent->_getIconOverrideSourceImpl() })
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Given a commandLine like the following:
|
||||
// * "C:\WINDOWS\System32\cmd.exe"
|
||||
// * "pwsh -WorkingDirectory ~"
|
||||
@ -633,6 +527,41 @@ void Profile::_logSettingIfSet(const std::string_view& setting, const bool isSet
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::ResolveMediaResources(const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (const auto icon{ _getIconImpl() }; icon && *icon)
|
||||
{
|
||||
const auto iconSource{ _getIconOverrideSourceImpl() };
|
||||
ResolveIconMediaResource(iconSource->_Origin, iconSource->SourceBasePath, *icon, resolver);
|
||||
|
||||
// If the icon was specified at any layer, but fails resolution *or* contains the empty string,
|
||||
// fall back to the normalized command line at or above this layer.
|
||||
if (!icon->Ok() || icon->Resolved().empty() && !iconSource->Commandline().empty())
|
||||
{
|
||||
// We want to resolve the icon to the commandline, but we risk that the icon was already
|
||||
// resolved. We don't want to do that (as MediaResource asserts that it was only resolved
|
||||
// one time). However, we can't just make a new empty resource and resolve it -- that
|
||||
// might replace the value in the user's settings when we serialize it again...
|
||||
// So instead, make a new one derived from the icon we started with, then resolve it
|
||||
// ourselves.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto container{ _DefaultAppearance.as<IMediaResourceContainer>() })
|
||||
{
|
||||
container->ResolveMediaResources(resolver);
|
||||
}
|
||||
|
||||
if (const auto container{ UnfocusedAppearance().try_as<IMediaResourceContainer>() })
|
||||
{
|
||||
container->ResolveMediaResources(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
void Profile::LogSettingChanges(std::set<std::string>& changes, const std::string_view& context) const
|
||||
{
|
||||
for (const auto& setting : _changeLog)
|
||||
|
||||
@ -50,6 +50,7 @@ Author(s):
|
||||
|
||||
#include "JsonUtils.h"
|
||||
#include <DefaultSettings.h>
|
||||
#include "MediaResourceSupport.h"
|
||||
#include "AppearanceConfig.h"
|
||||
#include "FontConfig.h"
|
||||
|
||||
@ -75,7 +76,7 @@ constexpr GUID RUNTIME_GENERATED_PROFILE_NAMESPACE_GUID = { 0xf65ddb7e, 0x706b,
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct Profile : ProfileT<Profile>, IInheritable<Profile>
|
||||
struct Profile : ProfileT<Profile, IMediaResourceContainer>, IInheritable<Profile>
|
||||
{
|
||||
public:
|
||||
Profile() noexcept = default;
|
||||
@ -108,16 +109,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
void LogSettingChanges(std::set<std::string>& changes, const std::string_view& context) const;
|
||||
|
||||
// EvaluatedIcon depends on Icon. It allows us to grab the
|
||||
// icon from an exe path.
|
||||
// As a result, we can't use the INHERITABLE_SETTING macro for Icon,
|
||||
// as we manually have to set/unset _evaluatedIcon when Icon changes.
|
||||
winrt::hstring EvaluatedIcon();
|
||||
hstring Icon() const;
|
||||
void Icon(const hstring& value);
|
||||
bool HasIcon() const;
|
||||
Model::Profile IconOverrideSource();
|
||||
void ClearIcon();
|
||||
void ResolveMediaResources(const Model::MediaResourceResolver& resolver);
|
||||
|
||||
void Icon(const winrt::hstring& path)
|
||||
{
|
||||
// Internal Helper (overload version)
|
||||
Icon(MediaResource::FromString(path));
|
||||
}
|
||||
|
||||
WINRT_PROPERTY(bool, Deleted, false);
|
||||
WINRT_PROPERTY(bool, Orphaned, false);
|
||||
@ -135,6 +133,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
INHERITABLE_SETTING(Model::Profile, guid, Guid, _GenerateGuidForProfile(Name(), Source()));
|
||||
INHERITABLE_SETTING(Model::Profile, hstring, Padding, DEFAULT_PADDING);
|
||||
|
||||
winrt::hstring SourceBasePath;
|
||||
|
||||
public:
|
||||
#define PROFILE_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
INHERITABLE_SETTING_WITH_LOGGING(Model::Profile, type, name, jsonKey, ##__VA_ARGS__)
|
||||
@ -145,17 +145,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Model::IAppearanceConfig _DefaultAppearance{ winrt::make<AppearanceConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
Model::FontConfig _FontInfo{ winrt::make<FontConfig>(weak_ref<Model::Profile>(*this)) };
|
||||
|
||||
std::optional<hstring> _Icon{ std::nullopt };
|
||||
std::optional<winrt::hstring> _evaluatedIcon{ std::nullopt };
|
||||
std::set<std::string> _changeLog;
|
||||
|
||||
static std::wstring EvaluateStartingDirectory(const std::wstring& directory);
|
||||
|
||||
static guid _GenerateGuidForProfile(const std::wstring_view& name, const std::wstring_view& source) noexcept;
|
||||
|
||||
winrt::hstring _evaluateIcon() const;
|
||||
std::optional<hstring> _getIconImpl() const;
|
||||
Model::Profile _getIconOverrideSourceImpl() const;
|
||||
void _logSettingSet(const std::string_view& setting);
|
||||
void _logSettingIfSet(const std::string_view& setting, const bool isSet);
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
All = 0xffffffff
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Profile : Windows.Foundation.IStringable, ISettingsModelObject {
|
||||
runtimeclass Profile : Windows.Foundation.IStringable, ISettingsModelObject {
|
||||
Profile();
|
||||
Profile(Guid guid);
|
||||
|
||||
@ -44,16 +44,12 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
// True if the user *kept* this Profile, but it disappeared from the system.
|
||||
Boolean Orphaned { get; };
|
||||
|
||||
// Helper for magically using a commandline for an icon for a profile
|
||||
// without an explicit icon.
|
||||
String EvaluatedIcon { get; };
|
||||
|
||||
INHERITABLE_PROFILE_SETTING(Guid, Guid);
|
||||
INHERITABLE_PROFILE_SETTING(String, Name);
|
||||
INHERITABLE_PROFILE_SETTING(String, Source);
|
||||
INHERITABLE_PROFILE_SETTING(Boolean, Hidden);
|
||||
INHERITABLE_PROFILE_SETTING(Guid, ConnectionType);
|
||||
INHERITABLE_PROFILE_SETTING(String, Icon);
|
||||
INHERITABLE_PROFILE_SETTING(IMediaResource, Icon);
|
||||
INHERITABLE_PROFILE_SETTING(CloseOnExitMode, CloseOnExit);
|
||||
INHERITABLE_PROFILE_SETTING(String, TabTitle);
|
||||
INHERITABLE_PROFILE_SETTING(Windows.Foundation.IReference<Microsoft.Terminal.Core.Color>, TabColor);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "pch.h"
|
||||
#include "ProfileEntry.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalSettingsSerializationHelpers.h"
|
||||
|
||||
#include "ProfileEntry.g.cpp"
|
||||
|
||||
@ -20,7 +21,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
}
|
||||
|
||||
ProfileEntry::ProfileEntry(const winrt::hstring& profile) noexcept :
|
||||
ProfileEntryT<ProfileEntry, NewTabMenuEntry>(NewTabMenuEntryType::Profile),
|
||||
ProfileEntryT<ProfileEntry, NewTabMenuEntry, IPathlessMediaResourceContainer>(NewTabMenuEntryType::Profile),
|
||||
_ProfileName{ profile }
|
||||
{
|
||||
}
|
||||
@ -47,7 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _Profile.Guid());
|
||||
}
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _icon);
|
||||
|
||||
return json;
|
||||
}
|
||||
@ -57,7 +58,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto entry = winrt::make_self<ProfileEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ProfileKey, entry->_ProfileName);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_icon);
|
||||
|
||||
return entry;
|
||||
}
|
||||
@ -68,7 +69,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
entry->_Profile = _Profile;
|
||||
entry->_ProfileIndex = _ProfileIndex;
|
||||
entry->_ProfileName = _ProfileName;
|
||||
entry->_Icon = _Icon;
|
||||
entry->_icon = _icon;
|
||||
return *entry;
|
||||
}
|
||||
|
||||
void ProfileEntry::ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver)
|
||||
{
|
||||
if (_icon)
|
||||
{
|
||||
// TODO GH#19191 (Hardcoded Origin, since that's the only place it could have come from)
|
||||
ResolveIconMediaResource(OriginTag::User, basePath, _icon, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,12 +17,13 @@ Author(s):
|
||||
|
||||
#include "NewTabMenuEntry.h"
|
||||
#include "ProfileEntry.g.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
|
||||
#include "Profile.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct ProfileEntry : ProfileEntryT<ProfileEntry, NewTabMenuEntry>
|
||||
struct ProfileEntry : ProfileEntryT<ProfileEntry, NewTabMenuEntry, IPathlessMediaResourceContainer>
|
||||
{
|
||||
public:
|
||||
ProfileEntry() noexcept;
|
||||
@ -40,13 +41,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// then CascadiaSettings::_resolveNewTabMenuProfiles() will populate
|
||||
// the Profile and ProfileIndex properties appropriately
|
||||
winrt::hstring ProfileName() const noexcept { return _ProfileName; };
|
||||
void ResolveMediaResourcesWithBasePath(const winrt::hstring& basePath, const Model::MediaResourceResolver& resolver) override;
|
||||
|
||||
IMediaResource Icon() const noexcept
|
||||
{
|
||||
return _icon ? _icon : MediaResource::Empty();
|
||||
}
|
||||
|
||||
void Icon(const IMediaResource& val)
|
||||
{
|
||||
_icon = val;
|
||||
}
|
||||
|
||||
WINRT_PROPERTY(Model::Profile, Profile);
|
||||
WINRT_PROPERTY(int, ProfileIndex);
|
||||
WINRT_PROPERTY(winrt::hstring, Icon);
|
||||
|
||||
private:
|
||||
winrt::hstring _ProfileName;
|
||||
IMediaResource _icon;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include "TerminalSettings.h"
|
||||
#include "../../types/inc/colorTable.hpp"
|
||||
|
||||
#include "AppearanceConfig.h"
|
||||
|
||||
#include "TerminalSettings.g.cpp"
|
||||
#include "TerminalSettingsCreateResult.g.cpp"
|
||||
|
||||
@ -250,9 +252,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
_CursorColor = til::color{ appearance.CursorColor().Value() };
|
||||
}
|
||||
if (!appearance.BackgroundImagePath().empty())
|
||||
|
||||
if (const auto backgroundImage{ appearance.BackgroundImagePath() })
|
||||
{
|
||||
_BackgroundImage = appearance.ExpandedBackgroundImagePath();
|
||||
_BackgroundImage = backgroundImage.Resolved();
|
||||
}
|
||||
|
||||
if (const auto pixelShader{ appearance.PixelShaderPath() })
|
||||
{
|
||||
_PixelShaderPath = pixelShader.Resolved();
|
||||
}
|
||||
|
||||
if (const auto pixelShaderImage{ appearance.PixelShaderImagePath() })
|
||||
{
|
||||
_PixelShaderImagePath = pixelShaderImage.Resolved();
|
||||
}
|
||||
|
||||
_BackgroundImageOpacity = appearance.BackgroundImageOpacity();
|
||||
@ -260,8 +273,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
std::tie(_BackgroundImageHorizontalAlignment, _BackgroundImageVerticalAlignment) = ConvertConvergedAlignment(appearance.BackgroundImageAlignment());
|
||||
|
||||
_RetroTerminalEffect = appearance.RetroTerminalEffect();
|
||||
_PixelShaderPath = winrt::hstring{ wil::ExpandEnvironmentStringsW<std::wstring>(appearance.PixelShaderPath().c_str()) };
|
||||
_PixelShaderImagePath = winrt::hstring{ wil::ExpandEnvironmentStringsW<std::wstring>(appearance.PixelShaderImagePath().c_str()) };
|
||||
|
||||
_IntenseIsBold = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bold);
|
||||
_IntenseIsBright = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bright);
|
||||
|
||||
@ -18,6 +18,7 @@ Abstract:
|
||||
#include "JsonUtils.h"
|
||||
#include "SettingsTypes.h"
|
||||
#include "ModelSerializationHelpers.h"
|
||||
#include "MediaResourceSupport.h"
|
||||
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::CursorStyle)
|
||||
{
|
||||
@ -767,6 +768,43 @@ struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winr
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winrt::Microsoft::Terminal::Settings::Model::IMediaResource>
|
||||
{
|
||||
::winrt::Microsoft::Terminal::Settings::Model::IMediaResource FromJson(const Json::Value& json)
|
||||
{
|
||||
if (json.isNull()) [[unlikely]]
|
||||
{
|
||||
// Do not use Empty here, as Empty is shared across all instances.
|
||||
return ::winrt::Microsoft::Terminal::Settings::Model::implementation::MediaResource::FromString(L"");
|
||||
}
|
||||
|
||||
winrt::hstring string{ til::u8u16(Detail::GetStringView(json)) };
|
||||
return ::winrt::Microsoft::Terminal::Settings::Model::implementation::MediaResource::FromString(string);
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString() || json.isNull();
|
||||
}
|
||||
|
||||
Json::Value ToJson(const ::winrt::Microsoft::Terminal::Settings::Model::IMediaResource& val)
|
||||
{
|
||||
if (!val || val.Path() == winrt::hstring{})
|
||||
{
|
||||
// empty string becomes null (is this correct?)
|
||||
return Json::Value::nullSingleton();
|
||||
}
|
||||
|
||||
return til::u16u8(val.Path());
|
||||
}
|
||||
|
||||
std::string TypeDescription() const
|
||||
{
|
||||
return "file path";
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::GraphicsAPI)
|
||||
{
|
||||
JSON_MAPPINGS(3) = {
|
||||
|
||||
@ -10,8 +10,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
MissingDefaultProfile = 0,
|
||||
DuplicateProfile,
|
||||
UnknownColorScheme,
|
||||
InvalidBackgroundImage,
|
||||
InvalidIcon,
|
||||
InvalidMediaResource,
|
||||
AtLeastOneKeybindingWarning,
|
||||
TooManyKeysForChord,
|
||||
MissingRequiredParameter,
|
||||
|
||||
14
src/cascadia/TerminalSettingsModel/TestHooks.cpp
Normal file
14
src/cascadia/TerminalSettingsModel/TestHooks.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include "pch.h"
|
||||
#include "winrt/Microsoft.Terminal.Settings.Model.h"
|
||||
|
||||
// Through the power of LTCG, this will all get deleted... and all call
|
||||
// sites too.
|
||||
// https://devblogs.microsoft.com/oldnewthing/20250416-00/?p=111077
|
||||
|
||||
bool TestHook_CascadiaSettings_ResolveSingleMediaResource(
|
||||
winrt::Microsoft::Terminal::Settings::Model::OriginTag,
|
||||
std::wstring_view,
|
||||
const winrt::Microsoft::Terminal::Settings::Model::IMediaResource&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
#include <Shlobj.h>
|
||||
#include <Shlobj_core.h>
|
||||
#include <wincodec.h>
|
||||
@ -132,10 +134,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
// If we fail to set the icon source using the "icon" as a path,
|
||||
// let's try it as a symbol/emoji.
|
||||
//
|
||||
// Anything longer than 2 wchar_t's _isn't_ an emoji or symbol, so
|
||||
// don't do this if it's just an invalid path.
|
||||
if (!iconSource && iconPath.size() <= 2)
|
||||
if (!iconSource && ::Microsoft::Console::Utils::IsLikelyToBeEmojiOrSymbolIcon(iconPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@ -869,7 +869,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
SettingsLoader loader{ userSettings, inboxSettings };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(L"TestFragment", fragment);
|
||||
loader.MergeFragmentIntoUserSettings(L"TestFragment", {}, fragment);
|
||||
loader.FinalizeLayering();
|
||||
loader.FixupUserSettings();
|
||||
const auto settings = winrt::make_self<CascadiaSettings>(std::move(loader));
|
||||
|
||||
@ -36,8 +36,6 @@ namespace SettingsModelUnitTests
|
||||
TEST_METHOD(TestHideAllProfiles);
|
||||
TEST_METHOD(TestInvalidColorSchemeName);
|
||||
TEST_METHOD(TestHelperFunctions);
|
||||
TEST_METHOD(TestProfileBackgroundImageWithEnvVar);
|
||||
TEST_METHOD(TestProfileBackgroundImageWithDesktopWallpaper);
|
||||
TEST_METHOD(TestCloseOnExitParsing);
|
||||
TEST_METHOD(TestCloseOnExitCompatibilityShim);
|
||||
TEST_METHOD(TestLayerUserDefaultsBeforeProfiles);
|
||||
@ -899,44 +897,6 @@ namespace SettingsModelUnitTests
|
||||
VERIFY_IS_NULL(settings->FindProfile(fakeGuid));
|
||||
}
|
||||
|
||||
void DeserializationTests::TestProfileBackgroundImageWithEnvVar()
|
||||
{
|
||||
const auto expectedPath = wil::ExpandEnvironmentStringsW<std::wstring>(L"%WINDIR%\\System32\\x_80.png");
|
||||
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"backgroundImage": "%WINDIR%\\System32\\x_80.png"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto settings = createSettings(settingsJson);
|
||||
VERIFY_ARE_NOT_EQUAL(0u, settings->AllProfiles().Size());
|
||||
VERIFY_ARE_EQUAL(expectedPath, settings->AllProfiles().GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestProfileBackgroundImageWithDesktopWallpaper()
|
||||
{
|
||||
const winrt::hstring expectedBackgroundImagePath{ L"desktopWallpaper" };
|
||||
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"backgroundImage": "desktopWallpaper"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto settings = createSettings(settingsJson);
|
||||
VERIFY_ARE_EQUAL(expectedBackgroundImagePath, settings->AllProfiles().GetAt(0).DefaultAppearance().BackgroundImagePath());
|
||||
VERIFY_ARE_NOT_EQUAL(expectedBackgroundImagePath, settings->AllProfiles().GetAt(0).DefaultAppearance().ExpandedBackgroundImagePath());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestCloseOnExitParsing()
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
@ -2131,7 +2091,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
VERIFY_IS_FALSE(loader.duplicateProfile);
|
||||
@ -2155,7 +2115,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
@ -2181,7 +2141,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
@ -2215,7 +2175,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
@ -2250,7 +2210,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
@ -2276,7 +2236,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
@ -2303,7 +2263,7 @@ namespace SettingsModelUnitTests
|
||||
|
||||
implementation::SettingsLoader loader{ std::string_view{}, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson);
|
||||
loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, {}, fragmentJson);
|
||||
loader.FinalizeLayering();
|
||||
|
||||
const auto oldSettings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
|
||||
1314
src/cascadia/UnitTests_SettingsModel/MediaResourceTests.cpp
Normal file
1314
src/cascadia/UnitTests_SettingsModel/MediaResourceTests.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -217,32 +217,32 @@ namespace SettingsModelUnitTests
|
||||
const auto profile3Json = VerifyParseSucceeded(profile3String);
|
||||
|
||||
auto profile0 = implementation::Profile::FromJson(profile0Json);
|
||||
VERIFY_IS_FALSE(profile0->Icon().empty());
|
||||
VERIFY_ARE_EQUAL(L"not-null.png", profile0->Icon());
|
||||
VERIFY_IS_FALSE(profile0->Icon().Path().empty());
|
||||
VERIFY_ARE_EQUAL(L"not-null.png", profile0->Icon().Path());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that layering an object the key set to null will clear the key"));
|
||||
profile0->LayerJson(profile1Json);
|
||||
VERIFY_IS_TRUE(profile0->Icon().empty());
|
||||
VERIFY_IS_TRUE(profile0->Icon().Path().empty());
|
||||
|
||||
profile0->LayerJson(profile2Json);
|
||||
VERIFY_IS_TRUE(profile0->Icon().empty());
|
||||
VERIFY_IS_TRUE(profile0->Icon().Path().empty());
|
||||
|
||||
profile0->LayerJson(profile3Json);
|
||||
VERIFY_IS_FALSE(profile0->Icon().empty());
|
||||
VERIFY_ARE_EQUAL(L"another-real.png", profile0->Icon());
|
||||
VERIFY_IS_FALSE(profile0->Icon().Path().empty());
|
||||
VERIFY_ARE_EQUAL(L"another-real.png", profile0->Icon().Path());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Verify that layering an object _without_ the key will not clear the key"));
|
||||
profile0->LayerJson(profile2Json);
|
||||
VERIFY_IS_FALSE(profile0->Icon().empty());
|
||||
VERIFY_ARE_EQUAL(L"another-real.png", profile0->Icon());
|
||||
VERIFY_IS_FALSE(profile0->Icon().Path().empty());
|
||||
VERIFY_ARE_EQUAL(L"another-real.png", profile0->Icon().Path());
|
||||
|
||||
auto profile1 = implementation::Profile::FromJson(profile1Json);
|
||||
VERIFY_IS_TRUE(profile1->Icon().empty());
|
||||
VERIFY_IS_TRUE(profile1->Icon().Path().empty());
|
||||
profile1->LayerJson(profile3Json);
|
||||
VERIFY_IS_FALSE(profile1->Icon().empty());
|
||||
VERIFY_ARE_EQUAL(L"another-real.png", profile1->Icon());
|
||||
VERIFY_IS_FALSE(profile1->Icon().Path().empty());
|
||||
VERIFY_ARE_EQUAL(L"another-real.png", profile1->Icon().Path());
|
||||
}
|
||||
|
||||
void ProfileTests::LayerProfilesOnArray()
|
||||
|
||||
@ -58,6 +58,8 @@ namespace SettingsModelUnitTests
|
||||
TEST_METHOD(RoundtripActionsSameNameDifferentCommandsAreRetained);
|
||||
TEST_METHOD(MultipleActionsAreCollapsed);
|
||||
|
||||
TEST_METHOD(ProfileWithInvalidIcon);
|
||||
|
||||
private:
|
||||
// Method Description:
|
||||
// - deserializes and reserializes a json string representing a settings object model of type T
|
||||
@ -1295,4 +1297,32 @@ namespace SettingsModelUnitTests
|
||||
|
||||
VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult));
|
||||
}
|
||||
|
||||
void SerializationTests::ProfileWithInvalidIcon()
|
||||
{
|
||||
static constexpr std::string_view settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe",
|
||||
"icon": "c:\\this_icon_had_better_not_exist.tiff"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
implementation::SettingsLoader loader{ settingsJson, implementation::LoadStringResource(IDR_DEFAULTS) };
|
||||
loader.MergeInboxIntoUserSettings();
|
||||
loader.FinalizeLayering();
|
||||
const auto settings = winrt::make_self<implementation::CascadiaSettings>(std::move(loader));
|
||||
const auto newResult{ settings->ToJson() };
|
||||
|
||||
// A profile that specifies an invalid icon will fall back to the commandline on load, but that should
|
||||
// not be reflected back in settings.json as null *or* as the commandline. The value should be exactly
|
||||
// what was written in the settings file.
|
||||
VERIFY_ARE_EQUAL(R"(c:\this_icon_had_better_not_exist.tiff)", newResult["profiles"]["list"][0]["icon"].asString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
<ClCompile Include="SerializationTests.cpp" />
|
||||
<ClCompile Include="TerminalSettingsTests.cpp" />
|
||||
<ClCompile Include="ThemeTests.cpp" />
|
||||
<ClCompile Include="MediaResourceTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
||||
@ -133,4 +133,5 @@ namespace Microsoft::Console::Utils
|
||||
|
||||
bool IsWindows11() noexcept;
|
||||
|
||||
bool IsLikelyToBeEmojiOrSymbolIcon(std::wstring_view text) noexcept;
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
#include "inc/colorTable.hpp"
|
||||
|
||||
#include <icu.h>
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
|
||||
// Routine Description:
|
||||
@ -1279,3 +1281,37 @@ bool Utils::IsWindows11() noexcept
|
||||
}();
|
||||
return isWindows11;
|
||||
}
|
||||
|
||||
bool Utils::IsLikelyToBeEmojiOrSymbolIcon(std::wstring_view text) noexcept
|
||||
{
|
||||
if (text.size() == 1 && !IS_HIGH_SURROGATE(til::at(text, 0)))
|
||||
{
|
||||
// If it's a single code unit, it's definitely either zero or one grapheme clusters.
|
||||
// If it turns out to be illegal Unicode, we don't really care.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (text.size() >= 2 && til::at(text, 0) <= 0x7F && til::at(text, 1) <= 0x7F)
|
||||
{
|
||||
// Two adjacent ASCII characters (as seen in most file paths) aren't a single
|
||||
// grapheme cluster.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use ICU to determine whether text is composed of a single grapheme cluster.
|
||||
int32_t off{ 0 };
|
||||
UErrorCode status{ U_ZERO_ERROR };
|
||||
|
||||
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
||||
const auto b{ ubrk_open(UBRK_CHARACTER,
|
||||
nullptr,
|
||||
reinterpret_cast<const UChar*>(text.data()),
|
||||
gsl::narrow_cast<int32_t>(text.size()),
|
||||
&status) };
|
||||
if (status <= U_ZERO_ERROR)
|
||||
{
|
||||
off = ubrk_next(b);
|
||||
ubrk_close(b);
|
||||
}
|
||||
return off == gsl::narrow_cast<int32_t>(text.size());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user