Repository: microsoft/terminal (language: cpp)

Issue:
Action for adjusting opacity at runtime
<!-- 
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨

I ACKNOWLEDGE THE FOLLOWING BEFORE PROCEEDING:
1. If I delete this entire template and go my own path, the core team may close my issue without further explanation or engagement.
2. If I list multiple bugs/concerns in this one issue, the core team may close my issue without further explanation or engagement.
3. If I write an issue that has many duplicates, the core team may close my issue without further explanation or engagement (and without necessarily spending time to find the exact duplicate ID number).
4. If I leave the title incomplete when filing the issue, the core team may close my issue without further explanation or engagement.
5. If I file something completely blank in the body, the core team may close my issue without further explanation or engagement.

All good? Then proceed!
-->

# Description of the new feature/enhancement
I love the pane of glass look for Terminal. However, when using a dark theme for example in Terminal and the window is in front-of a light colored windows (i.e. Browser with a white page), the text is unreadable  in terminal. And when the Terminal is in front of a dark background the text color in Terminal is nice and crisp. What would be great is if the Terminal can detect the underlying window color and dynamically adjust the opacity to ensure the text of the Terminal window remains readable regardless of the window that Terminal is in front of.

<!-- 
A clear and concise description of what the problem is that the new feature would solve.
Describe why and how a user would use this new functionality (if applicable).
-->
Can't read text in Terminal with transparency in front of a light window when using a dark theme. In order to keep the pane of glass look, I need to keep adjusting the transparency setting based on the background colors (background theme and/or window the Terminal is overlaying). New functionality would automatically adjust this or at a minimum provide a hot key to adjust transparency on demand

# Proposed technical implementation details (optional)

<!-- 
A clear and concise description of what you want to happen.
-->



Discussion / hints:
Wow, someone who wants a transparency feature that's not #11180, what a pleasant surprise 😅 

I'm pretty sure there's not a good way for the Terminal to be able to know what the contents of the screen are underneath our window. However:

>  or at a minimum provide a hot key to adjust transparency on demand

Now that's something I can get behind, and something I was already planning for landing once #11180 merged and once I did the control settings refactor for #5000, so I'll leave this open to track that. Thanks!

<hr>

edit: pseudo-spec

* `setOpactiy`: 
  * `"opacity"`: (**required**) an integer number to set the opacity to. 
  * `"relative"`: (defaults to `false`) 
    * If false, set the opacity to the given value, which should be between [0, 100]. 
    * If true, then use `opacity` as a relative adjustment to the current opacity. So the implementation would get the current opacity for this pane, then add this action's opacity to that value. Should be between [-100, 100]

This would allow both setting exactly 25% opacity with an action, and binding an action that increases/decreases the opacity one {amount}

Fix patch:
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index d4f972d9ede..437cc09c861 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -342,6 +342,7 @@
         "toggleShaderEffects",
         "wt",
         "quit",
+        "adjustOpacity",
         "restoreLastClosed",
         "unbound"
       ],
@@ -1385,6 +1386,34 @@
         }
       ]
     },
+    "AdjustOpacityAction": {
+      "description": "Changes the opacity of the active Terminal window. If `relative` is specified, then this action will increase/decrease relative to the current opacity.",
+      "allOf": [
+        {
+          "$ref": "#/$defs/ShortcutAction"
+        },
+        {
+          "properties": {
+            "action": {
+              "type": "string",
+              "const": "adjustOpacity"
+            },
+            "opacity": {
+              "type": "integer",
+              "minimum": -100,
+              "maximum": 100,
+              "default": 0,
+              "description": "How opaque the terminal should become or how much the opacity should be changed by, depending on the value of `relative`"
+            },
+            "relative": {
+              "type": "boolean",
+              "default": true,
+              "description": "If true, then adjust the current opacity by the given `opacity` parameter, additively. If false, set the opacity to exactly that value."
+            }
+          }
+        }
+      ]
+    },
     "Keybinding": {
       "additionalProperties": false,
       "properties": {
@@ -1493,6 +1522,9 @@
             {
               "$ref": "#/$defs/QuakeModeAction"
             },
+            {
+              "$ref": "#/$defs/AdjustOpacityAction"
+            },
             {
               "type": "null"
             }
diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp
index 0deb0570e3c..424f0ddcdc0 100644
--- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp
+++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp
@@ -1333,7 +1333,7 @@ namespace TerminalAppLocalTests
             Log::Comment(L"Emulate committing the SetColorScheme action");
 
             SetColorSchemeArgs args{ L"Vintage" };
-            page->_EndPreviewColorScheme();
+            page->_EndPreview();
             page->_HandleSetColorScheme(nullptr, ActionEventArgs{ args });
         });
 
