diff --git a/.gitignore b/.gitignore
index 75591d3db..0ec23cf05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,3 +360,6 @@ Src/Setup/Temp/
*.zip
*.bin
*.chm
+
+# Disposable native Shell regression test fixtures
+/build/open-containing-tests/
diff --git a/BUILDING.md b/BUILDING.md
new file mode 100644
index 000000000..79a1f0ad5
--- /dev/null
+++ b/BUILDING.md
@@ -0,0 +1,25 @@
+# Building the Start Menu with Visual Studio
+
+Open `OpenShell-VS2026.sln` in Visual Studio 2026 and choose **Release / x64**.
+Install the Desktop development with C++ workload, C++ ATL support for the
+selected toolset, and a Windows 11 SDK. Allow NuGet package restore.
+
+The root solution builds the Start Menu, its helper DLL, and the bundled skins.
+It does not build an installer or the Classic Explorer/Classic IE components.
+Build output is under `build/bin/Release64`; skins are copied into its `Skins`
+subfolder. Local binaries are unsigned. Language DLLs are distributed separately.
+
+From a Visual Studio developer command prompt:
+
+```bat
+MSBuild OpenShell-VS2026.sln /t:Restore /p:RestorePackagesConfig=true
+MSBuild OpenShell-VS2026.sln /m /p:Configuration=Release /p:Platform=x64
+```
+
+Projects select v145 with Visual Studio 2026 and retain v143 with Visual Studio
+2022. `OpenShellPlatformToolset` can override this selection. The existing
+`Src/OpenShell.sln` and release packaging scripts remain available; see
+`Src/BUILDME.txt` for the additional installer tools.
+
+Release scripts can continue to supply `CS_VERSION` and `CS_VERSION_ORIG`.
+Direct IDE builds default to version 4.4.1000.
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 000000000..6ba5aae28
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+
+ v145
+ v143
+ 4.4.1000
+ $(CS_VERSION)
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 000000000..d3636157a
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/NuGet.Config b/NuGet.Config
new file mode 100644
index 000000000..de1952cda
--- /dev/null
+++ b/NuGet.Config
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/OpenShell-VS2026.sln b/OpenShell-VS2026.sln
new file mode 100644
index 000000000..e786a5388
--- /dev/null
+++ b/OpenShell-VS2026.sln
@@ -0,0 +1,155 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.10.12201.205
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StartMenu", "Src\StartMenu\StartMenu.vcxproj", "{87D5FE20-AF86-458A-9AA3-3131EB06179B}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Lib", "Src\Lib\Lib.vcxproj", "{D42FE717-485B-492D-884A-1999F6D51154}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ClassicSkin7", "Src\Skins\ClassicSkin7\ClassicSkin7.vcxproj", "{31C016FB-9EA1-4AF5-987A-37210C04DA06}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ClassicSkin", "Src\Skins\ClassicSkin\ClassicSkin.vcxproj", "{9EC23CA9-384A-4EEB-979E-69879DC1A78C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FullGlass", "Src\Skins\FullGlass\FullGlass.vcxproj", "{066C9721-26D5-4C4D-868E-50C2BA0A8196}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Immersive7", "Src\Skins\Immersive7\Immersive7.vcxproj", "{75809D15-8403-420A-BBE6-05F478D88D8E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Immersive", "Src\Skins\Immersive\Immersive.vcxproj", "{BD28B058-230E-42DF-9FB1-FFBB0153F498}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Metallic7", "Src\Skins\Metallic7\Metallic7.vcxproj", "{CA5BFC96-428D-42F5-9F7D-CDDE048A357C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Metro7", "Src\Skins\Metro7\Metro7.vcxproj", "{598AB4AC-008E-4501-90B3-C5213834C1DA}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Metro", "Src\Skins\Metro\Metro.vcxproj", "{63BAF573-170B-4FA0-AEE3-16E04F3E9DF5}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Midnight7", "Src\Skins\Midnight7\Midnight7.vcxproj", "{7BD26CB3-5280-48FD-9A86-C13E321018D5}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SmokedGlass", "Src\Skins\SmokedGlass\SmokedGlass.vcxproj", "{66D1EAA4-65D1-45CC-9989-E616FC0575EB}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win7Aero7", "Src\Skins\Win7Aero7\Win7Aero7.vcxproj", "{A2CCDE9F-17CE-461E-8BD9-00261B8855A6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win7Aero", "Src\Skins\Win7Aero\Win7Aero.vcxproj", "{EA65FDDD-CB77-417F-8BB4-2F3ECB5B3E75}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win7Basic", "Src\Skins\Win7Basic\Win7Basic.vcxproj", "{404821C5-4EE4-4908-A759-5EF6DAC14AB6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win87", "Src\Skins\Win87\Win87.vcxproj", "{5C875214-0E3A-4CF0-BC0C-BFF6FAA4C089}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win8", "Src\Skins\Win8\Win8.vcxproj", "{ED74EBA9-1BCB-4B8F-9AE1-DC63B3C24A94}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinXP", "Src\Skins\WinXP\WinXP.vcxproj", "{81EB6336-366C-47DD-82CF-FF6C36CCD2B5}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StartMenuDLL", "Src\StartMenu\StartMenuDLL\StartMenuDLL.vcxproj", "{85DEECBB-1F9B-4983-9D54-3BF42182B7E7}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StartMenuHelper", "Src\StartMenu\StartMenuHelper\StartMenuHelper.vcxproj", "{A42C6159-ACA8-46D1-A0FB-19C398B137D5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Skins", "Skins", "{409484D8-C0DB-4991-AF03-124128EDEF98}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Release|x64 = Release|x64
+ Debug|x64 = Debug|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {87D5FE20-AF86-458A-9AA3-3131EB06179B}.Release|x64.ActiveCfg = Release|x64
+ {87D5FE20-AF86-458A-9AA3-3131EB06179B}.Release|x64.Build.0 = Release|x64
+ {87D5FE20-AF86-458A-9AA3-3131EB06179B}.Debug|x64.ActiveCfg = Debug|x64
+ {87D5FE20-AF86-458A-9AA3-3131EB06179B}.Debug|x64.Build.0 = Debug|x64
+ {D42FE717-485B-492D-884A-1999F6D51154}.Release|x64.ActiveCfg = Release|x64
+ {D42FE717-485B-492D-884A-1999F6D51154}.Release|x64.Build.0 = Release|x64
+ {D42FE717-485B-492D-884A-1999F6D51154}.Debug|x64.ActiveCfg = Debug|x64
+ {D42FE717-485B-492D-884A-1999F6D51154}.Debug|x64.Build.0 = Debug|x64
+ {31C016FB-9EA1-4AF5-987A-37210C04DA06}.Release|x64.ActiveCfg = Resource|Win32
+ {31C016FB-9EA1-4AF5-987A-37210C04DA06}.Release|x64.Build.0 = Resource|Win32
+ {31C016FB-9EA1-4AF5-987A-37210C04DA06}.Debug|x64.ActiveCfg = Resource|Win32
+ {31C016FB-9EA1-4AF5-987A-37210C04DA06}.Debug|x64.Build.0 = Resource|Win32
+ {9EC23CA9-384A-4EEB-979E-69879DC1A78C}.Release|x64.ActiveCfg = Resource|Win32
+ {9EC23CA9-384A-4EEB-979E-69879DC1A78C}.Release|x64.Build.0 = Resource|Win32
+ {9EC23CA9-384A-4EEB-979E-69879DC1A78C}.Debug|x64.ActiveCfg = Resource|Win32
+ {9EC23CA9-384A-4EEB-979E-69879DC1A78C}.Debug|x64.Build.0 = Resource|Win32
+ {066C9721-26D5-4C4D-868E-50C2BA0A8196}.Release|x64.ActiveCfg = Resource|Win32
+ {066C9721-26D5-4C4D-868E-50C2BA0A8196}.Release|x64.Build.0 = Resource|Win32
+ {066C9721-26D5-4C4D-868E-50C2BA0A8196}.Debug|x64.ActiveCfg = Resource|Win32
+ {066C9721-26D5-4C4D-868E-50C2BA0A8196}.Debug|x64.Build.0 = Resource|Win32
+ {75809D15-8403-420A-BBE6-05F478D88D8E}.Release|x64.ActiveCfg = Resource|Win32
+ {75809D15-8403-420A-BBE6-05F478D88D8E}.Release|x64.Build.0 = Resource|Win32
+ {75809D15-8403-420A-BBE6-05F478D88D8E}.Debug|x64.ActiveCfg = Resource|Win32
+ {75809D15-8403-420A-BBE6-05F478D88D8E}.Debug|x64.Build.0 = Resource|Win32
+ {BD28B058-230E-42DF-9FB1-FFBB0153F498}.Release|x64.ActiveCfg = Resource|Win32
+ {BD28B058-230E-42DF-9FB1-FFBB0153F498}.Release|x64.Build.0 = Resource|Win32
+ {BD28B058-230E-42DF-9FB1-FFBB0153F498}.Debug|x64.ActiveCfg = Resource|Win32
+ {BD28B058-230E-42DF-9FB1-FFBB0153F498}.Debug|x64.Build.0 = Resource|Win32
+ {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Release|x64.ActiveCfg = Resource|Win32
+ {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Release|x64.Build.0 = Resource|Win32
+ {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Debug|x64.ActiveCfg = Resource|Win32
+ {CA5BFC96-428D-42F5-9F7D-CDDE048A357C}.Debug|x64.Build.0 = Resource|Win32
+ {598AB4AC-008E-4501-90B3-C5213834C1DA}.Release|x64.ActiveCfg = Resource|Win32
+ {598AB4AC-008E-4501-90B3-C5213834C1DA}.Release|x64.Build.0 = Resource|Win32
+ {598AB4AC-008E-4501-90B3-C5213834C1DA}.Debug|x64.ActiveCfg = Resource|Win32
+ {598AB4AC-008E-4501-90B3-C5213834C1DA}.Debug|x64.Build.0 = Resource|Win32
+ {63BAF573-170B-4FA0-AEE3-16E04F3E9DF5}.Release|x64.ActiveCfg = Resource|Win32
+ {63BAF573-170B-4FA0-AEE3-16E04F3E9DF5}.Release|x64.Build.0 = Resource|Win32
+ {63BAF573-170B-4FA0-AEE3-16E04F3E9DF5}.Debug|x64.ActiveCfg = Resource|Win32
+ {63BAF573-170B-4FA0-AEE3-16E04F3E9DF5}.Debug|x64.Build.0 = Resource|Win32
+ {7BD26CB3-5280-48FD-9A86-C13E321018D5}.Release|x64.ActiveCfg = Resource|Win32
+ {7BD26CB3-5280-48FD-9A86-C13E321018D5}.Release|x64.Build.0 = Resource|Win32
+ {7BD26CB3-5280-48FD-9A86-C13E321018D5}.Debug|x64.ActiveCfg = Resource|Win32
+ {7BD26CB3-5280-48FD-9A86-C13E321018D5}.Debug|x64.Build.0 = Resource|Win32
+ {66D1EAA4-65D1-45CC-9989-E616FC0575EB}.Release|x64.ActiveCfg = Resource|Win32
+ {66D1EAA4-65D1-45CC-9989-E616FC0575EB}.Release|x64.Build.0 = Resource|Win32
+ {66D1EAA4-65D1-45CC-9989-E616FC0575EB}.Debug|x64.ActiveCfg = Resource|Win32
+ {66D1EAA4-65D1-45CC-9989-E616FC0575EB}.Debug|x64.Build.0 = Resource|Win32
+ {A2CCDE9F-17CE-461E-8BD9-00261B8855A6}.Release|x64.ActiveCfg = Resource|Win32
+ {A2CCDE9F-17CE-461E-8BD9-00261B8855A6}.Release|x64.Build.0 = Resource|Win32
+ {A2CCDE9F-17CE-461E-8BD9-00261B8855A6}.Debug|x64.ActiveCfg = Resource|Win32
+ {A2CCDE9F-17CE-461E-8BD9-00261B8855A6}.Debug|x64.Build.0 = Resource|Win32
+ {EA65FDDD-CB77-417F-8BB4-2F3ECB5B3E75}.Release|x64.ActiveCfg = Resource|Win32
+ {EA65FDDD-CB77-417F-8BB4-2F3ECB5B3E75}.Release|x64.Build.0 = Resource|Win32
+ {EA65FDDD-CB77-417F-8BB4-2F3ECB5B3E75}.Debug|x64.ActiveCfg = Resource|Win32
+ {EA65FDDD-CB77-417F-8BB4-2F3ECB5B3E75}.Debug|x64.Build.0 = Resource|Win32
+ {404821C5-4EE4-4908-A759-5EF6DAC14AB6}.Release|x64.ActiveCfg = Resource|Win32
+ {404821C5-4EE4-4908-A759-5EF6DAC14AB6}.Release|x64.Build.0 = Resource|Win32
+ {404821C5-4EE4-4908-A759-5EF6DAC14AB6}.Debug|x64.ActiveCfg = Resource|Win32
+ {404821C5-4EE4-4908-A759-5EF6DAC14AB6}.Debug|x64.Build.0 = Resource|Win32
+ {5C875214-0E3A-4CF0-BC0C-BFF6FAA4C089}.Release|x64.ActiveCfg = Resource|Win32
+ {5C875214-0E3A-4CF0-BC0C-BFF6FAA4C089}.Release|x64.Build.0 = Resource|Win32
+ {5C875214-0E3A-4CF0-BC0C-BFF6FAA4C089}.Debug|x64.ActiveCfg = Resource|Win32
+ {5C875214-0E3A-4CF0-BC0C-BFF6FAA4C089}.Debug|x64.Build.0 = Resource|Win32
+ {ED74EBA9-1BCB-4B8F-9AE1-DC63B3C24A94}.Release|x64.ActiveCfg = Resource|Win32
+ {ED74EBA9-1BCB-4B8F-9AE1-DC63B3C24A94}.Release|x64.Build.0 = Resource|Win32
+ {ED74EBA9-1BCB-4B8F-9AE1-DC63B3C24A94}.Debug|x64.ActiveCfg = Resource|Win32
+ {ED74EBA9-1BCB-4B8F-9AE1-DC63B3C24A94}.Debug|x64.Build.0 = Resource|Win32
+ {81EB6336-366C-47DD-82CF-FF6C36CCD2B5}.Release|x64.ActiveCfg = Resource|Win32
+ {81EB6336-366C-47DD-82CF-FF6C36CCD2B5}.Release|x64.Build.0 = Resource|Win32
+ {81EB6336-366C-47DD-82CF-FF6C36CCD2B5}.Debug|x64.ActiveCfg = Resource|Win32
+ {81EB6336-366C-47DD-82CF-FF6C36CCD2B5}.Debug|x64.Build.0 = Resource|Win32
+ {85DEECBB-1F9B-4983-9D54-3BF42182B7E7}.Release|x64.ActiveCfg = Release|x64
+ {85DEECBB-1F9B-4983-9D54-3BF42182B7E7}.Release|x64.Build.0 = Release|x64
+ {85DEECBB-1F9B-4983-9D54-3BF42182B7E7}.Debug|x64.ActiveCfg = Debug|x64
+ {85DEECBB-1F9B-4983-9D54-3BF42182B7E7}.Debug|x64.Build.0 = Debug|x64
+ {A42C6159-ACA8-46D1-A0FB-19C398B137D5}.Release|x64.ActiveCfg = Release|x64
+ {A42C6159-ACA8-46D1-A0FB-19C398B137D5}.Release|x64.Build.0 = Release|x64
+ {A42C6159-ACA8-46D1-A0FB-19C398B137D5}.Debug|x64.ActiveCfg = Debug|x64
+ {A42C6159-ACA8-46D1-A0FB-19C398B137D5}.Debug|x64.Build.0 = Debug|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {31C016FB-9EA1-4AF5-987A-37210C04DA06} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {9EC23CA9-384A-4EEB-979E-69879DC1A78C} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {066C9721-26D5-4C4D-868E-50C2BA0A8196} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {75809D15-8403-420A-BBE6-05F478D88D8E} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {BD28B058-230E-42DF-9FB1-FFBB0153F498} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {CA5BFC96-428D-42F5-9F7D-CDDE048A357C} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {598AB4AC-008E-4501-90B3-C5213834C1DA} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {63BAF573-170B-4FA0-AEE3-16E04F3E9DF5} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {7BD26CB3-5280-48FD-9A86-C13E321018D5} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {66D1EAA4-65D1-45CC-9989-E616FC0575EB} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {A2CCDE9F-17CE-461E-8BD9-00261B8855A6} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {EA65FDDD-CB77-417F-8BB4-2F3ECB5B3E75} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {404821C5-4EE4-4908-A759-5EF6DAC14AB6} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {5C875214-0E3A-4CF0-BC0C-BFF6FAA4C089} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {ED74EBA9-1BCB-4B8F-9AE1-DC63B3C24A94} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ {81EB6336-366C-47DD-82CF-FF6C36CCD2B5} = {409484D8-C0DB-4991-AF03-124128EDEF98}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/ClassicExplorer/ClassicExplorer.vcxproj b/Src/ClassicExplorer/ClassicExplorer.vcxproj
index 20faa958e..a15ec5964 100644
--- a/Src/ClassicExplorer/ClassicExplorer.vcxproj
+++ b/Src/ClassicExplorer/ClassicExplorer.vcxproj
@@ -59,7 +59,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.vcxproj b/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.vcxproj
index 1ebf5ecb1..33fefb9aa 100644
--- a/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.vcxproj
+++ b/Src/ClassicExplorer/ClassicExplorerSettings/ClassicExplorerSettings.vcxproj
@@ -23,7 +23,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/ClassicIE/ClassicIE.vcxproj b/Src/ClassicIE/ClassicIE.vcxproj
index c883b879a..7cefe675b 100644
--- a/Src/ClassicIE/ClassicIE.vcxproj
+++ b/Src/ClassicIE/ClassicIE.vcxproj
@@ -47,7 +47,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/ClassicIE/ClassicIEDLL/ClassicIEDLL.vcxproj b/Src/ClassicIE/ClassicIEDLL/ClassicIEDLL.vcxproj
index 4944070e7..49a040191 100644
--- a/Src/ClassicIE/ClassicIEDLL/ClassicIEDLL.vcxproj
+++ b/Src/ClassicIE/ClassicIEDLL/ClassicIEDLL.vcxproj
@@ -59,7 +59,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/Lib/Lib.vcxproj b/Src/Lib/Lib.vcxproj
index 03954dade..4234c33e9 100644
--- a/Src/Lib/Lib.vcxproj
+++ b/Src/Lib/Lib.vcxproj
@@ -43,7 +43,7 @@
StaticLibrary
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/Setup/Setup.vcxproj b/Src/Setup/Setup.vcxproj
index f937d143f..4cd9199a5 100644
--- a/Src/Setup/Setup.vcxproj
+++ b/Src/Setup/Setup.vcxproj
@@ -19,7 +19,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Unicode
true
diff --git a/Src/Setup/SetupHelper/SetupHelper.vcxproj b/Src/Setup/SetupHelper/SetupHelper.vcxproj
index 21afa7d9a..d99aa0633 100644
--- a/Src/Setup/SetupHelper/SetupHelper.vcxproj
+++ b/Src/Setup/SetupHelper/SetupHelper.vcxproj
@@ -19,7 +19,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Unicode
true
diff --git a/Src/Setup/Utility/Utility.vcxproj b/Src/Setup/Utility/Utility.vcxproj
index ba3c5ad49..5d8cf7d0a 100644
--- a/Src/Setup/Utility/Utility.vcxproj
+++ b/Src/Setup/Utility/Utility.vcxproj
@@ -35,7 +35,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/Setup/en-US/en-US.vcxproj b/Src/Setup/en-US/en-US.vcxproj
index 235ec9dc3..6ddcb2892 100644
--- a/Src/Setup/en-US/en-US.vcxproj
+++ b/Src/Setup/en-US/en-US.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/ClassicSkin/ClassicSkin.vcxproj b/Src/Skins/ClassicSkin/ClassicSkin.vcxproj
index 695405b96..042431333 100644
--- a/Src/Skins/ClassicSkin/ClassicSkin.vcxproj
+++ b/Src/Skins/ClassicSkin/ClassicSkin.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/ClassicSkin7/ClassicSkin7.vcxproj b/Src/Skins/ClassicSkin7/ClassicSkin7.vcxproj
index 5877c621c..49b1a29c8 100644
--- a/Src/Skins/ClassicSkin7/ClassicSkin7.vcxproj
+++ b/Src/Skins/ClassicSkin7/ClassicSkin7.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/FullGlass/FullGlass.vcxproj b/Src/Skins/FullGlass/FullGlass.vcxproj
index 1339fb114..87c0ed6cb 100644
--- a/Src/Skins/FullGlass/FullGlass.vcxproj
+++ b/Src/Skins/FullGlass/FullGlass.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Immersive/Immersive.vcxproj b/Src/Skins/Immersive/Immersive.vcxproj
index d3d17b969..5fabc1159 100644
--- a/Src/Skins/Immersive/Immersive.vcxproj
+++ b/Src/Skins/Immersive/Immersive.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Immersive7/Immersive7.vcxproj b/Src/Skins/Immersive7/Immersive7.vcxproj
index 17ac64983..2969d247b 100644
--- a/Src/Skins/Immersive7/Immersive7.vcxproj
+++ b/Src/Skins/Immersive7/Immersive7.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Metallic7/Metallic7.vcxproj b/Src/Skins/Metallic7/Metallic7.vcxproj
index b58e9d15b..b9ce88606 100644
--- a/Src/Skins/Metallic7/Metallic7.vcxproj
+++ b/Src/Skins/Metallic7/Metallic7.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Metro/Metro.vcxproj b/Src/Skins/Metro/Metro.vcxproj
index 7028f5689..577c798b0 100644
--- a/Src/Skins/Metro/Metro.vcxproj
+++ b/Src/Skins/Metro/Metro.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Metro7/Metro7.vcxproj b/Src/Skins/Metro7/Metro7.vcxproj
index 42575ab62..87059970d 100644
--- a/Src/Skins/Metro7/Metro7.vcxproj
+++ b/Src/Skins/Metro7/Metro7.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Midnight7/Midnight7.vcxproj b/Src/Skins/Midnight7/Midnight7.vcxproj
index 481e62396..3adf42ffc 100644
--- a/Src/Skins/Midnight7/Midnight7.vcxproj
+++ b/Src/Skins/Midnight7/Midnight7.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/SmokedGlass/SmokedGlass.vcxproj b/Src/Skins/SmokedGlass/SmokedGlass.vcxproj
index c00b9b276..e8f3c6f53 100644
--- a/Src/Skins/SmokedGlass/SmokedGlass.vcxproj
+++ b/Src/Skins/SmokedGlass/SmokedGlass.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Win7Aero/Win7Aero.vcxproj b/Src/Skins/Win7Aero/Win7Aero.vcxproj
index 78038a833..7a820913e 100644
--- a/Src/Skins/Win7Aero/Win7Aero.vcxproj
+++ b/Src/Skins/Win7Aero/Win7Aero.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Win7Aero7/Win7Aero7.vcxproj b/Src/Skins/Win7Aero7/Win7Aero7.vcxproj
index 8aa65f752..525fc5cfc 100644
--- a/Src/Skins/Win7Aero7/Win7Aero7.vcxproj
+++ b/Src/Skins/Win7Aero7/Win7Aero7.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Win7Basic/Win7Basic.vcxproj b/Src/Skins/Win7Basic/Win7Basic.vcxproj
index d80072d71..b9b5d28cd 100644
--- a/Src/Skins/Win7Basic/Win7Basic.vcxproj
+++ b/Src/Skins/Win7Basic/Win7Basic.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Win8/Win8.vcxproj b/Src/Skins/Win8/Win8.vcxproj
index c3fbbe0a8..b0491eaea 100644
--- a/Src/Skins/Win8/Win8.vcxproj
+++ b/Src/Skins/Win8/Win8.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/Win87/Win87.vcxproj b/Src/Skins/Win87/Win87.vcxproj
index c2d326ab8..4feab0200 100644
--- a/Src/Skins/Win87/Win87.vcxproj
+++ b/Src/Skins/Win87/Win87.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/Skins/WinXP/WinXP.vcxproj b/Src/Skins/WinXP/WinXP.vcxproj
index 4ffed1547..9535bf33d 100644
--- a/Src/Skins/WinXP/WinXP.vcxproj
+++ b/Src/Skins/WinXP/WinXP.vcxproj
@@ -15,7 +15,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
diff --git a/Src/StartMenu/StartMenu.vcxproj b/Src/StartMenu/StartMenu.vcxproj
index bfc0f016f..676089621 100644
--- a/Src/StartMenu/StartMenu.vcxproj
+++ b/Src/StartMenu/StartMenu.vcxproj
@@ -47,7 +47,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/StartMenu/StartMenuDLL/DragDrop.cpp b/Src/StartMenu/StartMenuDLL/DragDrop.cpp
index 7b3262db8..3250fb9eb 100644
--- a/Src/StartMenu/StartMenuDLL/DragDrop.cpp
+++ b/Src/StartMenu/StartMenuDLL/DragDrop.cpp
@@ -16,6 +16,86 @@
#include "FileHelper.h"
#include
+// Let the native shell target negotiate its own asynchronous transfer. A
+// synchronous file-conflict dialog can deadlock inside the OLE drag loop while
+// its UI thread releases a COM reference back to this STA.
+static HRESULT EnableShellAsyncDrop( IDataObject *pDataObj )
+{
+ CComQIPtr pAsync(pDataObj);
+ return pAsync?pAsync->SetAsyncMode(VARIANT_TRUE):E_NOINTERFACE;
+}
+
+static bool WasShellDropAsync( IDataObject *pDataObj )
+{
+ CComQIPtr pAsync(pDataObj);
+ BOOL asynchronous=FALSE;
+ // InOperation tells the source that StartOperation was used; it is not
+ // a completion event and must never be polled to wait for completion.
+ return pAsync && SUCCEEDED(pAsync->InOperation(&asynchronous)) && asynchronous;
+}
+
+// Our icon cache contains premultiplied 32-bit DIBs. The Shell drag-image
+// helper expects straight alpha and takes ownership of its bitmap on success.
+static HRESULT InitializeMenuDragImage( IDragSourceHelper2 *helper, IDataObject *data, HBITMAP icon )
+{
+ if (!helper || !data || !icon) return E_INVALIDARG;
+ HBITMAP copy=(HBITMAP)CopyImage(icon,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);
+ if (!copy) return E_OUTOFMEMORY;
+ BITMAP bitmap={};
+ if (!GetObject(copy,sizeof(bitmap),&bitmap) || !bitmap.bmBits || bitmap.bmBitsPixel!=32)
+ {
+ DeleteObject(copy);
+ return E_FAIL;
+ }
+ for (int y=0;y>24;
+ if (alpha && alpha<255)
+ {
+ DWORD red=(std::min)(255UL,(((pixel>>16)&255)*255+alpha/2)/alpha);
+ DWORD green=(std::min)(255UL,(((pixel>>8)&255)*255+alpha/2)/alpha);
+ DWORD blue=(std::min)(255UL,((pixel&255)*255+alpha/2)/alpha);
+ pixels[x]=(alpha<<24)|(red<<16)|(green<<8)|blue;
+ }
+ else if (!alpha)
+ pixels[x]=0;
+ }
+ }
+ SHDRAGIMAGE image={{bitmap.bmWidth,bitmap.bmHeight},{bitmap.bmWidth/2,bitmap.bmHeight},copy,CLR_NONE};
+ helper->SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);
+ HRESULT result=helper->InitializeFromBitmap(&image,data);
+ if (FAILED(result)) DeleteObject(copy);
+ return result;
+}
+
+// A newly raised topmost submenu can cover the existing Shell drag window.
+// Show(TRUE) and DragOver do not repair its Z order. Restart only the visual
+// helper, preserving the actual OLE target and the source data object identity.
+static HRESULT RestoreShellDragImage( IDropTargetHelper *helper, HWND target, IDataObject *data, const POINT &point, DWORD effect )
+{
+ if (!helper || !target || !data) return E_INVALIDARG;
+ POINT position=point;
+ helper->DragLeave();
+ HRESULT result=helper->DragEnter(target,data,&position,effect);
+ if (SUCCEEDED(result)) result=helper->DragOver(&position,effect);
+ return result;
+}
+
+void CMenuContainer::RestoreActiveDragImage( void )
+{
+ for (auto menu : s_Menus)
+ if (!menu->m_bDestroyed && menu->m_pDragObject && menu->m_pDropTargetHelper && menu->m_DragImageWindow)
+ {
+ CComPtr data=menu->m_pDragObject;
+ CComPtr helper=menu->m_pDropTargetHelper;
+ RestoreShellDragImage(helper,menu->m_DragImageWindow,data,menu->m_DragImagePoint,menu->m_DragImageEffect);
+ break;
+ }
+}
+
class CMetroDataObject: public IDataObject
{
public:
@@ -211,12 +291,8 @@ CComPtr CMenuContainer::CreateMetroDataObject( const CItemManager::
if (m_pDragSourceHelper)
{
g_ItemManager.UpdateItemInfo(pInfo,CItemManager::INFO_EXTRA_LARGE_ICON|CItemManager::INFO_REFRESH_NOW,false);
- int iconSize=CItemManager::EXTRA_LARGE_ICON_SIZE;
- SHDRAGIMAGE di={{iconSize,iconSize},{iconSize/2,iconSize},NULL,CLR_NONE};
- di.hbmpDragImage=(HBITMAP)CopyImage(pInfo->extraLargeIcon->bitmap,IMAGE_BITMAP,0,0,0);
- m_pDragSourceHelper->SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);
- if (di.hbmpDragImage)
- m_pDragSourceHelper->InitializeFromBitmap(&di,pDataObject);
+ if (pInfo->extraLargeIcon)
+ InitializeMenuDragImage(m_pDragSourceHelper,pDataObject,pInfo->extraLargeIcon->bitmap);
}
}
else
@@ -279,7 +355,6 @@ bool CMenuContainer::DragOut( int index, bool bApp )
pDataObj=CreateMetroDataObject(item.pItemInfo);
else
{
- bool bMetroLink=false;
if (FAILED(SHBindToParent(item.pItem1,IID_IShellFolder,(void**)&pFolder,&child)))
return true;
if (FAILED(pFolder->GetUIObjectOf(NULL,1,&child,IID_IDataObject,NULL,(void**)&pDataObj)))
@@ -288,17 +363,15 @@ bool CMenuContainer::DragOut( int index, bool bApp )
{
CItemManager::RWLock lock(&g_ItemManager,false,CItemManager::RWLOCK_ITEMS);
bProtectedLink=!m_bSubMenu || item.pItemInfo->IsProtectedLink();
- bMetroLink=item.pItemInfo->IsMetroLink();
}
- if (m_pDragSourceHelper && bMetroLink)
+ if (m_pDragSourceHelper && item.pItemInfo)
{
- g_ItemManager.UpdateItemInfo(item.pItemInfo,CItemManager::INFO_EXTRA_LARGE_ICON|CItemManager::INFO_REFRESH_NOW,false);
- int iconSize=CItemManager::EXTRA_LARGE_ICON_SIZE;
- SHDRAGIMAGE di={{iconSize,iconSize},{iconSize/2,iconSize},NULL,CLR_NONE};
- di.hbmpDragImage=(HBITMAP)CopyImage(item.pItemInfo->extraLargeIcon->bitmap,IMAGE_BITMAP,0,0,0);
- m_pDragSourceHelper->SetFlags(DSH_ALLOWDROPDESCRIPTIONTEXT);
- if (di.hbmpDragImage)
- m_pDragSourceHelper->InitializeFromBitmap(&di,pDataObj);
+ // Reuse the icon already displayed by the menu. Do not extract a new
+ // icon through Shell extensions or pump COM messages before the drag.
+ MenuSkin::TItemDrawType drawType=index==m_ProgramTreeIndex?MenuSkin::PROGRAMS_TREE_ITEM:item.drawType;
+ const CItemManager::IconInfo *icon=s_Skin.ItemSettings[drawType].iconSize==MenuSkin::ICON_SIZE_LARGE?
+ item.pItemInfo->largeIcon:item.pItemInfo->smallIcon;
+ if (icon) InitializeMenuDragImage(m_pDragSourceHelper,pDataObj,icon->bitmap);
}
}
if (bProtectedLink)
@@ -317,12 +390,8 @@ bool CMenuContainer::DragOut( int index, bool bApp )
}
}
- // force synchronous operation
- {
- CComQIPtr pAsync(pDataObj);
- if (pAsync)
- pAsync->SetAsyncMode(FALSE);
- }
+ // This shell-created data object owns its asynchronous lifetime.
+ EnableShellAsyncDrop(pDataObj);
// do drag drop
s_pDragSource=this;
@@ -336,6 +405,7 @@ bool CMenuContainer::DragOut( int index, bool bApp )
SetTimer(TIMER_DRAG,100);
DWORD dwEffect=DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK;
HRESULT res=SHDoDragDrop(NULL,pDataObj,NULL,dwEffect,&dwEffect);
+ bool bAsyncDrop=WasShellDropAsync(pDataObj);
s_pDragSource=NULL;
s_bDragFromTree=false;
@@ -352,6 +422,15 @@ bool CMenuContainer::DragOut( int index, bool bApp )
return true;
}
+ if (bAsyncDrop)
+ {
+ // Leave the menu usable while the native shell owns the transfer.
+ // Do not validate files early or take focus back from its dialog.
+ KeepAfterAsyncDrop();
+ QueueFolderRefresh();
+ return true;
+ }
+
if (pFolder && res==DRAGDROP_S_DROP && !m_bDestroyed)
{
// check if the item still exists. refresh the menu if it doesn't
@@ -465,6 +544,9 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::DragEnter( IDataObject *pDataObj, DWOR
{
s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
m_pDragObject=pDataObj;
+ m_DragImageWindow=m_hWnd;
+ m_DragImagePoint={pt.x,pt.y};
+ m_DragImageEffect=*pdwEffect;
if (m_pDropTargetHelper)
{
POINT p={pt.x,pt.y};
@@ -487,12 +569,17 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::DragEnter( IDataObject *pDataObj, DWOR
}
m_DragHoverTime=GetMessageTime()-10000;
m_DragHoverItem=-1;
+ // Also cover entry into an already raised topmost menu.
+ if (m_pDropTargetHelper)
+ RestoreShellDragImage(m_pDropTargetHelper,m_hWnd,m_pDragObject,m_DragImagePoint,*pdwEffect);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CMenuContainer::DragOver( DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
POINT p0={pt.x,pt.y};
+ m_DragImagePoint=p0;
+ m_DragImageEffect=*pdwEffect;
if (m_pDropTargetHelper)
m_pDropTargetHelper->DragOver(&p0,*pdwEffect);
@@ -557,7 +644,9 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::DragOver( DWORD grfKeyState, POINTL pt
{
SetDropTip(m_pDragObject,false);
SetInsertMark(-1,false);
- return m_pDropTarget->DragOver(grfKeyState,pt,pdwEffect);
+ HRESULT result=m_pDropTarget->DragOver(grfKeyState,pt,pdwEffect);
+ m_DragImageEffect=*pdwEffect;
+ return result;
}
s_bRightDrag=(grfKeyState&MK_RBUTTON)!=0;
@@ -667,11 +756,13 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::DragOver( DWORD grfKeyState, POINTL pt
}
else
SetDropTip(m_pDragObject,false);
+ m_DragImageEffect=*pdwEffect;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CMenuContainer::DragLeave( void )
{
+ m_DragImageWindow=NULL;
if (m_pDropTarget)
{
m_pDropTarget->DragLeave();
@@ -691,6 +782,7 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::DragLeave( void )
HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect )
{
+ m_DragImageWindow=NULL;
if (s_pDragSource)
{
if (!s_pDragSource->m_bDestroyed)
@@ -705,6 +797,11 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grf
{
m_pDragObject=NULL;
HRESULT res=m_pDropTarget->Drop(pDataObj,grfKeyState,pt,pdwEffect);
+ if (WasShellDropAsync(pDataObj))
+ {
+ KeepAfterAsyncDrop();
+ QueueFolderRefresh();
+ }
m_pDropTarget=NULL;
return res;
}
@@ -783,6 +880,7 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grf
*pdwEffect=DROPEFFECT_LINK;
bool bDropped=false;
+ bool bAsyncDrop=false;
if (!m_bSubMenu && (*pdwEffect&DROPEFFECT_LINK) && !s_bRightDrag)
{
// if dropping a single folder onto the main menu, create a fake folder
@@ -849,9 +947,7 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grf
dwEffect=*pdwEffect;
pTarget->DragOver(MK_LBUTTON|grfKeyState,pt,pdwEffect);
}
- CComQIPtr pAsync=pDataObj;
- if (pAsync)
- pAsync->SetAsyncMode(FALSE);
+ // Preserve the async mode negotiated by the data source.
for (auto& it : s_Menus)
{
if (!it->m_bDestroyed)
@@ -866,6 +962,8 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grf
s_bPreventClosing=true;
AddRef();
pTarget->Drop(pDataObj,grfKeyState,pt,pdwEffect);
+ bAsyncDrop=WasShellDropAsync(pDataObj);
+ if (bAsyncDrop) KeepAfterAsyncDrop();
s_bPreventClosing=bOld;
for (auto& it : s_Menus)
{
@@ -882,7 +980,7 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grf
AddRef();
}
- if (!m_bDestroyed)
+ if (!m_bDestroyed && !bAsyncDrop)
{
SetForegroundWindow(m_hWnd);
SetActiveWindow();
@@ -909,7 +1007,8 @@ HRESULT STDMETHODCALLTYPE CMenuContainer::Drop( IDataObject *pDataObj, DWORD grf
}
if (m_bTwoColumns && s_MenuMode==MODE_JUMPLIST)
SetMenuMode(MODE_NORMAL);
- PostRefreshMessage();
+ if (bAsyncDrop) QueueFolderRefresh();
+ else PostRefreshMessage();
Release();
}
return S_OK;
diff --git a/Src/StartMenu/StartMenuDLL/MenuCommands.cpp b/Src/StartMenu/StartMenuDLL/MenuCommands.cpp
index ac35f04f7..1be55825a 100644
--- a/Src/StartMenu/StartMenuDLL/MenuCommands.cpp
+++ b/Src/StartMenu/StartMenuDLL/MenuCommands.cpp
@@ -71,6 +71,21 @@ static HRESULT SHOpenFolderAndSelectItemsSafe(PCIDLIST_ABSOLUTE pidlFolder, UINT
});
}
+// Open a shortcut's target location, not the folder containing the shortcut itself.
+static HRESULT OpenContainingItemSafe(PCIDLIST_ABSOLUTE pidl)
+{
+ return ExecuteOnSTAThread([=]() {
+ CComPtr item, target;
+ CAbsolutePidl targetPidl;
+ if (SUCCEEDED(SHCreateItemFromIDList(pidl, IID_IShellItem, (void**)&item)) &&
+ SUCCEEDED(item->BindToHandler(NULL, BHID_LinkTargetItem, IID_IShellItem, (void**)&target)) &&
+ SUCCEEDED(SHGetIDListFromObject(target, &targetPidl)))
+ return SHOpenFolderAndSelectItems(targetPidl, 0, NULL, 0);
+
+ return SHOpenFolderAndSelectItems(pidl, 0, NULL, 0);
+ });
+}
+
static CString g_RenameText;
static POINT g_RenamePos;
@@ -3178,7 +3193,7 @@ void CMenuContainer::ActivateItem( int index, TActivateType type, const POINT *p
// Use the shell API on a worker STA for that operation, without passing
// the apartment-bound IContextMenu interface to another thread.
if (verb.CompareNoCase(L"opencontaining") == 0)
- hr = SHOpenFolderAndSelectItemsSafe(pItemPidl1, 0, NULL, 0);
+ hr = OpenContainingItemSafe(pItemPidl1);
else
hr = pInvokeMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info);
LOG_MENU(LOG_EXECUTE,L"Invoke command, ptr=%p, res=%d",this,hr);
diff --git a/Src/StartMenu/StartMenuDLL/MenuContainer.cpp b/Src/StartMenu/StartMenuDLL/MenuContainer.cpp
index 3337b66a8..5d2cdea0e 100644
--- a/Src/StartMenu/StartMenuDLL/MenuContainer.cpp
+++ b/Src/StartMenu/StartMenuDLL/MenuContainer.cpp
@@ -325,6 +325,8 @@ bool CMenuContainer::s_bNoClose=false;
bool CMenuContainer::s_bHasTouch=false;
char CMenuContainer::s_bActiveDirectory=-1;
bool CMenuContainer::s_bPreventClosing=false;
+bool CMenuContainer::s_bKeepAfterAsyncDrop=false;
+HHOOK CMenuContainer::s_AsyncDropMouseHook=NULL;
bool CMenuContainer::s_bDragClosed=false;
bool CMenuContainer::s_bTempHidden=false;
bool CMenuContainer::s_bDisableHover=false;
@@ -695,11 +697,13 @@ CMenuContainer::CMenuContainer( CMenuContainer *pParent, int index, int options,
InitializeSpecialFolders();
m_bDestroyed=false;
+ if (s_Menus.empty()) s_bKeepAfterAsyncDrop=false;
s_Menus.push_back(this);
m_Submenu=-1;
m_SubJumpItem=-1;
m_bTrackMouse=false;
m_RefreshPosted=0;
+ m_bFolderDirty=m_bTreeDirty=m_bFolderRefreshing=m_bFolderWatchStopped=false;
m_SubShowTime=0;
m_bInsertAfter=false;
@@ -743,6 +747,9 @@ CMenuContainer::CMenuContainer( CMenuContainer *pParent, int index, int options,
m_SearchHash=0;
m_pDropTargetInfo=NULL;
m_DropTargetIndex=-1;
+ m_DragImageWindow=NULL;
+ m_DragImagePoint.x=m_DragImagePoint.y=0;
+ m_DragImageEffect=DROPEFFECT_NONE;
m_PanPosY=m_Overpan=m_PointerId=0;
m_InputCookie=0;
m_ScrollTheme=NULL;
@@ -4483,6 +4490,7 @@ LRESULT CMenuContainer::OnCreate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&
}
m_pDropTargetProxy=new CDropTargetProxy(this);
RegisterDragDrop(m_hWnd,m_pDropTargetProxy);
+ StartFolderWatch();
if (!m_bSubMenu && s_pFrameworkInputPane)
s_pFrameworkInputPane->AdviseWithHWND(m_hWnd,this,&m_InputCookie);
PlayMenuSound(m_bSubMenu?SOUND_POPUP:SOUND_MAIN);
@@ -5135,6 +5143,163 @@ void CMenuContainer::SetActiveWindow( void )
SetWindowPos(s_TaskBar,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); // make sure the top menu stays behind the taskbar
}
+bool CMenuContainer::CanRefreshFolders( void ) const
+{
+ if ((m_Options&(CONTAINER_SEARCH|CONTAINER_JUMPLIST)) ||
+ s_bPreventClosing || s_pDragSource || m_pDragObject || m_pDropTarget ||
+ m_ContextItem>=0 || m_bFolderRefreshing)
+ return false;
+ HWND capture=GetCapture();
+ if (capture && (capture==m_hWnd || ::IsChild(m_hWnd,capture)))
+ return false;
+ if (m_pProgramsTree && m_pProgramsTree->m_hWnd && TreeView_GetEditControl(m_pProgramsTree->m_hWnd))
+ return false;
+ return !s_bWin7Style || s_MenuMode==MODE_NORMAL || s_MenuMode==MODE_PROGRAMS;
+}
+
+void CMenuContainer::QueueFolderRefresh( void )
+{
+ if (!m_bDestroyed && !m_bFolderWatchStopped)
+ {
+ m_bFolderDirty=true;
+ SetTimer(TIMER_FOLDER_REFRESH,250);
+ }
+}
+
+void CMenuContainer::StartFolderWatch( void )
+{
+ if (m_Options&(CONTAINER_SEARCH|CONTAINER_JUMPLIST)) return;
+ for (int i=0;i<2;i++)
+ {
+ m_FolderWatch.Add(m_hWnd,MCM_FOLDERCHANGED,m_Path1[i]);
+ m_FolderWatch.Add(m_hWnd,MCM_FOLDERCHANGED,m_Path2[i]);
+ }
+ if (!m_bSubMenu)
+ {
+ ShGetKnownFolderIDList(FOLDERID_Programs,&m_ProgramWatchRoots[0]);
+ if (!s_bNoCommonFolders)
+ ShGetKnownFolderIDList(FOLDERID_CommonPrograms,&m_ProgramWatchRoots[1]);
+ for (int i=0;i<2;i++)
+ m_FolderWatch.Add(m_hWnd,MCM_FOLDERCHANGED,m_ProgramWatchRoots[i],true);
+ }
+}
+
+LRESULT CMenuContainer::OnFolderChanged( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
+{
+ PIDLIST_ABSOLUTE *changed=NULL;
+ LONG event=0;
+ HANDLE notification=SHChangeNotification_Lock((HANDLE)wParam,(DWORD)lParam,&changed,&event);
+ if (!notification) return 0;
+ for (int side=0;side<2;side++)
+ {
+ if (!changed || !changed[side]) continue;
+ for (int i=0;i<2;i++)
+ {
+ if (CMenuFolderWatch::Contains(m_Path1[i],changed[side]) ||
+ CMenuFolderWatch::Contains(m_Path2[i],changed[side]))
+ m_bFolderDirty=true;
+ if (CMenuFolderWatch::Contains(m_ProgramWatchRoots[i],changed[side],true))
+ m_bTreeDirty=true;
+ }
+ }
+ SHChangeNotification_Unlock(notification);
+ // Coalesce a batch of shell notifications; never rebuild under its lock.
+ if (!m_bFolderWatchStopped && (m_bFolderDirty || m_bTreeDirty))
+ SetTimer(TIMER_FOLDER_REFRESH,250);
+ return 0;
+}
+
+int CMenuContainer::FindRefreshItem( PCIDLIST_ABSOLUTE pidl, TMenuID id, unsigned int nameHash ) const
+{
+ for (int i=0;i<(int)m_Items.size();i++)
+ {
+ const MenuItem &item=m_Items[i];
+ if (pidl)
+ {
+ if ((item.pItem1 && ILIsEqual(pidl,item.pItem1)) || (item.pItem2 && ILIsEqual(pidl,item.pItem2)))
+ return i;
+ }
+ else if (item.id==id && item.nameHash==nameHash)
+ return i;
+ }
+ return -1;
+}
+
+void CMenuContainer::RefreshFolderContents( void )
+{
+ // Keep the child window and its item identity while the parent list changes.
+ // The normal MCM_REFRESH handler intentionally closes children and takes focus.
+ CMenuContainer *child=NULL;
+ for (auto menu : s_Menus)
+ if (!menu->m_bDestroyed && menu->m_pParent==this) { child=menu; break; }
+ CComPtr childLifetime=child?static_cast(child):NULL;
+ CAbsolutePidl childPidl,hotPidl;
+ TMenuID childId=MENU_NO,hotId=MENU_NO;
+ unsigned int childHash=0,hotHash=0;
+ RECT oldAnchor={};
+ bool hadHot=m_HotItem>=0 && m_HotItem<(int)m_Items.size();
+ bool hotArrow=m_bHotArrow;
+ if (hadHot)
+ {
+ const MenuItem &item=m_Items[m_HotItem];
+ hotPidl.Clone(item.pItem1?item.pItem1:item.pItem2);
+ hotId=item.id; hotHash=item.nameHash;
+ }
+ if (child && child->m_ParentIndex>=0 && child->m_ParentIndex<(int)m_Items.size())
+ {
+ const MenuItem &item=m_Items[child->m_ParentIndex];
+ childPidl.Clone(item.pItem1?item.pItem1:item.pItem2);
+ childId=item.id; childHash=item.nameHash;
+ GetItemRect(child->m_ParentIndex,oldAnchor);
+ MapWindowPoints(NULL,&oldAnchor);
+ }
+ else child=NULL;
+ if (m_ScrollHeight>0 && m_FolderHash[0]) s_MenuScrolls[m_FolderHash[0]]=m_ScrollOffset;
+ else s_MenuScrolls.erase(m_FolderHash[0]);
+ KillTimer(TIMER_HOVER);
+ m_HoverItem=-1;
+ SetHotItem(-1);
+ InitItems();
+ if (m_bDestroyed) return;
+ int childIndex=child?FindRefreshItem(childPidl,childId,childHash):-1;
+ if (child && !child->m_bDestroyed)
+ {
+ child->m_ParentIndex=childIndex;
+ if (childIndex>=0) SetSubmenu(childIndex);
+ else
+ {
+ // An actually deleted/moved folder cannot retain a valid submenu.
+ for (auto menu : s_Menus)
+ for (auto parent=menu;parent;parent=parent->m_pParent)
+ if (parent==child) { if (!menu->m_bDestroyed) menu->PostMessage(WM_CLOSE); break; }
+ }
+ }
+ InitWindow();
+ SetHotItem(hadHot?FindRefreshItem(hotPidl,hotId,hotHash):-1,hotArrow,false);
+ if (childIndex>=0 && child && !child->m_bDestroyed)
+ {
+ RECT anchor,before,after;
+ GetItemRect(childIndex,anchor); MapWindowPoints(NULL,&anchor);
+ child->GetWindowRect(&before);
+ int dx=before.left>=oldAnchor.left?anchor.right-oldAnchor.right:anchor.left-oldAnchor.left;
+ child->SetWindowPos(NULL,before.left+dx,before.top+anchor.top-oldAnchor.top,0,0,
+ SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE);
+ child->InitWindow(); // constrain the updated placement to the work area
+ child->GetWindowRect(&after);
+ for (auto menu : s_Menus)
+ if (menu!=child && !menu->m_bDestroyed)
+ for (auto parent=menu->m_pParent;parent;parent=parent->m_pParent)
+ if (parent==child)
+ {
+ RECT rect; menu->GetWindowRect(&rect);
+ menu->SetWindowPos(NULL,rect.left+after.left-before.left,rect.top+after.top-before.top,
+ 0,0,SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE);
+ break;
+ }
+ }
+ Invalidate();
+}
+
void CMenuContainer::PostRefreshMessage( void )
{
if (!m_bDestroyed)
@@ -5240,6 +5405,26 @@ LRESULT CMenuContainer::OnWorkAreaChanged( UINT uMsg, WPARAM wParam, LPARAM lPar
LRESULT CMenuContainer::OnTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
+ if (wParam==TIMER_FOLDER_REFRESH)
+ {
+ if (m_bFolderWatchStopped || m_bDestroyed) { KillTimer(TIMER_FOLDER_REFRESH); return 0; }
+ if (!CanRefreshFolders())
+ return 0; // retry after the current drag, popup menu, or search
+ KillTimer(TIMER_FOLDER_REFRESH);
+ CComPtr keepAlive=static_cast(this);
+ bool treeDirty=m_bTreeDirty;
+ m_bTreeDirty=false;
+ if (m_bFolderDirty && !m_bDestroyed)
+ {
+ m_bFolderDirty=false;
+ m_bFolderRefreshing=true;
+ RefreshFolderContents();
+ m_bFolderRefreshing=false;
+ }
+ if (treeDirty && !m_bDestroyed && m_pProgramsTree && m_pProgramsTree->m_hWnd)
+ m_pProgramsTree->PostMessage(CProgramsTree::TVM_REFRESH);
+ return 0;
+ }
if (wParam==TIMER_HOVER)
{
LOG_MENU(LOG_MOUSE,L"End Hover, hover=%d%s, hot=%d, submenu=%d",m_HoverItem,m_bHoverArrow?L"(arrow)":L"",m_HotItem,m_Submenu);
@@ -6185,6 +6370,9 @@ LRESULT CMenuContainer::OnChar( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& b
LRESULT CMenuContainer::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
LOG_MENU(LOG_OPEN,L"Close Menu, ptr=%p",this);
+ m_bFolderWatchStopped=true;
+ m_FolderWatch.Clear();
+ KillTimer(TIMER_FOLDER_REFRESH);
if (m_pAccessible)
{
NotifyWinEvent(EVENT_SYSTEM_MENUPOPUPEND,m_hWnd,OBJID_CLIENT,CHILDID_SELF);
@@ -6226,6 +6414,9 @@ LRESULT CMenuContainer::OnDestroy( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL
m_bDestroyed=true;
if (this==s_Menus[0])
{
+ if (s_AsyncDropMouseHook) UnhookWindowsHookEx(s_AsyncDropMouseHook);
+ s_AsyncDropMouseHook=NULL;
+ s_bKeepAfterAsyncDrop=false;
// cleanup when the last menu is closed
if (s_Theme)
CloseThemeData(s_Theme);
@@ -6423,6 +6614,8 @@ LRESULT CMenuContainer::OnActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO
#ifndef PREVENT_CLOSING
if (s_bPreventClosing)
return 0;
+ if (s_bKeepAfterAsyncDrop && IsShellOperationWindow((HWND)lParam))
+ return 0;
if (lParam)
{
@@ -6448,6 +6641,53 @@ LRESULT CMenuContainer::OnActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOO
return 0;
}
+// The menu may already be inactive after a native transfer dialog took focus.
+// Observe subsequent button presses independently of WM_ACTIVATE. The hook runs
+// on this UI thread, never consumes input, and only posts work to the root menu.
+void CMenuContainer::KeepAfterAsyncDrop( void )
+{
+ s_bKeepAfterAsyncDrop=true;
+ if (!s_AsyncDropMouseHook && s_FirstMenu)
+ s_AsyncDropMouseHook=SetWindowsHookEx(WH_MOUSE_LL,AsyncDropMouseHook,g_Instance,0);
+}
+
+LRESULT CALLBACK CMenuContainer::AsyncDropMouseHook( int code, WPARAM wParam, LPARAM lParam )
+{
+ if (code==HC_ACTION && s_bKeepAfterAsyncDrop && !s_bPreventClosing && !s_pDragSource && s_FirstMenu &&
+ (wParam==WM_LBUTTONDOWN || wParam==WM_RBUTTONDOWN || wParam==WM_MBUTTONDOWN || wParam==WM_XBUTTONDOWN))
+ {
+ const MSLLHOOKSTRUCT *mouse=(const MSLLHOOKSTRUCT*)lParam;
+ // Resolve before the click can dismiss its dialog or popup.
+ HWND target=WindowFromPoint(mouse->pt);
+ bool operation=target && IsShellOperationWindow(GetAncestor(target,GA_ROOT));
+ ::PostMessage(s_FirstMenu,MCM_OUTSIDECLICK,(WPARAM)target,operation);
+ }
+ return CallNextHookEx(s_AsyncDropMouseHook,code,wParam,lParam);
+}
+
+LRESULT CMenuContainer::OnOutsideClick( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
+{
+ if (!s_bKeepAfterAsyncDrop || s_bPreventClosing || s_pDragSource || m_bDestroyed)
+ return 0;
+ for (std::vector::const_iterator it=s_Menus.begin();it!=s_Menus.end();++it)
+ if (!(*it)->m_bDestroyed && ((*it)->m_pDragObject || (*it)->m_pDropTarget || (*it)->m_ContextItem>=0))
+ return 0;
+
+ HWND target=(HWND)wParam;
+ if (IsMenuWindow(target) || target==s_StartButton || (s_StartButton && ::IsChild(s_StartButton,target)))
+ return 0;
+ if (lParam || (target && IsShellOperationWindow(GetAncestor(target,GA_ROOT))))
+ return 0;
+ // Use the existing close path without activating the taskbar or a previous app.
+ for (std::vector::reverse_iterator it=s_Menus.rbegin();it!=s_Menus.rend();++it)
+ if (!(*it)->m_bDestroyed && !(*it)->m_bClosing)
+ {
+ (*it)->m_bClosing=true;
+ (*it)->PostMessage(WM_CLOSE);
+ }
+ return 0;
+}
+
LRESULT CMenuContainer::OnMouseActivate( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
{
if (m_Submenu>=0 || (m_Options&CONTAINER_SEARCH))
diff --git a/Src/StartMenu/StartMenuDLL/MenuContainer.h b/Src/StartMenu/StartMenuDLL/MenuContainer.h
index 057982104..b5d3f6d7a 100644
--- a/Src/StartMenu/StartMenuDLL/MenuContainer.h
+++ b/Src/StartMenu/StartMenuDLL/MenuContainer.h
@@ -10,6 +10,7 @@
#include "SearchManager.h"
#include "DragDrop.h"
#include "TouchHelper.h"
+#include "MenuFolderWatch.h"
#include
#include