terminal/src/cascadia/WindowsTerminal/TrayIcon.cpp
Leon Liang a0edb12cd6
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
2021-08-12 19:54:39 +00:00

238 lines
8 KiB
C++

// 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);
}