@@ -1395,7 +1395,7 @@ namespace TerminalAppLocalTests
 
         TestOnUIThread([&page]() {
             Log::Comment(L"Emulate dismissing the SetColorScheme action");
-            page->_EndPreviewColorScheme();
+            page->_EndPreview();
         });
 
         TestOnUIThread([&page]() {
@@ -1469,7 +1469,7 @@ namespace TerminalAppLocalTests
             Log::Comment(L"Emulate committing the SetColorScheme action");
 
             SetColorSchemeArgs args{ L"One Half Light" };
-            page->_EndPreviewColorScheme();
+            page->_EndPreview();
             page->_HandleSetColorScheme(nullptr, ActionEventArgs{ args });
         });
 
diff --git a/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp b/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp
index 9965674f4e0..c43dcdf399a 100644
--- a/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp
+++ b/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp
@@ -49,8 +49,9 @@ namespace winrt::TerminalApp::implementation
         switch (_lastPreviewedCommand.ActionAndArgs().Action())
         {
         case ShortcutAction::SetColorScheme:
+        case ShortcutAction::AdjustOpacity:
         {
-            _EndPreviewColorScheme();
+            _RunRestorePreviews();
             break;
         }
         }
@@ -58,14 +59,13 @@ namespace winrt::TerminalApp::implementation
     }
 
     // Method Description:
-    // - Revert any changes from the preview on a SetColorScheme action. This
-    //   will remove the preview TerminalSettings we inserted into the control's
-    //   TerminalSettings graph, and update the control.
+    // - Revert any changes from the preview action. This will run the restore
+    //   function that the preview added to _restorePreviewFuncs
     // Arguments:
     // - <none>
     // Return Value:
     // - <none>
-    void TerminalPage::_EndPreviewColorScheme()
+    void TerminalPage::_RunRestorePreviews()
     {
         // Apply the reverts in reverse order - If we had multiple previews
         // stacked on top of each other, then this will ensure the first one in
@@ -111,6 +111,27 @@ namespace winrt::TerminalApp::implementation
         }
     }
 
+    void TerminalPage::_PreviewAdjustOpacity(const Settings::Model::AdjustOpacityArgs& args)
+    {
+        // Clear the saved preview funcs because we don't need to add a restore each time
+        // the preview changes, we only need to be able to restore the last one.
+        _restorePreviewFuncs.clear();
+
+        _ApplyToActiveControls([&](const auto& control) {
+            // Stash a copy of the original opacity.
+            auto originalOpacity{ control.BackgroundOpacity() };
+
+            // Apply the new opacity
+            control.AdjustOpacity(args.Opacity(), args.Relative());
+
+            _restorePreviewFuncs.emplace_back([=]() {
+                // On dismiss:
+                // Don't adjust relatively, just set outright.
+                control.AdjustOpacity(::base::saturated_cast<int>(originalOpacity * 100), false);
+            });
+        });
+    }
+
     // Method Description:
     // - Handler for the CommandPalette::PreviewAction event. The Command
     //   Palette will raise this even when an action is selected, but _not_
@@ -142,6 +163,11 @@ namespace winrt::TerminalApp::implementation
                 _PreviewColorScheme(args.ActionAndArgs().Args().try_as<SetColorSchemeArgs>());
                 break;
             }
+            case ShortcutAction::AdjustOpacity:
+            {
+                _PreviewAdjustOpacity(args.ActionAndArgs().Args().try_as<AdjustOpacityArgs>());
+                break;
+            }
             }
 
             // GH#9818 Other ideas for actions that could be preview-able:
diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index 213d514b063..c8dbacbcbde 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -958,4 +958,19 @@ namespace winrt::TerminalApp::implementation
             }
         }
     }
+
+    void TerminalPage::_HandleAdjustOpacity(const IInspectable& /*sender*/,
+                                            const ActionEventArgs& args)
+    {
+        if (args)
+        {
+            if (const auto& realArgs = args.ActionArgs().try_as<AdjustOpacityArgs>())
+            {
+                const auto res = _ApplyToActiveControls([&](auto& control) {
+                    control.AdjustOpacity(realArgs.Opacity(), realArgs.Relative());
+                });
+                args.Handled(res);
+            }
+        }
+    }
 }
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index bbe9e4f2ecb..91c76fd1040 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -398,8 +398,9 @@ namespace winrt::TerminalApp::implementation
 
         void _PreviewActionHandler(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& args);
         void _EndPreview();
-        void _EndPreviewColorScheme();
+        void _RunRestorePreviews();
         void _PreviewColorScheme(const Microsoft::Terminal::Settings::Model::SetColorSchemeArgs& args);
+        void _PreviewAdjustOpacity(const Microsoft::Terminal::Settings::Model::AdjustOpacityArgs& args);
         winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr };
         std::vector<std::function<void()>> _restorePreviewFuncs{};
 
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index f4eaf1c7554..7ec3056a6ab 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -445,11 +445,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
             return;
         }
 
-        auto newOpacity = std::clamp(Opacity() + adjustment,
-                                     0.0,
-                                     1.0);
+        _setOpacity(Opacity() + adjustment);
+    }
+
+    void ControlCore::_setOpacity(const double opacity)
+    {
+        const auto newOpacity = std::clamp(opacity,
+                                           0.0,
+                                           1.0);
+
+        if (newOpacity == Opacity())
+        {
+            return;
+        }
 
-        auto lock = _terminal->LockForWriting();
         // Update our runtime opacity value
         Opacity(newOpacity);
 
@@ -1701,6 +1710,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         _settings->FocusedAppearance()->SetColorTableEntry(15, scheme.BrightWhite);
 
         _terminal->ApplyScheme(scheme);
+
         _renderEngine->SetSelectionBackground(til::color{ _settings->SelectionBackground() });
 
         _renderer->TriggerRedrawAll();
@@ -1712,6 +1722,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         return _settings->HasUnfocusedAppearance();
     }
 
+    void ControlCore::AdjustOpacity(const int32_t& opacity, const bool& relative)
+    {
+        const double opacityAdjust = static_cast<double>(opacity) / 100.0;
+        if (relative)
+        {
+            AdjustOpacity(opacityAdjust);
+        }
+        else
+        {
+            _setOpacity(opacityAdjust);
+        }
+    }
+
     bool ControlCore::_isBackgroundTransparent()
     {
         // If we're:
diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h
index 0252b501221..38088a838d2 100644
--- a/src/cascadia/TerminalControl/ControlCore.h
+++ b/src/cascadia/TerminalControl/ControlCore.h
@@ -166,6 +166,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         static bool IsVintageOpacityAvailable() noexcept;
 
+        void AdjustOpacity(const int32_t& opacity, const bool& relative);
+
         RUNTIME_SETTING(double, Opacity, _settings->Opacity());
         RUNTIME_SETTING(bool, UseAcrylic, _settings->UseAcrylic());
 
@@ -269,6 +271,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         void _updateAntiAliasingMode();
         void _connectionOutputHandler(const hstring& hstr);
         void _updateHoveredCell(const std::optional<til::point> terminalPosition);
+        void _setOpacity(const double opacity);
 
         bool _isBackgroundTransparent();
 
diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl
index 2a4287f5a83..c23461412e2 100644
--- a/src/cascadia/TerminalControl/ControlCore.idl
+++ b/src/cascadia/TerminalControl/ControlCore.idl
@@ -98,6 +98,8 @@ namespace Microsoft.Terminal.Control
 
         String ReadEntireBuffer();
 
+        void AdjustOpacity(Int32 Opacity, Boolean relative);
+
         event FontSizeChangedEventArgs FontSizeChanged;
 
         event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index b6df41e3772..53206b16ed1 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -2001,7 +2001,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
             const winrt::Windows::Foundation::Size minSize{ 1, 1 };
             const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
             const auto dpi = ::base::saturated_cast<uint32_t>(USER_DEFAULT_SCREEN_DPI * scaleFactor);
-
             return GetProposedDimensions(_core.Settings(), dpi, minSize);
         }
     }
@@ -2708,4 +2707,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     {
         _core.ColorScheme(scheme);
     }
+
+    void TermControl::AdjustOpacity(const int32_t& opacity, const bool& relative)
+    {
+        _core.AdjustOpacity(opacity, relative);
+    }
+
+    // - You'd think this should just be "Opacity", but UIElement already
+    //   defines an "Opacity", which we're actually not setting at all. We're
+    //   not overriding or changing _that_ value. Callers that want the opacity
+    //   set by the settings should call this instead.
+    double TermControl::BackgroundOpacity() const
+    {
+        return _core.Opacity();
+    }
 }
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index b84632a09f6..4c360913b4c 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -14,6 +14,7 @@
 #include "SearchBoxControl.h"
 
 #include "ControlInteractivity.h"
