Add tray icon when quake window is minimized (#10179)

This PR is a small start in a broader "Minimize to Tray" feature (#5727).
This particular change is scoped only to the scenario when a quake window
is minimized. Currently the only way to bring back the quake window
when it's minimized is to press the global hotkey again. This gives another
option - to press the terminal icon in the tray.

Eventually though, minimize to tray will be available for any window, and
I'd like more time to flesh out the general porpoise scenarios and context
menus. Having just a bit in this PR also helps reviewers by keeping it small!
This commit is contained in:
Leon Liang 2021-07-08 08:25:43 -07:00 committed by GitHub
parent 1374396f10
commit 96f4a9daef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 141 additions and 12 deletions

View file

@ -68,6 +68,7 @@ ITab
ITaskbar
IUri
IVirtual
KEYSELECT
LCID
llabs
llu
@ -80,12 +81,16 @@ MULTIPLEUSE
NCHITTEST
NCLBUTTONDBLCLK
NCRBUTTONDBLCLK
NIF
NIN
NOAGGREGATION
NOASYNC
NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
NOREPEAT
NOTIFYICON
NOTIFYICONDATA
ntprivapi
oaidl
ocidl
@ -108,9 +113,11 @@ RSHIFT
schandle
semver
serializer
SETVERSION
SHELLEXECUTEINFOW
shobjidl
SHOWMINIMIZED
SHOWTIP
SINGLEUSE
SIZENS
smoothstep

View file

@ -2773,6 +2773,7 @@ Xes
xff
XFile
XFORM
xIcon
XManifest
XMath
XMFLOAT
@ -2806,6 +2807,7 @@ YCast
YCENTER
YCount
YDPI
yIcon
yml
YOffset
YPosition

View file

@ -15,7 +15,7 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ScopedResourceLoader.h" />
<ClInclude Include="inc\ScopedResourceLoader.h" />
<ClInclude Include="inc\LibraryResources.h" />
<ClInclude Include="inc\ThrottledFunc.h" />
<ClInclude Include="inc\Utils.h" />

View file

@ -9,6 +9,9 @@
#include "../WinRTUtils/inc/WtExeUtils.h"
#include "resource.h"
#include "VirtualDesktopUtils.h"
#include "icon.h"
#include <ScopedResourceLoader.h>
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Composition;
@ -77,9 +80,15 @@ 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())
{
@ -90,6 +99,11 @@ AppHost::AppHost() noexcept :
AppHost::~AppHost()
{
// destruction order is important for proper teardown here
if (_trayIconData)
{
Shell_NotifyIcon(NIM_DELETE, &_trayIconData.value());
_trayIconData.reset();
}
_window = nullptr;
_app.Close();
@ -925,12 +939,26 @@ void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspecta
void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable&,
const winrt::Windows::Foundation::IInspectable&)
{
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();
}
}
else if (!_window->IsQuakeWindow() && _logic.IsQuakeWindow())
{
_UpdateTrayIcon();
}
_window->IsQuakeWindow(_logic.IsQuakeWindow());
}
void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable&)
{
const Remoting::SummonWindowBehavior summonArgs{};
summonArgs.MoveToCurrentDesktop(false);
@ -939,3 +967,56 @@ void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspecta
summonArgs.ToggleVisibility(false); // Do not toggle, just make visible.
_HandleSummon(sender, summonArgs);
}
void AppHost::_HandleTrayIconPressed()
{
// Currently scoping "minimize to tray" to only
// the quake window.
if (_logic.IsQuakeWindow())
{
const Remoting::SummonWindowBehavior summonArgs{};
summonArgs.DropdownDuration(200);
_window->SummonWindow(summonArgs);
}
}
// Method Description:
// - Creates and adds an icon to the notification tray.
// Arguments:
// - <unused>
// Return Value:
// - <none>
void AppHost::_UpdateTrayIcon()
{
if (!_trayIconData && _window->GetHandle())
{
NOTIFYICONDATA nid{};
// 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;
}
}

View file

@ -85,4 +85,9 @@ private:
void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _UpdateTrayIcon();
void _HandleTrayIconPressed();
std::optional<NOTIFYICONDATA> _trayIconData;
};

View file

@ -3,9 +3,7 @@
#pragma once
// Custom window messages
#define CM_UPDATE_TITLE (WM_USER)
#include "CustomWindowMessages.h"
#include <wil/resource.h>
template<typename T>

View file

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
// Custom window messages
#define CM_UPDATE_TITLE (WM_USER)
#define CM_NOTIFY_FROM_TRAY (WM_USER + 1)

View file

@ -452,6 +452,7 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
{
if (wparam == SIZE_MINIMIZED && _isQuakeWindow)
{
_NotifyWindowHiddenHandlers();
ShowWindow(GetHandle(), SW_HIDE);
return 0;
}
@ -506,6 +507,19 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
case WM_THEMECHANGED:
UpdateWindowIconForActiveMetrics(_window.get());
return 0;
case CM_NOTIFY_FROM_TRAY:
{
switch (LOWORD(lparam))
{
case NIN_SELECT:
case NIN_KEYSELECT:
{
_NotifyTrayIconPressedHandlers();
return 0;
}
}
break;
}
}
// TODO: handle messages here...

View file

@ -51,6 +51,8 @@ public:
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()>);
protected:
void ForceResize()

View file

@ -44,6 +44,7 @@
<ClInclude Include="resource.h" />
<ClInclude Include="AppHost.h" />
<ClInclude Include="BaseWindow.h" />
<ClInclude Include="CustomWindowMessages.h" />
<ClInclude Include="IslandWindow.h" />
<ClInclude Include="NonClientIslandWindow.h" />
<ClInclude Include="VirtualDesktopUtils.h" />
@ -81,6 +82,10 @@
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WinRTUtils\WinRTUtils.vcxproj">
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<!-- Manually add a reference to Core here. We need this so MDMERGE will know

View file

@ -27,22 +27,27 @@ static int _GetActiveAppIconResource()
return iconResource;
}
void UpdateWindowIconForActiveMetrics(HWND window)
HANDLE GetActiveAppIconHandle(int size)
{
auto iconResource{ MAKEINTRESOURCEW(_GetActiveAppIconResource()) };
const auto smXIcon = size == ICON_SMALL ? SM_CXSMICON : SM_CXICON;
const auto smYIcon = size == ICON_SMALL ? SM_CYSMICON : SM_CYICON;
// These handles are loaded with LR_SHARED, so they are safe to "leak".
HANDLE smallIcon{ LoadImageW(wil::GetModuleInstanceHandle(), iconResource, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED) };
LOG_LAST_ERROR_IF_NULL(smallIcon);
HANDLE hIcon{ LoadImageW(wil::GetModuleInstanceHandle(), iconResource, IMAGE_ICON, GetSystemMetrics(smXIcon), GetSystemMetrics(smYIcon), LR_SHARED) };
LOG_LAST_ERROR_IF_NULL(hIcon);
HANDLE largeIcon{ LoadImageW(wil::GetModuleInstanceHandle(), iconResource, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED) };
LOG_LAST_ERROR_IF_NULL(largeIcon);
return hIcon;
}
if (smallIcon)
void UpdateWindowIconForActiveMetrics(HWND window)
{
if (auto smallIcon = GetActiveAppIconHandle(ICON_SMALL))
{
SendMessageW(window, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(smallIcon));
}
if (largeIcon)
if (auto largeIcon = GetActiveAppIconHandle(ICON_BIG))
{
SendMessageW(window, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(largeIcon));
}

View file

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

View file

@ -64,6 +64,7 @@ Abstract:
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include <winrt/TerminalApp.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>