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:
parent
1374396f10
commit
96f4a9daef
7
.github/actions/spelling/allow/apis.txt
vendored
7
.github/actions/spelling/allow/apis.txt
vendored
|
@ -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
|
||||
|
|
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
|
@ -2773,6 +2773,7 @@ Xes
|
|||
xff
|
||||
XFile
|
||||
XFORM
|
||||
xIcon
|
||||
XManifest
|
||||
XMath
|
||||
XMFLOAT
|
||||
|
@ -2806,6 +2807,7 @@ YCast
|
|||
YCENTER
|
||||
YCount
|
||||
YDPI
|
||||
yIcon
|
||||
yml
|
||||
YOffset
|
||||
YPosition
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
8
src/cascadia/WindowsTerminal/CustomWindowMessages.h
Normal file
8
src/cascadia/WindowsTerminal/CustomWindowMessages.h
Normal 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)
|
|
@ -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...
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
HANDLE GetActiveAppIconHandle(const int size);
|
||||
void UpdateWindowIconForActiveMetrics(HWND window);
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue