Add Minimize to Tray and Tray Icon (#10368)

A brief summary of the behavior of the tray icon:
- There will only ever be one tray icon representing all windows.
- Left-Click on a Tray Icon brings up the MRU window.
- Right-Click on a Tray Icon brings up a Context Menu:
```
Focus Terminal
----------------
Windows --> Window ID 1 - <unnamed window>
            Named Window
            Named Window Again
 ```
- Focus Terminal will bring up the MRU window.
- Clicking on any of the Window "names" in the submenu will summon the window.

## Settings Changes

Two new global settings are introduced: `alwaysShowTrayIcon` and `minimizeToTray`. Here's a chart explaining the behavior with the two settings.

|                      | `alwaysShowTrayIcon:true`                                          | `alwaysShowTrayIcon:false`                                         |
|----------------------|------------------------------------------------------------------|------------------------------------------------------------------|
| `minimizeToTray:true`  | tray icon is always shown. minimize button will hide the window. | tray icon is always shown. minimize button will hide the window. |
| `minimizeToTray:false` | tray icon is always shown.                                       | tray icon is not shown ever.                                     |

Closes #5727

## References
[Spec for Minimize to Tray](https://github.com/microsoft/terminal/blob/main/doc/specs/%23653%20-%20Quake%20Mode/%23653%20-%20Quake%20Mode.md#minimize-to-tray)
Docs PR - MicrosoftDocs/terminal#352
#10448 - My list of TODOs
This commit is contained in:
Leon Liang 2021-08-12 12:54:39 -07:00 committed by GitHub
parent d3f9859051
commit a0edb12cd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 775 additions and 87 deletions

View File

@ -2,11 +2,13 @@ ACCEPTFILES
ACCESSDENIED
alignas
alignof
APPLYTOSUBMENUS
bitfield
bitfields
BUILDBRANCH
BUILDMSG
BUILDNUMBER
BYPOSITION
charconv
CLASSNOTAVAILABLE
cmdletbinding
@ -78,6 +80,9 @@ llu
localtime
lround
LSHIFT
MENUCOMMAND
MENUDATA
MENUINFO
memicmp
mptt
mov
@ -94,6 +99,7 @@ NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
NOREPEAT
NOTIFYBYPOS
NOTIFYICON
NOTIFYICONDATA
ntprivapi
@ -141,6 +147,7 @@ Stubless
Subheader
Subpage
syscall
TASKBARCREATED
TBPF
THEMECHANGED
tlg

View File

@ -2801,6 +2801,7 @@ xes
xff
XFile
XFORM
xIcon
XManifest
XMath
XMFLOAT
@ -2833,6 +2834,7 @@ YCast
YCENTER
YCount
YDPI
yIcon
yml
YOffset
YPosition

View File

@ -1201,6 +1201,16 @@
"minimum": 0,
"type": [ "integer", "string" ],
"deprecated": true
},
"minimizeToTray": {
"default": "false",
"description": "When set to true, minimizing a Terminal window will no longer appear in the taskbar. Instead, a Terminal icon will appear in the system tray through which the user can access their windows.",
"type": "boolean"
},
"alwaysShowTrayIcon": {
"default": "false",
"description": "When set to true, the Terminal's tray icon will always be shown in the system tray.",
"type": "boolean"
},
"actions": {
"description": "Properties are specific to each custom action.",

View File

@ -28,8 +28,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
CATCH_LOG();
}
// This is a private constructor to be used in unit tests, where we don't
// want each Monarch to necessarily use the current PID.
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Monarch::Monarch(const uint64_t testPID) :
_ourPID{ testPID }
{
@ -78,6 +80,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
peasant.IdentifyWindowsRequested({ this, &Monarch::_identifyWindows });
peasant.RenameRequested({ this, &Monarch::_renameRequested });
peasant.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
peasant.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_peasants[newPeasantsId] = peasant;
TraceLoggingWrite(g_hRemotingProvider,
@ -738,20 +743,30 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
try
{
args.FoundMatch(false);
// If a WindowID is provided from the args, use that first.
uint64_t windowId = 0;
// If no name was provided, then just summon the MRU window.
if (searchedForName.empty())
if (args.WindowID())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
windowId = args.WindowID().Value();
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
// If no name was provided, then just summon the MRU window.
if (searchedForName.empty())
{
// Use the value of the `desktop` arg to determine if we should
// limit to the current desktop (desktop:onCurrent) or not
// (desktop:any or desktop:toCurrent)
windowId = _getMostRecentPeasantID(args.OnCurrentDesktop(), false);
}
else
{
// Try to find a peasant that currently has this name
windowId = _lookupPeasantIdForName(searchedForName);
}
}
if (auto targetPeasant{ _getPeasant(windowId) })
{
targetPeasant.Summon(args.SummonBehavior());
@ -789,4 +804,56 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}
// Method Description:
// - This method creates a map of peasant IDs to peasant names
// while removing dead peasants.
// Arguments:
// - <none>
// Return Value:
// - A map of peasant IDs to their names.
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> Monarch::GetPeasantNames()
{
auto names = winrt::single_threaded_map<uint64_t, winrt::hstring>();
std::vector<uint64_t> peasantsToErase{};
for (const auto& [id, p] : _peasants)
{
try
{
names.Insert(id, p.WindowName());
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
peasantsToErase.push_back(id);
}
}
// Remove the dead peasants we came across while iterating.
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
return names.GetView();
}
void Monarch::SummonAllWindows()
{
auto callback = [](auto&& p, auto&& /*id*/) {
SummonWindowBehavior args{};
args.ToggleVisibility(false);
p.Summon(args);
};
auto onError = [](auto&& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
}
}

View File

@ -41,6 +41,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
struct Monarch : public MonarchT<Monarch>
{
Monarch();
Monarch(const uint64_t testPID);
~Monarch();
uint64_t GetPID();
@ -51,10 +52,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void HandleActivatePeasant(const winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs& args);
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
Monarch(const uint64_t testPID);
uint64_t _ourPID;
uint64_t _nextPeasantID{ 1 };

View File

@ -28,6 +28,7 @@ namespace Microsoft.Terminal.Remoting
Boolean FoundMatch;
SummonWindowBehavior SummonBehavior;
Windows.Foundation.IReference<UInt64> WindowID;
}
@ -40,6 +41,11 @@ namespace Microsoft.Terminal.Remoting
void HandleActivatePeasant(WindowActivatedArgs args);
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
}

View File

@ -20,8 +20,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
}
// This is a private constructor to be used in unit tests, where we don't
// want each Peasant to necessarily use the current PID.
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Peasant::Peasant(const uint64_t testPID) :
_ourPID{ testPID }
{
@ -31,6 +33,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
_id = id;
}
uint64_t Peasant::GetID()
{
return _id;
@ -222,4 +225,36 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestShowTrayIcon()
{
try
{
_ShowTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestShowTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestHideTrayIcon()
{
try
{
_HideTrayIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestHideTrayIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
}

View File

@ -28,6 +28,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void RequestIdentifyWindows();
void DisplayWindowId();
void RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
@ -40,6 +42,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
Peasant(const uint64_t testPID);

View File

@ -64,6 +64,8 @@ namespace Microsoft.Terminal.Remoting
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);
void RequestShowTrayIcon();
void RequestHideTrayIcon();
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@ -71,6 +73,8 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, Object> DisplayWindowIdRequested;
event Windows.Foundation.TypedEventHandler<Object, RenameRequestArgs> RenameRequested;
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
[default_interface] runtimeclass Peasant : IPeasant

View File

@ -34,6 +34,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
WINRT_PROPERTY(bool, FoundMatch, false);
WINRT_PROPERTY(bool, OnCurrentDesktop, false);
WINRT_PROPERTY(SummonWindowBehavior, SummonBehavior);
WINRT_PROPERTY(Windows::Foundation::IReference<uint64_t>, WindowID);
};
}

View File

@ -254,6 +254,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// window, and when the current monarch dies.
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequestedHandlers(*this, nullptr); });
_monarch.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequestedHandlers(*this, nullptr); });
_BecameMonarchHandlers(*this, nullptr);
}
@ -509,4 +511,54 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_monarch.SummonWindow(args);
}
void WindowManager::SummonAllWindows()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
_monarch.SummonAllWindows();
}
}
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> WindowManager::GetPeasantNames()
{
// We should only get called when we're the monarch since the monarch
// is the only one that knows about all peasants.
return _monarch.GetPeasantNames();
}
// Method Description:
// - Ask the monarch to show a tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
void WindowManager::RequestShowTrayIcon()
{
_peasant.RequestShowTrayIcon();
}
// Method Description:
// - Ask the monarch to hide its tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
void WindowManager::RequestHideTrayIcon()
{
_peasant.RequestHideTrayIcon();
}
bool WindowManager::DoesQuakeWindowExist()
{
const auto names = GetPeasantNames();
for (const auto [id, name] : names)
{
if (name == QuakeWindowName)
{
return true;
}
}
return false;
}
}

