273 lines
9.9 KiB
C++
273 lines
9.9 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "icon.h"
|
|
#include "NotificationIcon.h"
|
|
#include "CustomWindowMessages.h"
|
|
|
|
#include <ScopedResourceLoader.h>
|
|
#include <LibraryResources.h>
|
|
|
|
using namespace winrt::Windows::Foundation::Collections;
|
|
using namespace winrt::Microsoft::Terminal;
|
|
|
|
NotificationIcon::NotificationIcon(const HWND owningHwnd) :
|
|
_owningHwnd{ owningHwnd }
|
|
{
|
|
CreateNotificationIcon();
|
|
}
|
|
|
|
NotificationIcon::~NotificationIcon()
|
|
{
|
|
RemoveIconFromNotificationArea();
|
|
}
|
|
|
|
void NotificationIcon::_CreateWindow()
|
|
{
|
|
WNDCLASSW wc{};
|
|
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wc.hInstance = wil::GetModuleInstanceHandle();
|
|
wc.lpszClassName = L"NOTIFICATION_ICON_HOSTING_WINDOW_CLASS";
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = DefWindowProcW;
|
|
wc.hIcon = static_cast<HICON>(GetActiveAppIconHandle(true));
|
|
RegisterClass(&wc);
|
|
|
|
_notificationIconHwnd = wil::unique_hwnd(CreateWindowW(wc.lpszClassName,
|
|
wc.lpszClassName,
|
|
WS_DISABLED,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
HWND_MESSAGE,
|
|
nullptr,
|
|
wc.hInstance,
|
|
nullptr));
|
|
|
|
WINRT_VERIFY(_notificationIconHwnd);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Creates and adds an icon to the notification area.
|
|
// If an icon already exists, update the HWND associated
|
|
// to the icon with this window's HWND.
|
|
// Arguments:
|
|
// - <unused>
|
|
// Return Value:
|
|
// - <none>
|
|
void NotificationIcon::CreateNotificationIcon()
|
|
{
|
|
if (!_notificationIconHwnd)
|
|
{
|
|
// Creating a disabled, non visible window just so we can set it
|
|
// as the foreground window when showing the context menu.
|
|
// This is done so that the context menu can be dismissed
|
|
// when clicking outside of it.
|
|
_CreateWindow();
|
|
}
|
|
|
|
NOTIFYICONDATA nid{};
|
|
nid.cbSize = sizeof(NOTIFYICONDATA);
|
|
|
|
// This HWND will receive the callbacks sent by the notification 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_NOTIFICATION_AREA;
|
|
|
|
// 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);
|
|
|
|
_notificationIconData = 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 NotificationIcon::ShowContextMenu(const til::point& coord,
|
|
IMapView<uint64_t, winrt::hstring> peasants)
|
|
{
|
|
if (const auto hMenu = _CreateContextMenu(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(_notificationIconHwnd.get());
|
|
|
|
// 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 notification icon.
|
|
// Arguments:
|
|
// - peasants: A map of all peasants' ID to their window name.
|
|
// Return Value:
|
|
// - The handle to the newly created context menu.
|
|
HMENU NotificationIcon::_CreateContextMenu(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>(NotificationIconMenuItemAction::FocusTerminal), RS_(L"NotificationIconFocusTerminal").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)NotificationIconMenuItemAction::SummonWindow;
|
|
SetMenuInfo(submenu, &submenuInfo);
|
|
|
|
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)submenu, RS_(L"NotificationIconWindowSubmenu").c_str());
|
|
}
|
|
}
|
|
return hMenu;
|
|
}
|
|
|
|
// Method Description:
|
|
// - This is the handler for when one of the menu items are selected within
|
|
// the notification 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 NotificationIcon::MenuItemSelected(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<NotificationIconMenuItemAction>(mi.dwMenuData) == NotificationIconMenuItemAction::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<NotificationIconMenuItemAction>(GetMenuItemID(menu, menuItemIndex));
|
|
switch (action)
|
|
{
|
|
case NotificationIconMenuItemAction::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 notification icon itself is left-clicked.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void NotificationIcon::NotificationIconPressed()
|
|
{
|
|
// 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 notification icon using our currently saved notification icon data.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void NotificationIcon::ReAddNotificationIcon()
|
|
{
|
|
Shell_NotifyIcon(NIM_ADD, &_notificationIconData);
|
|
Shell_NotifyIcon(NIM_SETVERSION, &_notificationIconData);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Deletes our notification icon.
|
|
// Arguments:
|
|
// - <none>
|
|
// Return Value:
|
|
// - <none>
|
|
void NotificationIcon::RemoveIconFromNotificationArea()
|
|
{
|
|
Shell_NotifyIcon(NIM_DELETE, &_notificationIconData);
|
|
}
|