From 5a7baeb1a9b7dd9715a227f8bb9d83837414edc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:29:05 +0000 Subject: [PATCH 1/2] Initial plan From 69f340bd86056943cddbcdcee1898c1187a4a97e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:44:03 +0000 Subject: [PATCH 2/2] Add MicaKind enum support to replace boolean useMica setting Co-authored-by: deepanshu-maliyan <115917329+deepanshu-maliyan@users.noreply.github.com> --- doc/cascadia/profiles.schema.json | 18 ++- .../TerminalSettingsEditor/MainPage.cpp | 2 +- .../TerminalSettingsModel/MTSMSettings.h | 12 +- .../TerminalSettingsSerializationHelpers.h | 40 +++++-- src/cascadia/TerminalSettingsModel/Theme.idl | 31 +++-- .../UnitTests_SettingsModel/ThemeTests.cpp | 109 ++++++++++++++++-- src/cascadia/WindowsTerminal/AppHost.cpp | 2 +- src/cascadia/WindowsTerminal/IslandWindow.cpp | 47 +++++--- src/cascadia/WindowsTerminal/IslandWindow.h | 3 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 22 ++-- .../WindowsTerminal/NonClientIslandWindow.h | 2 +- 11 files changed, 222 insertions(+), 66 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 275db4fa371..3fd238db7a3 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2022,9 +2022,21 @@ "type": "string" }, "useMica": { - "description": "True if the Terminal should use a Mica backdrop for the window. This will apply underneath all controls (including the terminal panes and the titlebar)", - "type": "boolean", - "default": false + "description": "Controls the Mica backdrop type for the window. Can be a boolean (true/false for backward compatibility) or a string specifying the Mica type. This will apply underneath all controls (including the terminal panes and the titlebar)", + "default": false, + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "none", + "mica", + "micaAlt" + ] + } + ] }, "experimental.rainbowFrame": { "description": "When enabled, the frame of the window will cycle through all the colors. Enabling this will override the `frame` and `unfocusedFrame` settings.", diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index e3cf92717be..b639ecbb2e1 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -984,7 +984,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // that our theme is different than the app's. const bool actuallyUseMica = isMicaAvailable && (appTheme == requestedTheme); - const auto bgKey = (theme.Window() != nullptr && theme.Window().UseMica()) && actuallyUseMica ? + const auto bgKey = (theme.Window() != nullptr && theme.Window().UseMica() != winrt::Microsoft::Terminal::Settings::Model::MicaKind::None) && actuallyUseMica ? L"SettingsPageMicaBackground" : L"SettingsPageBackground"; diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index ff66ab92782..7a49e3c67c9 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -152,12 +152,12 @@ Author(s): X(winrt::Microsoft::Terminal::Settings::Model::TabRowTheme, TabRow, "tabRow", nullptr) \ X(winrt::Microsoft::Terminal::Settings::Model::TabTheme, Tab, "tab", nullptr) -#define MTSM_THEME_WINDOW_SETTINGS(X) \ - X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "applicationTheme", winrt::Windows::UI::Xaml::ElementTheme::Default) \ - X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Frame, "frame", nullptr) \ - X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedFrame, "unfocusedFrame", nullptr) \ - X(bool, RainbowFrame, "experimental.rainbowFrame", false) \ - X(bool, UseMica, "useMica", false) +#define MTSM_THEME_WINDOW_SETTINGS(X) \ + X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "applicationTheme", winrt::Windows::UI::Xaml::ElementTheme::Default) \ + X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Frame, "frame", nullptr) \ + X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedFrame, "unfocusedFrame", nullptr) \ + X(bool, RainbowFrame, "experimental.rainbowFrame", false) \ + X(winrt::Microsoft::Terminal::Settings::Model::MicaKind, UseMica, "useMica", winrt::Microsoft::Terminal::Settings::Model::MicaKind::None) #define MTSM_THEME_SETTINGS_SETTINGS(X) \ X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "theme", winrt::Windows::UI::Xaml::ElementTheme::Default) diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index b2355ce4c73..6045f10372c 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -660,13 +660,39 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVi }; }; -JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::IconStyle) -{ - JSON_MAPPINGS(3) = { - pair_type{ "default", ValueType::Default }, - pair_type{ "hidden", ValueType::Hidden }, - pair_type{ "monochrome", ValueType::Monochrome }, - }; +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::IconStyle) +{ + JSON_MAPPINGS(3) = { + pair_type{ "default", ValueType::Default }, + pair_type{ "hidden", ValueType::Hidden }, + pair_type{ "monochrome", ValueType::Monochrome }, + }; +}; + +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::MicaKind) +{ + JSON_MAPPINGS(3) = { + pair_type{ "none", ValueType::None }, + pair_type{ "mica", ValueType::Mica }, + pair_type{ "micaAlt", ValueType::MicaAlt }, + }; + + // Override mapping parser to add boolean parsing for backward compatibility + ::winrt::Microsoft::Terminal::Settings::Model::MicaKind FromJson(const Json::Value& json) + { + if (json.isBool()) + { + return json.asBool() ? ValueType::Mica : ValueType::None; + } + return EnumMapper::FromJson(json); + } + + bool CanConvert(const Json::Value& json) + { + return EnumMapper::CanConvert(json) || json.isBool(); + } + + using EnumMapper::TypeDescription; }; // Possible ScrollToMarkDirection values diff --git a/src/cascadia/TerminalSettingsModel/Theme.idl b/src/cascadia/TerminalSettingsModel/Theme.idl index 9cf0806f25d..5803be86ab9 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.idl +++ b/src/cascadia/TerminalSettingsModel/Theme.idl @@ -18,12 +18,19 @@ namespace Microsoft.Terminal.Settings.Model TerminalBackground }; - enum TabCloseButtonVisibility - { - Always, - Hover, - Never, - ActiveOnly + enum TabCloseButtonVisibility + { + Always, + Hover, + Never, + ActiveOnly + }; + + enum MicaKind + { + None, + Mica, + MicaAlt }; [default_interface] runtimeclass ThemePair @@ -58,12 +65,12 @@ namespace Microsoft.Terminal.Settings.Model Windows.UI.Xaml.ElementTheme RequestedTheme { get; }; } - runtimeclass WindowTheme { - Windows.UI.Xaml.ElementTheme RequestedTheme { get; }; - Boolean UseMica { get; }; - Boolean RainbowFrame { get; }; - ThemeColor Frame { get; }; - ThemeColor UnfocusedFrame { get; }; + runtimeclass WindowTheme { + Windows.UI.Xaml.ElementTheme RequestedTheme { get; }; + MicaKind UseMica { get; }; + Boolean RainbowFrame { get; }; + ThemeColor Frame { get; }; + ThemeColor UnfocusedFrame { get; }; } runtimeclass TabRowTheme { diff --git a/src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp b/src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp index 34b4e5eb73d..1890acac0ac 100644 --- a/src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp @@ -22,12 +22,13 @@ namespace SettingsModelUnitTests { TEST_CLASS(ThemeTests); - TEST_METHOD(ParseSimpleTheme); - TEST_METHOD(ParseEmptyTheme); - TEST_METHOD(ParseNoWindowTheme); - TEST_METHOD(ParseNullWindowTheme); - TEST_METHOD(ParseThemeWithNullThemeColor); - TEST_METHOD(InvalidCurrentTheme); + TEST_METHOD(ParseSimpleTheme); + TEST_METHOD(ParseEmptyTheme); + TEST_METHOD(ParseNoWindowTheme); + TEST_METHOD(ParseNullWindowTheme); + TEST_METHOD(ParseThemeWithNullThemeColor); + TEST_METHOD(InvalidCurrentTheme); + TEST_METHOD(ParseMicaKindEnumValues); static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept { @@ -68,7 +69,7 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(theme->Window()); VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Light, theme->Window().RequestedTheme()); - VERIFY_ARE_EQUAL(true, theme->Window().UseMica()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::MicaKind::Mica, theme->Window().UseMica()); } void ThemeTests::ParseEmptyTheme() @@ -263,6 +264,96 @@ namespace SettingsModelUnitTests auto deserializationErrorMessage = til::u8u16(e.what()); Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str())); throw e; - } - } + } + } + + void ThemeTests::ParseMicaKindEnumValues() + { + Log::Comment(L"Test parsing of MicaKind enum values and backward compatibility with boolean"); + + // Test with boolean true (should map to Mica) + static constexpr std::string_view booleanTrueTheme{ R"({ + "name": "booleanTrue", + "window": { + "useMica": true + } + })" }; + + // Test with boolean false (should map to None) + static constexpr std::string_view booleanFalseTheme{ R"({ + "name": "booleanFalse", + "window": { + "useMica": false + } + })" }; + + // Test with string "mica" (should map to Mica) + static constexpr std::string_view stringMicaTheme{ R"({ + "name": "stringMica", + "window": { + "useMica": "mica" + } + })" }; + + // Test with string "micaAlt" (should map to MicaAlt) + static constexpr std::string_view stringMicaAltTheme{ R"({ + "name": "stringMicaAlt", + "window": { + "useMica": "micaAlt" + } + })" }; + + // Test with string "none" (should map to None) + static constexpr std::string_view stringNoneTheme{ R"({ + "name": "stringNone", + "window": { + "useMica": "none" + } + })" }; + + // Test boolean true + { + const auto schemeObject = VerifyParseSucceeded(booleanTrueTheme); + auto theme = Theme::FromJson(schemeObject); + VERIFY_ARE_EQUAL(L"booleanTrue", theme->Name()); + VERIFY_IS_NOT_NULL(theme->Window()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::MicaKind::Mica, theme->Window().UseMica()); + } + + // Test boolean false + { + const auto schemeObject = VerifyParseSucceeded(booleanFalseTheme); + auto theme = Theme::FromJson(schemeObject); + VERIFY_ARE_EQUAL(L"booleanFalse", theme->Name()); + VERIFY_IS_NOT_NULL(theme->Window()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::MicaKind::None, theme->Window().UseMica()); + } + + // Test string "mica" + { + const auto schemeObject = VerifyParseSucceeded(stringMicaTheme); + auto theme = Theme::FromJson(schemeObject); + VERIFY_ARE_EQUAL(L"stringMica", theme->Name()); + VERIFY_IS_NOT_NULL(theme->Window()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::MicaKind::Mica, theme->Window().UseMica()); + } + + // Test string "micaAlt" + { + const auto schemeObject = VerifyParseSucceeded(stringMicaAltTheme); + auto theme = Theme::FromJson(schemeObject); + VERIFY_ARE_EQUAL(L"stringMicaAlt", theme->Name()); + VERIFY_IS_NOT_NULL(theme->Window()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::MicaKind::MicaAlt, theme->Window().UseMica()); + } + + // Test string "none" + { + const auto schemeObject = VerifyParseSucceeded(stringNoneTheme); + auto theme = Theme::FromJson(schemeObject); + VERIFY_ARE_EQUAL(L"stringNone", theme->Name()); + VERIFY_IS_NOT_NULL(theme->Window()); + VERIFY_ARE_EQUAL(winrt::Microsoft::Terminal::Settings::Model::MicaKind::None, theme->Window().UseMica()); + } + } } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 51a616a5193..ec21348022a 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -941,7 +941,7 @@ void AppHost::_updateTheme() const auto colorOpacity = b ? color.A / 255.0 : 0.0; const auto brushOpacity = _opacityFromBrush(b); const auto opacity = std::min(colorOpacity, brushOpacity); - _window->UseMica(windowTheme ? windowTheme.UseMica() : false, opacity); + _window->UseMica(windowTheme ? windowTheme.UseMica() : winrt::Microsoft::Terminal::Settings::Model::MicaKind::None, opacity); // This is a hack to make the window borders dark instead of light. // It must be done before WM_NCPAINT so that the borders are rendered with diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index ba128e28148..dd2dfda5e83 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -6,9 +6,14 @@ #include "../types/inc/Viewport.hpp" #include "resource.h" #include "icon.h" -#include -#include -#include +#include +#include +#include + +// Define DWMSBT_TRANSIENTWINDOW if not available (for Mica Alt) +#ifndef DWMSBT_TRANSIENTWINDOW +#define DWMSBT_TRANSIENTWINDOW 3 +#endif extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -1839,17 +1844,31 @@ void IslandWindow::UseDarkTheme(const bool v) std::ignore = DwmSetWindowAttribute(GetHandle(), DWMWA_USE_IMMERSIVE_DARK_MODE, &attribute, sizeof(attribute)); } -void IslandWindow::UseMica(const bool newValue, const double /*titlebarOpacity*/) -{ - // This block of code enables Mica for our window. By all accounts, this - // version of the code will only work on Windows 11, SV2. There's a slightly - // different API surface for enabling Mica on Windows 11 22000.0. - // - // This API was only publicly supported as of Windows 11 SV2, 22621. Before - // that version, this API will just return an error and do nothing silently. - - const int attribute = newValue ? DWMSBT_MAINWINDOW : DWMSBT_NONE; - std::ignore = DwmSetWindowAttribute(GetHandle(), DWMWA_SYSTEMBACKDROP_TYPE, &attribute, sizeof(attribute)); +void IslandWindow::UseMica(const winrt::Microsoft::Terminal::Settings::Model::MicaKind micaKind, const double /*titlebarOpacity*/) +{ + // This block of code enables Mica for our window. By all accounts, this + // version of the code will only work on Windows 11, SV2. There's a slightly + // different API surface for enabling Mica on Windows 11 22000.0. + // + // This API was only publicly supported as of Windows 11 SV2, 22621. Before + // that version, this API will just return an error and do nothing silently. + + int attribute = DWMSBT_NONE; // Default to no backdrop + switch (micaKind) + { + case winrt::Microsoft::Terminal::Settings::Model::MicaKind::None: + attribute = DWMSBT_NONE; + break; + case winrt::Microsoft::Terminal::Settings::Model::MicaKind::Mica: + attribute = DWMSBT_MAINWINDOW; + break; + case winrt::Microsoft::Terminal::Settings::Model::MicaKind::MicaAlt: + // DWMSBT_TRANSIENTWINDOW is the constant for Mica Alt + attribute = DWMSBT_TRANSIENTWINDOW; + break; + } + + std::ignore = DwmSetWindowAttribute(GetHandle(), DWMWA_SYSTEMBACKDROP_TYPE, &attribute, sizeof(attribute)); } // Method Description: diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index a720b321d74..e0e7446248e 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -3,6 +3,7 @@ #pragma once #include "BaseWindow.h" +#include struct SystemMenuItemInfo { @@ -70,7 +71,7 @@ class IslandWindow : void RemoveFromSystemMenu(const winrt::hstring& itemLabel); void UseDarkTheme(const bool v); - virtual void UseMica(const bool newValue, const double titlebarOpacity); + virtual void UseMica(const winrt::Microsoft::Terminal::Settings::Model::MicaKind micaKind, const double titlebarOpacity); til::event> DragRegionClicked; til::event> WindowCloseButtonClicked; diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 95d971ecea1..fbac9f5b9ae 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -1189,15 +1189,15 @@ void NonClientIslandWindow::SetTitlebarBackground(winrt::Windows::UI::Xaml::Medi _titlebar.Background(brush); } -void NonClientIslandWindow::UseMica(const bool newValue, const double titlebarOpacity) -{ - // Stash internally if we're using Mica. If we aren't, we don't want to - // totally blow away our titlebar with DwmExtendFrameIntoClientArea, - // especially on Windows 10 - _useMica = newValue; - _titlebarOpacity = titlebarOpacity; - - IslandWindow::UseMica(newValue, titlebarOpacity); - - _UpdateFrameMargins(); +void NonClientIslandWindow::UseMica(const winrt::Microsoft::Terminal::Settings::Model::MicaKind micaKind, const double titlebarOpacity) +{ + // Stash internally if we're using Mica. If we aren't, we don't want to + // totally blow away our titlebar with DwmExtendFrameIntoClientArea, + // especially on Windows 10 + _useMica = micaKind != winrt::Microsoft::Terminal::Settings::Model::MicaKind::None; + _titlebarOpacity = titlebarOpacity; + + IslandWindow::UseMica(micaKind, titlebarOpacity); + + _UpdateFrameMargins(); } diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 6fc0761a3c6..9bc235ca93a 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -48,7 +48,7 @@ class NonClientIslandWindow : public IslandWindow void SetTitlebarBackground(winrt::Windows::UI::Xaml::Media::Brush brush); void SetShowTabsFullscreen(const bool newShowTabsFullscreen) override; - virtual void UseMica(const bool newValue, const double titlebarOpacity) override; + virtual void UseMica(const winrt::Microsoft::Terminal::Settings::Model::MicaKind micaKind, const double titlebarOpacity) override; private: std::optional _oldIslandPos;