View File

@ -40,8 +40,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool IsMonarch();
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
bool DoesQuakeWindowExist();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
private:
bool _shouldCreateWindow{ false };

View File

@ -12,7 +12,14 @@ namespace Microsoft.Terminal.Remoting
IPeasant CurrentWindow();
Boolean IsMonarch { get; };
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideTrayIconRequested;
};
}

View File

@ -1442,4 +1442,39 @@ namespace winrt::TerminalApp::implementation
return _root->IsQuakeWindow();
}
bool AppLogic::GetMinimizeToTray()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().MinimizeToTray();
}
else
{
return false;
}
}
bool AppLogic::GetAlwaysShowTrayIcon()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings.GlobalSettings().AlwaysShowTrayIcon();
}
else
{
return false;
}
}
}

View File

@ -92,6 +92,9 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TaskbarState TaskbarState();
bool GetMinimizeToTray();
bool GetAlwaysShowTrayIcon();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
Windows::Foundation::Collections::IMapView<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> GlobalHotkeys();

View File

@ -70,6 +70,9 @@ namespace TerminalApp
TaskbarState TaskbarState{ get; };
Boolean GetMinimizeToTray();
Boolean GetAlwaysShowTrayIcon();
FindTargetWindowResult FindTargetWindow(String[] args);
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();

View File

