From e950ba2e880b29f26997add4902b6454c30b49d0 Mon Sep 17 00:00:00 2001
From: projektion0 <98270100+projektion0@users.noreply.github.com>
Date: Sat, 17 Oct 2026 23:59:28 +0200
Subject: [PATCH 1/5] Build: support Visual Studio 2026 and add a Start Menu
solution
---
BUILDING.md | 25 +++
Directory.Build.props | 10 ++
Directory.Build.targets | 10 ++
NuGet.Config | 7 +
OpenShell-VS2026.sln | 155 ++++++++++++++++++
Src/ClassicExplorer/ClassicExplorer.vcxproj | 2 +-
.../ClassicExplorerSettings.vcxproj | 2 +-
Src/ClassicIE/ClassicIE.vcxproj | 2 +-
.../ClassicIEDLL/ClassicIEDLL.vcxproj | 2 +-
Src/Lib/Lib.vcxproj | 2 +-
Src/Setup/Setup.vcxproj | 2 +-
Src/Setup/SetupHelper/SetupHelper.vcxproj | 2 +-
Src/Setup/Utility/Utility.vcxproj | 2 +-
Src/Setup/en-US/en-US.vcxproj | 2 +-
Src/Skins/ClassicSkin/ClassicSkin.vcxproj | 2 +-
Src/Skins/ClassicSkin7/ClassicSkin7.vcxproj | 2 +-
Src/Skins/FullGlass/FullGlass.vcxproj | 2 +-
Src/Skins/Immersive/Immersive.vcxproj | 2 +-
Src/Skins/Immersive7/Immersive7.vcxproj | 2 +-
Src/Skins/Metallic7/Metallic7.vcxproj | 2 +-
Src/Skins/Metro/Metro.vcxproj | 2 +-
Src/Skins/Metro7/Metro7.vcxproj | 2 +-
Src/Skins/Midnight7/Midnight7.vcxproj | 2 +-
Src/Skins/SmokedGlass/SmokedGlass.vcxproj | 2 +-
Src/Skins/Win7Aero/Win7Aero.vcxproj | 2 +-
Src/Skins/Win7Aero7/Win7Aero7.vcxproj | 2 +-
Src/Skins/Win7Basic/Win7Basic.vcxproj | 2 +-
Src/Skins/Win8/Win8.vcxproj | 2 +-
Src/Skins/Win87/Win87.vcxproj | 2 +-
Src/Skins/WinXP/WinXP.vcxproj | 2 +-
Src/StartMenu/StartMenu.vcxproj | 2 +-
.../StartMenuDLL/StartMenuDLL.vcxproj | 2 +-
.../StartMenuHelper/StartMenuHelper.vcxproj | 2 +-
.../DesktopToasts/DesktopToasts.vcxproj | 2 +-
Src/Update/Update.vcxproj | 2 +-
35 files changed, 237 insertions(+), 30 deletions(-)
create mode 100644 BUILDING.md
create mode 100644 Directory.Build.props
create mode 100644 Directory.Build.targets
create mode 100644 NuGet.Config
create mode 100644 OpenShell-VS2026.sln
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/StartMenuDLL.vcxproj b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj
index 0412e588c..7d828042f 100644
--- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj
+++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.vcxproj
@@ -47,7 +47,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj
index 3e7790656..af7994c0b 100644
--- a/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj
+++ b/Src/StartMenu/StartMenuHelper/StartMenuHelper.vcxproj
@@ -59,7 +59,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
diff --git a/Src/Update/DesktopToasts/DesktopToasts.vcxproj b/Src/Update/DesktopToasts/DesktopToasts.vcxproj
index 5561e5bfc..1968484ee 100644
--- a/Src/Update/DesktopToasts/DesktopToasts.vcxproj
+++ b/Src/Update/DesktopToasts/DesktopToasts.vcxproj
@@ -20,7 +20,7 @@
DynamicLibrary
- v143
+ $(OpenShellPlatformToolset)
Unicode
true
diff --git a/Src/Update/Update.vcxproj b/Src/Update/Update.vcxproj
index 52f961ac1..c253acb16 100644
--- a/Src/Update/Update.vcxproj
+++ b/Src/Update/Update.vcxproj
@@ -19,7 +19,7 @@
Application
- v143
+ $(OpenShellPlatformToolset)
Static
Unicode
true
From a68cd87b30be3b18c68a7b6c0ac0661a4838c5b8 Mon Sep 17 00:00:00 2001
From: projektion0 <98270100+projektion0@users.noreply.github.com>
Date: Sat, 17 Oct 2026 23:59:28 +0200
Subject: [PATCH 2/5] StartMenu: allow a separate left Start button on Windows
11
---
Src/StartMenu/StartMenuDLL/SettingsUI.cpp | 8 ++-
Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 66 +++++++++++++++++----
Src/StartMenu/StartMenuDLL/StartMenuDLL.h | 3 +-
Src/StartMenu/StartMenuDLL/StartMenuDLL.rc | 16 +++++
Src/StartMenu/StartMenuDLL/resource.h | 2 +
5 files changed, 80 insertions(+), 15 deletions(-)
diff --git a/Src/StartMenu/StartMenuDLL/SettingsUI.cpp b/Src/StartMenu/StartMenuDLL/SettingsUI.cpp
index af9376103..c86bd0d5d 100644
--- a/Src/StartMenu/StartMenuDLL/SettingsUI.cpp
+++ b/Src/StartMenu/StartMenuDLL/SettingsUI.cpp
@@ -4458,6 +4458,7 @@ CSetting g_Settings[]={
{L"StartButton",CSetting::TYPE_GROUP,IDS_START_BUTTON},
{L"EnableStartButton",CSetting::TYPE_BOOL,IDS_ENABLE_BUTTON,IDS_ENABLE_BUTTON_TIP,1},
+ {L"StartButtonLeft",CSetting::TYPE_BOOL,IDS_BUTTON_LEFT,IDS_BUTTON_LEFT_TIP,0,0,L"EnableStartButton"},
{L"AllTaskbars",CSetting::TYPE_BOOL,IDS_ENABLE_TASKBARS,IDS_ENABLE_TASKBARS_TIP,1,0,L"EnableStartButton"},
{L"StartButtonTip",CSetting::TYPE_STRING,IDS_BUTTON_TIP,IDS_BUTTON_TIP_TIP,L"$Menu.Start",0,L"EnableStartButton"},
{L"StartButtonType",CSetting::TYPE_INT,IDS_BUTTON_TYPE,IDS_BUTTON_TYPE_TIP,0,0,L"EnableStartButton"},
@@ -4734,6 +4735,8 @@ void UpdateSettings( void )
UpdateSetting(L"Language",language,false);
}
+ HideSetting(L"StartButtonLeft",!IsWin11());
+
TMenuStyle menuStyle=MENU_CLASSIC1;
{
const CSetting *pSetting=FindSetting(L"MenuStyle");
@@ -5169,11 +5172,12 @@ void InitSettings( void )
}
static int g_ButtonPath, g_ButtonSize, g_ButtonIcon, g_ButtonOffset, g_ButtonIconSize, g_ButtonText, g_ButtonTip, g_ButtonAllTaskbars;
-static bool g_bButtonEnable, g_bTaskbarsChanged, g_bTaskbarsUpdated;
+static bool g_bButtonEnable, g_bButtonLeft, g_bTaskbarsChanged, g_bTaskbarsUpdated;
static void StoreButtonSettings( void )
{
g_bButtonEnable=GetSettingBool(L"EnableStartButton");
+ g_bButtonLeft=GetSettingBool(L"StartButtonLeft");
g_ButtonAllTaskbars=GetSettingBool(L"AllTaskbars");
g_ButtonTip=CalcFNVHash(GetSettingString(L"StartButtonTip"));
g_ButtonPath=GetSettingInt(L"StartButtonType");
@@ -5205,7 +5209,7 @@ static void UpdateButtons( bool bForce )
}
if (path==START_BUTTON_CUSTOM)
path=CalcFNVHash(GetSettingString(L"StartButtonPath"));
- bool bRecreate=(g_bButtonEnable!=GetSettingBool(L"EnableStartButton") || path!=g_ButtonPath || g_ButtonSize!=GetSettingInt(L"StartButtonSize") || g_ButtonOffset!=GetSettingInt(L"StartButtonOffset")
+ bool bRecreate=(g_bButtonLeft!=GetSettingBool(L"StartButtonLeft") || 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_ButtonAllTaskbars!=allTaskbars);
if ((g_bTaskbarsChanged && !g_bTaskbarsUpdated) || bForce || bRecreate)
{
diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
index 583c5b765..c08ec4d20 100644
--- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
+++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp
@@ -387,10 +387,17 @@ TaskbarInfo *GetTaskbarInfo( size_t taskbarId )
return (it==g_TaskbarInfos.end())?NULL:&it->second;
}
+// Keep the native Windows 11 button independent of our optional left button.
+// ExplorerPatcher uses the legacy taskbar layout and keeps its existing behavior.
+static bool IsStartButtonLeft( void )
+{
+ return IsWin11() && !g_epTaskbar && GetSettingBool(L"StartButtonLeft");
+}
+
static TaskbarInfo *FindTaskBarInfoButton( HWND button )
{
for (id_taskbar_map::iterator it=g_TaskbarInfos.begin();it!=g_TaskbarInfos.end();++it)
- if (it->second.startButton==button || it->second.oldButton==button)
+ if (it->second.startButton==button || (!IsStartButtonLeft() && it->second.oldButton==button))
return &it->second;
return NULL;
}
@@ -686,6 +693,16 @@ bool PointAroundStartButton( size_t taskbarId, const CPoint &pt )
if (!PtInRect(&rc,pt))
return false;
+ // Never include the native button (or the gap leading to it) in our hit area.
+ if (IsStartButtonLeft())
+ {
+ if (!taskBar->bReplaceButton || !taskBar->startButton)
+ return false;
+ CRect rcButton;
+ GetWindowRect(taskBar->startButton,&rcButton);
+ return PtInRect(&rcButton,pt)!=FALSE;
+ }
+
bool rtl=GetWindowLongPtr(taskBar->taskBar,GWL_EXSTYLE)&WS_EX_LAYOUTRTL;
CRect rcStart;
@@ -851,7 +868,7 @@ class CMonitorModeEvents: public IAppVisibilityEvents
if (g_CurrentWSMTaskbar!=-1 && g_CurrentWSMTaskbar!=g_CurrentCSMTaskbar)
PressStartButton(g_CurrentWSMTaskbar,false);
g_CurrentWSMTaskbar=taskbarId;
- if (g_CurrentWSMTaskbar!=-1)
+ if (g_CurrentWSMTaskbar!=-1 && !IsStartButtonLeft())
PressStartButton(g_CurrentWSMTaskbar,true);
}
}
@@ -1315,6 +1332,7 @@ static void UpdateStartButtonPosition(const TaskbarInfo* taskBar, const WINDOWPO
}
}
+ const bool bLeft=IsStartButtonLeft();
RECT rcOldButton;
if (taskBar->oldButton)
GetWindowRect(taskBar->oldButton, &rcOldButton);
@@ -1328,11 +1346,13 @@ static void UpdateStartButtonPosition(const TaskbarInfo* taskBar, const WINDOWPO
x = rcTask.left;
else
x = rcTask.right - taskBar->startButtonSize.cx;
- y = taskBar->oldButton ? rcOldButton.top : rcTask.top;
+ y = (taskBar->oldButton && !bLeft) ? rcOldButton.top : rcTask.top;
}
else
{
- if (GetWindowLongPtr(taskBar->rebar, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
+ if (bLeft)
+ x = rcTask.left;
+ else if (GetWindowLongPtr(taskBar->rebar, GWL_EXSTYLE) & WS_EX_LAYOUTRTL)
x = (taskBar->oldButton ? rcOldButton.right : rcTask.right) - taskBar->startButtonSize.cx;
else
x = taskBar->oldButton ? rcOldButton.left : rcTask.left;
@@ -1345,7 +1365,7 @@ static void UpdateStartButtonPosition(const TaskbarInfo* taskBar, const WINDOWPO
// Start button on Win11 is a bit shifted to the right
// We will shift our Aero button to cover original button
- if (IsWin11() && (x == info.rcMonitor.left) && (GetStartButtonType() == START_BUTTON_AERO) && !g_epTaskbar)
+ if (!bLeft && IsWin11() && (x == info.rcMonitor.left) && (GetStartButtonType() == START_BUTTON_AERO) && !g_epTaskbar)
x += ScaleForDpi(taskBar->taskBar, 6);
if (GetStartButtonType() == START_BUTTON_CUSTOM)
@@ -1402,10 +1422,10 @@ static LRESULT CALLBACK SubclassWin81StartButton( HWND hWnd, UINT uMsg, WPARAM w
PostMessage(taskBar->taskBar,WM_SIZE,SIZE_RESTORED,MAKELONG(rcTask.right-rcTask.left,rcTask.bottom-rcTask.top));
}
}
- if (uMsg==WM_POINTERACTIVATE && CMenuContainer::IsMenuOpened())
+ if (!IsStartButtonLeft() && uMsg==WM_POINTERACTIVATE && CMenuContainer::IsMenuOpened())
return MA_NOACTIVATE;
#ifdef START_TOUCH
- if (uMsg==WM_POINTERUP || uMsg==WM_POINTERUPDATE || uMsg==WM_POINTERUP)
+ if (!IsStartButtonLeft() && (uMsg==WM_POINTERUP || uMsg==WM_POINTERUPDATE || uMsg==WM_POINTERUP))
{
POINTER_INPUT_TYPE type;
GetPointerType2(GET_POINTERID_WPARAM(wParam),&type);
@@ -1697,7 +1717,7 @@ static LRESULT CALLBACK SubclassTrayButtonProc( HWND hWnd, UINT uMsg, WPARAM wPa
if (uMsg==WM_WINDOWPOSCHANGING)
{
const TaskbarInfo *taskBar=GetTaskbarInfo((int)dwRefData);
- if (taskBar && (taskBar->bReplaceButton || taskBar->bHideButton))
+ if (taskBar && !IsStartButtonLeft() && (taskBar->bReplaceButton || taskBar->bHideButton))
{
WINDOWPOS *pPos=(WINDOWPOS*)lParam;
if (!(pPos->flags&SWP_NOMOVE) || !(pPos->flags&SWP_NOSIZE))
@@ -2126,7 +2146,7 @@ static LRESULT CALLBACK SubclassRebarProc( HWND hWnd, UINT uMsg, WPARAM wParam,
if (uMsg==WM_WINDOWPOSCHANGING)
{
const TaskbarInfo *taskBar=GetTaskbarInfo((int)dwRefData);
- if (taskBar && (taskBar->bReplaceButton || taskBar->bHideButton))
+ if (taskBar && !IsStartButtonLeft() && (taskBar->bReplaceButton || taskBar->bHideButton))
{
WINDOWPOS *pPos=(WINDOWPOS*)lParam;
if (!(pPos->flags&SWP_NOMOVE) || !(pPos->flags&SWP_NOSIZE))
@@ -2496,7 +2516,28 @@ void UpdateTaskBars( TUpdateTaskbar update )
{
TaskbarInfo &taskBar=it->second;
bool bButton2=bButton && (bAll || taskBar.taskBar==g_TaskBar);
- bool bHideButton2=bButton;
+ bool bHideButton2=bButton && !IsStartButtonLeft();
+ if (taskBar.bSeparateButton!=IsStartButtonLeft())
+ {
+ // Restore the native button's drag target when the two buttons are separate.
+ // The custom button always keeps its own target in RecreateStartButton.
+ taskBar.bSeparateButton=IsStartButtonLeft();
+ if (taskBar.oldButton)
+ {
+ RevokeDragDrop(taskBar.oldButton);
+ if (taskBar.bSeparateButton)
+ {
+ if (taskBar.pOriginalTarget)
+ RegisterDragDrop(taskBar.oldButton,taskBar.pOriginalTarget);
+ }
+ else
+ {
+ CStartMenuTarget *pNewTarget=new CStartMenuTarget(taskBar.taskbarId);
+ RegisterDragDrop(taskBar.oldButton,pNewTarget);
+ pNewTarget->Release();
+ }
+ }
+ }
if (taskBar.oldButton)
{
if (bHideButton2 && !bButton2)
@@ -3629,14 +3670,15 @@ static LRESULT CALLBACK HookDesktopThreadMouse(int code, WPARAM wParam, LPARAM l
if (!taskBar)
{
taskBar = FindTaskBarInfoBar(GetAncestor(info->hwnd, GA_ROOT)); // click on taskbar
- if (taskBar && !PointAroundStartButton(taskBar->taskbarId))
+ if (taskBar && !PointAroundStartButton(taskBar->taskbarId,info->pt))
taskBar = NULL;
}
if (taskBar && (info->hwnd != taskBar->startButton) && taskBar->oldButton)
{
// steal messages from other than our custom button window
- PostMessage(taskBar->oldButton, (UINT)wParam, 0, MAKELPARAM(info->pt.x, info->pt.y));
+ HWND target=IsStartButtonLeft()?taskBar->startButton:taskBar->oldButton;
+ PostMessage(target, (UINT)wParam, 0, MAKELPARAM(info->pt.x, info->pt.y));
return 1;
}
}
diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.h b/Src/StartMenu/StartMenuDLL/StartMenuDLL.h
index 456a88475..81b8ed39f 100644
--- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.h
+++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.h
@@ -47,7 +47,7 @@ void EnableStartTooltip( bool bEnable );
struct TaskbarInfo
{
- TaskbarInfo( void ) { taskbarId=pointerId=0; taskBar=startButton=oldButton=rebar=taskList=chevron=news=desktop=NULL; startButtonSize.cx=startButtonSize.cy=0; oldButtonSize.cx=oldButtonSize.cy=0; bTimer=bCustomLook=bReplaceButton=bHideButton=bRecreatingButton=bThemeChanging=false; }
+ TaskbarInfo( void ) { taskbarId=pointerId=0; taskBar=startButton=oldButton=rebar=taskList=chevron=news=desktop=NULL; startButtonSize.cx=startButtonSize.cy=0; oldButtonSize.cx=oldButtonSize.cy=0; bTimer=bCustomLook=bReplaceButton=bHideButton=bRecreatingButton=bThemeChanging=bSeparateButton=false; }
int taskbarId;
HWND taskBar;
HWND startButton; // either own start button or the win7 start button (depending on bReplaceButton)
@@ -66,6 +66,7 @@ struct TaskbarInfo
bool bHideButton; // hide old start button (if we have own button)
bool bRecreatingButton;
bool bThemeChanging;
+ bool bSeparateButton; // native button drag target restored for the separate left button
std::vector trayButtons; // ordered by Z order (for win10)
std::vector taskbarParts;
CComPtr pOriginalTarget;
diff --git a/Src/StartMenu/StartMenuDLL/StartMenuDLL.rc b/Src/StartMenu/StartMenuDLL/StartMenuDLL.rc
index d65ffce32..60e87266b 100644
--- a/Src/StartMenu/StartMenuDLL/StartMenuDLL.rc
+++ b/Src/StartMenu/StartMenuDLL/StartMenuDLL.rc
@@ -1339,7 +1339,23 @@ BEGIN
IDS_PINNED_PATH_TIP "The path to use as the Pinned folder. If the path does not exist, it will be created (if possible) after opening the start menu. Close this window after updating this setting to update your context menu.\n\nNote: If you do not have permissions for the selected path, you will not be able to pin items until you take ownership of the new folder"
END
+STRINGTABLE
+BEGIN
+ IDS_BUTTON_LEFT "Keep Open-Shell start button at left edge (Windows 11)"
+ IDS_BUTTON_LEFT_TIP "Places the Open-Shell start button at the left edge of each enabled taskbar and keeps the Windows start button available at its normal position. Requires the Windows 11 taskbar. This option does not apply to ExplorerPatcher's legacy taskbar."
+END
+
#endif // English (U.S.) resources
+
+// These new IDs have their own string-table block, so existing localized
+// resources continue to fall back to the external language DLL as before.
+LANGUAGE LANG_GERMAN, SUBLANG_GERMAN
+STRINGTABLE
+BEGIN
+ IDS_BUTTON_LEFT "Open-Shell-Startknopf links anordnen (Windows 11)"
+ IDS_BUTTON_LEFT_TIP "Ordnet den Open-Shell-Startknopf am linken Rand jeder aktivierten Taskleiste an. Der Windows-Startknopf bleibt an seiner normalen Position erreichbar. Erfordert die Windows-11-Taskleiste. Gilt nicht fuer die klassische Taskleiste von ExplorerPatcher."
+END
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
/////////////////////////////////////////////////////////////////////////////
diff --git a/Src/StartMenu/StartMenuDLL/resource.h b/Src/StartMenu/StartMenuDLL/resource.h
index 81acb7478..5ab23ac3d 100644
--- a/Src/StartMenu/StartMenuDLL/resource.h
+++ b/Src/StartMenu/StartMenuDLL/resource.h
@@ -788,6 +788,8 @@
#define IDS_ALT_ACCELERATORS_TIP 3689
#define IDS_BUTTON_OFFSET 3690
#define IDS_BUTTON_OFFSET_TIP 3691
+#define IDS_BUTTON_LEFT 7120
+#define IDS_BUTTON_LEFT_TIP 7121
#define IDS_STRING7001 7001
#define IDS_STRING7002 7002
#define IDS_STRING7003 7003
From 824b0631bb73f47b5c4d08ba1922aa4fca72f994 Mon Sep 17 00:00:00 2001
From: projektion0 <98270100+projektion0@users.noreply.github.com>
Date: Sat, 17 Oct 2026 23:59:55 +0200
Subject: [PATCH 3/5] StartMenu: keep native async drops responsive and refresh
open menus
Preserve native Shell async transfer negotiation, retain menus without stealing focus from transfer dialogs, and observe folder changes. Restore outside-click dismissal and keep the Shell drag preview visible above raised submenus.
---
Src/StartMenu/StartMenuDLL/DragDrop.cpp | 155 +++++++++--
Src/StartMenu/StartMenuDLL/MenuContainer.cpp | 240 ++++++++++++++++++
Src/StartMenu/StartMenuDLL/MenuContainer.h | 24 ++
Src/StartMenu/StartMenuDLL/MenuFolderWatch.h | 38 +++
Src/StartMenu/StartMenuDLL/MenuPaint.cpp | 5 +
Src/StartMenu/StartMenuDLL/ProgramsTree.cpp | 18 +-
Src/StartMenu/StartMenuDLL/StartMenuDLL.cpp | 27 ++
Src/StartMenu/StartMenuDLL/StartMenuDLL.h | 3 +
.../StartMenuDLL/StartMenuDLL.vcxproj | 1 +
.../StartMenuDLL/StartMenuDLL.vcxproj.filters | 3 +
10 files changed, 482 insertions(+), 32 deletions(-)
create mode 100644 Src/StartMenu/StartMenuDLL/MenuFolderWatch.h
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/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