From ab6ba9bdbbbc8a47e5ed194721321fbfc75aaf5f Mon Sep 17 00:00:00 2001 From: Sergey <45919738+serd2011@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:47:58 +0300 Subject: [PATCH] Add settings entry into titlebar context menu (#11404) ## Summary of the Pull Request Adds ability for app to change system context menu ## PR Checklist * [x] Closes #9666 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Validation Steps Performed --- .github/actions/spelling/allow/apis.txt | 2 + src/cascadia/TerminalApp/AppLogic.cpp | 8 +++ src/cascadia/TerminalApp/AppLogic.h | 14 +++++ src/cascadia/TerminalApp/AppLogic.idl | 15 +++++ src/cascadia/TerminalApp/TerminalPage.cpp | 4 +- src/cascadia/TerminalApp/TerminalPage.h | 4 +- src/cascadia/WindowsTerminal/AppHost.cpp | 22 +++++++ src/cascadia/WindowsTerminal/AppHost.h | 3 + src/cascadia/WindowsTerminal/IslandWindow.cpp | 57 +++++++++++++++++++ src/cascadia/WindowsTerminal/IslandWindow.h | 11 ++++ 10 files changed, 136 insertions(+), 4 deletions(-) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 19f5fb487..b17163a05 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -9,6 +9,7 @@ BUILDBRANCH BUILDMSG BUILDNUMBER BYPOSITION +BYCOMMAND charconv CLASSNOTAVAILABLE cmdletbinding @@ -85,6 +86,7 @@ LSHIFT MENUCOMMAND MENUDATA MENUINFO +MENUITEMINFOW memicmp mptt mov diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index b7e1c0ff3..c2b24d029 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -334,6 +334,9 @@ namespace winrt::TerminalApp::implementation _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); + auto args = winrt::make_self(RS_(L"SettingsMenuItem"), SystemMenuChangeAction::Add, SystemMenuItemHandler(this, &AppLogic::_OpenSettingsUI)); + _SystemMenuChangeRequestedHandlers(*this, *args); + TraceLoggingWrite( g_hTerminalAppProvider, "AppCreated", @@ -1051,6 +1054,11 @@ namespace winrt::TerminalApp::implementation _SettingsChangedHandlers(*this, nullptr); } + void AppLogic::_OpenSettingsUI() + { + _root->OpenSettingsUI(); + } + // Method Description: // - Returns a pointer to the global shared settings. [[nodiscard]] CascadiaSettings AppLogic::GetSettings() const noexcept diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index e121043d1..9301a6698 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -5,6 +5,7 @@ #include "AppLogic.g.h" #include "FindTargetWindowResult.g.h" +#include "SystemMenuChangeArgs.g.h" #include "Jumplist.h" #include "LanguageProfileNotifier.h" #include "TerminalPage.h" @@ -35,6 +36,17 @@ namespace winrt::TerminalApp::implementation FindTargetWindowResult(id, L""){}; }; + struct SystemMenuChangeArgs : SystemMenuChangeArgsT + { + WINRT_PROPERTY(winrt::hstring, Name, L""); + WINRT_PROPERTY(SystemMenuChangeAction, Action, SystemMenuChangeAction::Add); + WINRT_PROPERTY(SystemMenuItemHandler, Handler, nullptr); + + public: + SystemMenuChangeArgs(const winrt::hstring& name, SystemMenuChangeAction action, SystemMenuItemHandler handler = nullptr) : + _Name{ name }, _Action{ action }, _Handler{ handler } {}; + }; + struct AppLogic : AppLogicT { public: @@ -113,6 +125,7 @@ namespace winrt::TerminalApp::implementation // -------------------------------- WinRT Events --------------------------------- TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme); TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs); private: bool _isUwp{ false }; @@ -163,6 +176,7 @@ namespace winrt::TerminalApp::implementation void _RegisterSettingsChange(); fire_and_forget _DispatchReloadSettings(); void _ReloadSettings(); + void _OpenSettingsUI(); void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 674322d35..e6aa40788 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -19,6 +19,20 @@ namespace TerminalApp String WindowName { get; }; }; + delegate void SystemMenuItemHandler(); + + enum SystemMenuChangeAction + { + Add = 0, + Remove = 1 + }; + + [default_interface] runtimeclass SystemMenuChangeArgs { + String Name { get; }; + SystemMenuChangeAction Action { get; }; + SystemMenuItemHandler Handler { get; }; + }; + [default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter { AppLogic(); @@ -110,5 +124,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler CloseRequested; event Windows.Foundation.TypedEventHandler OpenSystemMenu; event Windows.Foundation.TypedEventHandler QuitRequested; + event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; } } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 4c16c1a0a..044ae308b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2111,7 +2111,7 @@ namespace winrt::TerminalApp::implementation { if (target == SettingsTarget::SettingsUI) { - _OpenSettingsUI(); + OpenSettingsUI(); } else { @@ -2759,7 +2759,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalPage::_OpenSettingsUI() + void TerminalPage::OpenSettingsUI() { // If we're holding the settings tab's switch command, don't create a new one, switch to the existing one. if (!_settingsTab) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index f6de6a28b..28a156f79 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -121,6 +121,8 @@ namespace winrt::TerminalApp::implementation bool IsQuakeWindow() const noexcept; bool IsElevated() const noexcept; + void OpenSettingsUI(); + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); // -------------------------------- WinRT Events --------------------------------- @@ -366,8 +368,6 @@ namespace winrt::TerminalApp::implementation void _UnZoomIfNeeded(); - void _OpenSettingsUI(); - static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll); static uint32_t _ReadSystemRowsToScroll(); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 7b6e74ef3..849c4e804 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -325,6 +325,7 @@ void AppHost::Initialize() _logic.FocusModeChanged({ this, &AppHost::_FocusModeChanged }); _logic.AlwaysOnTopChanged({ this, &AppHost::_AlwaysOnTopChanged }); _logic.RaiseVisualBell({ this, &AppHost::_RaiseVisualBell }); + _logic.SystemMenuChangeRequested({ this, &AppHost::_SystemMenuChangeRequested }); _logic.Create(); @@ -1248,6 +1249,27 @@ void AppHost::_OpenSystemMenu(const winrt::Windows::Foundation::IInspectable&, _window->OpenSystemMenu(std::nullopt, std::nullopt); } +void AppHost::_SystemMenuChangeRequested(const winrt::Windows::Foundation::IInspectable&, const winrt::TerminalApp::SystemMenuChangeArgs& args) +{ + switch (args.Action()) + { + case winrt::TerminalApp::SystemMenuChangeAction::Add: + { + auto handler = args.Handler(); + _window->AddToSystemMenu(args.Name(), [handler]() { handler(); }); + break; + } + case winrt::TerminalApp::SystemMenuChangeAction::Remove: + { + _window->RemoveFromSystemMenu(args.Name()); + break; + } + default: + { + } + } +} + // Method Description: // - Creates a Notification Icon and hooks up its handlers // Arguments: diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 149c65705..6675e8209 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -96,6 +96,9 @@ private: void _OpenSystemMenu(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _SystemMenuChangeRequested(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::TerminalApp::SystemMenuChangeArgs& args); + winrt::fire_and_forget _QuitRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 16ac019af..a28ccaa21 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -24,6 +24,7 @@ using namespace ::Microsoft::Console::Types; using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers; #define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS" +#define IDM_SYSTEM_MENU_BEGIN 0x1000 const UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated"); @@ -321,6 +322,8 @@ void IslandWindow::Initialize() } } + _systemMenuNextItemId = IDM_SYSTEM_MENU_BEGIN; + // Enable vintage opacity by removing the XAML emergency backstop, GH#603. // We don't really care if this failed or not. TerminalTrySetTransparentBackground(true); @@ -608,6 +611,15 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize _NotifyNotificationIconMenuItemSelectedHandlers((HMENU)lparam, (UINT)wparam); return 0; } + case WM_SYSCOMMAND: + { + auto search = _systemMenuItems.find(LOWORD(wparam)); + if (search != _systemMenuItems.end()) + { + search->second.callback(); + } + break; + } default: // We'll want to receive this message when explorer.exe restarts // so that we can re-add our icon to the notification area. @@ -1717,5 +1729,50 @@ void IslandWindow::OpenSystemMenu(const std::optional mouseX, const std::op } } +void IslandWindow::AddToSystemMenu(const winrt::hstring& itemLabel, winrt::delegate callback) +{ + const HMENU systemMenu = GetSystemMenu(_window.get(), FALSE); + UINT wID = _systemMenuNextItemId; + + MENUITEMINFOW item; + item.cbSize = sizeof(MENUITEMINFOW); + item.fMask = MIIM_STATE | MIIM_ID | MIIM_STRING; + item.fState = MF_ENABLED; + item.wID = wID; + item.dwTypeData = const_cast(itemLabel.c_str()); + item.cch = static_cast(itemLabel.size()); + + if (LOG_LAST_ERROR_IF(!InsertMenuItemW(systemMenu, wID, FALSE, &item))) + { + return; + } + _systemMenuItems.insert({ wID, { itemLabel, callback } }); + _systemMenuNextItemId++; +} + +void IslandWindow::RemoveFromSystemMenu(const winrt::hstring& itemLabel) +{ + const HMENU systemMenu = GetSystemMenu(_window.get(), FALSE); + int itemCount = GetMenuItemCount(systemMenu); + if (LOG_LAST_ERROR_IF(itemCount == -1)) + { + return; + } + + auto it = std::find_if(_systemMenuItems.begin(), _systemMenuItems.end(), [&itemLabel](const std::pair& elem) { + return elem.second.label == itemLabel; + }); + if (it == _systemMenuItems.end()) + { + return; + } + + if (LOG_LAST_ERROR_IF(!DeleteMenu(systemMenu, it->first, MF_BYCOMMAND))) + { + return; + } + _systemMenuItems.erase(it->first); +} + DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); DEFINE_EVENT(IslandWindow, WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 8f6b283c1..0ac7b1e0b 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -8,6 +8,12 @@ void SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept; +struct SystemMenuItemInfo +{ + winrt::hstring label; + winrt::delegate callback; +}; + class IslandWindow : public BaseWindow { @@ -54,6 +60,8 @@ public: void SetMinimizeToNotificationAreaBehavior(bool MinimizeToNotificationArea) noexcept; void OpenSystemMenu(const std::optional mouseX, const std::optional mouseY) const noexcept; + void AddToSystemMenu(const winrt::hstring& itemLabel, winrt::delegate callback); + void RemoveFromSystemMenu(const winrt::hstring& itemLabel); DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); @@ -131,6 +139,9 @@ protected: bool _minimizeToNotificationArea{ false }; + std::unordered_map _systemMenuItems; + UINT _systemMenuNextItemId; + private: // This minimum width allows for width the tabs fit static constexpr long minimumWidth = 460L;