@ -653,6 +653,14 @@
<data name="CommandPaletteMenuItem" xml:space="preserve">
<value>Command Palette</value>
</data>
<data name="TrayIconFocusTerminal" xml:space="preserve">
<value>Focus Terminal</value>
<comment>This is displayed as a label for the context menu item that focuses the terminal.</comment>
</data>
<data name="TrayIconWindowSubmenu" xml:space="preserve">
<value>Windows</value>
<comment>This is displayed as a label for the context menu item that holds the submenu of available windows.</comment>
</data>
<data name="ShellExtension_OpenInTerminalMenuItem_Dev" xml:space="preserve">
<value>Open in Windows Terminal (Dev)</value>
<comment>{Locked} The dev build will never be seen in multiple languages</comment>

View File

@ -48,6 +48,8 @@ static constexpr std::string_view StartupActionsKey{ "startupActions" };
static constexpr std::string_view FocusFollowMouseKey{ "focusFollowMouse" };
static constexpr std::string_view WindowingBehaviorKey{ "windowingBehavior" };
static constexpr std::string_view TrimBlockSelectionKey{ "trimBlockSelection" };
static constexpr std::string_view AlwaysShowTrayIconKey{ "alwaysShowTrayIcon" };
static constexpr std::string_view MinimizeToTrayKey{ "minimizeToTray" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
@ -129,6 +131,8 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_WindowingBehavior = _WindowingBehavior;
globals->_TrimBlockSelection = _TrimBlockSelection;
globals->_DetectURLs = _DetectURLs;
globals->_MinimizeToTray = _MinimizeToTray;
globals->_AlwaysShowTrayIcon = _AlwaysShowTrayIcon;
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
globals->_validDefaultProfile = _validDefaultProfile;
@ -319,6 +323,10 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, DetectURLsKey, _DetectURLs);
JsonUtils::GetValueForKey(json, MinimizeToTrayKey, _MinimizeToTray);
JsonUtils::GetValueForKey(json, AlwaysShowTrayIconKey, _AlwaysShowTrayIcon);
// This is a helper lambda to get the keybindings and commands out of both
// and array of objects. We'll use this twice, once on the legacy
// `keybindings` key, and again on the newer `bindings` key.
@ -414,6 +422,8 @@ Json::Value GlobalAppSettings::ToJson() const
JsonUtils::SetValueForKey(json, WindowingBehaviorKey, _WindowingBehavior);
JsonUtils::SetValueForKey(json, TrimBlockSelectionKey, _TrimBlockSelection);
JsonUtils::SetValueForKey(json, DetectURLsKey, _DetectURLs);
JsonUtils::SetValueForKey(json, MinimizeToTrayKey, _MinimizeToTray);
JsonUtils::SetValueForKey(json, AlwaysShowTrayIconKey, _AlwaysShowTrayIcon);
// clang-format on
json[JsonKey(ActionsKey)] = _actionMap->ToJson();

View File

@ -98,6 +98,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::GlobalAppSettings, Model::WindowingMode, WindowingBehavior, Model::WindowingMode::UseNew);
INHERITABLE_SETTING(Model::GlobalAppSettings, bool, TrimBlockSelection, false);
INHERITABLE_SETTING(Model::GlobalAppSettings, bool, DetectURLs, true);
INHERITABLE_SETTING(Model::GlobalAppSettings, bool, MinimizeToTray, false);
INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysShowTrayIcon, false);
private:
guid _defaultProfile;

View File

@ -73,6 +73,8 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(WindowingMode, WindowingBehavior);
INHERITABLE_SETTING(Boolean, TrimBlockSelection);
INHERITABLE_SETTING(Boolean, DetectURLs);
INHERITABLE_SETTING(Boolean, MinimizeToTray);
INHERITABLE_SETTING(Boolean, AlwaysShowTrayIcon);
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);

View File

@ -436,4 +436,7 @@
<value>Windows Console Host</value>
<comment>Name describing the usage of the classic windows console as the terminal UI. (`conhost.exe`)</comment>
</data>
<data name="MinimizeToTrayCommandKey" xml:space="preserve">
<value>Minimize current window to tray</value>
</data>
</root>

View File

