Skip to content

Commit 0562d76

Browse files
committed
PRE-MERGE #20064 [Unpackaged] Fix taskbar glomming due to AUMID
2 parents 06a225c + 73af681 commit 0562d76

3 files changed

Lines changed: 131 additions & 1 deletion

File tree

.github/actions/spelling/expect/expect.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,7 @@ WCIA
18821882
WCIW
18831883
wcs
18841884
WCSHELPER
1885+
wcsicmp
18851886
wcsrev
18861887
wcswidth
18871888
wddm

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{ FindFirstFileW(taskbarGlob.c_str(), &findData) };
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 (_wcsicmp(targetPath, ourExePath.c_str()) != 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
@@ -68,6 +68,7 @@ class WindowEmperor
6868
void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state) const;
6969
void _finalizeSessionPersistence() const;
7070
void _checkWindowsForNotificationIcon();
71+
void _setupAumid(const std::wstring& aumid);
7172

7273
wil::unique_hwnd _window;
7374
winrt::TerminalApp::App _app{ nullptr };
@@ -83,6 +84,8 @@ class WindowEmperor
8384
std::optional<bool> _currentSystemThemeIsDark;
8485
int32_t _windowCount = 0;
8586
int32_t _messageBoxCount = 0;
87+
std::wstring _pendingAumidLnkPath;
88+
std::wstring _pendingAumid;
8689

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

0 commit comments

Comments
 (0)