Provide the focused tab title in the Tray Icon's context menu (#11043)

This PR adds a bit more information to each item in the Tray Icon's  window selection submenu. 
Currently it only shows the window ID and window name if given one. 
Now each item will instead show`{Window ID} : {Active Tab Title} [{Window Name}]`

![image](https://user-images.githubusercontent.com/57155886/130883675-7a76e674-2429-4b26-b869-2455a9e4b4f6.png)
This commit is contained in:
Leon Liang 2021-09-03 11:32:23 -07:00 committed by GitHub
parent 4f6f3b98b8
commit 424414ec97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 167 additions and 91 deletions

View file

@ -613,39 +613,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
return *result;
}
// Method Description:
// - Helper for doing something on each and every peasant, with no regard
// for if the peasant is living or dead.
// - We'll try calling callback on every peasant.
// - If any single peasant is dead, then we'll call errorCallback, and move on.
// - We're taking an errorCallback here, because the thing we usually want
// to do is TraceLog a message, but TraceLoggingWrite is actually a macro
// that _requires_ the second arg to be a string literal. It can't just be
// a variable.
// Arguments:
// - callback: The function to call on each peasant
// - errorCallback: The function to call if a peasant is dead.
// Return Value:
// - <none>
void Monarch::_forAllPeasantsIgnoringTheDead(std::function<void(const Remoting::IPeasant&, const uint64_t)> callback,
std::function<void(const uint64_t)> errorCallback)
{
for (const auto& [id, p] : _peasants)
{
try
{
callback(p, id);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
// If this fails, we don't _really_ care. Just move on to the
// next one. Someone else will clean up the dead peasant.
errorCallback(id);
}
}
}
// Method Description:
// - This is an event handler for the IdentifyWindowsRequested event. A
// Peasant may raise that event if they want _all_ windows to identify
@ -660,17 +627,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
// Notify all the peasants to display their ID.
auto callback = [](auto&& p, auto&& /*id*/) {
const auto callback = [&](const auto& /*id*/, const auto& p) {
p.DisplayWindowId();
};
auto onError = [](auto&& id) {
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_identifyWindows_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
_forEachPeasant(callback, onError);
}
// Method Description:
@ -812,48 +780,68 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - <none>
// Return Value:
// - A map of peasant IDs to their names.
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> Monarch::GetPeasantNames()
Windows::Foundation::Collections::IVectorView<PeasantInfo> Monarch::GetPeasantInfos()
{
auto names = winrt::single_threaded_map<uint64_t, winrt::hstring>();
std::vector<PeasantInfo> names;
names.reserve(_peasants.size());
std::vector<uint64_t> peasantsToErase{};
for (const auto& [id, p] : _peasants)
{
try
const auto func = [&](const auto& id, const auto& p) -> void {
names.push_back({ id, p.WindowName(), p.ActiveTabTitle() });
};
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_identifyWindows_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not identify"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(func, onError);
return winrt::single_threaded_vector<PeasantInfo>(std::move(names)).GetView();
}
bool Monarch::DoesQuakeWindowExist()
{
bool result = false;
const auto func = [&](const auto& /*id*/, const auto& p) {
if (p.WindowName() == QuakeWindowName)
{
names.Insert(id, p.WindowName());
result = true;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
peasantsToErase.push_back(id);
}
}
// continue if we didn't get a positive result
return !result;
};
// Remove the dead peasants we came across while iterating.
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_DoesQuakeWindowExist_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not ask for its name"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
return names.GetView();
_forEachPeasant(func, onError);
return result;
}
void Monarch::SummonAllWindows()
{
auto callback = [](auto&& p, auto&& /*id*/) {
const auto func = [&](const auto& /*id*/, const auto& p) {
SummonWindowBehavior args{};
args.ToggleVisibility(false);
p.Summon(args);
};
auto onError = [](auto&& id) {
const auto onError = [&](const auto& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_SummonAll_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not summon"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forAllPeasantsIgnoringTheDead(callback, onError);
_forEachPeasant(func, onError);
}
}

View file

@ -53,7 +53,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
bool DoesQuakeWindowExist();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowTrayIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@ -89,6 +90,67 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void _renameRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
// Method Description:
// - Helper for doing something on each and every peasant.
// - We'll try calling func on every peasant.
// - If the return type of func is void, it will perform it for all peasants.
// - If the return type is a boolean, we'll break out of the loop once func
// returns false.
// - If any single peasant is dead, then we'll call onError and then add it to a
// list of peasants to clean up once the loop ends.
// Arguments:
// - func: The function to call on each peasant
// - onError: The function to call if a peasant is dead.
// Return Value:
// - <none>
template<typename F, typename T>
void _forEachPeasant(F&& func, T&& onError)
{
using Map = decltype(_peasants);
using R = std::invoke_result_t<F, Map::key_type, Map::mapped_type>;
static constexpr auto IsVoid = std::is_void_v<R>;
std::vector<uint64_t> peasantsToErase;
for (const auto& [id, p] : _peasants)
{
try
{
if constexpr (IsVoid)
{
func(id, p);
}
else
{
if (!func(id, p))
{
break;
}
}
}
catch (const winrt::hresult_error& exception)
{
onError(id);
if (exception.code() == 0x800706ba) // The RPC server is unavailable.
{
peasantsToErase.emplace_back(id);
}
else
{
LOG_CAUGHT_EXCEPTION();
throw;
}
}
}
for (const auto& id : peasantsToErase)
{
_peasants.erase(id);
_clearOldMruEntries(id);
}
}
friend class RemotingUnitTests::RemotingTests;
};
}

View file

@ -31,6 +31,12 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.IReference<UInt64> WindowID;
}
struct PeasantInfo
{
UInt64 Id;
String Name;
String TabTitle;
};
[default_interface] runtimeclass Monarch {
Monarch();
@ -42,7 +48,8 @@ namespace Microsoft.Terminal.Remoting
void SummonWindow(SummonWindowSelectionArgs args);
void SummonAllWindows();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames { get; };
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;

View file

@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
TYPED_EVENT(WindowActivated, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs);
TYPED_EVENT(ExecuteCommandlineRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::CommandlineArgs);

View file

@ -61,6 +61,7 @@ namespace Microsoft.Terminal.Remoting
void DisplayWindowId(); // Tells us to display its own ID (which causes a DisplayWindowIdRequested to be raised)
String WindowName { get; };
String ActiveTabTitle { get; };
void RequestIdentifyWindows(); // Tells us to raise a IdentifyWindowsRequested
void RequestRename(RenameRequestArgs args); // Tells us to raise a RenameRequested
void Summon(SummonWindowBehavior behavior);

View file

@ -519,11 +519,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> WindowManager::GetPeasantNames()
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> WindowManager::GetPeasantInfos()
{
// We should only get called when we're the monarch since the monarch
// is the only one that knows about all peasants.
return _monarch.GetPeasantNames();
return _monarch.GetPeasantInfos();
}
// Method Description:
@ -553,15 +553,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
bool WindowManager::DoesQuakeWindowExist()
{
const auto names = GetPeasantNames();
for (const auto [id, name] : names)
{
if (name == QuakeWindowName)
{
return true;
}
}
return false;
return _monarch.DoesQuakeWindowExist();
}
void WindowManager::UpdateActiveTabTitle(winrt::hstring title)
{
winrt::get_self<implementation::Peasant>(_peasant)->ActiveTabTitle(title);
}
}