@ -29,6 +29,8 @@
"disableAnimations": false,
"startupActions": "",
"focusFollowMouse": false,
"minimizeToTray": false,
"alwaysShowTrayIcon": false,
"profiles":
[

View File

@ -71,12 +71,16 @@ namespace RemotingUnitTests
Remoting::WindowActivatedArgs GetLastActivatedArgs() { throw winrt::hresult_error{}; }
void RequestRename(const Remoting::RenameRequestArgs& /*args*/) { throw winrt::hresult_error{}; }
void Summon(const Remoting::SummonWindowBehavior& /*args*/) { throw winrt::hresult_error{}; };
void RequestShowTrayIcon() { throw winrt::hresult_error{}; };
void RequestHideTrayIcon() { throw winrt::hresult_error{}; };
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, Remoting::WindowActivatedArgs);
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, Remoting::CommandlineArgs);
TYPED_EVENT(IdentifyWindowsRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(DisplayWindowIdRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(RenameRequested, winrt::Windows::Foundation::IInspectable, Remoting::RenameRequestArgs);
TYPED_EVENT(SummonRequested, winrt::Windows::Foundation::IInspectable, Remoting::SummonWindowBehavior);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
};
class RemotingTests

View File

@ -11,8 +11,6 @@
#include "VirtualDesktopUtils.h"
#include "icon.h"
#include <ScopedResourceLoader.h>
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Composition;
using namespace winrt::Windows::UI::Xaml;
@ -65,6 +63,8 @@ AppHost::AppHost() noexcept :
// Update our own internal state tracking if we're in quake mode or not.
_IsQuakeWindowChanged(nullptr, nullptr);
_window->SetMinimizeToTrayBehavior(_logic.GetMinimizeToTray());
// Tell the window to callback to us when it's about to handle a WM_CREATE
auto pfn = std::bind(&AppHost::_HandleCreateWindow,
this,
@ -80,15 +80,9 @@ AppHost::AppHost() noexcept :
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->WindowActivated({ this, &AppHost::_WindowActivated });
_window->HotkeyPressed({ this, &AppHost::_GlobalHotkeyPressed });
_window->NotifyTrayIconPressed({ this, &AppHost::_HandleTrayIconPressed });
_window->SetAlwaysOnTop(_logic.GetInitialAlwaysOnTop());
_window->MakeWindow();
if (_window->IsQuakeWindow())
{
_UpdateTrayIcon();
}
_windowManager.BecameMonarch({ this, &AppHost::_BecomeMonarch });
if (_windowManager.IsMonarch())
{
@ -98,13 +92,12 @@ AppHost::AppHost() noexcept :
AppHost::~AppHost()
{
// destruction order is important for proper teardown here
if (_trayIconData)
if (_window->IsQuakeWindow())
{
Shell_NotifyIcon(NIM_DELETE, &_trayIconData.value());
_trayIconData.reset();
_windowManager.RequestHideTrayIcon();
}
// destruction order is important for proper teardown here
_window = nullptr;
_app.Close();
_app = nullptr;
@ -662,6 +655,20 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
_setupGlobalHotkeys();
// The monarch is just going to be THE listener for inbound connections.
_listenForInboundConnections();
if (_windowManager.DoesQuakeWindowExist() ||
_window->IsQuakeWindow() ||
(_logic.GetAlwaysShowTrayIcon() || _logic.GetMinimizeToTray()))
{
_CreateTrayIcon();
}
// These events are coming from peasants that become or un-become quake windows.
_windowManager.ShowTrayIconRequested([this](auto&&, auto&&) { _ShowTrayIconRequested(); });
_windowManager.HideTrayIconRequested([this](auto&&, auto&&) { _HideTrayIconRequested(); });
}
void AppHost::_listenForInboundConnections()
@ -947,24 +954,50 @@ void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspecta
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
_setupGlobalHotkeys();
// If we're monarch, we need to check some conditions to show the tray icon.
// If there's a Quake window somewhere, we'll want to keep the tray icon.
// There's two settings - MinimizeToTray and AlwaysShowTrayIcon. If either
// one of them are true, we want to make sure there's a tray icon.
// If both are false, we want to remove our icon from the tray.
// When we remove our icon from the tray, we'll also want to re-summon
// any hidden windows, but right now we're not keeping track of who's hidden,
// so just summon them all. Tracking the work to do a "summon all minimized" in
// GH#10448
if (_windowManager.IsMonarch())
{
if (!_windowManager.DoesQuakeWindowExist())
{
if (!_trayIcon && (_logic.GetMinimizeToTray() || _logic.GetAlwaysShowTrayIcon()))
{
_CreateTrayIcon();
}
else if (_trayIcon && !_logic.GetMinimizeToTray() && !_logic.GetAlwaysShowTrayIcon())
{
_windowManager.SummonAllWindows();
_DestroyTrayIcon();
}
}
}
_window->SetMinimizeToTrayBehavior(_logic.GetMinimizeToTray());
}
void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable&,
const winrt::Windows::Foundation::IInspectable&)
{
if (_window->IsQuakeWindow() && !_logic.IsQuakeWindow())
// We want the quake window to be accessible through the tray icon.
// This means if there's a quake window _somewhere_, we want the tray icon
// to show regardless of the tray icon settings.
// This also means we'll need to destroy the tray icon if it was created
// specifically for the quake window. If not, it should not be destroyed.
if (!_window->IsQuakeWindow() && _logic.IsQuakeWindow())
{
// If we're exiting quake mode, we should make our
// tray icon disappear.
if (_trayIconData)
{
Shell_NotifyIcon(NIM_DELETE, &_trayIconData.value());
_trayIconData.reset();
}
_ShowTrayIconRequested();
}
else if (!_window->IsQuakeWindow() && _logic.IsQuakeWindow())
else if (_window->IsQuakeWindow() && !_logic.IsQuakeWindow())
{
_UpdateTrayIcon();
_HideTrayIconRequested();
}
_window->IsQuakeWindow(_logic.IsQuakeWindow());
@ -981,55 +1014,84 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta
_HandleSummon(sender, summonArgs);
}
void AppHost::_HandleTrayIconPressed()
// Method Description:
// - Creates a Tray Icon and hooks up its handlers
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppHost::_CreateTrayIcon()
{
// Currently scoping "minimize to tray" to only
// the quake window.
if (_logic.IsQuakeWindow())
if constexpr (Feature_TrayIcon::IsEnabled())
{
const Remoting::SummonWindowBehavior summonArgs{};
summonArgs.DropdownDuration(200);
_window->SummonWindow(summonArgs);
_trayIcon = std::make_unique<TrayIcon>(_window->GetHandle());
// Hookup the handlers, save the tokens for revoking if settings change.
_ReAddTrayIconToken = _window->NotifyReAddTrayIcon([this]() { _trayIcon->ReAddTrayIcon(); });
_TrayIconPressedToken = _window->NotifyTrayIconPressed([this]() { _trayIcon->TrayIconPressed(); });
_ShowTrayContextMenuToken = _window->NotifyShowTrayContextMenu([this](til::point coord) { _trayIcon->ShowTrayContextMenu(coord, _windowManager.GetPeasantNames()); });
_TrayMenuItemSelectedToken = _window->NotifyTrayMenuItemSelected([this](HMENU hm, UINT idx) { _trayIcon->TrayMenuItemSelected(hm, idx); });
_trayIcon->SummonWindowRequested([this](auto& args) { _windowManager.SummonWindow(args); });
}
}
// Method Description:
// - Creates and adds an icon to the notification tray.
// - Deletes our tray icon if we have one.
// Arguments:
// - <unused>
// - <none>
// Return Value:
// - <none>
void AppHost::_UpdateTrayIcon()
void AppHost::_DestroyTrayIcon()
{
if (!_trayIconData && _window->GetHandle())
if constexpr (Feature_TrayIcon::IsEnabled())
{
NOTIFYICONDATA nid{};
_window->NotifyReAddTrayIcon(_ReAddTrayIconToken);
_window->NotifyTrayIconPressed(_TrayIconPressedToken);
_window->NotifyShowTrayContextMenu(_ShowTrayContextMenuToken);
_window->NotifyTrayMenuItemSelected(_TrayMenuItemSelectedToken);
// This HWND will receive the callbacks sent by the tray icon.
nid.hWnd = _window->GetHandle();
// App-defined identifier of the icon. The HWND and ID are used
// to identify which icon to operate on when calling Shell_NotifyIcon.
// Multiple icons can be associated with one HWND, but here we're only
// going to be showing one so the ID doesn't really matter.
nid.uID = 1;
nid.uCallbackMessage = CM_NOTIFY_FROM_TRAY;
ScopedResourceLoader cascadiaLoader{ L"Resources" };
nid.hIcon = static_cast<HICON>(GetActiveAppIconHandle(ICON_SMALL));
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), cascadiaLoader.GetLocalizedString(L"AppName").c_str());
nid.uFlags = NIF_MESSAGE | NIF_SHOWTIP | NIF_TIP | NIF_ICON;
Shell_NotifyIcon(NIM_ADD, &nid);
// For whatever reason, the NIM_ADD call doesn't seem to set the version
// properly, resulting in us being unable to receive the expected notification
// events. We actually have to make a separate NIM_SETVERSION call for it to
// work properly.
nid.uVersion = NOTIFYICON_VERSION_4;
Shell_NotifyIcon(NIM_SETVERSION, &nid);
_trayIconData = nid;
_trayIcon->RemoveIconFromTray();
_trayIcon = nullptr;
}
}
winrt::fire_and_forget AppHost::_ShowTrayIconRequested()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
co_await winrt::resume_background();
if (_windowManager.IsMonarch())
{
if (!_trayIcon)
{
_CreateTrayIcon();
}
}
else
{
_windowManager.RequestShowTrayIcon();
}
}
}
winrt::fire_and_forget AppHost::_HideTrayIconRequested()
{
if constexpr (Feature_TrayIcon::IsEnabled())
{
co_await winrt::resume_background();
if (_windowManager.IsMonarch())
{
// Destroy it only if our settings allow it
if (_trayIcon &&
!_logic.GetAlwaysShowTrayIcon() &&
!_logic.GetMinimizeToTray())
{
_DestroyTrayIcon();
}
}
else
{
_windowManager.RequestHideTrayIcon();
}
}
}

