Skip to content

Commit 154aed0

Browse files
carlos-zamoraclaude
authored andcommitted
[Unpackaged] Fix taskbar glomming due to AUMID (microsoft#20064)
The bug was caused by an AUMID mismatch between the Taskbar's .lnk file and Windows Terminal. Since no AUMID was associated with the .exe, the OS automatically creates one for us. However, microsoft#20018 added an AUMID for unpackaged scenarios, so now there was a mismatch, resulting in a new taskbar entry being created. To fix this, we check if a .lnk points to our .exe in the taskbar. There's 3 cases here: 1. no .lnk of interest exists --> set the AUMID normally 2. the .lnk carries our AUMID --> set the AUMID normally 3. the .lnk doesn't have an AUMID (aka uses the auto-resolved one) --> - for this launch: don't set the AUMID so that we both use the auto-resolved one - on next launch: set the AUMID on the .lnk and process so that they all agree ## Validation Steps Performed In unpackaged folder, move WindowsTerminal.exe to the taskbar (creates .lnk)... ✅ Double-click the .exe --> Same taskbar entry is used ✅ Double-click the .exe _again_ --> second window goes to same taskbar entry The first window doesn't have to close for this to work. It just * works *! Bug introduced in microsoft#20018 Closes microsoft#20053
1 parent dfee88c commit 154aed0

2 files changed

Lines changed: 130 additions & 1 deletion

File tree

src/cascadia/WindowsTerminal/WindowEmperor.cpp

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <wil/token_helpers.h>
1212
#include <winrt/TerminalApp.h>
1313
#include <sddl.h>
14+
#include <propkey.h>
15+
#include <propvarutil.h>
1416

1517
#include "AppHost.h"
1618
#include "resource.h"
@@ -313,6 +315,107 @@ AppHost* WindowEmperor::_mostRecentWindow() const noexcept
313315
return mostRecent;
314316
}
315317

318+
// GH#20053: The shell resolves taskbar grouping identity as: per-window AUMID >
319+
// per-process AUMID > auto-derived from exe path. Before we started setting a
320+
// process AUMID, both the pinned .lnk and the process used auto-derived
321+
// identity, so they matched. Now that we set an explicit AUMID, a pinned .lnk
322+
// that predates the AUMID change has no AUMID and still uses auto-derived
323+
// identity, causing a mismatch and a duplicate taskbar button.
324+
//
325+
// To fix this, we check if a pinned taskbar shortcut (.lnk) points to our exe.
326+
// If it already carries our AUMID (or no pin exists), we set the process AUMID
327+
// normally. If a pin exists WITHOUT our AUMID, we skip setting the process
328+
// AUMID for THIS launch (both sides use auto-derived identity, so they match)
329+
// and defer stamping the shortcut to process exit. On the next launch, the pin
330+
// has our AUMID, so we set the process AUMID to match, and both agree.
331+
//
332+
// NOTE: On the first launch after pinning, the process AUMID is not set. If
333+
// toast notifications are needed in the future, use
334+
// ToastNotificationManager::CreateToastNotifier(aumid) with the AUMID string
335+
// directly. That API does not depend on SetCurrentProcessExplicitAppUserModelID.
336+
// A Start Menu shortcut with the AUMID (separate from the taskbar pin) is also
337+
// required for toast routing; see
338+
// https://learn.microsoft.com/windows/apps/develop/notifications/app-notifications/send-local-toast-other-apps
339+
void WindowEmperor::_setupAumid(const std::wstring& aumid)
340+
{
341+
const auto ourExePath = wil::GetModuleFileNameW<std::wstring>(nullptr);
342+
343+
bool needsDeferredStamping = false;
344+
std::wstring pinnedLnkPath;
345+
346+
const auto taskbarGlob = wil::ExpandEnvironmentStringsW<std::wstring>(
347+
LR"(%APPDATA%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\*.lnk)");
348+
349+
WIN32_FIND_DATAW findData{};
350+
const wil::unique_hfind findHandle{ FindFirstFileExW(taskbarGlob.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH) };
351+
if (findHandle)
352+
{
353+
const auto lastSlash = taskbarGlob.rfind(L'\\');
354+
const auto taskbarDir = taskbarGlob.substr(0, lastSlash + 1);
355+
356+
do
357+
{
358+
const auto lnkPath = taskbarDir + findData.cFileName;
359+
360+
wil::com_ptr<IShellLinkW> shellLink;
361+
if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink))))
362+
{
363+
continue;
364+
}
365+
366+
const auto persistFile = shellLink.try_query<IPersistFile>();
367+
if (!persistFile || FAILED(persistFile->Load(lnkPath.c_str(), STGM_READ)))
368+
{
369+
continue;
370+
}
371+
372+
wchar_t targetPath[MAX_PATH]{};
373+
if (FAILED(shellLink->GetPath(targetPath, MAX_PATH, nullptr, SLGP_RAWPATH)))
374+
{
375+
continue;
376+
}
377+
378+
if (til::compare_ordinal_insensitive(targetPath, ourExePath) != 0)
379+
{
380+
continue;
381+
}
382+
383+
// Found a pin pointing to us. Assume it needs stamping unless
384+
// we confirm it already has our AUMID.
385+
pinnedLnkPath = lnkPath;
386+
needsDeferredStamping = true;
387+
388+
if (const auto propertyStore = shellLink.try_query<IPropertyStore>())
389+
{
390+
wil::unique_prop_variant pv;
391+
if (SUCCEEDED(propertyStore->GetValue(PKEY_AppUserModel_ID, &pv)) &&
392+
pv.vt == VT_LPWSTR && pv.pwszVal &&
393+
aumid == pv.pwszVal)
394+
{
395+
needsDeferredStamping = false;
396+
}
397+
}
398+
399+
break;
400+
} while (FindNextFileW(findHandle.get(), &findData));
401+
}
402+
403+
if (needsDeferredStamping)
404+
{
405+
// The pin exists but doesn't have our AUMID yet. Don't set the process
406+
// AUMID or stamp the shortcut now. Writing the shortcut causes the
407+
// shell to re-read it immediately, changing the pin's cached identity
408+
// mid-launch and creating a mismatch in the opposite direction. Instead,
409+
// stamp it at shutdown when the taskbar association no longer matters.
410+
_pendingAumidLnkPath = std::move(pinnedLnkPath);
411+
_pendingAumid = aumid;
412+
}
413+
else
414+
{
415+
LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(aumid.c_str()));
416+
}
417+
}
418+
316419
void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
317420
{
318421
// When running without package identity, set an explicit AppUserModelID so
@@ -373,7 +476,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
373476
#else
374477
fmt::format_to(std::back_inserter(unpackagedAumid), FMT_COMPILE(L".{:08x}"), hash);
375478
#endif
376-
LOG_IF_FAILED(SetCurrentProcessExplicitAppUserModelID(unpackagedAumid.c_str()));
479+
_setupAumid(unpackagedAumid);
377480
}
378481
}
379482