+#include "ControlSettings.h"
 
 namespace Microsoft::Console::VirtualTerminal
 {
@@ -56,6 +57,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         int BufferHeight() const;
 
         bool BracketedPasteEnabled() const noexcept;
+
+        double BackgroundOpacity() const;
 #pragma endregion
 
         void ScrollViewport(int viewTop);
@@ -107,6 +110,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         winrt::Microsoft::Terminal::Core::Scheme ColorScheme() const noexcept;
         void ColorScheme(const winrt::Microsoft::Terminal::Core::Scheme& scheme) const noexcept;
 
+        void AdjustOpacity(const int32_t& opacity, const bool& relative);
+
         // -------------------------------- WinRT Events ---------------------------------
         // clang-format off
         WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index a87cdc8edbe..4862c408b3d 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -72,5 +72,14 @@ namespace Microsoft.Terminal.Control
         void ToggleReadOnly();
 
         String ReadEntireBuffer();
+
+        void AdjustOpacity(Int32 Opacity, Boolean relative);
+
+        // You'd think this should just be "Opacity", but UIElement already
+        // defines an "Opacity", which we're actually not setting at all. We're
+        // not overriding or changing _that_ value. Callers that want the
+        // opacity set by the settings should call this instead.
+        Double BackgroundOpacity { get; };
+
     }
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
index b6d8d2b1f3e..7abb6b33f7a 100644
--- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp
@@ -73,6 +73,7 @@ static constexpr std::string_view ExportBufferKey{ "exportBuffer" };
 static constexpr std::string_view ClearBufferKey{ "clearBuffer" };
 static constexpr std::string_view MultipleActionsKey{ "multipleActions" };
 static constexpr std::string_view QuitKey{ "quit" };
+static constexpr std::string_view AdjustOpacityKey{ "adjustOpacity" };
 static constexpr std::string_view RestoreLastClosedKey{ "restoreLastClosed" };
 
 static constexpr std::string_view ActionKey{ "action" };
@@ -383,6 +384,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
                 { ShortcutAction::ClearBuffer, L"" }, // Intentionally omitted, must be generated by GenerateName
                 { ShortcutAction::MultipleActions, L"" }, // Intentionally omitted, must be generated by GenerateName
                 { ShortcutAction::Quit, RS_(L"QuitCommandKey") },
+                { ShortcutAction::AdjustOpacity, L"" }, // Intentionally omitted, must be generated by GenerateName
                 { ShortcutAction::RestoreLastClosed, RS_(L"RestoreLastClosedCommandKey") },
             };
         }();
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
index c13e0e42313..81387f4770f 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp
@@ -40,6 +40,7 @@
 #include "ExportBufferArgs.g.cpp"
 #include "ClearBufferArgs.g.cpp"
 #include "MultipleActionsArgs.g.cpp"
+#include "AdjustOpacityArgs.g.cpp"
 
 #include <LibraryResources.h>
 #include <WtExeUtils.h>
@@ -769,4 +770,35 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
     {
         return L"";
     }
+
+    winrt::hstring AdjustOpacityArgs::GenerateName() const
+    {
+        if (Relative())
+        {
+            if (Opacity() >= 0)
+            {
+                // "Increase background opacity by {Opacity}%"
+                return winrt::hstring{
+                    fmt::format(std::wstring_view(RS_(L"IncreaseOpacityCommandKey")),
+                                Opacity())
+                };
+            }
+            else
+            {
+                // "Decrease background opacity by {Opacity}%"
+                return winrt::hstring{
+                    fmt::format(std::wstring_view(RS_(L"DecreaseOpacityCommandKey")),
+                                Opacity())
+                };
+            }
+        }
+        else
+        {
+            // "Set background opacity to {Opacity}%"
+            return winrt::hstring{
+                fmt::format(std::wstring_view(RS_(L"AdjustOpacityCommandKey")),
+                            Opacity())
+            };
+        }
+    }
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h
index 2246d9baab5..05bd831a3ae 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.h
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h
@@ -42,6 +42,7 @@
 #include "ExportBufferArgs.g.h"
 #include "ClearBufferArgs.g.h"
 #include "MultipleActionsArgs.g.h"
+#include "AdjustOpacityArgs.g.h"
 
 #include "JsonUtils.h"
 #include "HashUtils.h"
@@ -219,6 +220,11 @@ private:
 #define CLEAR_BUFFER_ARGS(X) \
     X(winrt::Microsoft::Terminal::Control::ClearBufferType, Clear, "clear", false, winrt::Microsoft::Terminal::Control::ClearBufferType::All)
 
