diff --git a/src/cascadia/Remoting/Monarch.cpp b/src/cascadia/Remoting/Monarch.cpp index f65422d3f9d..3ad10db53a5 100644 --- a/src/cascadia/Remoting/Monarch.cpp +++ b/src/cascadia/Remoting/Monarch.cpp @@ -762,4 +762,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } } + + Windows::Foundation::Collections::IMap Monarch::GetPeasantNames() + { + auto names = winrt::single_threaded_map(); + for (auto [key, value] : _peasants) + { + names.Insert(key, value.WindowName()); + } + return names; + } } diff --git a/src/cascadia/Remoting/Monarch.h b/src/cascadia/Remoting/Monarch.h index 9dc8fc77e8a..a77cebb90db 100644 --- a/src/cascadia/Remoting/Monarch.h +++ b/src/cascadia/Remoting/Monarch.h @@ -51,6 +51,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args); void SummonWindow(const Remoting::SummonWindowSelectionArgs& args); + Windows::Foundation::Collections::IMap GetPeasantNames(); + TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs); private: diff --git a/src/cascadia/Remoting/Monarch.idl b/src/cascadia/Remoting/Monarch.idl index d87d780b3b6..31e3bc8b3bb 100644 --- a/src/cascadia/Remoting/Monarch.idl +++ b/src/cascadia/Remoting/Monarch.idl @@ -40,6 +40,8 @@ namespace Microsoft.Terminal.Remoting void HandleActivatePeasant(WindowActivatedArgs args); void SummonWindow(SummonWindowSelectionArgs args); + Windows.Foundation.Collections.IMap GetPeasantNames(); + event Windows.Foundation.TypedEventHandler FindTargetWindowRequested; }; } diff --git a/src/cascadia/Remoting/Peasant.cpp b/src/cascadia/Remoting/Peasant.cpp index 58a71acba26..3c0d44f6a98 100644 --- a/src/cascadia/Remoting/Peasant.cpp +++ b/src/cascadia/Remoting/Peasant.cpp @@ -30,6 +30,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation void Peasant::AssignID(uint64_t id) { _id = id; + + // Provide a default name if we're currently unnamed. + if (_WindowName.empty()) + { + _WindowName = fmt::format(L"Window {}", _id); + } } uint64_t Peasant::GetID() { diff --git a/src/cascadia/Remoting/WindowManager.cpp b/src/cascadia/Remoting/WindowManager.cpp index 88e0adbf071..da2b93d3a74 100644 --- a/src/cascadia/Remoting/WindowManager.cpp +++ b/src/cascadia/Remoting/WindowManager.cpp @@ -274,7 +274,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation p->AssignID(givenID.value()); } - // If the name wasn't specified, this will be an empty string. p->WindowName(givenName); _peasant = *p; @@ -509,4 +508,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation _monarch.SummonWindow(args); } + Windows::Foundation::Collections::IMap WindowManager::GetPeasantNames() + { + assert(_monarch); + return _monarch.GetPeasantNames(); + } + } diff --git a/src/cascadia/Remoting/WindowManager.h b/src/cascadia/Remoting/WindowManager.h index 0b87075f975..60c78b4c083 100644 --- a/src/cascadia/Remoting/WindowManager.h +++ b/src/cascadia/Remoting/WindowManager.h @@ -40,6 +40,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation bool IsMonarch(); void SummonWindow(const Remoting::SummonWindowSelectionArgs& args); + Windows::Foundation::Collections::IMap GetPeasantNames(); + TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs); TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); diff --git a/src/cascadia/Remoting/WindowManager.idl b/src/cascadia/Remoting/WindowManager.idl index 547b96f9aa5..2dd2239ea0d 100644 --- a/src/cascadia/Remoting/WindowManager.idl +++ b/src/cascadia/Remoting/WindowManager.idl @@ -12,6 +12,7 @@ namespace Microsoft.Terminal.Remoting IPeasant CurrentWindow(); Boolean IsMonarch { get; }; void SummonWindow(SummonWindowSelectionArgs args); + Windows.Foundation.Collections.IMap GetPeasantNames(); event Windows.Foundation.TypedEventHandler FindTargetWindowRequested; event Windows.Foundation.TypedEventHandler BecameMonarch; }; diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index d3d51c56af7..8db545379cf 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -815,4 +815,11 @@ namespace winrt::TerminalApp::implementation } } } + + void TerminalPage::_HandleMinimizeToTray(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + _MinimizeToTrayRequestedHandlers(*this, nullptr); + args.Handled(true); + } } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 50b7b95f09d..78d1f666fe8 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -166,6 +166,7 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested); FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged); FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested); + FORWARDED_TYPED_EVENT(MinimizeToTrayRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, MinimizeToTrayRequested); #ifdef UNIT_TESTING friend class TerminalAppLocalTests::CommandlineTest; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index bb334d26f47..63ac2ee45dd 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -93,5 +93,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SettingsChanged; event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; event Windows.Foundation.TypedEventHandler SummonWindowRequested; + event Windows.Foundation.TypedEventHandler MinimizeToTrayRequested; } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 9de1ded9b74..712c9fffe3f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -125,6 +125,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs); TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable); TYPED_EVENT(SummonWindowRequested, IInspectable, IInspectable); + TYPED_EVENT(MinimizeToTrayRequested, IInspectable, IInspectable); private: friend struct TerminalPageT; // for Xaml to bind events diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index 0d055782268..79d23adb7b9 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -57,5 +57,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler RenameWindowRequested; event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; event Windows.Foundation.TypedEventHandler SummonWindowRequested; + event Windows.Foundation.TypedEventHandler MinimizeToTrayRequested; } } diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index fd6334e768f..6e264a4aff4 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -61,6 +61,7 @@ static constexpr std::string_view OpenWindowRenamerKey{ "openWindowRenamer" }; static constexpr std::string_view GlobalSummonKey{ "globalSummon" }; static constexpr std::string_view QuakeModeKey{ "quakeMode" }; static constexpr std::string_view FocusPaneKey{ "focusPane" }; +static constexpr std::string_view MinimizeToTrayKey{ "minimizeToTray" }; static constexpr std::string_view ActionKey{ "action" }; @@ -330,6 +331,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::GlobalSummon, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::QuakeMode, RS_(L"QuakeModeCommandKey") }, { ShortcutAction::FocusPane, L"" }, // Intentionally omitted, must be generated by GenerateName + { ShortcutAction::MinimizeToTray, RS_(L"MinimizeToTrayCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index 5cfaf187a2f..10eabf5da99 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -75,7 +75,8 @@ ON_ALL_ACTIONS(OpenWindowRenamer) \ ON_ALL_ACTIONS(GlobalSummon) \ ON_ALL_ACTIONS(QuakeMode) \ - ON_ALL_ACTIONS(FocusPane) + ON_ALL_ACTIONS(FocusPane) \ + ON_ALL_ACTIONS(MinimizeToTray) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \ diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index c85167ef33b..0c5d2ca6cd3 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -413,4 +413,7 @@ Windows Console Host Name describing the usage of the classic windows console as the terminal UI. (`conhost.exe`) + + Minimize window to tray + diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 1070b5ec0af..be9a386046c 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -79,15 +79,11 @@ AppHost::AppHost() noexcept : _window->WindowActivated({ this, &AppHost::_WindowActivated }); _window->HotkeyPressed({ this, &AppHost::_GlobalHotkeyPressed }); _window->NotifyTrayIconPressed({ this, &AppHost::_HandleTrayIconPressed }); - _window->NotifyShowTrayContextMenu({this, &AppHost::_CreateTrayContextMenu }); + _window->NotifyShowTrayContextMenu({this, &AppHost::_ShowTrayContextMenu }); + _window->NotifyTrayMenuItemSelected({this, &AppHost::_TrayMenuItemSelected }); _window->SetAlwaysOnTop(_logic.GetInitialAlwaysOnTop()); _window->MakeWindow(); - if (_window->IsQuakeWindow()) - { - _UpdateTrayIcon(); - } - _windowManager.BecameMonarch({ this, &AppHost::_BecomeMonarch }); if (_windowManager.IsMonarch()) { @@ -275,6 +271,7 @@ void AppHost::Initialize() _logic.SettingsChanged({ this, &AppHost::_HandleSettingsChanged }); _logic.IsQuakeWindowChanged({ this, &AppHost::_IsQuakeWindowChanged }); _logic.SummonWindowRequested({ this, &AppHost::_SummonWindowRequested }); + _logic.MinimizeToTrayRequested({ this, &AppHost::_MinimizeToTrayRequested }); _window->UpdateTitle(_logic.Title()); @@ -649,6 +646,7 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s const winrt::Windows::Foundation::IInspectable& /*args*/) { _UpdateTrayIcon(); + _CreateTrayContextMenu(); _setupGlobalHotkeys(); // The monarch is just going to be THE listener for inbound connections. @@ -946,6 +944,12 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta _HandleSummon(sender, summonArgs); } +void AppHost::_MinimizeToTrayRequested(const winrt::Windows::Foundation::IInspectable&, + const winrt::Windows::Foundation::IInspectable&) +{ + ShowWindow(_window->GetHandle(), SW_HIDE); +} + void AppHost::_HandleTrayIconPressed() { // No name in the args means summon the mru window. @@ -998,20 +1002,11 @@ void AppHost::_UpdateTrayIcon() } } -void AppHost::_CreateTrayContextMenu(const til::point coord) +void AppHost::_ShowTrayContextMenu(const til::point coord) { - auto hmenu = CreatePopupMenu(); - if (hmenu) + _trayContextMenu = _CreateTrayContextMenu(); + if (_trayContextMenu) { - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuinfo - MENUINFO mi{}; - mi.fMask = MIM_APPLYTOSUBMENUS | MIM_STYLE; - mi.dwStyle = MNS_NOCHECK | MNS_NOTIFYBYPOS; - SetMenuInfo(hmenu, &mi); - - // Just testing how to insert menu items - InsertMenu(hmenu, 0, MF_STRING, 0, L"Start"); - // We'll need to set our window to the foreground before calling // TrackPopupMenuEx or else the menu won't dismiss when clicking away. SetForegroundWindow(_window->GetHandle()); @@ -1026,7 +1021,36 @@ void AppHost::_CreateTrayContextMenu(const til::point coord) uFlags |= TPM_LEFTALIGN; } - TrackPopupMenuEx(hmenu, uFlags, (int)coord.x(), (int)coord.y(), _window->GetHandle(), NULL); - DestroyMenu(hmenu); + TrackPopupMenuEx(_trayContextMenu.value(), uFlags, (int)coord.x(), (int)coord.y(), _window->GetHandle(), NULL); + } +} + +HMENU AppHost::_CreateTrayContextMenu() +{ + auto hmenu = CreatePopupMenu(); + if (hmenu) + { + MENUINFO mi{}; + mi.fMask = MIM_STYLE; + mi.dwStyle = MNS_NOCHECK; + assert(SetMenuInfo(hmenu, &mi)); + + // Get all peasants' window names + for (auto [id, name] : _windowManager.GetPeasantNames()) + { + AppendMenu(hmenu, MF_STRING, id, name.c_str()); + } } + return hmenu; +} + +void AppHost::_TrayMenuItemSelected(const UINT menuItemID) +{ + // Grab the window name associated to the given context menu item ID. + WCHAR name[255]; + GetMenuString(_trayContextMenu.value(), menuItemID, name, 255, MF_BYCOMMAND); + + Remoting::SummonWindowSelectionArgs args{ name }; + args.SummonBehavior().ToggleVisibility(false); + _windowManager.SummonWindow(args); } diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 8daa2fc7b08..1396b27ffbe 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -86,9 +86,15 @@ class AppHost void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _MinimizeToTrayRequested(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::Foundation::IInspectable&); + void _UpdateTrayIcon(); void _HandleTrayIconPressed(); - void _CreateTrayContextMenu(const til::point coord); + void _ShowTrayContextMenu(const til::point coord); + HMENU _CreateTrayContextMenu(); + void _TrayMenuItemSelected(const UINT menuItemID); std::optional _trayIconData; + std::optional _trayContextMenu; }; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 63e00b4b777..e8117f545c1 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -527,6 +527,7 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize } case WM_COMMAND: { + _NotifyTrayMenuItemSelectedHandlers(static_cast(wparam)); return 0; } } diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index cb2997aff93..cef9ea79766 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -55,6 +55,7 @@ class IslandWindow : WINRT_CALLBACK(HotkeyPressed, winrt::delegate); WINRT_CALLBACK(NotifyTrayIconPressed, winrt::delegate); WINRT_CALLBACK(NotifyShowTrayContextMenu, winrt::delegate); + WINRT_CALLBACK(NotifyTrayMenuItemSelected, winrt::delegate); protected: void ForceResize()