View File

@ -2,8 +2,8 @@
// Licensed under the MIT license.
#include "pch.h"
#include "NonClientIslandWindow.h"
#include "TrayIcon.h"
class AppHost
{
@ -84,8 +84,13 @@ private:
void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _UpdateTrayIcon();
void _HandleTrayIconPressed();
std::optional<NOTIFYICONDATA> _trayIconData;
void _CreateTrayIcon();
void _DestroyTrayIcon();
winrt::fire_and_forget _ShowTrayIconRequested();
winrt::fire_and_forget _HideTrayIconRequested();
std::unique_ptr<TrayIcon> _trayIcon;
winrt::event_token _ReAddTrayIconToken;
winrt::event_token _TrayIconPressedToken;
winrt::event_token _ShowTrayContextMenuToken;
winrt::event_token _TrayMenuItemSelectedToken;
};

View File

@ -6,6 +6,7 @@
#include "../types/inc/Viewport.hpp"
#include "resource.h"
#include "icon.h"
#include "TrayIcon.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
@ -22,6 +23,8 @@ using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
const UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
IslandWindow::IslandWindow() noexcept :
_interopWindowHandle{ nullptr },
_rootGrid{ nullptr },
@ -453,7 +456,6 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
{
if (wparam == SIZE_MINIMIZED && _isQuakeWindow)
{
_NotifyWindowHiddenHandlers();
ShowWindow(GetHandle(), SW_HIDE);
return 0;
}
@ -573,9 +575,30 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
_NotifyTrayIconPressedHandlers();
return 0;
}
case WM_CONTEXTMENU:
{
const til::point eventPoint{ GET_X_LPARAM(wparam), GET_Y_LPARAM(wparam) };
_NotifyShowTrayContextMenuHandlers(eventPoint);
return 0;
}
}
break;
}
case WM_MENUCOMMAND:
{
_NotifyTrayMenuItemSelectedHandlers((HMENU)lparam, (UINT)wparam);
return 0;
}
default:
// We'll want to receive this message when explorer.exe restarts
// so that we can re-add our icon to the tray.
// This unfortunately isn't a switch case because we register the
// message at runtime.
if (message == WM_TASKBARCREATED)
{
_NotifyReAddTrayIconHandlers();
return 0;
}
}
// TODO: handle messages here...
@ -600,6 +623,10 @@ void IslandWindow::OnResize(const UINT width, const UINT height)
void IslandWindow::OnMinimize()
{
// TODO GH#1989 Stop rendering island content when the app is minimized.
if (_minimizeToTray)
{
HideWindow();
}
}
// Method Description:
@ -1603,5 +1630,15 @@ til::rectangle IslandWindow::_getQuakeModeSize(HMONITOR hmon)
return til::rectangle{ origin, dimensions };
}
void IslandWindow::HideWindow()
{
ShowWindow(GetHandle(), SW_HIDE);
}
void IslandWindow::SetMinimizeToTrayBehavior(bool minimizeToTray) noexcept
{
_minimizeToTray = minimizeToTray;
}
DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
DEFINE_EVENT(IslandWindow, WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);

View File

@ -47,13 +47,19 @@ public:
bool IsQuakeWindow() const noexcept;
void IsQuakeWindow(bool isQuakeWindow) noexcept;
void HideWindow();
void SetMinimizeToTrayBehavior(bool minimizeToTray) noexcept;
DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
WINRT_CALLBACK(WindowActivated, winrt::delegate<void()>);
WINRT_CALLBACK(HotkeyPressed, winrt::delegate<void(long)>);
WINRT_CALLBACK(NotifyTrayIconPressed, winrt::delegate<void()>);
WINRT_CALLBACK(NotifyWindowHidden, winrt::delegate<void()>);
WINRT_CALLBACK(NotifyShowTrayContextMenu, winrt::delegate<void(til::point)>);
WINRT_CALLBACK(NotifyTrayMenuItemSelected, winrt::delegate<void(HMENU, UINT)>);
WINRT_CALLBACK(NotifyReAddTrayIcon, winrt::delegate<void()>);
protected:
void ForceResize()
@ -116,6 +122,8 @@ protected:
void _summonWindowRoutineBody(winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior args);
bool _minimizeToTray{ false };
private:
// This minimum width allows for width the tabs fit
static constexpr long minimumWidth = 460L;

View File

@ -0,0 +1,237 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "icon.h"
#include "TrayIcon.h"
#include "CustomWindowMessages.h"
#include <ScopedResourceLoader.h>
#include <LibraryResources.h>
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Microsoft::Terminal;
TrayIcon::TrayIcon(const HWND owningHwnd) :
_owningHwnd{ owningHwnd }
{
CreateTrayIcon();
}
TrayIcon::~TrayIcon()
{
RemoveIconFromTray();
}
// Method Description:
// - Creates and adds an icon to the notification tray.
// If an icon already exists, update the HWND associated
// to the icon with this window's HWND.
// Arguments:
// - <unused>
// Return Value:
// - <none>
void TrayIcon::CreateTrayIcon()
{
NOTIFYICONDATA nid{};
nid.cbSize = sizeof(NOTIFYICONDATA);
// This HWND will receive the callbacks sent by the tray icon.
nid.hWnd = _owningHwnd;
// App-defined identifier of the icon. The HWND and ID are used
// to identify which icon to operate on when calling Shell_NotifyIcon.
// Multiple icons can be associated with one HWND, but here we're only
// going to be showing one so the ID doesn't really matter.
nid.uID = 1;
nid.uCallbackMessage = CM_NOTIFY_FROM_TRAY;
// AppName happens to be in CascadiaPackage's Resources.
ScopedResourceLoader loader{ L"Resources" };
const auto appNameLoc = loader.GetLocalizedString(L"AppName");
nid.hIcon = static_cast<HICON>(GetActiveAppIconHandle(true));
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), appNameLoc.c_str());
nid.uFlags = NIF_MESSAGE | NIF_SHOWTIP | NIF_TIP | NIF_ICON;
Shell_NotifyIcon(NIM_ADD, &nid);
// For whatever reason, the NIM_ADD call doesn't seem to set the version
// properly, resulting in us being unable to receive the expected notification
// events. We actually have to make a separate NIM_SETVERSION call for it to
// work properly.
nid.uVersion = NOTIFYICON_VERSION_4;
Shell_NotifyIcon(NIM_SETVERSION, &nid);
_trayIconData = nid;
}
// Method Description:
// - This creates our context menu and displays it at the given
// screen coordinates.
// Arguments:
// - coord: The coordinates where we should be showing the context menu.
// - peasants: The map of all peasants that should be available in the context menu.
// Return Value:
// - <none>
void TrayIcon::ShowTrayContextMenu(const til::point coord,
IMapView<uint64_t, winrt::hstring> peasants)
{
if (const auto hMenu = _CreateTrayContextMenu(peasants))
{
// We'll need to set our window to the foreground before calling
// TrackPopupMenuEx or else the menu won't dismiss when clicking away.
SetForegroundWindow(_owningHwnd);
// User can select menu items with the left and right buttons.
UINT uFlags = TPM_RIGHTBUTTON;
// Nonzero if drop-down menus are right-aligned with the corresponding menu-bar item
// 0 if the menus are left-aligned.
if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0)
{
uFlags |= TPM_RIGHTALIGN;
}
else
{
uFlags |= TPM_LEFTALIGN;
}
TrackPopupMenuEx(hMenu, uFlags, gsl::narrow_cast<int>(coord.x()), gsl::narrow_cast<int>(coord.y()), _owningHwnd, NULL);
}
}
// Method Description:
// - This creates the context menu for our tray icon.
// Arguments:
// - peasants: A map of all peasants' ID to their window name.
// Return Value:
// - The handle to the newly created context menu.
HMENU TrayIcon::_CreateTrayContextMenu(IMapView<uint64_t, winrt::hstring> peasants)
{
auto hMenu = CreatePopupMenu();
if (hMenu)
{
MENUINFO mi{};
mi.cbSize = sizeof(MENUINFO);
mi.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS | MIM_MENUDATA;
mi.dwStyle = MNS_NOTIFYBYPOS;
mi.dwMenuData = NULL;
SetMenuInfo(hMenu, &mi);
// Focus Current Terminal Window
AppendMenu(hMenu, MF_STRING, gsl::narrow<UINT_PTR>(TrayMenuItemAction::FocusTerminal), RS_(L"TrayIconFocusTerminal").c_str());
AppendMenu(hMenu, MF_SEPARATOR, 0, L"");
// Submenu for Windows
if (auto submenu = CreatePopupMenu())
{
const auto locWindow = RS_(L"WindowIdLabel");
const auto locUnnamed = RS_(L"UnnamedWindowName");
for (const auto [id, name] : peasants)
{
winrt::hstring displayText = name;
if (name.empty())
{
displayText = fmt::format(L"{} {} - <{}>", locWindow, id, locUnnamed);
}
AppendMenu(submenu, MF_STRING, gsl::narrow<UINT_PTR>(id), displayText.c_str());
}
MENUINFO submenuInfo{};
submenuInfo.cbSize = sizeof(MENUINFO);
submenuInfo.fMask = MIM_MENUDATA;
submenuInfo.dwStyle = MNS_NOTIFYBYPOS;
submenuInfo.dwMenuData = (UINT_PTR)TrayMenuItemAction::SummonWindow;
SetMenuInfo(submenu, &submenuInfo);
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)submenu, RS_(L"TrayIconWindowSubmenu").c_str());
}
}
return hMenu;
}
// Method Description:
// - This is the handler for when one of the menu items are selected within
// the tray icon's context menu.
// Arguments:
// - menu: The handle to the menu that holds the menu item that was selected.
// - menuItemIndex: The index of the menu item within the given menu.
// Return Value:
// - <none>
void TrayIcon::TrayMenuItemSelected(const HMENU menu, const UINT menuItemIndex)
{
// Check the menu's data for a specific action.
MENUINFO mi{};
mi.cbSize = sizeof(MENUINFO);
mi.fMask = MIM_MENUDATA;
GetMenuInfo(menu, &mi);
if (mi.dwMenuData)
{
if (gsl::narrow<TrayMenuItemAction>(mi.dwMenuData) == TrayMenuItemAction::SummonWindow)
{
winrt::Microsoft::Terminal::Remoting::SummonWindowSelectionArgs args{};
args.WindowID(GetMenuItemID(menu, menuItemIndex));
args.SummonBehavior().ToggleVisibility(false);
args.SummonBehavior().MoveToCurrentDesktop(false);
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
_SummonWindowRequestedHandlers(args);
return;
}
}
// Now check the menu item itself for an action.
const auto action = gsl::narrow<TrayMenuItemAction>(GetMenuItemID(menu, menuItemIndex));
switch (action)
{
case TrayMenuItemAction::FocusTerminal:
{
winrt::Microsoft::Terminal::Remoting::SummonWindowSelectionArgs args{};
args.SummonBehavior().ToggleVisibility(false);
args.SummonBehavior().MoveToCurrentDesktop(false);
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
_SummonWindowRequestedHandlers(args);
break;
}
}
}
// Method Description:
// - This is the handler for when the tray icon itself is left-clicked.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TrayIcon::TrayIconPressed()
{
// No name in the args means summon the mru window.
winrt::Microsoft::Terminal::Remoting::SummonWindowSelectionArgs args{};
args.SummonBehavior().MoveToCurrentDesktop(false);
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
args.SummonBehavior().ToggleVisibility(false);
_SummonWindowRequestedHandlers(args);
}
// Method Description:
// - Re-add a tray icon using our currently saved tray icon data.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TrayIcon::ReAddTrayIcon()
{
Shell_NotifyIcon(NIM_ADD, &_trayIconData);
Shell_NotifyIcon(NIM_SETVERSION, &_trayIconData);
}
// Method Description:
// - Deletes our tray icon.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TrayIcon::RemoveIconFromTray()
{
Shell_NotifyIcon(NIM_DELETE, &_trayIconData);
}