@@ -553,6 +656,29 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
553656
Shell_NotifyIconW(NIM_DELETE, &_notificationIcon);
554657
}
555658

659+
// GH#20053: Deferred shortcut stamping. See _setupAumid() for context.
660+
if (!_pendingAumidLnkPath.empty())
661+
{
662+
wil::com_ptr<IShellLinkW> shellLink;
663+
if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink))))
664+
{
665+
if (const auto persistFile = shellLink.try_query<IPersistFile>();
666+
persistFile && SUCCEEDED(persistFile->Load(_pendingAumidLnkPath.c_str(), STGM_READWRITE)))
667+
{
668+
if (const auto propertyStore = shellLink.try_query<IPropertyStore>())
669+
{
670+
wil::unique_prop_variant pv;
671+
if (SUCCEEDED(InitPropVariantFromString(_pendingAumid.c_str(), &pv)) &&
672+
SUCCEEDED(propertyStore->SetValue(PKEY_AppUserModel_ID, pv)) &&
673+
SUCCEEDED(propertyStore->Commit()))
674+
{
675+
persistFile->Save(_pendingAumidLnkPath.c_str(), TRUE);
676+
}
677+
}
678+
}
679+
}
680+
}
681+
556682
// There's a mysterious crash in XAML on Windows 10 if you just let _app get destroyed (GH#15410).
557683
// We also need to ensure that all UI threads exit before WindowEmperor leaves the scope on the main thread (MSFT:46744208).
558684
// Both problems can be solved and the shutdown accelerated by using TerminateProcess.

src/cascadia/WindowsTerminal/WindowEmperor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class WindowEmperor
6969
void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state) const;
7070
void _finalizeSessionPersistence() const;
7171
void _checkWindowsForNotificationIcon();
72+
void _setupAumid(const std::wstring& aumid);
7273

7374
wil::unique_hwnd _window;
7475
winrt::TerminalApp::App _app{ nullptr };
@@ -84,6 +85,8 @@ class WindowEmperor
8485
std::optional<bool> _currentSystemThemeIsDark;
8586
int32_t _windowCount = 0;
8687
int32_t _messageBoxCount = 0;
88+
std::wstring _pendingAumidLnkPath;
89+
std::wstring _pendingAumid;
8790

8891
#if 0 // #ifdef NDEBUG
8992
static constexpr void _assertIsMainThread() noexcept

0 commit comments

Comments
 (0)