diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 032570e55f8..7bd696c07c6 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -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 diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 00fe5ab1e10..ef0a37c6dd7 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -2773,6 +2773,7 @@ Xes xff XFile XFORM +xIcon XManifest XMath XMFLOAT @@ -2806,6 +2807,7 @@ YCast YCENTER YCount YDPI +yIcon yml YOffset YPosition diff --git a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj index be5cee9e08e..2bd60e14348 100644 --- a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj +++ b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj @@ -15,7 +15,7 @@ - + diff --git a/src/cascadia/WinRTUtils/ScopedResourceLoader.h b/src/cascadia/WinRTUtils/inc/ScopedResourceLoader.h similarity index 100% rename from src/cascadia/WinRTUtils/ScopedResourceLoader.h rename to src/cascadia/WinRTUtils/inc/ScopedResourceLoader.h diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 753166bf1fe..8566f221914 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -9,6 +9,9 @@ #include "../WinRTUtils/inc/WtExeUtils.h" #include "resource.h" #include "VirtualDesktopUtils.h" +#include "icon.h" + +#include 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: +// - +// Return Value: +// - +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(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; + } +} diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 1efaf2bf9e0..a8511dbc258 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -85,4 +85,9 @@ class AppHost void _SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + + void _UpdateTrayIcon(); + void _HandleTrayIconPressed(); + + std::optional _trayIconData; }; diff --git a/src/cascadia/WindowsTerminal/BaseWindow.h b/src/cascadia/WindowsTerminal/BaseWindow.h index d1a8b86d775..581b386172b 100644 --- a/src/cascadia/WindowsTerminal/BaseWindow.h +++ b/src/cascadia/WindowsTerminal/BaseWindow.h @@ -3,9 +3,7 @@ #pragma once -// Custom window messages -#define CM_UPDATE_TITLE (WM_USER) - +#include "CustomWindowMessages.h" #include template diff --git a/src/cascadia/WindowsTerminal/CustomWindowMessages.h b/src/cascadia/WindowsTerminal/CustomWindowMessages.h new file mode 100644 index 00000000000..beb0766affe --- /dev/null +++ b/src/cascadia/WindowsTerminal/CustomWindowMessages.h @@ -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) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 95503aaa241..8ecd355c1bb 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -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... diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index a7fe739da2a..0e5859bc4fd 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -51,6 +51,8 @@ class IslandWindow : WINRT_CALLBACK(MouseScrolled, winrt::delegate); WINRT_CALLBACK(WindowActivated, winrt::delegate); WINRT_CALLBACK(HotkeyPressed, winrt::delegate); + WINRT_CALLBACK(NotifyTrayIconPressed, winrt::delegate); + WINRT_CALLBACK(NotifyWindowHidden, winrt::delegate); protected: void ForceResize() diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index b92f8589fd1..ee7de8ced20 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -44,6 +44,7 @@ + @@ -81,6 +82,10 @@ + + {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} + false +