View file

@ -41,11 +41,12 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
void SummonAllWindows();
Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> GetPeasantNames();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
winrt::fire_and_forget RequestShowTrayIcon();
winrt::fire_and_forget RequestHideTrayIcon();
bool DoesQuakeWindowExist();
void UpdateActiveTabTitle(winrt::hstring title);
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);

View file

@ -15,8 +15,9 @@ namespace Microsoft.Terminal.Remoting
void SummonAllWindows();
void RequestShowTrayIcon();
void RequestHideTrayIcon();
void UpdateActiveTabTitle(String title);
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IMapView<UInt64, String> GetPeasantNames();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowTrayIconRequested;

View file

@ -1477,4 +1477,9 @@ namespace winrt::TerminalApp::implementation
return false;
}
}
bool AppLogic::GetShowTitleInTitlebar()
{
return _settings.GlobalSettings().ShowTitleInTitlebar();
}
}

View file

@ -94,6 +94,7 @@ namespace winrt::TerminalApp::implementation
bool GetMinimizeToTray();
bool GetAlwaysShowTrayIcon();
bool GetShowTitleInTitlebar();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);

View file

@ -72,6 +72,7 @@ namespace TerminalApp
Boolean GetMinimizeToTray();
Boolean GetAlwaysShowTrayIcon();
Boolean GetShowTitleInTitlebar();
FindTargetWindowResult FindTargetWindow(String[] args);

View file

@ -1019,7 +1019,7 @@ namespace winrt::TerminalApp::implementation
{
auto newTabTitle = tab.Title();
if (_settings.GlobalSettings().ShowTitleInTitlebar() && tab == _GetFocusedTab())
if (tab == _GetFocusedTab())
{
_TitleChangedHandlers(*this, newTabTitle);
}

View file

@ -62,6 +62,8 @@ namespace RemotingUnitTests
void AssignID(uint64_t /*id*/) { throw winrt::hresult_error{}; };
uint64_t GetID() { throw winrt::hresult_error{}; };
winrt::hstring WindowName() { throw winrt::hresult_error{}; };
winrt::hstring ActiveTabTitle() { throw winrt::hresult_error{}; };
void ActiveTabTitle(const winrt::hstring& /*value*/) { throw winrt::hresult_error{}; };
uint64_t GetPID() { throw winrt::hresult_error{}; };
bool ExecuteCommandline(const Remoting::CommandlineArgs& /*args*/) { throw winrt::hresult_error{}; }
void ActivateWindow(const Remoting::WindowActivatedArgs& /*args*/) { throw winrt::hresult_error{}; }

View file

@ -299,8 +299,9 @@ void AppHost::Initialize()
}
// Method Description:
// - Called when the app's title changes. Fires off a window message so we can
// update the window's title on the main thread.
// - Called everytime when the active tab's title changes. We'll also fire off
// a window message so we can update the window's title on the main thread,
// though we'll only do so if the settings are configured for that.
// Arguments:
// - sender: unused
// - newTitle: the string to use as the new window title
@ -308,7 +309,11 @@ void AppHost::Initialize()
// - <none>
void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, winrt::hstring newTitle)
{
_window->UpdateTitle(newTitle);
if (_logic.GetShowTitleInTitlebar())
{
_window->UpdateTitle(newTitle);
}
_windowManager.UpdateActiveTabTitle(newTitle);
}
// Method Description:
@ -1031,7 +1036,7 @@ void AppHost::_CreateTrayIcon()
// Hookup the handlers, save the tokens for revoking if settings change.
_ReAddTrayIconToken = _window->NotifyReAddTrayIcon([this]() { _trayIcon->ReAddTrayIcon(); });
_TrayIconPressedToken = _window->NotifyTrayIconPressed([this]() { _trayIcon->TrayIconPressed(); });
_ShowTrayContextMenuToken = _window->NotifyShowTrayContextMenu([this](til::point coord) { _trayIcon->ShowTrayContextMenu(coord, _windowManager.GetPeasantNames()); });
_ShowTrayContextMenuToken = _window->NotifyShowTrayContextMenu([this](til::point coord) { _trayIcon->ShowTrayContextMenu(coord, _windowManager.GetPeasantInfos()); });
_TrayMenuItemSelectedToken = _window->NotifyTrayMenuItemSelected([this](HMENU hm, UINT idx) { _trayIcon->TrayMenuItemSelected(hm, idx); });
_trayIcon->SummonWindowRequested([this](auto& args) { _windowManager.SummonWindow(args); });
}

View file

@ -109,8 +109,8 @@ void TrayIcon::CreateTrayIcon()
// - 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)
void TrayIcon::ShowTrayContextMenu(const til::point& coord,
const IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants)
{
if (const auto hMenu = _CreateTrayContextMenu(peasants))
{
@ -142,7 +142,7 @@ void TrayIcon::ShowTrayContextMenu(const til::point coord,
// - 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)
HMENU TrayIcon::_CreateTrayContextMenu(const IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants)
{
auto hMenu = CreatePopupMenu();
if (hMenu)
@ -161,17 +161,22 @@ HMENU TrayIcon::_CreateTrayContextMenu(IMapView<uint64_t, winrt::hstring> peasan
// 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)
for (const auto& p : peasants)
{
winrt::hstring displayText = name;
if (name.empty())
std::wstringstream displayText;
displayText << L"#" << p.Id;
if (!p.TabTitle.empty())
{
displayText = fmt::format(L"{} {} - <{}>", locWindow, id, locUnnamed);
displayText << L": " << std::wstring_view{ p.TabTitle };
}
AppendMenu(submenu, MF_STRING, gsl::narrow<UINT_PTR>(id), displayText.c_str());
if (!p.Name.empty())
{
displayText << L" [" << std::wstring_view{ p.Name } << L"]";
}
AppendMenu(submenu, MF_STRING, gsl::narrow<UINT_PTR>(p.Id), displayText.str().c_str());
}
MENUINFO submenuInfo{};

View file

@ -24,14 +24,14 @@ public:
void ReAddTrayIcon();
void TrayIconPressed();
void ShowTrayContextMenu(const til::point coord, winrt::Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> peasants);
void ShowTrayContextMenu(const til::point& coord, const winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants);
void TrayMenuItemSelected(const HMENU menu, const UINT menuItemIndex);
WINRT_CALLBACK(SummonWindowRequested, winrt::delegate<void(winrt::Microsoft::Terminal::Remoting::SummonWindowSelectionArgs)>);
private:
void _CreateWindow();
HMENU _CreateTrayContextMenu(winrt::Windows::Foundation::Collections::IMapView<uint64_t, winrt::hstring> peasants);
HMENU _CreateTrayContextMenu(const winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo>& peasants);
wil::unique_hwnd _trayIconHwnd;
HWND _owningHwnd;