View File

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "../cascadia/inc/cppwinrt_utils.h"
// This enumerates all the possible actions
// that our tray icon context menu could do.
enum class TrayMenuItemAction
{
FocusTerminal, // Focus the MRU terminal.
SummonWindow
};
class TrayIcon
{
public:
TrayIcon() = delete;
TrayIcon(const HWND owningHwnd);
~TrayIcon();
void CreateTrayIcon();
void RemoveIconFromTray();
void ReAddTrayIcon();
void TrayIconPressed();
void ShowTrayContextMenu(const til::point coord, winrt::Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> peasants);
void TrayMenuItemSelected(const HMENU menu, const UINT menuItemIndex);
WINRT_CALLBACK(SummonWindowRequested, winrt::delegate<void(winrt::Microsoft::Terminal::Remoting::SummonWindowSelectionArgs)>);
private:
HMENU _CreateTrayContextMenu(winrt::Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> peasants);
HWND _owningHwnd;
NOTIFYICONDATA _trayIconData;
};

View File

@ -47,6 +47,7 @@
<ClInclude Include="CustomWindowMessages.h" />
<ClInclude Include="IslandWindow.h" />
<ClInclude Include="NonClientIslandWindow.h" />
<ClInclude Include="TrayIcon.h" />
<ClInclude Include="VirtualDesktopUtils.h" />
</ItemGroup>
<ItemGroup>
@ -57,6 +58,7 @@
<ClCompile Include="AppHost.cpp" />
<ClCompile Include="IslandWindow.cpp" />
<ClCompile Include="NonClientIslandWindow.cpp" />
<ClCompile Include="TrayIcon.cpp" />
<ClCompile Include="VirtualDesktopUtils.cpp" />
<ClCompile Include="icon.cpp" />
</ItemGroup>
@ -175,4 +177,4 @@
</Target>
<Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
<Import Project="..\..\..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.3.210521003\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.3.210521003\build\native\Microsoft.Internal.Windows.Terminal.ThemeHelpers.targets')" />
</Project>
</Project>