+////////////////////////////////////////////////////////////////////////////////
+#define ADJUST_OPACITY_ARGS(X)               \
+    X(int32_t, Opacity, "opacity", false, 0) \
+    X(bool, Relative, "relative", false, true)
+
 ////////////////////////////////////////////////////////////////////////////////
 
 namespace winrt::Microsoft::Terminal::Settings::Model::implementation
@@ -690,6 +696,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
             return h.finalize();
         }
     };
+
+    ACTION_ARGS_STRUCT(AdjustOpacityArgs, ADJUST_OPACITY_ARGS);
+
 }
 
 namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
@@ -723,4 +732,5 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
     BASIC_FACTORY(ExportBufferArgs);
     BASIC_FACTORY(ClearBufferArgs);
     BASIC_FACTORY(MultipleActionsArgs);
+    BASIC_FACTORY(AdjustOpacityArgs);
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
index bcbc63b1b6c..b3d0d5d3d94 100644
--- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl
+++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl
@@ -359,4 +359,11 @@ namespace Microsoft.Terminal.Settings.Model
         MultipleActionsArgs();
         Windows.Foundation.Collections.IVector<ActionAndArgs> Actions;
     };
+
+    [default_interface] runtimeclass AdjustOpacityArgs : IActionArgs
+    {
+        AdjustOpacityArgs();
+        Int32 Opacity { get; };
+        Boolean Relative { get; };
+    };
 }
diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
index 91154ffbfc5..32ba5d6b631 100644
--- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
+++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h
@@ -87,6 +87,7 @@
     ON_ALL_ACTIONS(ClearBuffer)            \
     ON_ALL_ACTIONS(MultipleActions)        \
     ON_ALL_ACTIONS(Quit)                   \
+    ON_ALL_ACTIONS(AdjustOpacity)          \
     ON_ALL_ACTIONS(RestoreLastClosed)
 
 #define ALL_SHORTCUT_ACTIONS_WITH_ARGS             \
@@ -124,4 +125,5 @@
     ON_ALL_ACTIONS_WITH_ARGS(FocusPane)            \
     ON_ALL_ACTIONS_WITH_ARGS(ExportBuffer)         \
     ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer)          \
-    ON_ALL_ACTIONS_WITH_ARGS(MultipleActions)
+    ON_ALL_ACTIONS_WITH_ARGS(MultipleActions)      \
+    ON_ALL_ACTIONS_WITH_ARGS(AdjustOpacity)
diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
index a055f4d150e..ef1a25c33ec 100644
--- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw
@@ -503,6 +503,21 @@
   <data name="QuitCommandKey" xml:space="preserve">
     <value>Quit the Terminal</value>
   </data>
+  <data name="SetOpacityParentCommandName" xml:space="preserve">
+    <value>Set background opacity...</value>
+  </data>
+  <data name="IncreaseOpacityCommandKey" xml:space="preserve">
+    <value>Increase background opacity by {0}%</value>
+    <comment>A command to change how transparent the background of the window is</comment>
+  </data>
+  <data name="DecreaseOpacityCommandKey" xml:space="preserve">
+    <value>Decrease background opacity by {0}%</value>
+    <comment>A command to change how transparent the background of the window is</comment>
+  </data>
+  <data name="AdjustOpacityCommandKey" xml:space="preserve">
+    <value>Set background opacity to {0}%</value>
+    <comment>A command to change how transparent the background of the window is</comment>
+  </data>
   <data name="RestoreLastClosedCommandKey" xml:space="preserve">
     <value>Restore the last closed pane or tab</value>
   </data>
diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json
index 2c4f458ed64..89f78f630ff 100644
--- a/src/cascadia/TerminalSettingsModel/defaults.json
+++ b/src/cascadia/TerminalSettingsModel/defaults.json
@@ -454,6 +454,17 @@
                     ]
                 }
             ]
-        }
+        },
+        {
+            // Set opacity...
+            "name": { "key": "SetOpacityParentCommandName" },
+            "commands": [
+                { "command": { "action": "adjustOpacity", "opacity": 0, "relative": false } },
+                { "command": { "action": "adjustOpacity", "opacity": 25, "relative": false } },
+                { "command": { "action": "adjustOpacity", "opacity": 50, "relative": false } },
+                { "command": { "action": "adjustOpacity", "opacity": 75, "relative": false } },
+                { "command": { "action": "adjustOpacity", "opacity": 100, "relative": false } }
+            ]
+        },
     ]
 }


Test patch: