From c0f9a198c62d23dfcd9912c8f2506f411ed84bf6 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 5 Aug 2025 15:47:50 -0500 Subject: [PATCH] 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. --- .github/actions/spelling/allow/apis.txt | 1 + .github/actions/spelling/allow/microsoft.txt | 1 + .github/actions/spelling/allow/names.txt | 1 + .../TerminalApp/CommandPaletteItems.h | 2 +- src/cascadia/TerminalApp/Jumplist.cpp | 37 +- .../Resources/de-DE/Resources.resw | 8 - .../Resources/en-US/Resources.resw | 10 +- .../Resources/es-ES/Resources.resw | 8 - .../Resources/fr-FR/Resources.resw | 8 - .../Resources/it-IT/Resources.resw | 8 - .../Resources/ja-JP/Resources.resw | 8 - .../Resources/ko-KR/Resources.resw | 8 - .../Resources/pt-BR/Resources.resw | 8 - .../Resources/qps-ploc/Resources.resw | 8 - .../Resources/qps-ploca/Resources.resw | 8 - .../Resources/qps-plocm/Resources.resw | 8 - .../Resources/ru-RU/Resources.resw | 8 - .../Resources/zh-CN/Resources.resw | 8 - .../Resources/zh-TW/Resources.resw | 8 - src/cascadia/TerminalApp/TerminalPage.cpp | 24 +- .../TerminalApp/TerminalPaneContent.cpp | 2 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 3 +- .../TerminalSettingsEditor/AddProfile.xaml | 2 +- .../TerminalSettingsEditor/Appearances.cpp | 23 +- .../TerminalSettingsEditor/Appearances.idl | 2 +- .../TerminalSettingsEditor/Appearances.xaml | 2 +- .../TerminalSettingsEditor/Extensions.xaml | 2 +- .../TerminalSettingsEditor/Launch.xaml | 2 +- .../TerminalSettingsEditor/MainPage.cpp | 2 +- .../TerminalSettingsEditor/NewTabMenu.xaml | 4 +- .../NewTabMenuViewModel.cpp | 2 +- .../NewTabMenuViewModel.h | 4 +- .../NewTabMenuViewModel.idl | 2 +- .../ProfileViewModel.cpp | 28 +- .../TerminalSettingsEditor/ProfileViewModel.h | 8 +- .../ProfileViewModel.idl | 3 +- .../TerminalSettingsEditor/Profiles_Base.cpp | 2 +- .../TerminalSettingsEditor/Profiles_Base.xaml | 4 +- .../TerminalSettingsModel/ActionEntry.cpp | 18 +- .../TerminalSettingsModel/ActionEntry.h | 19 +- .../TerminalSettingsModel/ActionMap.cpp | 26 +- .../TerminalSettingsModel/ActionMap.h | 2 + .../TerminalSettingsModel/ActionMap.idl | 3 +- .../AppearanceConfig.cpp | 60 +- .../TerminalSettingsModel/AppearanceConfig.h | 7 +- .../AppearanceConfig.idl | 2 +- .../CascadiaSettings.cpp | 231 +-- .../TerminalSettingsModel/CascadiaSettings.h | 7 +- .../CascadiaSettings.idl | 4 +- .../CascadiaSettingsSerialization.cpp | 20 +- .../TerminalSettingsModel/Command.cpp | 56 +- src/cascadia/TerminalSettingsModel/Command.h | 8 +- .../TerminalSettingsModel/Command.idl | 6 +- .../TerminalSettingsModel/FolderEntry.cpp | 28 +- .../TerminalSettingsModel/FolderEntry.h | 19 +- .../GlobalAppSettings.cpp | 21 + .../TerminalSettingsModel/GlobalAppSettings.h | 4 + .../IAppearanceConfig.idl | 8 +- .../TerminalSettingsModel/IInheritable.h | 29 +- .../ISettingsModelObject.idl | 15 + .../TerminalSettingsModel/MTSMSettings.h | 7 +- .../MediaResourceSupport.cpp | 3 + .../MediaResourceSupport.h | 147 ++ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 3 + ...Terminal.Settings.ModelLib.vcxproj.filters | 1 + .../TerminalSettingsModel/NewTabMenuEntry.idl | 6 +- .../TerminalSettingsModel/Profile.cpp | 141 +- src/cascadia/TerminalSettingsModel/Profile.h | 27 +- .../TerminalSettingsModel/Profile.idl | 8 +- .../TerminalSettingsModel/ProfileEntry.cpp | 18 +- .../TerminalSettingsModel/ProfileEntry.h | 16 +- .../TerminalSettings.cpp | 19 +- .../TerminalSettingsSerializationHelpers.h | 38 + .../TerminalWarnings.idl | 3 +- .../TerminalSettingsModel/TestHooks.cpp | 14 + src/cascadia/UIHelpers/IconPathConverter.cpp | 7 +- .../ColorSchemeTests.cpp | 2 +- .../DeserializationTests.cpp | 54 +- .../MediaResourceTests.cpp | 1314 +++++++++++++++++ .../UnitTests_SettingsModel/ProfileTests.cpp | 22 +- .../SerializationTests.cpp | 30 + .../SettingsModel.UnitTests.vcxproj | 1 + src/types/inc/utils.hpp | 1 + src/types/utils.cpp | 36 + 84 files changed, 2178 insertions(+), 610 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/MediaResourceSupport.cpp create mode 100644 src/cascadia/TerminalSettingsModel/MediaResourceSupport.h create mode 100644 src/cascadia/TerminalSettingsModel/TestHooks.cpp create mode 100644 src/cascadia/UnitTests_SettingsModel/MediaResourceTests.cpp diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 336e62ff2a..e1307fcfd8 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -174,6 +174,7 @@ tokeninfo tolower toupper TRACKMOUSEEVENT +ubrk UChar UFIELD ULARGE diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index 3f639c4746..f43408388b 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -34,6 +34,7 @@ issecret libucrt libucrtd LOCKFILE +LTCG Lxss makepri microsoft diff --git a/.github/actions/spelling/allow/names.txt b/.github/actions/spelling/allow/names.txt index a97ec94ac9..1b52d13a22 100644 --- a/.github/actions/spelling/allow/names.txt +++ b/.github/actions/spelling/allow/names.txt @@ -28,6 +28,7 @@ jerrysh Kaiyu leonardder lhecker +Lovecraft masserano menger migrie diff --git a/src/cascadia/TerminalApp/CommandPaletteItems.h b/src/cascadia/TerminalApp/CommandPaletteItems.h index cc68072eb5..cffdb08850 100644 --- a/src/cascadia/TerminalApp/CommandPaletteItems.h +++ b/src/cascadia/TerminalApp/CommandPaletteItems.h @@ -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); diff --git a/src/cascadia/TerminalApp/Jumplist.cpp b/src/cascadia/TerminalApp/Jumplist.cpp index d558c5433f..e713514973 100644 --- a/src/cascadia/TerminalApp/Jumplist.cpp +++ b/src/cascadia/TerminalApp/Jumplist.cpp @@ -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(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())); } diff --git a/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw b/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw index 07d4509b7c..528ebdc930 100644 --- a/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/de-DE/Resources.resw @@ -270,14 +270,6 @@ Registerkarte kopieren - - 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. - {Locked="\"backgroundImage\""} - - - 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. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Beim Analysieren Ihrer Tastenzuordnungen wurden Warnungen gefunden: diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 9ff10d00f4..0dd84b19a4 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -271,13 +271,9 @@ Duplicate tab - - 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. - {Locked="\"backgroundImage\""} - - - 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. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. + + One or more resources (such as icon or backgroundImage) specified in your settings could not be found. + {Locked="icon","backgroundImage"} Indicates that something has gone wrong while reading a user's settings. Warnings were found while parsing your keybindings: diff --git a/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw b/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw index 3ef00fe6a1..7280c8c864 100644 --- a/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/es-ES/Resources.resw @@ -267,14 +267,6 @@ Duplicar pestaña - - 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. - {Locked="\"backgroundImage\""} - - - 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. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Se encontraron advertencias al analizar los enlaces de teclado: diff --git a/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw index 6d897738c0..d3aeefa1af 100644 --- a/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/fr-FR/Resources.resw @@ -267,14 +267,6 @@ Dupliquer l’onglet - - 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. - {Locked="\"backgroundImage\""} - - - 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. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Des avertissements ont été détectés lors de l’analyse de vos combinaisons de touches : diff --git a/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw b/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw index c8ae13a833..cb35143be4 100644 --- a/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/it-IT/Resources.resw @@ -267,14 +267,6 @@ Duplica scheda - - È 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. - {Locked="\"backgroundImage\""} - - - 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. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Sono stati trovati avvisi durante l'analisi delle associazioni di tasti: diff --git a/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw index 462193c2ce..653e8e4225 100644 --- a/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/ja-JP/Resources.resw @@ -268,14 +268,6 @@ タブを複製する - - 無効な "backgroundImage" を持つプロファイルが見つかりました。既定では、そのプロファイルに背景画像はありません。"backgroundImage" を設定するときに、値が画像への有効なファイル パスとなっていることをご確認ください。 - {Locked="\"backgroundImage\""} - - - 無効な "icon" を持つプロファイルが見つかりました。既定では、そのプロファイルにアイコンはありません。"icon" を設定するときに、値が画像への有効なファイル パスとなっていることをご確認ください。 - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - キー バインドの解析中に警告が検出されました: diff --git a/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw index 14fdffdaa8..12113e7553 100644 --- a/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/ko-KR/Resources.resw @@ -267,14 +267,6 @@ 탭 복제 - - 잘못된 "backgroundImage" 프로필을 찾았습니다. 해당 프로필을 배경 이미지가 없는 기본값으로 설정합니다. "backgroundImage"를 설정할 때 값이 이미지에 대한 유효한 파일 경로인지 확인합니다. - {Locked="\"backgroundImage\""} - - - 잘못된 "icon"이 있는 프로필을 발견했습니다. 해당 프로필에 아이콘이 없도록 기본값을 설정합니다. "icon" 설정 시 값이 이미지에 대한 올바른 파일 경로인지 확인합니다. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - 키 바인딩 구문을 분석하는 동안 경고를 발견했습니다. diff --git a/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw index b75bf1c3b1..3df04c5483 100644 --- a/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/pt-BR/Resources.resw @@ -267,14 +267,6 @@ Duplicar guia - - 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. - {Locked="\"backgroundImage\""} - - - 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. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Os avisos foram encontrados durante a análise das suas ligações de teclas: diff --git a/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw index f737463a65..82b9f46348 100644 --- a/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/qps-ploc/Resources.resw @@ -271,14 +271,6 @@ Ďϋφľіčάтέ τàв !!! - - ₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! ! - {Locked="\"backgroundImage\""} - - - ₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!! diff --git a/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw index f737463a65..82b9f46348 100644 --- a/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/qps-ploca/Resources.resw @@ -271,14 +271,6 @@ Ďϋφľіčάтέ τàв !!! - - ₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! ! - {Locked="\"backgroundImage\""} - - - ₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!! diff --git a/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw index f737463a65..82b9f46348 100644 --- a/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/qps-plocm/Resources.resw @@ -271,14 +271,6 @@ Ďϋφľіčάтέ τàв !!! - - ₣σúŋδ ą φѓοƒĩļé ẃϊţħ äй ïηνàĺìď "backgroundImage". Đēƒãųŀŧϊпğ ťнªт φѓőƒĭļè το нªνе πō ьąçќġгθúпδ ιмãġė. Маĸē śμѓē ŧћäţ ẁђēή šêťτϊлġ å "backgroundImage", ţĥě νаłųё ïŝ ά νάľîď ƒĩŀê φąťħ ţŏ άń ΐмąġė. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! ! - {Locked="\"backgroundImage\""} - - - ₣ǿũиđ à рřöƒϊℓз ŵĩţн аñ įņνàŀїδ "icon". Ðěƒаúľτīŋğ ţħаτ ρřόƒìŀё тб ђâνє пǿ íčой. Мàĸë ŝùřë ŧĥаţ ωĥĕл ŝеτŧīлĝ ăй "icon", τħε νāłϋë ïŝ ă νàľīđ ƒïŀè рªтн ţő äи ïмäģё. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - Щαѓńΐňģš ώĕřе ƒбŭπδ ώħīļë рăяşìⁿġ ўσυŕ κёỳвĩиðīήġş: !!! !!! !!! !!! !!! diff --git a/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw index 4bff030fdd..1c19663b0f 100644 --- a/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/ru-RU/Resources.resw @@ -267,14 +267,6 @@ Дублировать вкладку - - Найден профиль с недопустимым объектом "backgroundImage". По умолчанию для этого профиля не используется фоновое изображение. Убедитесь, что значение, заданное для "backgroundImage", является допустимым путем файла к изображению. - {Locked="\"backgroundImage\""} - - - Найден профиль с недопустимым объектом "icon". По умолчанию для этого профиля не используется значок. Убедитесь, что значение, заданное для "icon", является допустимым путем файла к изображению. - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - При анализе настраиваемых сочетаний клавиш найдены предупреждения: diff --git a/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw index d5935358d5..4e4c928624 100644 --- a/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/zh-CN/Resources.resw @@ -267,14 +267,6 @@ 复制标签页 - - 找到一个具有无效 "backgroundImage" 的配置文件。将该配置文件设置为默认设置为不包含背景图像。请确保在设置 "backgroundImage" 时,该值是指向图像的有效文件路径。 - {Locked="\"backgroundImage\""} - - - 找到一个带有无效 "icon" 的配置文件。将该配置文件默认为无图标。确保设置 "icon" 时,该值是图像的有效文件路径。 - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - 分析键绑定时发现警告: diff --git a/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw index 26689ad636..e1b3f5987b 100644 --- a/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/zh-TW/Resources.resw @@ -267,14 +267,6 @@ 複製索引標籤 - - 找到具有無效 "backgroundImage" 的設定檔。將該設定檔的預設值設為沒有背景影像。請確定設定 "backgroundImage" 時,該值是影像的有效檔案路徑。 - {Locked="\"backgroundImage\""} - - - 已發現具有無效 "icon" 的設定檔。將該設定檔預設為無圖示。設定 "icon" 時,請確認值是有效的影像檔案路徑。 - {Locked="\"icon\""} The word "icon" in quotes is locked, the word icon OUTSIDE of quotes should be localized. - 剖析金鑰繫結時發現警告: diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index ee7c4e3818..7ce2dd1118 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -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); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 42e6e195f0..5d5e32ed69 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -82,7 +82,7 @@ namespace winrt::TerminalApp::implementation winrt::hstring TerminalPaneContent::Icon() const { - return _profile.EvaluatedIcon(); + return _profile.Icon().Resolved(); } Windows::Foundation::IReference TerminalPaneContent::TabColor() const noexcept diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 0b329a8ee4..a2166c0169 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -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"), diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml index 3ea92ad0f6..1db1bbd4d3 100644 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml +++ b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml @@ -59,7 +59,7 @@ + IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon.Resolved), Mode=OneTime}" /> diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 030239b0a3..236485128e 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -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); } } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index aedb516473..42ce83f07c 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -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); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 7c77717311..2459762739 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -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}" />