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:
parent
d3f9859051
commit
a0edb12cd6
|
@ -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
|
||||
|
|
|
@ -2801,6 +2801,7 @@ xes
|
|||
xff
|
||||
XFile
|
||||
XFORM
|
||||
xIcon
|
||||
XManifest
|
||||
XMath
|
||||
XMFLOAT
|
||||
|
@ -2833,6 +2834,7 @@ YCast
|
|||
YCENTER
|
||||
YCount
|
||||
YDPI
|
||||
yIcon
|
||||
yml
|
||||
YOffset
|
||||
YPosition
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
"disableAnimations": false,
|
||||
"startupActions": "",
|
||||
"focusFollowMouse": false,
|
||||
"minimizeToTray": false,
|
||||
"alwaysShowTrayIcon": false,
|
||||
|
||||
"profiles":
|
||||
[
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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<>);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
HANDLE GetActiveAppIconHandle(const int size);
|
||||
HANDLE GetActiveAppIconHandle(bool smallIcon);
|
||||
void UpdateWindowIconForActiveMetrics(HWND window);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue