Compare commits

..
8 Commits
Author SHA1 Message Date
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
jbgrimsbyandjbgrimsby 1384d4eb04 Improve screen reader, keyboard, and High Contrast accessibility (#2518)
* Improve screen reader and keyboard accessibility

* Fix settings page repaint regression

---------

Co-authored-by: jbgrimsby <314385300+jbgrimsby@users.noreply.github.com>
2026-09-15 13:56:16 +02:00
hamid da73edf4ea Added setting to horizontally offset start button (#2524) 2026-08-30 14:06:16 +02:00
ge0rdi f66d5ce50b Fix source indexing
`actions/checkout@v6` clones repo from URL that doesn't contain `.git`
at the end.

We were removing last 4 characters of the URL which resulted in wrong `REPO_URL`
in source index and debugger was thus unable to fetch sources.

The fix is to remove last 4 characters of URL only if there is `.git` at the end.
2026-08-30 13:27:52 +02:00
ge0rdi c9e791fc79 Update README with code signing section 2026-07-24 19:42:41 +02:00
ge0rdi fe6b144058 Delete temporary artefacts during official build
They are needed only for signing.
2026-07-19 12:10:03 +02:00
18 changed files with 446 additions and 44 deletions
+5 -2
View File
@@ -158,11 +158,14 @@ jobs:
# `overwrite: true` doesn't work with `archive: false`, so we have to delete the original first # `overwrite: true` doesn't work with `archive: false`, so we have to delete the original first
# https://github.com/actions/upload-artifact/issues/769 # https://github.com/actions/upload-artifact/issues/769
# https://github.com/actions/upload-artifact/issues/785 # https://github.com/actions/upload-artifact/issues/785
- name: Delete setup - name: Delete temporary artifacts
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' # Only manual master builds if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' # Only manual master builds
uses: geekyeggo/delete-artifact@v6 uses: geekyeggo/delete-artifact@v6
with: with:
name: OpenShellSetup*.exe name: |
OpenShellSetup*.exe
Binaries
MSI
- name: Upload setup (signed) - name: Upload setup (signed)
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' # Only manual master builds if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master' # Only manual master builds
+3 -3
View File
@@ -23,15 +23,15 @@ You can find the latest stable version here:
[![GitHub All Releases](https://img.shields.io/github/downloads/Open-Shell/Open-Shell-Menu/total?style=for-the-badge&color=4bc2ee&logo=github)](https://github.com/Open-Shell/Open-Shell-Menu/releases/latest) [![GitHub All Releases](https://img.shields.io/github/downloads/Open-Shell/Open-Shell-Menu/total?style=for-the-badge&color=4bc2ee&logo=github)](https://github.com/Open-Shell/Open-Shell-Menu/releases/latest)
> [!NOTE]
> Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
> [!IMPORTANT] > [!IMPORTANT]
> #### Windows for ARM compatibility > #### Windows for ARM compatibility
> Open-Shell is compatible with Windows for ARM since version [4.4.196](https://github.com/Open-Shell/Open-Shell-Menu/releases/tag/v4.4.196). > Open-Shell is compatible with Windows for ARM since version [4.4.196](https://github.com/Open-Shell/Open-Shell-Menu/releases/tag/v4.4.196).
> >
> If you install older one on a Windows for ARM installation (ex. using Parallels Desktop on an Apple Silicon Mac), you will no longer be able to log into your account the next time you reboot. Please refrain from installing Open-Shell on Windows for ARM. > If you install older one on a Windows for ARM installation (ex. using Parallels Desktop on an Apple Silicon Mac), you will no longer be able to log into your account the next time you reboot. Please refrain from installing Open-Shell on Windows for ARM.
### Code signing
Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
### Temporary Translation/Language Solution ### Temporary Translation/Language Solution
1. Download [language DLL](https://coddec.github.io/Classic-Shell/www.classicshell.net/translations/index.html) 1. Download [language DLL](https://coddec.github.io/Classic-Shell/www.classicshell.net/translations/index.html)
2. Place it either in the Open-Shell's __install folder__ or in the `%ALLUSERSPROFILE%\OpenShell\Languages` folder 2. Place it either in the Open-Shell's __install folder__ or in the `%ALLUSERSPROFILE%\OpenShell\Languages` folder
@@ -135,6 +135,8 @@ int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpstrC
FARPROC proc=GetProcAddress(dll,"ShowExplorerSettings"); FARPROC proc=GetProcAddress(dll,"ShowExplorerSettings");
if (!proc) return 2; if (!proc) return 2;
HRESULT comResult=CoInitialize(NULL);
proc(); proc();
if (SUCCEEDED(comResult)) CoUninitialize();
return 0; return 0;
} }
+2
View File
@@ -252,6 +252,8 @@ int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdL
return 0; return 0;
} }
HRESULT comResult=CoInitialize(NULL);
ShowIESettings(); ShowIESettings();
if (SUCCEEDED(comResult)) CoUninitialize();
return 0; return 0;
} }
+3 -1
View File
@@ -70,7 +70,7 @@ BEGIN
EDITTEXT IDC_EDITSEARCH,22,7,120,12,ES_AUTOHSCROLL EDITTEXT IDC_EDITSEARCH,22,7,120,12,ES_AUTOHSCROLL
CONTROL "Show all settings",IDC_CHECKALL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,7,79,12 CONTROL "Show all settings",IDC_CHECKALL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,154,7,79,12
CONTROL "<a>Help...</a>",IDC_LINKHELP,"SysLink",WS_TABSTOP,348,9,26,10,WS_EX_TRANSPARENT CONTROL "<a>Help...</a>",IDC_LINKHELP,"SysLink",WS_TABSTOP,348,9,26,10,WS_EX_TRANSPARENT
CONTROL "",IDC_TABSETTINGS,"SysTabControl32",TCS_MULTILINE | TCS_FOCUSNEVER,7,20,367,169 CONTROL "",IDC_TABSETTINGS,"SysTabControl32",TCS_MULTILINE | WS_TABSTOP,7,20,367,169
CONTROL "<a>Open-Shell Homepage</a>",IDC_LINKWEB,"SysLink",WS_TABSTOP,7,195,75,10,WS_EX_TRANSPARENT CONTROL "<a>Open-Shell Homepage</a>",IDC_LINKWEB,"SysLink",WS_TABSTOP,7,195,75,10,WS_EX_TRANSPARENT
CONTROL "Name of translator goes <a href=""http://www.yoursite.com"">here</a>",IDC_SYSLINKLOC, CONTROL "Name of translator goes <a href=""http://www.yoursite.com"">here</a>",IDC_SYSLINKLOC,
"SysLink",NOT WS_VISIBLE | WS_TABSTOP,80,195,111,10 "SysLink",NOT WS_VISIBLE | WS_TABSTOP,80,195,111,10
@@ -282,6 +282,8 @@ END
STRINGTABLE STRINGTABLE
BEGIN BEGIN
IDS_SETTINGS_PAGES "Settings pages"
IDS_SETTING_MODIFIED "modified"
IDS_INITIATE_FAIL "Failed to initiate the download." IDS_INITIATE_FAIL "Failed to initiate the download."
IDS_UPDATE_TITLE "Open-Shell Update" IDS_UPDATE_TITLE "Open-Shell Update"
IDS_INST_DOWNLOAD_FAIL "Failed to download the new version." IDS_INST_DOWNLOAD_FAIL "Failed to download the new version."
+81 -11
View File
@@ -1110,6 +1110,7 @@ class CSettingsDlg: public CResizeableDlg<CSettingsDlg>
public: public:
CSettingsDlg( void ); CSettingsDlg( void );
void Init( CSetting *pSettings, ICustomSettings *pCustom, int tab, const wchar_t* appId ); void Init( CSetting *pSettings, ICustomSettings *pCustom, int tab, const wchar_t* appId );
bool NavigateTab( bool bPrevious );
BEGIN_MSG_MAP( CSettingsDlg ) BEGIN_MSG_MAP( CSettingsDlg )
MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
@@ -1186,7 +1187,7 @@ private:
const wchar_t* m_AppId; const wchar_t* m_AppId;
void AddTabs( int name, const CSetting *pSelect=NULL ); void AddTabs( int name, const CSetting *pSelect=NULL );
void SetCurTab( int index, bool bReset, const CSetting *pSelect=NULL ); void SetCurTab( int index, bool bReset, const CSetting *pSelect=NULL, bool bFocusPanel=true );
bool IsTabValid( void ); bool IsTabValid( void );
void StorePlacement( void ); void StorePlacement( void );
@@ -1312,6 +1313,7 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO
icon=(HICON)LoadImage(GetModuleHandle(L"shell32.dll"),MAKEINTRESOURCE(323),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),LR_DEFAULTCOLOR); icon=(HICON)LoadImage(GetModuleHandle(L"shell32.dll"),MAKEINTRESOURCE(323),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),LR_DEFAULTCOLOR);
SendDlgItemMessage(IDC_ICONSEARCH,STM_SETICON,(WPARAM)icon); SendDlgItemMessage(IDC_ICONSEARCH,STM_SETICON,(WPARAM)icon);
SendDlgItemMessage(IDC_EDITSEARCH,EM_SETCUEBANNER,FALSE,(LPARAM)(const wchar_t*)LoadStringEx(IDS_SEARCH_PROMPT)); SendDlgItemMessage(IDC_EDITSEARCH,EM_SETCUEBANNER,FALSE,(LPARAM)(const wchar_t*)LoadStringEx(IDS_SEARCH_PROMPT));
SetControlAccessibleName(GetDlgItem(IDC_EDITSEARCH),LoadStringEx(IDS_SEARCH_PROMPT));
SetWindowSubclass(GetDlgItem(IDC_EDITSEARCH),SubclassSearchBoxProc,'CLSH',0); SetWindowSubclass(GetDlgItem(IDC_EDITSEARCH),SubclassSearchBoxProc,'CLSH',0);
CWindow tooltip; CWindow tooltip;
@@ -1364,6 +1366,7 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO
SIZE maxSize={0,0}; SIZE maxSize={0,0};
m_Tabs=GetDlgItem(IDC_TABSETTINGS); m_Tabs=GetDlgItem(IDC_TABSETTINGS);
m_Tabs.SetWindowText(LoadStringEx(IDS_SETTINGS_PAGES));
m_Panel=NULL; m_Panel=NULL;
int idx=0; int idx=0;
for (const CSetting *pSetting=m_pSettings;pSetting->name;pSetting++) for (const CSetting *pSetting=m_pSettings;pSetting->name;pSetting++)
@@ -1418,7 +1421,8 @@ LRESULT CSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO
ShowWindow(SW_MAXIMIZE); ShowWindow(SW_MAXIMIZE);
} }
return TRUE; GetDlgItem(IDC_BUTTONBACKUP).SetFocus();
return FALSE;
} }
LRESULT CSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) LRESULT CSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
@@ -1477,6 +1481,14 @@ void CSettingsDlg::AddTabs( int name, const CSetting *pSelect )
if (pSetting->nameID==name) if (pSetting->nameID==name)
idx=i; idx=i;
} }
int tabCount=TabCtrl_GetItemCount(m_Tabs);
LONG_PTR tabStyle=m_Tabs.GetWindowLongPtr(GWL_STYLE);
if (tabCount<=1)
tabStyle=(tabStyle&~WS_TABSTOP)|TCS_FOCUSNEVER;
else
tabStyle=(tabStyle|WS_TABSTOP)&~TCS_FOCUSNEVER;
m_Tabs.SetWindowLongPtr(GWL_STYLE,tabStyle);
m_Tabs.SetWindowPos(NULL,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_FRAMECHANGED);
m_Index=-1; m_Index=-1;
TabCtrl_SetCurSel(m_Tabs,idx); TabCtrl_SetCurSel(m_Tabs,idx);
m_Tabs.InvalidateRect(NULL); m_Tabs.InvalidateRect(NULL);
@@ -1499,20 +1511,23 @@ LRESULT CSettingsDlg::OnKeyDown( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
if (wParam==VK_TAB && GetKeyState(VK_CONTROL)<0) if (wParam==VK_TAB && GetKeyState(VK_CONTROL)<0)
{ {
int sel=TabCtrl_GetCurSel(m_Tabs); int sel=TabCtrl_GetCurSel(m_Tabs);
int count=TabCtrl_GetItemCount(m_Tabs);
if (GetKeyState(VK_SHIFT)<0) if (GetKeyState(VK_SHIFT)<0)
{ {
if (sel>0) if (count>0)
{ {
TabCtrl_SetCurSel(m_Tabs,sel-1); int next=(sel+count-1)%count;
SetCurTab(sel-1,false); TabCtrl_SetCurSel(m_Tabs,next);
SetCurTab(next,false);
} }
} }
else else
{ {
if (sel<TabCtrl_GetItemCount(m_Tabs)-1) if (count>0)
{ {
TabCtrl_SetCurSel(m_Tabs,sel+1); int next=(sel+1)%count;
SetCurTab(sel+1,false); TabCtrl_SetCurSel(m_Tabs,next);
SetCurTab(next,false);
} }
} }
} }
@@ -1740,6 +1755,7 @@ LRESULT CSettingsDlg::OnCheckAll( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL
{ {
m_bBasic=bBasic; m_bBasic=bBasic;
AddTabs(-1); AddTabs(-1);
::SetFocus(hWndCtl);
} }
return 0; return 0;
} }
@@ -1758,7 +1774,7 @@ LRESULT CSettingsDlg::OnSearchChange( WORD wNotifyCode, WORD wID, HWND hWndCtl,
return 0; return 0;
} }
void CSettingsDlg::SetCurTab( int index, bool bReset, const CSetting *pSelect ) void CSettingsDlg::SetCurTab( int index, bool bReset, const CSetting *pSelect, bool bFocusPanel )
{ {
if (m_Index==index && !bReset) return; if (m_Index==index && !bReset) return;
m_Index=index; m_Index=index;
@@ -1778,12 +1794,59 @@ void CSettingsDlg::SetCurTab( int index, bool bReset, const CSetting *pSelect )
::MapWindowPoints(NULL,m_hWnd,(POINT*)&rc,2); ::MapWindowPoints(NULL,m_hWnd,(POINT*)&rc,2);
TabCtrl_AdjustRect(m_Tabs,FALSE,&rc); TabCtrl_AdjustRect(m_Tabs,FALSE,&rc);
HWND hwnd=pPanel->Activate(pGroup,rc,bReset); HWND hwnd=pPanel->Activate(pGroup,rc,bReset);
CString pageTitle=LoadStringEx(pGroup->nameID);
::SetWindowText(hwnd,pageTitle);
if (hwnd!=m_Panel) if (hwnd!=m_Panel)
{ {
if (m_Panel) ::ShowWindow(m_Panel,SW_HIDE); if (m_Panel) ::ShowWindow(m_Panel,SW_HIDE);
m_Panel=hwnd; m_Panel=hwnd;
::SetFocus(m_Panel);
} }
if (bFocusPanel) ::SetFocus(m_Panel);
}
bool CSettingsDlg::NavigateTab( bool bPrevious )
{
HWND focus=::GetFocus();
HWND firstPanel=m_Panel?::GetNextDlgTabItem(m_Panel,NULL,FALSE):NULL;
HWND lastPanel=m_Panel?::GetNextDlgTabItem(m_Panel,NULL,TRUE):NULL;
HWND targets[]={
GetDlgItem(IDC_BUTTONBACKUP),
GetDlgItem(IDC_CHECKALL),
GetDlgItem(IDC_EDITSEARCH),
GetDlgItem(IDC_TABSETTINGS),
firstPanel,
GetDlgItem(IDOK),
GetDlgItem(IDCANCEL),
GetDlgItem(IDC_LINKHELP),
GetDlgItem(IDC_LINKWEB),
};
int current=-1;
if (m_Panel && (focus==m_Panel || ::IsChild(m_Panel,focus)))
{
HWND next=::GetNextDlgTabItem(m_Panel,focus,bPrevious);
HWND wrapped=bPrevious?lastPanel:firstPanel;
if (next && next!=wrapped)
return false; // Let IsDialogMessage move within the active page.
current=4;
}
else
{
for (int i=0;i<_countof(targets);i++)
if (focus==targets[i]) { current=i; break; }
}
if (current<0) return false;
for (int step=1;step<=_countof(targets);step++)
{
int index=(current+(bPrevious?-step:step)+_countof(targets)*2)%_countof(targets);
HWND target=(index==4 && bPrevious)?lastPanel:targets[index];
if (!target || !::IsWindowVisible(target) || !::IsWindowEnabled(target)) continue;
if (index==3 && !(::GetWindowLongPtr(target,GWL_STYLE)&WS_TABSTOP)) continue;
::SetFocus(target);
return true;
}
return false;
} }
LRESULT CSettingsDlg::OnSelChanging( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ) LRESULT CSettingsDlg::OnSelChanging( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
@@ -1793,7 +1856,9 @@ LRESULT CSettingsDlg::OnSelChanging( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
LRESULT CSettingsDlg::OnSelChange( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ) LRESULT CSettingsDlg::OnSelChange( int idCtrl, LPNMHDR pnmh, BOOL& bHandled )
{ {
SetCurTab(TabCtrl_GetCurSel(m_Tabs),false); // Mouse clicks and Left/Right navigation keep focus on the tab strip.
// Ctrl+Tab is handled separately and moves focus into the new page.
SetCurTab(TabCtrl_GetCurSel(m_Tabs),false,NULL,false);
return 0; return 0;
} }
@@ -1926,6 +1991,11 @@ bool IsSettingsMessage( MSG *msg )
g_SettingsDlg.SendMessage(WM_KEYDOWN,VK_TAB,msg->lParam); g_SettingsDlg.SendMessage(WM_KEYDOWN,VK_TAB,msg->lParam);
return true; return true;
} }
if (msg->message==WM_KEYDOWN && msg->wParam==VK_TAB)
{
if (g_SettingsDlg.NavigateTab(GetKeyState(VK_SHIFT)<0))
return true;
}
if (msg->message==WM_KEYDOWN && msg->wParam==VK_RETURN && GetKeyState(VK_CONTROL)<0) if (msg->message==WM_KEYDOWN && msg->wParam==VK_RETURN && GetKeyState(VK_CONTROL)<0)
{ {
g_SettingsDlg.SendMessage(WM_COMMAND,IDOK,0); g_SettingsDlg.SendMessage(WM_COMMAND,IDOK,0);
+256 -2
View File
@@ -16,6 +16,183 @@
#include <map> #include <map>
#include <algorithm> #include <algorithm>
#include <thread> #include <thread>
#include <oleacc.h>
// Local constants avoid forcing every executable that links the Lib static
// library to define the oleacc GUID symbols.
static const GUID CLSID_SettingsAccPropServices={0xb5f8350b,0x0548,0x48b1,{0xa6,0xee,0x88,0xbd,0x00,0xb4,0xa5,0xe7}};
static const GUID PROPID_SettingsAccState={0xa8d4d5b0,0x0a21,0x42d0,{0xa5,0xc0,0x51,0x4e,0x98,0x4f,0x45,0x7b}};
static const GUID PROPID_SettingsAccName={0x608d3df8,0x8128,0x4aa7,{0xa4,0x28,0xf5,0x5e,0x49,0x26,0x72,0x91}};
static const GUID PROPID_SettingsAccRole={0xcb905ff2,0x7bd1,0x4c05,{0xb3,0xc8,0xe6,0xc2,0x41,0x36,0x4d,0x70}};
class CSettingsTreeAccServer: public IAccPropServer
{
public:
CSettingsTreeAccServer( void ): m_RefCount(1)
{
CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&m_Props));
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID iid, void **object )
{
if (!object) return E_POINTER;
*object=NULL;
if (iid==IID_IUnknown || iid==IID_IAccPropServer)
{
*object=static_cast<IAccPropServer*>(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef( void ) { return InterlockedIncrement(&m_RefCount); }
virtual ULONG STDMETHODCALLTYPE Release( void )
{
ULONG count=InterlockedDecrement(&m_RefCount);
if (!count) delete this;
return count;
}
virtual HRESULT STDMETHODCALLTYPE GetPropValue( const BYTE *identity, DWORD identitySize, MSAAPROPID prop, VARIANT *value, BOOL *hasProp )
{
if (!value || !hasProp) return E_POINTER;
VariantInit(value);
*hasProp=FALSE;
if (!m_Props) return S_OK;
HWND tree=NULL;
DWORD objectId=0, childId=0;
if (FAILED(m_Props->DecomposeHwndIdentityString(identity,identitySize,&tree,&objectId,&childId)) || objectId!=OBJID_CLIENT || childId==CHILDID_SELF)
return S_OK;
HTREEITEM hItem=TreeView_MapAccIDToHTREEITEM(tree,childId);
if (!hItem) return S_OK;
wchar_t text[256];
TVITEM item={TVIF_IMAGE|TVIF_STATE|TVIF_TEXT,hItem,0,TVIS_SELECTED|TVIS_EXPANDED|TVIS_CUT,text,_countof(text)};
if (!TreeView_GetItem(tree,&item)) return S_OK;
if (prop==PROPID_SettingsAccRole)
{
if (item.iImage&SETTING_STATE_CHECKBOX)
value->lVal=ROLE_SYSTEM_CHECKBUTTON;
else if (item.iImage&SETTING_STATE_RADIO)
value->lVal=ROLE_SYSTEM_RADIOBUTTON;
else
return S_OK;
value->vt=VT_I4;
*hasProp=TRUE;
return S_OK;
}
if (prop==PROPID_SettingsAccName)
{
// A sighted user can see which child choice is checked without
// opening the group. Give screen-reader users the same summary.
for (HTREEITEM child=TreeView_GetChild(tree,hItem);child;child=TreeView_GetNextSibling(tree,child))
{
wchar_t childText[256];
TVITEM childItem={TVIF_IMAGE|TVIF_TEXT,child,0,0,childText,_countof(childText)};
if (TreeView_GetItem(tree,&childItem) && (childItem.iImage&SETTING_STATE_CHECKED))
{
CString name;
name.Format(L"%s: %s",text,childText);
value->vt=VT_BSTR;
value->bstrVal=SysAllocString(name);
if (!value->bstrVal) return E_OUTOFMEMORY;
*hasProp=TRUE;
return S_OK;
}
}
return S_OK;
}
if (prop!=PROPID_SettingsAccState) return S_OK;
LONG state=STATE_SYSTEM_SELECTABLE|STATE_SYSTEM_FOCUSABLE;
if (item.state&TVIS_SELECTED)
{
state|=STATE_SYSTEM_SELECTED;
if (GetFocus()==tree) state|=STATE_SYSTEM_FOCUSED;
}
if (TreeView_GetChild(tree,hItem))
state|=(item.state&TVIS_EXPANDED)?STATE_SYSTEM_EXPANDED:STATE_SYSTEM_COLLAPSED;
if (item.state&TVIS_CUT || (item.iImage&SETTING_STATE_DISABLED))
state|=STATE_SYSTEM_UNAVAILABLE;
if ((item.iImage&(SETTING_STATE_CHECKBOX|SETTING_STATE_RADIO)) && (item.iImage&SETTING_STATE_CHECKED))
state|=STATE_SYSTEM_CHECKED;
RECT rect;
if (!TreeView_GetItemRect(tree,hItem,&rect,FALSE))
state|=STATE_SYSTEM_INVISIBLE;
value->vt=VT_I4;
value->lVal=state;
*hasProp=TRUE;
return S_OK;
}
private:
LONG m_RefCount;
CComPtr<IAccPropServices> m_Props;
};
void EnableSettingsTreeAccessibility( HWND tree )
{
CComPtr<IAccPropServices> props;
if (FAILED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props)))) return;
CSettingsTreeAccServer *server=new CSettingsTreeAccServer;
MSAAPROPID property=PROPID_SettingsAccState;
props->SetHwndPropServer(tree,OBJID_CLIENT,CHILDID_SELF,&property,1,server,ANNO_CONTAINER);
server->Release();
}
void ClearSettingsTreeItemAccessibility( 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};
for (HTREEITEM item=TreeView_GetRoot(tree);item;)
{
props->ClearHwndProps(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),properties,_countof(properties));
HTREEITEM next=TreeView_GetChild(tree,item);
if (!next)
{
next=TreeView_GetNextSibling(tree,item);
while (!next && (item=TreeView_GetParent(tree,item))!=NULL)
next=TreeView_GetNextSibling(tree,item);
}
item=next;
}
}
void SetControlAccessibleName( HWND control, const wchar_t *name )
{
CComPtr<IAccPropServices> props;
if (SUCCEEDED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props))))
props->SetHwndPropStr(control,OBJID_CLIENT,CHILDID_SELF,PROPID_SettingsAccName,name);
}
void SetSettingsTreeAccessibleName( HWND tree, const wchar_t *name )
{
SetControlAccessibleName(tree,name);
}
void SetSettingsTreeItemAccessibleName( HWND tree, HTREEITEM item, const wchar_t *name )
{
CComPtr<IAccPropServices> props;
if (SUCCEEDED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props))))
props->SetHwndPropStr(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),PROPID_SettingsAccName,name);
}
void SetSettingsTreeItemAccessibleRole( HWND tree, HTREEITEM item, LONG role )
{
CComPtr<IAccPropServices> props;
if (SUCCEEDED(CoCreateInstance(CLSID_SettingsAccPropServices,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&props))))
{
// Some settings hosts do not ask a container annotation server for
// descendant state. Bind the dynamic state provider to the item too.
CSettingsTreeAccServer *server=new CSettingsTreeAccServer;
MSAAPROPID stateProperty=PROPID_SettingsAccState;
props->SetHwndPropServer(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),&stateProperty,1,server,ANNO_THIS);
server->Release();
CComVariant value(role);
props->SetHwndProp(tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(tree,item),PROPID_SettingsAccRole,value);
}
}
const KNOWNFOLDERID FOLDERID_DesktopRoot={'DESK', 'TO', 'P', {'D', 'E', 'S', 'K', 'T', 'O', 'P', 0x00}}; const KNOWNFOLDERID FOLDERID_DesktopRoot={'DESK', 'TO', 'P', {'D', 'E', 'S', 'K', 'T', 'O', 'P', 0x00}};
@@ -2334,7 +2511,6 @@ public:
NOTIFY_HANDLER( IDC_SETTINGS, TVN_KEYDOWN, OnKeyDown ) NOTIFY_HANDLER( IDC_SETTINGS, TVN_KEYDOWN, OnKeyDown )
NOTIFY_HANDLER( IDC_SETTINGS, TVN_GETINFOTIP, OnGetInfoTip ) NOTIFY_HANDLER( IDC_SETTINGS, TVN_GETINFOTIP, OnGetInfoTip )
NOTIFY_HANDLER( IDC_SETTINGS, TVN_SELCHANGED, OnSelChanged ) NOTIFY_HANDLER( IDC_SETTINGS, TVN_SELCHANGED, OnSelChanged )
NOTIFY_HANDLER( IDC_SETTINGS, TVN_ITEMEXPANDING, OnExpanding )
END_MSG_MAP() END_MSG_MAP()
BEGIN_RESIZE_MAP BEGIN_RESIZE_MAP
@@ -2364,7 +2540,6 @@ protected:
LRESULT OnClick( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ); LRESULT OnClick( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
LRESULT OnGetInfoTip( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ); LRESULT OnGetInfoTip( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
LRESULT OnSelChanged( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ); LRESULT OnSelChanged( int idCtrl, LPNMHDR pnmh, BOOL& bHandled );
LRESULT OnExpanding( int idCtrl, LPNMHDR pnmh, BOOL& bHandled ) { return TRUE; } // prevent collapsing
private: private:
CWindow m_Tree; CWindow m_Tree;
@@ -2398,6 +2573,32 @@ static LRESULT CALLBACK SubclassTreeProc( HWND hWnd, UINT uMsg, WPARAM wParam, L
{ {
if (uMsg==WM_COMMAND) if (uMsg==WM_COMMAND)
return SendMessage(GetParent(hWnd),uMsg,wParam,lParam); return SendMessage(GetParent(hWnd),uMsg,wParam,lParam);
if (uMsg==WM_KEYDOWN && (wParam==VK_LEFT || wParam==VK_RIGHT) && GetKeyState(VK_MENU)>=0)
{
HTREEITEM item=TreeView_GetSelection(hWnd);
if (!item) return 0;
HTREEITEM child=TreeView_GetChild(hWnd,item);
TVITEM state={TVIF_STATE,item,0,TVIS_EXPANDED};
TreeView_GetItem(hWnd,&state);
if (wParam==VK_LEFT)
{
if (child && (state.state&TVIS_EXPANDED))
TreeView_Expand(hWnd,item,TVE_COLLAPSE);
else
{
HTREEITEM parent=TreeView_GetParent(hWnd,item);
if (parent) TreeView_SelectItem(hWnd,parent);
}
}
else if (child)
{
if (state.state&TVIS_EXPANDED)
TreeView_SelectItem(hWnd,child);
else
TreeView_Expand(hWnd,item,TVE_EXPAND);
}
return 0;
}
return DefSubclassProc(hWnd,uMsg,wParam,lParam); return DefSubclassProc(hWnd,uMsg,wParam,lParam);
} }
@@ -2534,6 +2735,16 @@ LRESULT CTreeSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam,
SetWindowTheme(m_Tree,L"Explorer",NULL); SetWindowTheme(m_Tree,L"Explorer",NULL);
} }
m_Tree.SendMessage(TVM_SETEXTENDEDSTYLE,TVS_EX_DOUBLEBUFFER,TVS_EX_DOUBLEBUFFER); m_Tree.SendMessage(TVM_SETEXTENDEDSTYLE,TVS_EX_DOUBLEBUFFER,TVS_EX_DOUBLEBUFFER);
EnableSettingsTreeAccessibility(m_Tree);
CString tip;
GetDlgItemText(IDC_STATICTIP,tip);
int line=tip.Find(L'\n');
if (line>=0)
{
tip=tip.Mid(line+1);
tip.Trim();
SetControlAccessibleName(GetDlgItem(IDC_STATICTIP),tip);
}
SetWindowSubclass(m_Tree,SubclassTreeProc,'CLSH',0); SetWindowSubclass(m_Tree,SubclassTreeProc,'CLSH',0);
HINSTANCE hInstance=_AtlBaseModule.GetResourceInstance(); HINSTANCE hInstance=_AtlBaseModule.GetResourceInstance();
@@ -3201,6 +3412,7 @@ void CTreeSettingsDlg::ItemSelected( HTREEITEM hItem, CSetting *pSetting, bool b
if (mode!=EDIT_NONE) if (mode!=EDIT_NONE)
{ {
CString str=LoadStringEx(pSetting->nameID); CString str=LoadStringEx(pSetting->nameID);
SetControlAccessibleName(m_EditBox,str);
TVITEM item={TVIF_TEXT,hItem,0,0,(LPWSTR)(LPCWSTR)str}; TVITEM item={TVIF_TEXT,hItem,0,0,(LPWSTR)(LPCWSTR)str};
TreeView_SetItem(m_Tree,&item); TreeView_SetItem(m_Tree,&item);
m_Tree.GetClientRect(&rc); m_Tree.GetClientRect(&rc);
@@ -3242,6 +3454,12 @@ void CTreeSettingsDlg::ItemSelected( HTREEITEM hItem, CSetting *pSetting, bool b
m_EditBox.SetWindowPos(NULL,&rc,SWP_NOZORDER|SWP_SHOWWINDOW); m_EditBox.SetWindowPos(NULL,&rc,SWP_NOZORDER|SWP_SHOWWINDOW);
SendMessage(WM_NEXTDLGCTL,(LPARAM)m_EditBox.m_hWnd,TRUE); SendMessage(WM_NEXTDLGCTL,(LPARAM)m_EditBox.m_hWnd,TRUE);
SetWindowSubclass(m_EditBox,SubclassEditProc,'CLSH',(mode==EDIT_HOTKEY)?1:((mode==EDIT_HOTKEY_ANY)?2:0)); SetWindowSubclass(m_EditBox,SubclassEditProc,'CLSH',(mode==EDIT_HOTKEY)?1:((mode==EDIT_HOTKEY_ANY)?2:0));
// The same overlay edit is reused while Up/Down moves through the
// tree. Its HWND and keyboard focus do not change, so explicitly
// announce that its accessible name has changed.
NotifyWinEvent(EVENT_OBJECT_NAMECHANGE,m_EditBox,OBJID_CLIENT,CHILDID_SELF);
if (::GetFocus()==m_EditBox)
NotifyWinEvent(EVENT_OBJECT_FOCUS,m_EditBox,OBJID_CLIENT,CHILDID_SELF);
} }
else else
m_EditBox.ShowWindow(SW_HIDE); m_EditBox.ShowWindow(SW_HIDE);
@@ -3385,11 +3603,14 @@ HTREEITEM CTreeSettingsDlg::FindSettingsItem( const wchar_t *name )
void CTreeSettingsDlg::SetGroup( CSetting *pGroup, const CString &filter, const CSetting *pSelect ) void CTreeSettingsDlg::SetGroup( CSetting *pGroup, const CString &filter, const CSetting *pSelect )
{ {
CString groupName=LoadStringEx(pGroup->nameID);
SetSettingsTreeAccessibleName(m_Tree,groupName);
ItemSelected(NULL,NULL,false); ItemSelected(NULL,NULL,false);
m_pGroup=pGroup; m_pGroup=pGroup;
// fill tree control // fill tree control
m_Tree.SendMessage(WM_SETREDRAW,FALSE); m_Tree.SendMessage(WM_SETREDRAW,FALSE);
TreeView_SelectItem(m_Tree,NULL); TreeView_SelectItem(m_Tree,NULL);
ClearSettingsTreeItemAccessibility(m_Tree);
TreeView_DeleteAllItems(m_Tree); TreeView_DeleteAllItems(m_Tree);
int level=0; int level=0;
HTREEITEM hRadioParent=NULL; HTREEITEM hRadioParent=NULL;
@@ -3566,9 +3787,14 @@ void CTreeSettingsDlg::UpdateGroup( const CSetting *pModified )
RECT rc; RECT rc;
TreeView_GetItemRect(m_Tree,hItem,&rc,FALSE); TreeView_GetItemRect(m_Tree,hItem,&rc,FALSE);
m_Tree.InvalidateRect(&rc); m_Tree.InvalidateRect(&rc);
NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hItem));
} }
if (pSetting->type==CSetting::TYPE_BOOL)
SetSettingsTreeItemAccessibleRole(m_Tree,hItem,ROLE_SYSTEM_CHECKBUTTON);
// update radio buttons // update radio buttons
wchar_t selectedText[256];
selectedText[0]=0;
if (pSetting->type==CSetting::TYPE_INT && pSetting[1].type==CSetting::TYPE_RADIO) if (pSetting->type==CSetting::TYPE_INT && pSetting[1].type==CSetting::TYPE_RADIO)
{ {
int val=0; int val=0;
@@ -3598,9 +3824,37 @@ void CTreeSettingsDlg::UpdateGroup( const CSetting *pModified )
RECT rc; RECT rc;
TreeView_GetItemRect(m_Tree,hRadio,&rc,FALSE); TreeView_GetItemRect(m_Tree,hRadio,&rc,FALSE);
m_Tree.InvalidateRect(&rc); m_Tree.InvalidateRect(&rc);
NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hRadio));
}
SetSettingsTreeItemAccessibleRole(m_Tree,hRadio,ROLE_SYSTEM_RADIOBUTTON);
}
for (HTREEITEM hRadio=TreeView_GetChild(m_Tree,hItem);hRadio;hRadio=TreeView_GetNextSibling(m_Tree,hRadio))
{
TVITEM radioName={TVIF_IMAGE|TVIF_TEXT,hRadio,0,0,selectedText,_countof(selectedText)};
if (TreeView_GetItem(m_Tree,&radioName) && (radioName.iImage&SETTING_STATE_CHECKED)) break;
selectedText[0]=0;
} }
} }
// Bold is the visual indication that a setting differs from its
// default. Expose the same information in the accessible name.
wchar_t parentText[256];
TVITEM parentName={TVIF_TEXT,hItem,0,0,parentText,_countof(parentText)};
TreeView_GetItem(m_Tree,&parentName);
CString accessibleName=parentText;
if (selectedText[0])
{
accessibleName+=L": ";
accessibleName+=selectedText;
} }
if (!bDefault)
{
accessibleName+=L", ";
accessibleName+=LoadStringEx(IDS_SETTING_MODIFIED);
}
SetSettingsTreeItemAccessibleName(m_Tree,hItem,accessibleName);
if (pSetting==pModified)
NotifyWinEvent(EVENT_OBJECT_NAMECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hItem));
} }
} }
+10
View File
@@ -14,6 +14,16 @@ class CSettingsTree;
class ISettingsPanel; class ISettingsPanel;
struct CSetting; struct CSetting;
// Adds the checked/unchecked and unavailable states used by the settings
// trees to their MSAA/UIA representation. The visual state remains provided
// by Open-Shell's existing image list.
void EnableSettingsTreeAccessibility( HWND tree );
void ClearSettingsTreeItemAccessibility( HWND tree );
void SetControlAccessibleName( HWND control, const wchar_t *name );
void SetSettingsTreeAccessibleName( HWND tree, const wchar_t *name );
void SetSettingsTreeItemAccessibleName( HWND tree, HTREEITEM item, const wchar_t *name );
void SetSettingsTreeItemAccessibleRole( HWND tree, HTREEITEM item, LONG role );
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// CResizeableDlg - a dialog that rearranges its controls when it gets resized // CResizeableDlg - a dialog that rearranges its controls when it gets resized
+2
View File
@@ -139,6 +139,8 @@
#define IDS_BMP_FILTERS2 4077 #define IDS_BMP_FILTERS2 4077
#define IDS_UNSAVED_CHANGES 4078 #define IDS_UNSAVED_CHANGES 4078
#define IDS_UNSAVED_TITLE 4079 #define IDS_UNSAVED_TITLE 4079
#define IDS_SETTINGS_PAGES 4080
#define IDS_SETTING_MODIFIED 4081
// Next default values for new objects // Next default values for new objects
// //
+2 -2
View File
@@ -12,8 +12,8 @@ set SRC_ROOT=%~fdp1
rem get repo url rem get repo url
for /f %%A in ('git remote get-url origin') do set REPO_URL=%%A for /f %%A in ('git remote get-url origin') do set REPO_URL=%%A
rem remove .git from the end rem remove eventual .git from the end
set REPO_URL=%REPO_URL:~0,-4% if /I "%REPO_URL:~-4%"==".git" set "REPO_URL=%REPO_URL:~0,-4%"
rem get actual commit id rem get actual commit id
for /f %%A in ('git rev-parse HEAD') do set COMMIT=%%A for /f %%A in ('git rev-parse HEAD') do set COMMIT=%%A
+17 -2
View File
@@ -53,7 +53,14 @@ HRESULT STDMETHODCALLTYPE CMenuAccessible::get_accName( VARIANT varChild, BSTR *
if (!m_pOwner) return RPC_E_DISCONNECTED; if (!m_pOwner) return RPC_E_DISCONNECTED;
*pszName=NULL; *pszName=NULL;
if (varChild.vt!=VT_I4) return S_FALSE; if (varChild.vt!=VT_I4) return S_FALSE;
if (varChild.lVal==CHILDID_SELF) return S_FALSE; if (varChild.lVal==CHILDID_SELF)
{
if (m_pOwner->m_pParent && m_pOwner->m_ParentIndex>=0 && m_pOwner->m_ParentIndex<(int)m_pOwner->m_pParent->m_Items.size())
*pszName=SysAllocString(m_pOwner->m_pParent->m_Items[m_pOwner->m_ParentIndex].name);
else
*pszName=SysAllocString(L"Start");
return *pszName?S_OK:E_OUTOFMEMORY;
}
int index=varChild.lVal-1; int index=varChild.lVal-1;
if (index<0 || index>=(int)m_pOwner->m_Items.size()) return S_FALSE; if (index<0 || index>=(int)m_pOwner->m_Items.size()) return S_FALSE;
if (m_pOwner->m_Items[index].id==MENU_SEPARATOR) return S_FALSE; if (m_pOwner->m_Items[index].id==MENU_SEPARATOR) return S_FALSE;
@@ -105,7 +112,10 @@ HRESULT STDMETHODCALLTYPE CMenuAccessible::get_accState( VARIANT varChild, VARIA
if (m_pOwner->m_HotItem==index) if (m_pOwner->m_HotItem==index)
flags|=STATE_SYSTEM_FOCUSED; flags|=STATE_SYSTEM_FOCUSED;
if (item.bFolder) if (item.bFolder)
{
flags|=STATE_SYSTEM_HASPOPUP; flags|=STATE_SYSTEM_HASPOPUP;
flags|=(m_pOwner->m_Submenu==index)?STATE_SYSTEM_EXPANDED:STATE_SYSTEM_COLLAPSED;
}
if (item.id==MENU_SEPARATOR) if (item.id==MENU_SEPARATOR)
flags=0; flags=0;
RECT rc; RECT rc;
@@ -167,7 +177,12 @@ HRESULT STDMETHODCALLTYPE CMenuAccessible::get_accDefaultAction( VARIANT varChil
return S_FALSE; return S_FALSE;
const CMenuContainer::MenuItem &item=m_pOwner->m_Items[index]; const CMenuContainer::MenuItem &item=m_pOwner->m_Items[index];
if (item.id!=MENU_SEPARATOR && item.id!=MENU_EMPTY && item.id!=MENU_EMPTY_TOP) if (item.id!=MENU_SEPARATOR && item.id!=MENU_EMPTY && item.id!=MENU_EMPTY_TOP)
*pszDefaultAction=SysAllocString(item.bFolder?FindTranslation(L"Menu.ActionOpen",L"Open"):FindTranslation(L"Menu.ActionExecute",L"Execute")); {
if (item.bFolder)
*pszDefaultAction=SysAllocString(m_pOwner->m_Submenu==index?FindTranslation(L"Menu.ActionClose",L"Close"):FindTranslation(L"Menu.ActionOpen",L"Open"));
else
*pszDefaultAction=SysAllocString(FindTranslation(L"Menu.ActionExecute",L"Execute"));
}
return S_OK; return S_OK;
} }
+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);
} }
+13 -2
View File
@@ -207,6 +207,10 @@ LRESULT CSkinSettingsDlg::OnInitDialog( UINT uMsg, WPARAM wParam, LPARAM lParam,
EnableThemeDialogTexture(m_hWnd,ETDT_ENABLETAB); EnableThemeDialogTexture(m_hWnd,ETDT_ENABLETAB);
m_Tree=GetDlgItem(IDC_SKINOPTIONS); m_Tree=GetDlgItem(IDC_SKINOPTIONS);
EnableSettingsTreeAccessibility(m_Tree);
CString optionsName;
GetDlgItemText(IDC_STATICOPT,optionsName);
SetSettingsTreeAccessibleName(m_Tree,optionsName);
SetWindowSubclass(m_Tree,SubclassTreeProc,'CLSH',0); SetWindowSubclass(m_Tree,SubclassTreeProc,'CLSH',0);
TreeView_SetImageList(m_Tree,GetSettingsImageList(m_Tree),TVSIL_NORMAL); TreeView_SetImageList(m_Tree,GetSettingsImageList(m_Tree),TVSIL_NORMAL);
m_Tree.SendMessage(TVM_SETEXTENDEDSTYLE,TVS_EX_DOUBLEBUFFER,TVS_EX_DOUBLEBUFFER); m_Tree.SendMessage(TVM_SETEXTENDEDSTYLE,TVS_EX_DOUBLEBUFFER,TVS_EX_DOUBLEBUFFER);
@@ -290,6 +294,7 @@ LRESULT CSkinSettingsDlg::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BO
void CSkinSettingsDlg::InitSkinUI( void ) void CSkinSettingsDlg::InitSkinUI( void )
{ {
m_Tree.SetRedraw(FALSE); m_Tree.SetRedraw(FALSE);
ClearSettingsTreeItemAccessibility(m_Tree);
TreeView_DeleteAllItems(m_Tree); TreeView_DeleteAllItems(m_Tree);
m_VariationIndex=-1; m_VariationIndex=-1;
m_EditItemIndex=-1; m_EditItemIndex=-1;
@@ -396,10 +401,12 @@ void CSkinSettingsDlg::UpdateSkinSettings( void )
{ {
item.iImage=item.iSelectedImage=image; item.iImage=item.iSelectedImage=image;
TreeView_SetItem(m_Tree,&item); TreeView_SetItem(m_Tree,&item);
NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hVar));
RECT rc; RECT rc;
TreeView_GetItemRect(m_Tree,hVar,&rc,FALSE); TreeView_GetItemRect(m_Tree,hVar,&rc,FALSE);
m_Tree.InvalidateRect(&rc); m_Tree.InvalidateRect(&rc);
} }
SetSettingsTreeItemAccessibleRole(m_Tree,hVar,ROLE_SYSTEM_RADIOBUTTON);
} }
hItem=TreeView_GetNextSibling(m_Tree,hItem); hItem=TreeView_GetNextSibling(m_Tree,hItem);
} }
@@ -442,6 +449,8 @@ void CSkinSettingsDlg::UpdateSkinSettings( void )
item.iImage=item.iSelectedImage=image; item.iImage=item.iSelectedImage=image;
item.mask|=TVIF_TEXT; item.mask|=TVIF_TEXT;
TreeView_SetItem(m_Tree,&item); TreeView_SetItem(m_Tree,&item);
NotifyWinEvent(EVENT_OBJECT_STATECHANGE,m_Tree,OBJID_CLIENT,TreeView_MapHTREEITEMToAccID(m_Tree,hItem));
SetSettingsTreeItemAccessibleRole(m_Tree,hItem,option.groupId>=0?ROLE_SYSTEM_RADIOBUTTON:ROLE_SYSTEM_CHECKBUTTON);
item.mask&=~TVIF_TEXT; item.mask&=~TVIF_TEXT;
RECT rc; RECT rc;
TreeView_GetItemRect(m_Tree,hItem,&rc,FALSE); TreeView_GetItemRect(m_Tree,hItem,&rc,FALSE);
@@ -4456,6 +4465,7 @@ CSetting g_Settings[]={
{L"CustomButton",CSetting::TYPE_RADIO,IDS_CUSTOM_BUTTON,IDS_CUSTOM_BUTTON_TIP}, {L"CustomButton",CSetting::TYPE_RADIO,IDS_CUSTOM_BUTTON,IDS_CUSTOM_BUTTON_TIP},
{L"StartButtonPath",CSetting::TYPE_BITMAP,IDS_BUTTON_IMAGE,IDS_BUTTON_IMAGE_TIP,L"",0,L"#StartButtonType=2",L"CustomButton"}, {L"StartButtonPath",CSetting::TYPE_BITMAP,IDS_BUTTON_IMAGE,IDS_BUTTON_IMAGE_TIP,L"",0,L"#StartButtonType=2",L"CustomButton"},
{L"StartButtonSize",CSetting::TYPE_INT,IDS_BUTTON_SIZE,IDS_BUTTON_SIZE_TIP2,0,0,L"#StartButtonType=2",L"CustomButton"}, {L"StartButtonSize",CSetting::TYPE_INT,IDS_BUTTON_SIZE,IDS_BUTTON_SIZE_TIP2,0,0,L"#StartButtonType=2",L"CustomButton"},
{L"StartButtonOffset",CSetting::TYPE_INT,IDS_BUTTON_OFFSET,IDS_BUTTON_OFFSET_TIP,0,0,L"#StartButtonType=2",L"CustomButton"},
{L"StartButtonAlign",CSetting::TYPE_BOOL,IDS_BUTTON_ALIGN,IDS_BUTTON_ALIGN_TIP,0,0,L"#StartButtonType=2",L"CustomButton"}, {L"StartButtonAlign",CSetting::TYPE_BOOL,IDS_BUTTON_ALIGN,IDS_BUTTON_ALIGN_TIP,0,0,L"#StartButtonType=2",L"CustomButton"},
{L"StartButtonIcon",CSetting::TYPE_ICON,IDS_BUTTON_ICON,IDS_BUTTON_ICON_TIP,L",1",0,L"#StartButtonType=1",L"ClasicButton"}, {L"StartButtonIcon",CSetting::TYPE_ICON,IDS_BUTTON_ICON,IDS_BUTTON_ICON_TIP,L",1",0,L"#StartButtonType=1",L"ClasicButton"},
{L"StartButtonIconSize",CSetting::TYPE_INT,IDS_BUTTON_ICON_SIZE,IDS_BUTTON_ICON_SIZE_TIP,0,0,L"#StartButtonType=1",L"ClasicButton"}, {L"StartButtonIconSize",CSetting::TYPE_INT,IDS_BUTTON_ICON_SIZE,IDS_BUTTON_ICON_SIZE_TIP,0,0,L"#StartButtonType=1",L"ClasicButton"},
@@ -5157,7 +5167,7 @@ void InitSettings( void )
InitSettings(g_Settings,COMPONENT_MENU,&g_CustomSettings); InitSettings(g_Settings,COMPONENT_MENU,&g_CustomSettings);
} }
static int g_ButtonPath, g_ButtonSize, g_ButtonIcon, g_ButtonIconSize, g_ButtonText, g_ButtonTip; static int g_ButtonPath, g_ButtonSize, g_ButtonIcon, g_ButtonOffset, g_ButtonIconSize, g_ButtonText, g_ButtonTip;
static bool g_bButtonEnable, g_bTaskbarsChanged, g_bTaskbarsUpdated; static bool g_bButtonEnable, g_bTaskbarsChanged, g_bTaskbarsUpdated;
static void StoreButtonSettings( void ) static void StoreButtonSettings( void )
@@ -5175,6 +5185,7 @@ static void StoreButtonSettings( void )
if (g_ButtonPath==START_BUTTON_CUSTOM) if (g_ButtonPath==START_BUTTON_CUSTOM)
g_ButtonPath=CalcFNVHash(GetSettingString(L"StartButtonPath")); g_ButtonPath=CalcFNVHash(GetSettingString(L"StartButtonPath"));
g_ButtonSize=GetSettingInt(L"StartButtonSize"); g_ButtonSize=GetSettingInt(L"StartButtonSize");
g_ButtonOffset=GetSettingInt(L"StartButtonOffset");
g_ButtonIconSize=GetSettingInt(L"StartButtonIconSize"); g_ButtonIconSize=GetSettingInt(L"StartButtonIconSize");
} }
@@ -5191,7 +5202,7 @@ static void UpdateButtons( bool bForce )
} }
if (path==START_BUTTON_CUSTOM) if (path==START_BUTTON_CUSTOM)
path=CalcFNVHash(GetSettingString(L"StartButtonPath")); path=CalcFNVHash(GetSettingString(L"StartButtonPath"));
bool bRecreate=(g_bButtonEnable!=GetSettingBool(L"EnableStartButton") || path!=g_ButtonPath || g_ButtonSize!=GetSettingInt(L"StartButtonSize") bool bRecreate=(g_bButtonEnable!=GetSettingBool(L"EnableStartButton") || path!=g_ButtonPath || g_ButtonSize!=GetSettingInt(L"StartButtonSize") || g_ButtonOffset!=GetSettingInt(L"StartButtonOffset")
|| g_ButtonIcon!=icon || g_ButtonIconSize!=GetSettingInt(L"StartButtonIconSize") || g_ButtonText!=text || g_ButtonTip!=tip); || g_ButtonIcon!=icon || g_ButtonIconSize!=GetSettingInt(L"StartButtonIconSize") || g_ButtonText!=text || g_ButtonTip!=tip);
if ((g_bTaskbarsChanged && !g_bTaskbarsUpdated) || bForce || bRecreate) if ((g_bTaskbarsChanged && !g_bTaskbarsUpdated) || bForce || bRecreate)
{ {
@@ -2918,6 +2918,28 @@ bool MenuSkin::LoadSkin( HMODULE hMod, const wchar_t *variation, const wchar_t *
ItemSettings[COLUMN2_INLINE].bmpIconFrame.bIsOwned=false; ItemSettings[COLUMN2_INLINE].bmpIconFrame.bIsOwned=false;
ItemSettings[COLUMN2_INLINE].bmpSeparator.bIsOwned=false; ItemSettings[COLUMN2_INLINE].bmpSeparator.bIsOwned=false;
// A skin's "new item" colors must not override the user's High Contrast
// palette. This only changes the highlighted-new variants while High
// Contrast is active; normal themed rendering remains untouched.
HIGHCONTRAST contrast={sizeof(contrast)};
if (SystemParametersInfo(SPI_GETHIGHCONTRAST,sizeof(contrast),&contrast,0) && (contrast.dwFlags&HCF_HIGHCONTRASTON))
{
const int newItems[]={COLUMN1_NEW,COLUMN2_NEW,SUBMENU_NEW,PROGRAMS_TREE_NEW,PROGRAMS_BUTTON_NEW,PROGRAMS_CASCADING_NEW};
for (int i=0;i<_countof(newItems);i++)
{
ItemDrawSettings &settings=ItemSettings[newItems[i]];
settings.bmpSelection.Reset(true);
settings.bmpSelection=GetSysColor(COLOR_HIGHLIGHT);
settings.glowSize=0;
for (int j=0;j<_countof(settings.textColors);j++)
{
settings.textColors[j]=GetSysColor(COLOR_HIGHLIGHTTEXT);
settings.textShadowColors[j]=GetSysColor(COLOR_HIGHLIGHT);
}
settings.arrColors[0]=settings.arrColors[1]=GetSysColor(COLOR_HIGHLIGHTTEXT);
}
}
HDC hdc=CreateCompatibleDC(NULL); HDC hdc=CreateCompatibleDC(NULL);
HGDIOBJ font0=GetCurrentObject(hdc,OBJ_FONT); HGDIOBJ font0=GetCurrentObject(hdc,OBJ_FONT);
for (int i=0;i<_countof(ItemSettings);i++) for (int i=0;i<_countof(ItemSettings);i++)
@@ -1347,6 +1347,9 @@ static void UpdateStartButtonPosition(const TaskbarInfo* taskBar, const WINDOWPO
// We will shift our Aero button to cover original button // We will shift our Aero button to cover original button
if (IsWin11() && (x == info.rcMonitor.left) && (GetStartButtonType() == START_BUTTON_AERO) && !g_epTaskbar) if (IsWin11() && (x == info.rcMonitor.left) && (GetStartButtonType() == START_BUTTON_AERO) && !g_epTaskbar)
x += ScaleForDpi(taskBar->taskBar, 6); x += ScaleForDpi(taskBar->taskBar, 6);
if (GetStartButtonType() == START_BUTTON_CUSTOM)
x += ScaleForDpi(taskBar->taskBar, GetSettingInt(L"StartButtonOffset"));
} }
RECT rcButton = { x, y, x + taskBar->startButtonSize.cx, y + taskBar->startButtonSize.cy }; RECT rcButton = { x, y, x + taskBar->startButtonSize.cx, y + taskBar->startButtonSize.cy };
@@ -1073,6 +1073,8 @@ BEGIN
IDS_MENU_GLASS "Enable Aero Glass (requires Aero Glass mod)" IDS_MENU_GLASS "Enable Aero Glass (requires Aero Glass mod)"
IDS_MENU_GLASS_TIP "Check this to use glass transparency (requires the Aero Glass mod to be installed)" IDS_MENU_GLASS_TIP "Check this to use glass transparency (requires the Aero Glass mod to be installed)"
IDS_MAIN_MENU_SETTINGS "Main Menu" IDS_MAIN_MENU_SETTINGS "Main Menu"
IDS_BUTTON_OFFSET "Button offset"
IDS_BUTTON_OFFSET_TIP "Offset the start button horizontally by the amount of pixels specified"
END END
STRINGTABLE STRINGTABLE
+2
View File
@@ -786,6 +786,8 @@
#define IDS_ENABLE_ACCELERATORS_TIP 3687 #define IDS_ENABLE_ACCELERATORS_TIP 3687
#define IDS_ALT_ACCELERATORS 3688 #define IDS_ALT_ACCELERATORS 3688
#define IDS_ALT_ACCELERATORS_TIP 3689 #define IDS_ALT_ACCELERATORS_TIP 3689
#define IDS_BUTTON_OFFSET 3690
#define IDS_BUTTON_OFFSET_TIP 3691
#define IDS_STRING7001 7001 #define IDS_STRING7001 7001
#define IDS_STRING7002 7002 #define IDS_STRING7002 7002
#define IDS_STRING7003 7003 #define IDS_STRING7003 7003