View File

@ -27,12 +27,14 @@ static int _GetActiveAppIconResource()
return iconResource;
}
HANDLE GetActiveAppIconHandle(int size)
// There's only two possible sizes - ICON_SMALL and ICON_BIG.
// So, use true for smallIcon if you want small and false for big.
HANDLE GetActiveAppIconHandle(bool smallIcon)
{
auto iconResource{ MAKEINTRESOURCEW(_GetActiveAppIconResource()) };
const auto smXIcon = size == ICON_SMALL ? SM_CXSMICON : SM_CXICON;
const auto smYIcon = size == ICON_SMALL ? SM_CYSMICON : SM_CYICON;
const auto smXIcon = smallIcon ? SM_CXSMICON : SM_CXICON;
const auto smYIcon = smallIcon ? SM_CYSMICON : SM_CYICON;
// These handles are loaded with LR_SHARED, so they are safe to "leak".
HANDLE hIcon{ LoadImageW(wil::GetModuleInstanceHandle(), iconResource, IMAGE_ICON, GetSystemMetrics(smXIcon), GetSystemMetrics(smYIcon), LR_SHARED) };
@ -43,11 +45,11 @@ HANDLE GetActiveAppIconHandle(int size)
void UpdateWindowIconForActiveMetrics(HWND window)
{
if (auto smallIcon = GetActiveAppIconHandle(ICON_SMALL))
if (auto smallIcon = GetActiveAppIconHandle(true))
{
SendMessageW(window, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(smallIcon));
}
if (auto largeIcon = GetActiveAppIconHandle(ICON_BIG))
if (auto largeIcon = GetActiveAppIconHandle(false))
{
SendMessageW(window, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(largeIcon));
}

View File

@ -3,5 +3,5 @@
#pragma once
HANDLE GetActiveAppIconHandle(const int size);
HANDLE GetActiveAppIconHandle(bool smallIcon);
void UpdateWindowIconForActiveMetrics(HWND window);

View File

@ -22,6 +22,15 @@ TRACELOGGING_DEFINE_PROVIDER(
(0x56c06166, 0x2e2e, 0x5f4d, 0x7f, 0xf3, 0x74, 0xf4, 0xb7, 0x8c, 0x87, 0xd6),
TraceLoggingOptionMicrosoftTelemetry());
// !! BODGY !!
// Manually use the resources from TerminalApp as our resources.
// The WindowsTerminal project doesn't actually build a Resources.resw file, but
// we still need to be able to localize strings for the tray icon menu. Anything
// you want localized for WindowsTerminal.exe should be stuck in
// ...\TerminalApp\Resources\en-US\Resources.resw
#include <LibraryResources.h>
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"TerminalApp/Resources");
// Routine Description:
// - Takes an image architecture and locates a string resource that maps to that architecture.
// Arguments:

View File

@ -64,6 +64,13 @@
</alwaysEnabledBrandingTokens>
</feature>
<feature>
<name>Feature_TrayIcon</name>
<description>Controls whether the Tray Icon and related settings (aka. MinimizeToTray and AlwaysShowTrayIcon) are enabled</description>
<stage>AlwaysEnabled</stage>
<alwaysDisabledReleaseTokens/>
</feature>
<feature>
<name>Feature_ShowProfileDefaultsInSettings</name>
<description>Whether to show the "defaults" page in the Terminal settings UI</description>