Compare commits

...
8 Commits
Author SHA1 Message Date
YellowNest e1fe220d18 Release settings accessibility servers before DLL unload (#2558) 2026-10-07 22:02:16 +02:00
YellowNest a43cf39441 Cancel pending hover timer before showing Win+X (#2554)
Cancel hover timer before showing Win+X
2026-10-05 16:56:35 +02:00
YellowNest 93db2153c0 Prevent out-of-bounds user image writes in menu background (#2548)
Clip user image alpha writes to menu bitmap bounds
2026-10-05 16:52:01 +02:00
YellowNest 88e8cd8653 Restore Explorer shell if setup leaves it stopped (#2552)
Restore Explorer shell after successful setup
2026-10-05 08:52:18 +02:00
YellowNest ef61ce1dc0 Fix false empty entry in custom submenus (#2551)
Fix empty placeholder in custom submenus
2026-10-05 08:14:13 +02:00
YellowNest 2e2f681c5b Prevent cache writer use-after-free during cleanup (#2550)
Protect cache save snapshots from concurrent cleanup
2026-10-04 17:47:53 +02:00
YellowNest 523ff4b9f4 Validate cached icon indices before remapping (#2549) 2026-10-04 17:45:54 +02:00
YellowNest 1e7a52a5ab Fix context menu command thread affinity (#2546) 2026-10-04 17:41:09 +02:00
9 changed files with 150 additions and 30 deletions
+23 -3
View File
@@ -140,10 +140,8 @@ void EnableSettingsTreeAccessibility( HWND tree )
server->Release(); server->Release();
} }
void ClearSettingsTreeItemAccessibility( HWND tree ) static void ClearSettingsTreeItemAccessibilityImpl( IAccPropServices *props, HWND tree )
{ {
CComPtr<IAccPropServices> props;
if (FAILED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) return;
MSAAPROPID properties[]={PROPID_SettingsAccState,PROPID_SettingsAccName,PROPID_SettingsAccRole}; MSAAPROPID properties[]={PROPID_SettingsAccState,PROPID_SettingsAccName,PROPID_SettingsAccRole};
for (HTREEITEM item=TreeView_GetRoot(tree);item;) for (HTREEITEM item=TreeView_GetRoot(tree);item;)
{ {
@@ -159,6 +157,27 @@ void ClearSettingsTreeItemAccessibility( HWND tree )
} }
} }
void ClearSettingsTreeItemAccessibility( HWND tree )
{
if (!tree || !IsWindow(tree)) return;
CComPtr<IAccPropServices> props;
if (FAILED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) return;
ClearSettingsTreeItemAccessibilityImpl(props,tree);
}
void ClearSettingsTreeAccessibility( HWND tree )
{
if (!tree || !IsWindow(tree)) return;
CComPtr<IAccPropServices> props;
if (FAILED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) return;
// Release all per-item servers while their accessibility IDs can still be
// resolved, then remove the container server and the tree-level name.
ClearSettingsTreeItemAccessibilityImpl(props,tree);
MSAAPROPID properties[]={PROPID_SettingsAccState,PROPID_SettingsAccName,PROPID_SettingsAccRole};
props->ClearHwndProps(tree,OBJID_CLIENT,CHILDID_SELF,properties,_countof(properties));
}
void SetControlAccessibleName( HWND control, const wchar_t *name ) void SetControlAccessibleName( HWND control, const wchar_t *name )
{ {
CComPtr<IAccPropServices> props; CComPtr<IAccPropServices> props;
@@ -2778,6 +2797,7 @@ LRESULT CTreeSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam,
LRESULT CTreeSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) LRESULT CTreeSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{ {
ClearSettingsTreeAccessibility(m_Tree);
DestroyIcon(m_PlayIcon); DestroyIcon(m_PlayIcon);
bHandled=FALSE; bHandled=FALSE;
m_EditMode=EDIT_NONE; m_EditMode=EDIT_NONE;
+1
View File
@@ -19,6 +19,7 @@ struct CSetting;
// by Open-Shell's existing image list. // by Open-Shell's existing image list.
void EnableSettingsTreeAccessibility( HWND tree ); void EnableSettingsTreeAccessibility( HWND tree );
void ClearSettingsTreeItemAccessibility( HWND tree ); void ClearSettingsTreeItemAccessibility( HWND tree );
void ClearSettingsTreeAccessibility( HWND tree );
void SetControlAccessibleName( HWND control, const wchar_t *name ); void SetControlAccessibleName( HWND control, const wchar_t *name );
void SetSettingsTreeAccessibleName( HWND tree, const wchar_t *name ); void SetSettingsTreeAccessibleName( HWND tree, const wchar_t *name );
void SetSettingsTreeItemAccessibleName( HWND tree, HTREEITEM item, const wchar_t *name ); void SetSettingsTreeItemAccessibleName( HWND tree, HTREEITEM item, const wchar_t *name );
+74
View File
@@ -47,6 +47,69 @@ struct Chunk
int start1, start2, len; int start1, start2, len;
}; };
static bool IsExplorerShellRunning( void )
{
HWND shellWindow=GetShellWindow();
if (!shellWindow)
return false;
DWORD shellProcessId=0;
GetWindowThreadProcessId(shellWindow,&shellProcessId);
if (!shellProcessId)
return false;
DWORD currentSession=0;
DWORD shellSession=0;
if (!ProcessIdToSessionId(GetCurrentProcessId(),&currentSession) ||
!ProcessIdToSessionId(shellProcessId,&shellSession) ||
currentSession!=shellSession)
{
return false;
}
HANDLE process=OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,FALSE,shellProcessId);
if (!process)
return false;
wchar_t path[_MAX_PATH];
DWORD pathSize=_countof(path);
bool isExplorer=false;
if (QueryFullProcessImageName(process,0,path,&pathSize))
isExplorer=_wcsicmp(PathFindFileName(path),L"explorer.exe")==0;
CloseHandle(process);
return isExplorer;
}
static void RestoreExplorerShellIfNeeded( bool wasRunning )
{
if (!wasRunning)
return;
// Restart Manager normally brings Explorer back before msiexec exits.
// Give it a short grace period before applying our fallback so we don't
// race a normal restart or create an extra Explorer process.
for (int i=0;i<50;i++)
{
if (IsExplorerShellRunning())
return;
Sleep(100);
}
wchar_t explorerPath[_MAX_PATH];
UINT len=GetWindowsDirectory(explorerPath,_countof(explorerPath));
if (!len || len>=_countof(explorerPath) || !PathAppend(explorerPath,L"explorer.exe"))
return;
STARTUPINFO startupInfo={sizeof(startupInfo)};
PROCESS_INFORMATION processInfo={};
if (CreateProcess(explorerPath,NULL,NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo))
{
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
}
}
static void WriteFileXOR( HANDLE hFile, const unsigned char *buf, int size ) static void WriteFileXOR( HANDLE hFile, const unsigned char *buf, int size )
{ {
if (size>0) if (size>0)
@@ -409,6 +472,11 @@ int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm
if (extractType == ARM64) if (extractType == ARM64)
Wow64DisableWow64FsRedirection(&wow64FsRedirVal); Wow64DisableWow64FsRedirection(&wow64FsRedirVal);
// Remember whether this interactive session had the Explorer desktop shell.
// If Restart Manager closes it during MSI processing but fails to bring it
// back, restore only the shell that was present before setup started.
const bool explorerWasRunning=IsExplorerShellRunning();
// start the installer // start the installer
STARTUPINFO startupInfo={sizeof(startupInfo)}; STARTUPINFO startupInfo={sizeof(startupInfo)};
PROCESS_INFORMATION processInfo; PROCESS_INFORMATION processInfo;
@@ -438,6 +506,12 @@ int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCm
GetExitCodeProcess(processInfo.hProcess,&code); GetExitCodeProcess(processInfo.hProcess,&code);
CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hProcess);
DeleteFile(msiName); DeleteFile(msiName);
// Only repair the shell after a successful transaction. Reboot-required
// outcomes deliberately keep Windows Installer in control of recovery.
if (code==ERROR_SUCCESS)
RestoreExplorerShellIfNeeded(explorerWasRunning);
return code; return code;
} }
} }
+14 -5
View File
@@ -3337,19 +3337,19 @@ void CItemManager::LoadCacheFile( void )
info.iconColor=data.iconColor; info.iconColor=data.iconColor;
info.iconIndex=data.iconIndex; info.iconIndex=data.iconIndex;
info.smallIcon=data.smallIcon<(int)remapIcons.size()?remapIcons[data.smallIcon]:NULL; info.smallIcon=data.smallIcon>=0 && (size_t)data.smallIcon<remapIcons.size()?remapIcons[data.smallIcon]:NULL;
if (!info.smallIcon) if (!info.smallIcon)
{ {
info.validFlags&=~INFO_SMALL_ICON; info.validFlags&=~INFO_SMALL_ICON;
info.smallIcon=m_DefaultSmallIcon; info.smallIcon=m_DefaultSmallIcon;
} }
info.largeIcon=data.largeIcon<(int)remapIcons.size()?remapIcons[data.largeIcon]:NULL; info.largeIcon=data.largeIcon>=0 && (size_t)data.largeIcon<remapIcons.size()?remapIcons[data.largeIcon]:NULL;
if (!info.largeIcon) if (!info.largeIcon)
{ {
info.validFlags&=~INFO_LARGE_ICON; info.validFlags&=~INFO_LARGE_ICON;
info.largeIcon=m_DefaultLargeIcon; info.largeIcon=m_DefaultLargeIcon;
} }
info.extraLargeIcon=data.extraLargeIcon<(int)remapIcons.size()?remapIcons[data.extraLargeIcon]:NULL; info.extraLargeIcon=data.extraLargeIcon>=0 && (size_t)data.extraLargeIcon<remapIcons.size()?remapIcons[data.extraLargeIcon]:NULL;
if (!info.extraLargeIcon) if (!info.extraLargeIcon)
{ {
info.validFlags&=~INFO_EXTRA_LARGE_ICON; info.validFlags&=~INFO_EXTRA_LARGE_ICON;
@@ -3422,7 +3422,7 @@ DWORD CALLBACK CItemManager::SaveCacheFileThread( void *param )
RWLock lock(pThis,false,RWLOCK_ICONS); RWLock lock(pThis,false,RWLOCK_ICONS);
for (std::multimap<unsigned int,IconInfo>::const_iterator it=pThis->m_IconInfos.begin();it!=pThis->m_IconInfos.end();++it) for (std::multimap<unsigned int,IconInfo>::const_iterator it=pThis->m_IconInfos.begin();it!=pThis->m_IconInfos.end();++it)
{ {
if (!it->second.PATH.IsEmpty() && it->second.PATH[1]!='#' && it->first!=0) if (!it->second.bTemp && !it->second.bMetro && !it->second.PATH.IsEmpty() && it->second.PATH[1]!='#' && it->first!=0)
iconInfos.push_back(&*it); iconInfos.push_back(&*it);
} }
} }
@@ -3433,7 +3433,7 @@ DWORD CALLBACK CItemManager::SaveCacheFileThread( void *param )
RWLock lock(pThis,false,RWLOCK_ITEMS); RWLock lock(pThis,false,RWLOCK_ITEMS);
for (std::multimap<unsigned int,ItemInfo>::const_iterator it=pThis->m_ItemInfos.begin();it!=pThis->m_ItemInfos.end();++it) for (std::multimap<unsigned int,ItemInfo>::const_iterator it=pThis->m_ItemInfos.begin();it!=pThis->m_ItemInfos.end();++it)
{ {
if (it->first!=0) if (!it->second.bTemp && it->first!=0)
itemInfos.push_back(&*it); itemInfos.push_back(&*it);
} }
for (std::set<unsigned int>::const_iterator it=pThis->m_BlackListInfos10.begin();it!=pThis->m_BlackListInfos10.end();++it) for (std::set<unsigned int>::const_iterator it=pThis->m_BlackListInfos10.begin();it!=pThis->m_BlackListInfos10.end();++it)
@@ -3566,6 +3566,15 @@ void CItemManager::SaveCacheFile( void )
void CItemManager::ClearCache( void ) void CItemManager::ClearCache( void )
{ {
// The save thread keeps pointers to persistent cache entries while serializing them.
// Let it finish before clearing the containers those pointers refer to.
if (m_SaveCacheThread)
{
WaitForSingleObject(m_SaveCacheThread,INFINITE);
CloseHandle(m_SaveCacheThread);
m_SaveCacheThread=NULL;
}
Lock cleanupLock(this,LOCK_CLEANUP); Lock cleanupLock(this,LOCK_CLEANUP);
RWLock itemLock(this,true,RWLOCK_ITEMS); RWLock itemLock(this,true,RWLOCK_ITEMS);
RWLock iconLock(this,true,RWLOCK_ICONS); RWLock iconLock(this,true,RWLOCK_ICONS);
+6 -13
View File
@@ -63,14 +63,6 @@ static auto ExecuteOnSTAThread(TFunc&& func) -> decltype(func())
return result; return result;
} }
// Wrapper for IContextMenu::InvokeCommand that runs on separate STA thread and pumps messages
static HRESULT InvokeCommandSafe(IContextMenu* pMenu, LPCMINVOKECOMMANDINFO pInfo)
{
return ExecuteOnSTAThread([&]() {
return pMenu->InvokeCommand(pInfo);
});
}
// Wrapper for SHOpenFolderAndSelectItems that runs on separate STA thread and pumps messages // Wrapper for SHOpenFolderAndSelectItems that runs on separate STA thread and pumps messages
static HRESULT SHOpenFolderAndSelectItemsSafe(PCIDLIST_ABSOLUTE pidlFolder, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD dwFlags) static HRESULT SHOpenFolderAndSelectItemsSafe(PCIDLIST_ABSOLUTE pidlFolder, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD dwFlags)
{ {
@@ -3182,12 +3174,13 @@ void CMenuContainer::ActivateItem( int index, TActivateType type, const POINT *p
{ {
HRESULT hr{}; HRESULT hr{};
auto verb = GetContextMenuItemVerb(pInvokeMenu, (UINT_PTR)info.lpVerbW); auto verb = GetContextMenuItemVerb(pInvokeMenu, (UINT_PTR)info.lpVerbW);
// if the verb is "properties", we need to invoke it directly in this UI thread (as it needs to be called on thread that crated context menu object), // "opencontaining" may block the UI thread on recent Windows 11 versions.
// otherwise we will invoke the command on separate STA thread to make sure it won't block UI thread // Use the shell API on a worker STA for that operation, without passing
if (verb.CompareNoCase(L"properties") == 0) // the apartment-bound IContextMenu interface to another thread.
hr = pInvokeMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info); if (verb.CompareNoCase(L"opencontaining") == 0)
hr = SHOpenFolderAndSelectItemsSafe(pItemPidl1, 0, NULL, 0);
else else
hr = InvokeCommandSafe(pInvokeMenu, (LPCMINVOKECOMMANDINFO)&info); hr = pInvokeMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info);
LOG_MENU(LOG_EXECUTE,L"Invoke command, ptr=%p, res=%d",this,hr); LOG_MENU(LOG_EXECUTE,L"Invoke command, ptr=%p, res=%d",this,hr);
executeSuccess=SUCCEEDED(hr); executeSuccess=SUCCEEDED(hr);
} }
+4 -3
View File
@@ -2537,10 +2537,11 @@ void CMenuContainer::InitItems( void )
} }
} }
if (m_Items.empty() && m_Path1[0] && m_pDropFolder[0]) if (!m_bSubMenu && m_Items.empty() && m_Path1[0] && m_pDropFolder[0])
{ {
// add (Empty) item to the empty submenus // Keep the top-level drop target placeholder. Submenus defer their
MenuItem item(m_bSubMenu?MENU_EMPTY:MENU_EMPTY_TOP); // empty-state decision until after custom/standard items are appended.
MenuItem item(MENU_EMPTY_TOP);
item.name=FindTranslation(L"Menu.Empty",L"(Empty)"); item.name=FindTranslation(L"Menu.Empty",L"(Empty)");
m_Items.push_back(item); m_Items.push_back(item);
} }
+16 -6
View File
@@ -1185,13 +1185,23 @@ void CMenuContainer::CreateBackground( int width1, int width2, int height1, int
if (opacity!=MenuSkin::OPACITY_SOLID && !bMask) if (opacity!=MenuSkin::OPACITY_SOLID && !bMask)
{ {
// set to opaque // set the visible part to opaque. BitBlt/AlphaBlend above clip to
// the destination bitmap automatically, but this direct pixel pass
// must do the same before forming a pointer into the DIB.
SelectObject(hdc,bmp0); // deselect m_Bitmap so all the GDI operations get flushed SelectObject(hdc,bmp0); // deselect m_Bitmap so all the GDI operations get flushed
unsigned int *bits2=bits+pos.y*totalWidth+pos.x; RECT imageRect={pos.x,pos.y,pos.x+s_Skin.User_image_size,pos.y+s_Skin.User_image_size};
alpha<<=24; RECT bitmapRect={0,0,totalWidth,totalHeight};
for (int y=0;y<s_Skin.User_image_size;y++,bits2+=totalWidth) RECT clippedRect;
for (int x=0;x<s_Skin.User_image_size;x++) if (IntersectRect(&clippedRect,&imageRect,&bitmapRect))
bits2[x]=alpha|(bits2[x]&0xFFFFFF); {
unsigned int opaqueAlpha=alpha<<24;
for (int y=clippedRect.top;y<clippedRect.bottom;y++)
{
unsigned int *row=bits+y*totalWidth+clippedRect.left;
for (int x=clippedRect.left;x<clippedRect.right;x++,row++)
*row=opaqueAlpha|(*row&0xFFFFFF);
}
}
SelectObject(hdc,m_Bitmap); SelectObject(hdc,m_Bitmap);
} }
@@ -285,6 +285,7 @@ LRESULT CSkinSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam,
LRESULT CSkinSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) LRESULT CSkinSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{ {
ClearSettingsTreeAccessibility(m_Tree);
m_EditMode=SKIN_OPTION_NONE; m_EditMode=SKIN_OPTION_NONE;
bHandled=FALSE; bHandled=FALSE;
return 0; return 0;
@@ -4229,6 +4229,17 @@ if (!g_bTrimHooks)
} }
else if (bShowWinX) else if (bShowWinX)
{ {
// Win+X is shown asynchronously on Windows 11. Cancel any pending
// hover-open timer first, otherwise it can open the configured
// hover menu a moment later and dismiss Win+X again.
if (taskBar->bTimer)
{
taskBar->bTimer=false;
if (taskBar->startButton)
KillTimer(taskBar->startButton,'CLSM');
if (taskBar->oldButton && taskBar->oldButton!=taskBar->startButton)
KillTimer(taskBar->oldButton,'CLSM');
}
ShowWinX(); ShowWinX();
} }
else if (bShowWin7) else if (bShowWin7)