diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 325b984c1cd3..fa9c77d5a411 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -50,6 +50,7 @@ APeriod
apidl
APIENTRY
APIIs
+apng
Apm
APPBARDATA
APPEXECLINK
@@ -170,6 +171,8 @@ CENTERALIGN
certlm
certmgr
cfp
+CEscaped
+cguid
CHANGECBCHAIN
changecursor
CHILDACTIVATE
@@ -727,6 +730,8 @@ jpe
jpnime
Jsons
jsonval
+junja
+jxl
jxr
keybd
KEYBDDATA
@@ -1154,6 +1159,8 @@ PINDIR
pinfo
pinvoke
pipename
+pjp
+pjpeg
PKBDLLHOOKSTRUCT
plib
ploc
@@ -1184,6 +1191,7 @@ pprm
pproc
ppshv
ppsi
+ppsia
ppsid
ppsrm
ppsrree
@@ -1329,6 +1337,7 @@ rgbs
rgelt
rgf
rgn
+rgpidl
rgs
RIDEV
RIGHTSCROLLBAR
@@ -1677,6 +1686,9 @@ UNCPRIORITY
UNDNAME
unhiding
UNICODETEXT
+uninstaller
+uninstantiated
+uniquifier
Uniquifies
unitconverter
unittests
@@ -1768,6 +1780,7 @@ vswhere
Vtbl
WANTMAPPINGHANDLE
WANTPALM
+Wagnerp
wbem
WBounds
Wca
diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt
index f8c9761933e8..00725cb50ff1 100644
--- a/.github/actions/spell-check/patterns.txt
+++ b/.github/actions/spell-check/patterns.txt
@@ -232,6 +232,14 @@ _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b
+# CRC names
+
+CRC(32|64)(Decimal|Hex)
+
+# WSL paths
+
+\/mnt\/.*
+
# ZoomIt menu items with accelerator keys
E&xit
St&yle
diff --git a/.gitignore b/.gitignore
index 89541c3a2e05..089dda889836 100644
--- a/.gitignore
+++ b/.gitignore
@@ -353,3 +353,6 @@ src/common/Telemetry/*.etl
# MSBuildCache
/MSBuildCacheLogs/
+
+# Temp telemetry files.
+*.etl
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index 13181ebfd1bc..60741d41242e 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -109,6 +109,7 @@
"FileLocksmithContextMenuPackage.msix",
"WinUI3Apps\\Peek.Common.dll",
+ "WinUI3Apps\\Peek.Helpers.dll",
"WinUI3Apps\\Peek.FilePreviewer.dll",
"WinUI3Apps\\Powertoys.Peek.UI.dll",
"WinUI3Apps\\Powertoys.Peek.UI.exe",
@@ -156,6 +157,20 @@
"RunPlugins\\ValueGenerator\\Community.PowerToys.Run.Plugin.ValueGenerator.dll",
"RunPlugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll",
"RunPlugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll",
+
+ "WinUI3Apps\\PowerToys.FileActionsMenu.dll",
+ "WinUI3Apps\\FileActionsMenu.Helpers.dll",
+ "WinUI3Apps\\FileActionsMenu.Interfaces.dll",
+ "WinUI3Apps\\PowerToys.FileActionsMenu.Ui.exe",
+ "WinUI3Apps\\PowerToys.FileActionsMenu.Ui.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\ExecutableActions\\PowerToys.FileActionsMenu.Plugins.ExecutableActions.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\FileContentActions\\PowerToys.FileActionsMenu.Plugins.FileContentActions.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\FileProperties\\PowerToys.FileActionsMenu.Plugins.FileProperties.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\Hashes\\PowerToys.FileActionsMenu.Plugins.Hashes.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\ImageClipboardActions\\PowerToys.FileActionsMenu.Plugins.ImageClipboardActions.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\MoveCopyActions\\PowerToys.FileActionsMenu.Plugins.MoveCopyActions.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\PathCopy\\PowerToys.FileActionsMenu.Plugins.PathCopy.dll",
+ "WinUI3Apps\\FileActionsMenuPlugins\\PowerToys\\PowerToys.FileActionsMenu.Plugins.PowerToys.dll",
"WinUI3Apps\\PowerToys.MeasureToolModuleInterface.dll",
"WinUI3Apps\\PowerToys.MeasureToolCore.dll",
@@ -330,7 +345,12 @@
"ColorCode.UWP.dll",
"UnitsNet.dll",
"UtfUnknown.dll",
- "Wpf.Ui.dll"
+ "Wpf.Ui.dll",
+ "WinUI3Apps\\Microsoft.WindowsAPICodePack.Core.dll",
+ "WinUI3Apps\\Microsoft.WindowsAPICodePack.ExtendedLinguisticServices.dll",
+ "WinUI3Apps\\Microsoft.WindowsAPICodePack.Sensors.dll",
+ "WinUI3Apps\\Microsoft.WindowsAPICodePack.Shell.dll",
+ "WinUI3Apps\\Microsoft.WindowsAPICodePack.ShellExtensions.dll"
],
"SigningInfo": {
"Operations": [
diff --git a/Directory.Packages.props b/Directory.Packages.props
index c04a97ec804f..58f26f73ff89 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -81,6 +81,7 @@
+
@@ -90,7 +91,8 @@
-
+
+
diff --git a/NOTICE.md b/NOTICE.md
index 84c89322827e..20e810e2b6a9 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -38,6 +38,22 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+## Utility: File Actions menu
+
+### Windows API Code Pack
+
+**Source**: https://github.com/Wagnerp/Windows-API-CodePack-NET
+
+MIT License
+
+Copyright (c) 2009 - 2010 Microsoft Corporation, then modifications by Peter William Wagner 2017 - 2023
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
## Utility: File Explorer Add-ins
### Monaco Editor
@@ -1361,6 +1377,7 @@ EXHIBIT A -Mozilla Public License.
- System.Drawing.Common 9.0.1
- System.IO.Abstractions 21.0.29
- System.IO.Abstractions.TestingHelpers 21.0.29
+- System.IO.Hashing 9.0.1
- System.Management 9.0.1
- System.Reactive 6.0.1
- System.Runtime.Caching 9.0.1
@@ -1370,5 +1387,6 @@ EXHIBIT A -Mozilla Public License.
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
- UTF.Unknown 2.5.1
-- WinUIEx 2.2.0
+- WindowsAPICodePack 8.0.6
+- WinUIEx 2.3.4
- WPF-UI 3.0.5
diff --git a/PowerToys.sln b/PowerToys.sln
index 870999f12c3b..5cfc9332132e 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -552,10 +552,43 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiThumbnailProvi
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdNotFoundModuleInterface", "src\modules\cmdNotFound\CmdNotFoundModuleInterface\CmdNotFoundModuleInterface.vcxproj", "{0014D652-901F-4456-8D65-06FC5F997FB0}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileActionsMenu.Ui", "src\modules\FileActionsMenu\FileActionsMenu.Ui\FileActionsMenu.Ui.csproj", "{2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileActionsMenu", "src\modules\FileActionsMenu\FileActionsMenu\FileActionsMenu.vcxproj", "{3A2398AB-D8F5-49C5-9EA7-483BED398BEC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileActionsMenu", "FileActionsMenu", "{89A38AD7-DA2F-46B9-AA9B-C213AA172350}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{95B63A44-6287-4E7E-B96A-2294C21C3230}"
+ ProjectSection(SolutionItems) = preProject
+ src\modules\FileActionsMenu\FileActionsMenu.Plugins\FileActionsMenuPlugin.props = src\modules\FileActionsMenu\FileActionsMenu.Plugins\FileActionsMenuPlugin.props
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileActionsMenu.Interfaces", "src\modules\FileActionsMenu\FileActionsMenu.Interfaces\FileActionsMenu.Interfaces.csproj", "{9913D6FE-BAA1-4065-9825-C25AB4BB4475}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileActionsMenu.Helpers", "src\modules\FileActionsMenu\FileActionsMenu.Helpers\FileActionsMenu.Helpers.csproj", "{DA473AC1-D274-410D-B177-2E30F77953D1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.MoveCopyActions", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.MoveCopyActions\PowerToys.FileActionsMenu.Plugins.MoveCopyActions.csproj", "{8C133101-CA38-4E6F-AE67-C47441E7864E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.Hashes", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.Hashes\PowerToys.FileActionsMenu.Plugins.Hashes.csproj", "{C055B76D-90FC-46B2-A1C2-B73C30D94953}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.PowerToys", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.PowerToys\PowerToys.FileActionsMenu.Plugins.PowerToys.csproj", "{7AB55C3E-7CBB-4544-995C-4C804C132AF3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.PathCopy", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.PathCopy\PowerToys.FileActionsMenu.Plugins.PathCopy.csproj", "{7FBD0D7C-9D72-41C4-803B-4FF0228CA267}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.ImageClipboardActions", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.ImageClipboardActions\PowerToys.FileActionsMenu.Plugins.ImageClipboardActions.csproj", "{50B33187-448C-43D2-8858-5004A84D1F64}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Helpers", "src\modules\peek\Peek.Helpers\Peek.Helpers.csproj", "{AC5129DF-CC8C-4195-87BA-855A65B6A4D9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.FileContentActions", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.FileContentActions\PowerToys.FileActionsMenu.Plugins.FileContentActions.csproj", "{9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}"
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithContextMenu", "src\modules\FileLocksmith\FileLocksmithContextMenu\FileLocksmithContextMenu.vcxproj", "{799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLib", "src\modules\FileLocksmith\FileLocksmithLib\FileLocksmithLib.vcxproj", "{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.ExecutableActions", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.ExecutableActions\PowerToys.FileActionsMenu.Plugins.ExecutableActions.csproj", "{0EFA84EA-74D5-499C-8496-29BE1CB281B9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.FileActionsMenu.Plugins.FileProperties", "src\modules\FileActionsMenu\FileActionsMenu.Plugins\PowerToys.FileActionsMenu.Plugins.FileProperties\PowerToys.FileActionsMenu.Plugins.FileProperties.csproj", "{60779C23-BFF6-4759-98CB-105C8B8F2C02}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedPaste", "src\modules\AdvancedPaste\AdvancedPaste\AdvancedPaste.csproj", "{C32D254F-7597-4CBE-BF74-D922D81CDF29}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts", "src\modules\Hosts\Hosts\Hosts.csproj", "{02DD46D3-F761-47D9-8894-2D6DA0124650}"
@@ -2494,6 +2527,138 @@ Global
{0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x64.Build.0 = Release|x64
{0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x86.ActiveCfg = Release|Win32
{0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x86.Build.0 = Release|Win32
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Debug|ARM64.Build.0 = Debug|ARM64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Debug|x64.ActiveCfg = Debug|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Debug|x64.Build.0 = Debug|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Debug|x86.ActiveCfg = Debug|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Debug|x86.Build.0 = Debug|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Release|ARM64.ActiveCfg = Release|ARM64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Release|ARM64.Build.0 = Release|ARM64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Release|x64.ActiveCfg = Release|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Release|x64.Build.0 = Release|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Release|x86.ActiveCfg = Release|x64
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0}.Release|x86.Build.0 = Release|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Debug|ARM64.Build.0 = Debug|ARM64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Debug|x64.ActiveCfg = Debug|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Debug|x64.Build.0 = Debug|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Debug|x86.ActiveCfg = Debug|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Debug|x86.Build.0 = Debug|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Release|ARM64.ActiveCfg = Release|ARM64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Release|ARM64.Build.0 = Release|ARM64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Release|x64.ActiveCfg = Release|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Release|x64.Build.0 = Release|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Release|x86.ActiveCfg = Release|x64
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}.Release|x86.Build.0 = Release|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Debug|ARM64.Build.0 = Debug|ARM64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Debug|x64.ActiveCfg = Debug|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Debug|x64.Build.0 = Debug|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Debug|x86.ActiveCfg = Debug|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Debug|x86.Build.0 = Debug|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Release|ARM64.ActiveCfg = Release|ARM64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Release|ARM64.Build.0 = Release|ARM64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Release|x64.ActiveCfg = Release|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Release|x64.Build.0 = Release|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Release|x86.ActiveCfg = Release|x64
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475}.Release|x86.Build.0 = Release|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Debug|ARM64.Build.0 = Debug|ARM64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Debug|x64.ActiveCfg = Debug|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Debug|x64.Build.0 = Debug|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Debug|x86.ActiveCfg = Debug|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Debug|x86.Build.0 = Debug|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Release|ARM64.ActiveCfg = Release|ARM64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Release|ARM64.Build.0 = Release|ARM64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Release|x64.ActiveCfg = Release|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Release|x64.Build.0 = Release|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Release|x86.ActiveCfg = Release|x64
+ {DA473AC1-D274-410D-B177-2E30F77953D1}.Release|x86.Build.0 = Release|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Debug|ARM64.Build.0 = Debug|ARM64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Debug|x64.ActiveCfg = Debug|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Debug|x64.Build.0 = Debug|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Debug|x86.ActiveCfg = Debug|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Debug|x86.Build.0 = Debug|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Release|ARM64.ActiveCfg = Release|ARM64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Release|ARM64.Build.0 = Release|ARM64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Release|x64.ActiveCfg = Release|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Release|x64.Build.0 = Release|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Release|x86.ActiveCfg = Release|x64
+ {8C133101-CA38-4E6F-AE67-C47441E7864E}.Release|x86.Build.0 = Release|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Debug|ARM64.Build.0 = Debug|ARM64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Debug|x64.ActiveCfg = Debug|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Debug|x64.Build.0 = Debug|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Debug|x86.ActiveCfg = Debug|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Debug|x86.Build.0 = Debug|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Release|ARM64.ActiveCfg = Release|ARM64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Release|ARM64.Build.0 = Release|ARM64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Release|x64.ActiveCfg = Release|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Release|x64.Build.0 = Release|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Release|x86.ActiveCfg = Release|x64
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953}.Release|x86.Build.0 = Release|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Debug|ARM64.Build.0 = Debug|ARM64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Debug|x64.ActiveCfg = Debug|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Debug|x64.Build.0 = Debug|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Debug|x86.ActiveCfg = Debug|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Debug|x86.Build.0 = Debug|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Release|ARM64.ActiveCfg = Release|ARM64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Release|ARM64.Build.0 = Release|ARM64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Release|x64.ActiveCfg = Release|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Release|x64.Build.0 = Release|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Release|x86.ActiveCfg = Release|x64
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3}.Release|x86.Build.0 = Release|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Debug|ARM64.Build.0 = Debug|ARM64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Debug|x64.ActiveCfg = Debug|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Debug|x64.Build.0 = Debug|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Debug|x86.ActiveCfg = Debug|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Debug|x86.Build.0 = Debug|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Release|ARM64.ActiveCfg = Release|ARM64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Release|ARM64.Build.0 = Release|ARM64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Release|x64.ActiveCfg = Release|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Release|x64.Build.0 = Release|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Release|x86.ActiveCfg = Release|x64
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267}.Release|x86.Build.0 = Release|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Debug|ARM64.Build.0 = Debug|ARM64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Debug|x64.ActiveCfg = Debug|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Debug|x64.Build.0 = Debug|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Debug|x86.ActiveCfg = Debug|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Debug|x86.Build.0 = Debug|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Release|ARM64.ActiveCfg = Release|ARM64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Release|ARM64.Build.0 = Release|ARM64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Release|x64.ActiveCfg = Release|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Release|x64.Build.0 = Release|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Release|x86.ActiveCfg = Release|x64
+ {50B33187-448C-43D2-8858-5004A84D1F64}.Release|x86.Build.0 = Release|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Debug|ARM64.Build.0 = Debug|ARM64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Debug|x64.ActiveCfg = Debug|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Debug|x64.Build.0 = Debug|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Debug|x86.ActiveCfg = Debug|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Debug|x86.Build.0 = Debug|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Release|ARM64.ActiveCfg = Release|ARM64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Release|ARM64.Build.0 = Release|ARM64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Release|x64.ActiveCfg = Release|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Release|x64.Build.0 = Release|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Release|x86.ActiveCfg = Release|x64
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9}.Release|x86.Build.0 = Release|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Debug|ARM64.Build.0 = Debug|ARM64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Debug|x64.ActiveCfg = Debug|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Debug|x64.Build.0 = Debug|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Debug|x86.ActiveCfg = Debug|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Debug|x86.Build.0 = Debug|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Release|ARM64.ActiveCfg = Release|ARM64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Release|ARM64.Build.0 = Release|ARM64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Release|x64.ActiveCfg = Release|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Release|x64.Build.0 = Release|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Release|x86.ActiveCfg = Release|x64
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5}.Release|x86.Build.0 = Release|x64
{799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|ARM64.Build.0 = Debug|ARM64
{799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|x64.ActiveCfg = Debug|x64
@@ -2518,6 +2683,30 @@ Global
{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x64.Build.0 = Release|x64
{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x86.ActiveCfg = Release|x64
{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x86.Build.0 = Release|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Debug|ARM64.Build.0 = Debug|ARM64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Debug|x64.ActiveCfg = Debug|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Debug|x64.Build.0 = Debug|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Debug|x86.ActiveCfg = Debug|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Debug|x86.Build.0 = Debug|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Release|ARM64.ActiveCfg = Release|ARM64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Release|ARM64.Build.0 = Release|ARM64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Release|x64.ActiveCfg = Release|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Release|x64.Build.0 = Release|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Release|x86.ActiveCfg = Release|x64
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9}.Release|x86.Build.0 = Release|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Debug|ARM64.Build.0 = Debug|ARM64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Debug|x64.ActiveCfg = Debug|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Debug|x64.Build.0 = Debug|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Debug|x86.ActiveCfg = Debug|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Debug|x86.Build.0 = Debug|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Release|ARM64.ActiveCfg = Release|ARM64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Release|ARM64.Build.0 = Release|ARM64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Release|x64.ActiveCfg = Release|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Release|x64.Build.0 = Release|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Release|x86.ActiveCfg = Release|x64
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02}.Release|x86.Build.0 = Release|x64
{C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.ActiveCfg = Debug|ARM64
{C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.Build.0 = Debug|ARM64
{C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.Deploy.0 = Debug|ARM64
@@ -3036,8 +3225,23 @@ Global
{3940AD4D-F748-4BE4-9083-85769CD553EF} = {2F305555-C296-497E-AC20-5FA1B237996A}
{F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} = {2F305555-C296-497E-AC20-5FA1B237996A}
{0014D652-901F-4456-8D65-06FC5F997FB0} = {4C0D0746-BE5B-49EE-BD5D-A7811628AE8B}
+ {2ED5F735-787A-4A86-9B0D-4A2DD06FAAF0} = {89A38AD7-DA2F-46B9-AA9B-C213AA172350}
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC} = {89A38AD7-DA2F-46B9-AA9B-C213AA172350}
+ {89A38AD7-DA2F-46B9-AA9B-C213AA172350} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
+ {95B63A44-6287-4E7E-B96A-2294C21C3230} = {89A38AD7-DA2F-46B9-AA9B-C213AA172350}
+ {9913D6FE-BAA1-4065-9825-C25AB4BB4475} = {89A38AD7-DA2F-46B9-AA9B-C213AA172350}
+ {DA473AC1-D274-410D-B177-2E30F77953D1} = {89A38AD7-DA2F-46B9-AA9B-C213AA172350}
+ {8C133101-CA38-4E6F-AE67-C47441E7864E} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
+ {C055B76D-90FC-46B2-A1C2-B73C30D94953} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
+ {7AB55C3E-7CBB-4544-995C-4C804C132AF3} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
+ {7FBD0D7C-9D72-41C4-803B-4FF0228CA267} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
+ {50B33187-448C-43D2-8858-5004A84D1F64} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
+ {AC5129DF-CC8C-4195-87BA-855A65B6A4D9} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
+ {9F20CFE6-3E70-4EE3-8C31-18DA588E65E5} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
{799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
+ {0EFA84EA-74D5-499C-8496-29BE1CB281B9} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
+ {60779C23-BFF6-4759-98CB-105C8B8F2C02} = {95B63A44-6287-4E7E-B96A-2294C21C3230}
{C32D254F-7597-4CBE-BF74-D922D81CDF29} = {9873BA05-4C41-4819-9283-CF45D795431B}
{02DD46D3-F761-47D9-8894-2D6DA0124650} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{8E23E173-7127-4A5F-9F93-3049F2B68047} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
diff --git a/doc/images/icons/FileActionsMenu.png b/doc/images/icons/FileActionsMenu.png
new file mode 100644
index 000000000000..59677b93885e
Binary files /dev/null and b/doc/images/icons/FileActionsMenu.png differ
diff --git a/doc/images/overview/FileActionsMenu_large.png b/doc/images/overview/FileActionsMenu_large.png
new file mode 100644
index 000000000000..95605ad531e6
Binary files /dev/null and b/doc/images/overview/FileActionsMenu_large.png differ
diff --git a/doc/images/overview/FileActionsMenu_small.png b/doc/images/overview/FileActionsMenu_small.png
new file mode 100644
index 000000000000..c01cf5d327d3
Binary files /dev/null and b/doc/images/overview/FileActionsMenu_small.png differ
diff --git a/doc/images/overview/Original/FileActionsMenu.png b/doc/images/overview/Original/FileActionsMenu.png
new file mode 100644
index 000000000000..e2241e752a23
Binary files /dev/null and b/doc/images/overview/Original/FileActionsMenu.png differ
diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi
index 21855a79363a..adf4a0d906d1 100644
--- a/installer/PowerToysSetup/Common.wxi
+++ b/installer/PowerToysSetup/Common.wxi
@@ -18,6 +18,7 @@
+
diff --git a/installer/PowerToysSetup/FileActionsMenu.wxs b/installer/PowerToysSetup/FileActionsMenu.wxs
new file mode 100644
index 000000000000..00303295ddf9
--- /dev/null
+++ b/installer/PowerToysSetup/FileActionsMenu.wxs
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj
index 7ce39b82cfa7..30ff303c241f 100644
--- a/installer/PowerToysSetup/PowerToysInstaller.wixproj
+++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj
@@ -38,6 +38,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs
call move /Y ..\..\..\Hosts.wxs.bk ..\..\..\Hosts.wxs
+ call move /Y ..\..\..\FileActionsMenu.wxs.bk ..\..\..\FileActionsMenu.wxs
call move /Y ..\..\..\ImageResizer.wxs.bk ..\..\..\ImageResizer.wxs
call move /Y ..\..\..\KeyboardManager.wxs.bk ..\..\..\KeyboardManager.wxs
call move /Y ..\..\..\MouseWithoutBorders.wxs.bk ..\..\..\MouseWithoutBorders.wxs
@@ -99,6 +100,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
+
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index 33dc8d0e550e..aa35b09310d9 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -58,6 +58,7 @@
+
diff --git a/installer/PowerToysSetup/generateAllFileComponents.ps1 b/installer/PowerToysSetup/generateAllFileComponents.ps1
index 3592b14362ba..a9cde4f4b53a 100644
--- a/installer/PowerToysSetup/generateAllFileComponents.ps1
+++ b/installer/PowerToysSetup/generateAllFileComponents.ps1
@@ -164,6 +164,34 @@ Generate-FileComponents -fileListName "ColorPickerAssetsFiles" -wxsFilePath $PSS
Generate-FileList -fileDepsJson "" -fileListName EnvironmentVariablesAssetsFiles -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\EnvironmentVariables"
Generate-FileComponents -fileListName "EnvironmentVariablesAssetsFiles" -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -regroot $registryroot
+#File Actions Menu
+## Plugins
+###Executable actions
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\ExecutableActions\PowerToys.FileActionsMenu.Plugins.ExecutableActions.deps.json" -fileListName executableActionsComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "executableActionsComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###File Content actions
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\FileContentActions\PowerToys.FileActionsMenu.Plugins.FileContentActions.deps.json" -fileListName fileContentActionsComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "fileContentActionsComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###File properties
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\FileProperties\PowerToys.FileActionsMenu.Plugins.FileProperties.deps.json" -fileListName filePropertiesComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "filePropertiesComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###Hashes
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\Hashes\PowerToys.FileActionsMenu.Plugins.Hashes.deps.json" -fileListName hashesComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "hashesComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###Image Clipboard Actions
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\ImageClipboardActions\PowerToys.FileActionsMenu.Plugins.ImageClipboardActions.deps.json" -fileListName imageClipboardActionsComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "imageClipboardActionsComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###Move Copy actions
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\MoveCopyActions\PowerToys.FileActionsMenu.Plugins.MoveCopyActions.deps.json" -fileListName moveCopyActionsComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "moveCopyActionsComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###Path copy
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\PathCopy\PowerToys.FileActionsMenu.Plugins.PathCopy.deps.json" -fileListName pathCopyComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "pathCopyComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+###PowerToys
+Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\FileActionsMenuPlugins\PowerToys\PowerToys.FileActionsMenu.Plugins.PowerToys.deps.json" -fileListName powerToysComponentFiles -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs
+Generate-FileComponents -fileListName "powerToysComponentFiles" -wxsFilePath $PSScriptRoot\FileActionsMenu.wxs -regroot $registryroot
+## Plugins
+
#FileExplorerAdd-ons
Generate-FileList -fileDepsJson "" -fileListName MonacoPreviewHandlerMonacoAssetsFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco"
Generate-FileList -fileDepsJson "" -fileListName MonacoPreviewHandlerCustomLanguagesFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco\customLanguages"
diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp
index d0aca611fda4..82eb8023c4f6 100644
--- a/installer/PowerToysSetupCustomActions/CustomAction.cpp
+++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp
@@ -1128,13 +1128,14 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
- std::array processesToTerminate = {
+ std::array processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.AdvancedPaste.exe",
L"PowerToys.Awake.exe",
L"PowerToys.FancyZones.exe",
L"PowerToys.FancyZonesEditor.exe",
+ L"PowerToys.FileActionsMenu.Ui.exe",
L"PowerToys.FileLocksmithUI.exe",
L"PowerToys.MouseJumpUI.exe",
L"PowerToys.ColorPickerUI.exe",
diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj
index e2de4a40658b..11088e0b9034 100644
--- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj
+++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj
@@ -57,6 +57,7 @@
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Core.wxs"" ""$(ProjectDir)..\PowerToysSetup\Core.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\EnvironmentVariables.wxs"" ""$(ProjectDir)..\PowerToysSetup\EnvironmentVariables.wxs.bk""""
+ call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileActionsMenu.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileActionsMenu.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileExplorerPreview.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileExplorerPreview.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileLocksmith.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileLocksmith.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Hosts.wxs"" ""$(ProjectDir)..\PowerToysSetup\Hosts.wxs.bk""""
diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp
index 02075707b7e4..2e6accd48681 100644
--- a/src/common/GPOWrapper/GPOWrapper.cpp
+++ b/src/common/GPOWrapper/GPOWrapper.cpp
@@ -172,6 +172,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue());
}
+ GpoRuleConfigured GPOWrapper::GetConfiguredFileActionsMenuEnabledValue()
+ {
+ return static_cast(powertoys_gpo::getConfiguredFileActionsMenuEnabledValue());
+ }
GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOnlineAIModelsValue()
{
return static_cast(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h
index 71f9799c857b..e4e55385a033 100644
--- a/src/common/GPOWrapper/GPOWrapper.h
+++ b/src/common/GPOWrapper/GPOWrapper.h
@@ -49,6 +49,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
+ static GpoRuleConfigured GetConfiguredFileActionsMenuEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl
index 256a77a7399f..ebacf408497c 100644
--- a/src/common/GPOWrapper/GPOWrapper.idl
+++ b/src/common/GPOWrapper/GPOWrapper.idl
@@ -53,6 +53,7 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
+ static GpoRuleConfigured GetConfiguredFileActionsMenuEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs
index 6cb91a69ac83..7b837a5a2346 100644
--- a/src/common/GPOWrapperProjection/GPOWrapper.cs
+++ b/src/common/GPOWrapperProjection/GPOWrapper.cs
@@ -57,6 +57,21 @@ public static GpoRuleConfigured GetConfiguredPeekEnabledValue()
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredPeekEnabledValue();
}
+ public static GpoRuleConfigured GetConfiguredFileActionsMenuEnabledValue()
+ {
+ return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredFileActionsMenuEnabledValue();
+ }
+
+ public static GpoRuleConfigured GetConfiguredPowerRenameEnabledValue()
+ {
+ return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredPowerRenameEnabledValue();
+ }
+
+ public static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue()
+ {
+ return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredFileLocksmithEnabledValue();
+ }
+
public static GpoRuleConfigured GetRunPluginEnabledValue(string pluginID)
{
return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID);
diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs
index 5b95af43d83b..4e5904d15160 100644
--- a/src/common/ManagedCommon/ModuleType.cs
+++ b/src/common/ManagedCommon/ModuleType.cs
@@ -13,6 +13,7 @@ public enum ModuleType
CropAndLock,
EnvironmentVariables,
FancyZones,
+ FileActionsMenu,
FileLocksmith,
FindMyMouse,
Hosts,
diff --git a/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj b/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj
index 3929c6061826..edd5595129f3 100644
--- a/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj
+++ b/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj
@@ -1,5 +1,6 @@
+
diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h
index 1c4808ad6a21..5f7f911061c4 100644
--- a/src/common/interop/shared_constants.h
+++ b/src/common/interop/shared_constants.h
@@ -107,6 +107,9 @@ namespace CommonSharedConstants
// Path to the event used by MarkdownPreviewHandler
const wchar_t SVG_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysSvgPreviewResizeEvent-0701a4fc-d5a1-4ee7-b885-f83982c62a0d";
+ // Path to the event used by File Actions Menu
+ const wchar_t SHOW_FILE_ACTIONS_MENU_SHARED_EVENT[] = L"Local\\PowerToysFileActionsMenu-ef5601f4-04d1-432f-92e8-2262d37a013a";
+
// Path to the event used to show Peek
const wchar_t SHOW_PEEK_SHARED_EVENT[] = L"Local\\ShowPeekEvent";
// Path to the event used to terminate Peek
diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h
index cd10b776a616..e0ac054a5ba9 100644
--- a/src/common/utils/gpo.h
+++ b/src/common/utils/gpo.h
@@ -30,6 +30,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
+ const std::wstring POLICY_CONFIGURE_ENABLED_FILE_ACTIONS_MENU = L"ConfigureEnabledUtilityFileActionsMenu";
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_MARKDOWN_PREVIEW = L"ConfigureEnabledUtilityFileExplorerMarkdownPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_MONACO_PREVIEW = L"ConfigureEnabledUtilityFileExplorerMonacoPreview";
@@ -294,6 +295,11 @@ namespace powertoys_gpo {
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH);
}
+ inline gpo_rule_configured_t getConfiguredFileActionsMenuEnabledValue()
+ {
+ return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FILE_ACTIONS_MENU);
+ }
+
inline gpo_rule_configured_t getConfiguredSvgPreviewEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_SVG_PREVIEW);
diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx
index 74b8d9fdc5e9..7e870ec70699 100644
--- a/src/gpo/assets/PowerToys.admx
+++ b/src/gpo/assets/PowerToys.admx
@@ -145,6 +145,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml
index 68da74c6debb..19459ee284a0 100644
--- a/src/gpo/assets/en-US/PowerToys.adml
+++ b/src/gpo/assets/en-US/PowerToys.adml
@@ -231,6 +231,7 @@ If you don't configure this policy, the user takes control over the setting and
STL file thumbnail: Configure enabled state
Hosts file editor: Configure enabled state
Image Resizer: Configure enabled state
+
Keyboard Manager: Configure enabled state
Find My Mouse: Configure enabled state
Mouse Highlighter: Configure enabled state
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Extensions.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Extensions.cs
new file mode 100644
index 000000000000..65d77f64ad98
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Extensions.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace FileActionsMenu.Ui.Helpers
+{
+ public static class Extensions
+ {
+ ///
+ /// Returns if it is not null, otherwise throws an .
+ ///
+ /// The type of the value.
+ /// The value to check and return.
+ /// The specified value.
+ /// When is null.
+ public static T GetOrArgumentNullException(this T? value)
+ {
+ return value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ /// Checks whether the file is an image.
+ ///
+ /// A filename or a file path.
+ /// If the file has an image file extension.
+ public static bool IsImage(this string fileName)
+ {
+ string extension = System.IO.Path.GetExtension(fileName).ToLowerInvariant();
+ return extension switch
+ {
+ ".bmp" => true,
+ ".dib" => true,
+ ".exif" => true,
+ ".gif" => true,
+ ".jfif" => true,
+ ".jpe" => true,
+ ".jpeg" => true,
+ ".jpg" => true,
+ ".jxr" => true,
+ ".png" => true,
+ ".rle" => true,
+ ".tif" => true,
+ ".tiff" => true,
+ ".wdp" => true,
+ _ => false,
+ };
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/FileActionProgressHelper.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/FileActionProgressHelper.cs
new file mode 100644
index 000000000000..a793e2fd3271
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/FileActionProgressHelper.cs
@@ -0,0 +1,226 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Media;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using FileActionsMenu.Helpers.Telemetry;
+using Microsoft.PowerToys.Telemetry;
+using TaskDialog = Microsoft.WindowsAPICodePack.Dialogs.TaskDialog;
+using TaskDialogButton = Microsoft.WindowsAPICodePack.Dialogs.TaskDialogButton;
+using TaskDialogProgressBar = Microsoft.WindowsAPICodePack.Dialogs.TaskDialogProgressBar;
+using TaskDialogProgressBarState = Microsoft.WindowsAPICodePack.Dialogs.TaskDialogProgressBarState;
+
+namespace FileActionsMenu.Helpers
+{
+ ///
+ /// A helper class to show progress of file actions.
+ ///
+ public class FileActionProgressHelper : IDisposable
+ {
+ private readonly TaskDialogProgressBar _progressBar;
+ private readonly TaskDialog _taskDialog;
+ private readonly string _actionName;
+ private TaskDialog? _conflictTaskDialog;
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Opens a new progress dialog.
+ ///
+ /// Name of the action.
+ /// Number of items.
+ /// Action to execute when the action is cancelled.
+ public FileActionProgressHelper(string actionName, int count, Action onCancel)
+ {
+ _actionName = actionName;
+
+ Application.EnableVisualStyles();
+ _progressBar = new()
+ {
+ State = TaskDialogProgressBarState.Normal,
+ Maximum = count,
+ Minimum = 0,
+ Value = 0,
+ };
+
+ TaskDialogButton cancelButton = new()
+ {
+ Text = ResourceHelper.GetResource("Progress.Cancel"),
+ };
+
+ _taskDialog = new()
+ {
+ ProgressBar = _progressBar,
+ Caption = actionName,
+ Text = actionName,
+ Cancelable = true,
+ Controls = { cancelButton },
+ };
+
+ cancelButton.Click += (sender, e) =>
+ {
+ onCancel();
+ _taskDialog.Close();
+ _conflictTaskDialog?.Close();
+ _conflictTaskDialog = null;
+ };
+
+ _taskDialog.Closing += (sender, e) =>
+ {
+ onCancel();
+ _conflictTaskDialog?.Close();
+ _conflictTaskDialog = null;
+ };
+ _taskDialog.StandardButtons = Microsoft.WindowsAPICodePack.Dialogs.TaskDialogStandardButtons.None;
+ Task.Run(() =>
+ {
+ _firstDialogOpened.SetResult();
+ _taskDialog.Show();
+ });
+ }
+
+ public void UpdateProgress(int current, string fileName)
+ {
+ _progressBar.Value = current;
+ _taskDialog.Text = $"{_actionName}: {fileName}";
+ }
+
+ ///
+ /// Shows a conflict dialog.
+ ///
+ /// The conflicting file.
+ /// Action to execute when the user presses "Replace".
+ /// Action to execute when the user presses "Ignore".
+ [STAThread]
+ public async Task Conflict(string fileName, Action onReplace, Action onIgnore)
+ {
+ TaskCompletionSource taskCompletionSource = new();
+
+ _conflictTaskDialog?.Close();
+
+ // Add newline after 45 characters to prevent cutoffs (Thanks copilot)
+ string AddNewlines(string input, int maxLength)
+ {
+ string[] parts = input.Split('\\');
+ string result = string.Empty;
+ string currentLine = string.Empty;
+
+ foreach (string part in parts)
+ {
+ if (currentLine.Length + part.Length + 1 > maxLength)
+ {
+ if (currentLine.Length > 0)
+ {
+ result += currentLine + "\\" + Environment.NewLine;
+ }
+
+ string tempPart = part;
+
+ while (tempPart.Length > maxLength)
+ {
+ result += tempPart.AsSpan(0, maxLength).ToString() + Environment.NewLine;
+ tempPart = tempPart.Substring(maxLength);
+ }
+
+ currentLine = tempPart;
+ }
+ else
+ {
+ if (currentLine.Length > 0)
+ {
+ currentLine += "\\";
+ }
+
+ currentLine += part;
+ }
+ }
+
+ if (currentLine.Length > 0)
+ {
+ result += currentLine;
+ }
+
+ return result;
+ }
+
+ fileName = AddNewlines(fileName, 45);
+
+#pragma warning disable CA1863 // Use 'CompositeFormat'
+ _conflictTaskDialog = new()
+ {
+ Text = string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Progress.Conflict.Content"), fileName),
+ Caption = ResourceHelper.GetResource("Progress.Conflict.Title"),
+ };
+#pragma warning restore CA1863 // Use 'CompositeFormat'
+ TaskDialogButton replaceButton = new()
+ {
+ Text = ResourceHelper.GetResource("Progress.Conflict.Replace"),
+ };
+ replaceButton.Click += (sender, e) =>
+ {
+ onReplace();
+ _progressBar.State = TaskDialogProgressBarState.Normal;
+ taskCompletionSource.SetResult();
+ _conflictTaskDialog.Close();
+ PowerToysTelemetry.Log.WriteEvent(new FileActionsMenuProgressConflictEvent() { ReplaceChosen = true });
+ };
+ TaskDialogButton ignoreButton = new()
+ {
+ Text = ResourceHelper.GetResource("Progress.Conflict.Ignore"),
+ };
+ ignoreButton.Click += (sender, e) =>
+ {
+ onIgnore();
+ _progressBar.State = TaskDialogProgressBarState.Normal;
+ taskCompletionSource.SetResult();
+ _conflictTaskDialog.Close();
+ PowerToysTelemetry.Log.WriteEvent(new FileActionsMenuProgressConflictEvent() { ReplaceChosen = false });
+ };
+ TaskDialogButton cancelButton = new()
+ {
+ Text = ResourceHelper.GetResource("Progress.Cancel"),
+ };
+ cancelButton.Click += (sender, e) =>
+ {
+ taskCompletionSource.SetResult();
+ _taskDialog.Close(Microsoft.WindowsAPICodePack.Dialogs.TaskDialogResult.Cancel);
+ _conflictTaskDialog?.Close();
+ };
+ _conflictTaskDialog.Closing += (sender, e) =>
+ {
+ if (!taskCompletionSource.Task.IsCompleted)
+ {
+ onIgnore();
+ taskCompletionSource.SetResult();
+ }
+ };
+ _progressBar.State = TaskDialogProgressBarState.Paused;
+ _conflictTaskDialog.Controls.Add(replaceButton);
+ _conflictTaskDialog.Controls.Add(ignoreButton);
+ _conflictTaskDialog.Controls.Add(cancelButton);
+ Thread t = new(new ThreadStart(async () =>
+ {
+ await _firstDialogOpened.Task;
+ SystemSounds.Exclamation.Play();
+ Thread.Sleep(100);
+ _conflictTaskDialog.Show();
+ }));
+
+ t.Start();
+ await taskCompletionSource.Task;
+ }
+
+ private TaskCompletionSource _firstDialogOpened = new();
+
+ public void Dispose()
+ {
+ GC.SuppressFinalize(this);
+ _taskDialog.Dispose();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/FileActionsMenu.Helpers.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/FileActionsMenu.Helpers.csproj
new file mode 100644
index 000000000000..2262e9a18289
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/FileActionsMenu.Helpers.csproj
@@ -0,0 +1,51 @@
+
+
+
+
+
+ enable
+ true
+ ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps
+ false
+ false
+ true
+ true
+ app.manifest
+
+
+
+
+ tlbimp
+ 0
+ 1
+ 50a7e9b0-70ef-11d1-b75a-00a0c90564fe
+ 0
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resource.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resource.Designer.cs
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/HashEnums.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/HashEnums.cs
new file mode 100644
index 000000000000..ba18ce28bc97
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/HashEnums.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace FileActionsMenu.Helpers
+{
+ ///
+ /// The enums for hash types and generate or verify mode.
+ ///
+ ///
+ /// New values shall be added to the end of the enum to avoid breaking changes.
+ ///
+ public static class HashEnums
+ {
+ ///
+ /// Hash type
+ ///
+ public enum HashType
+ {
+ MD5,
+ SHA1,
+ SHA256,
+ SHA384,
+ SHA512,
+ SHA3_256,
+ SHA3_384,
+ SHA3_512,
+ CRC32Hex,
+ CRC32Decimal,
+ CRC64Hex,
+ CRC64Decimal,
+ }
+
+ ///
+ /// Generate or verify mode
+ ///
+ public enum GenerateOrVerifyMode
+ {
+ ///
+ /// Hashes are saved in a file called hashes.
+ ///
+ SingleFile,
+
+ ///
+ /// Hashes are saved in multiple files with the same name as the original file.
+ ///
+ MultipleFiles,
+
+ ///
+ /// Hash is in the filename.
+ ///
+ Filename,
+
+ ///
+ /// Hash is in the clipboard.
+ ///
+ Clipboard,
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/IconHelper.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/IconHelper.cs
new file mode 100644
index 000000000000..cd308876c592
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/IconHelper.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Reflection;
+using Microsoft.UI.Xaml.Controls;
+
+namespace FileActionsMenu.Ui.Helpers
+{
+ public sealed class IconHelper
+ {
+ ///
+ /// Gets a from a module name.
+ ///
+ /// Name of the module.
+ /// The containing the icon of the module.
+ public static BitmapIcon GetIconFromModuleName(string moduleName)
+ {
+ var outputDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException();
+ return new BitmapIcon() { UriSource = new Uri(Path.Combine(outputDirectory, "Assets\\Settings\\Icons\\" + moduleName + ".png")), ShowAsMonochrome = false };
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Properties/Resource.Designer.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Properties/Resource.Designer.cs
new file mode 100644
index 000000000000..7e66f4d95e8c
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Properties/Resource.Designer.cs
@@ -0,0 +1,909 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace FileActionsMenu.Helpers.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resource() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FileActionsMenu.Helpers.Properties.Resource", typeof(Resource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Close menu.
+ ///
+ internal static string Close {
+ get {
+ return ResourceManager.GetString("Close", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Error.
+ ///
+ internal static string Error {
+ get {
+ return ResourceManager.GetString("Error", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Adds actions for .exe and .dll files..
+ ///
+ internal static string Executable_Actions_Description {
+ get {
+ return ResourceManager.GetString("Executable_Actions.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Executable actions.
+ ///
+ internal static string Executable_Actions_Name {
+ get {
+ return ResourceManager.GetString("Executable_Actions.Name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Uninstall application.
+ ///
+ internal static string Executable_Actions_Uninstall_Title {
+ get {
+ return ResourceManager.GetString("Executable_Actions.Uninstall.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Collapse folder structure.
+ ///
+ internal static string File_Content_Actions_CollapseFolder_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CollapseFolder.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy file content.
+ ///
+ internal static string File_Content_Actions_CopyContent_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyContent.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to As C escaped string.
+ ///
+ internal static string File_Content_Actions_CopyContentAsCString_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyContentAsCString.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to As base64 encoded data url.
+ ///
+ internal static string File_Content_Actions_CopyContentAsDataUrl_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyContentAsDataUrl.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to As plain text.
+ ///
+ internal static string File_Content_Actions_CopyContentAsPlaintext_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyContentAsPlaintext.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to As URI encoded string.
+ ///
+ internal static string File_Content_Actions_CopyContentAsUriEncoded_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyContentAsUriEncoded.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to As XML encoded string.
+ ///
+ internal static string File_Content_Actions_CopyContentAsXmlEncoded_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyContentAsXmlEncoded.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy folder tree.
+ ///
+ internal static string File_Content_Actions_CopyDirectoryTree_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.CopyDirectoryTree.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enables diverse actions for working with the contents of a file.
+ ///
+ internal static string File_Content_Actions_Description {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to All files.
+ ///
+ internal static string File_Content_Actions_MergeFiles_Dialog_Filter {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.MergeFiles.Dialog.Filter", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Save merged file.
+ ///
+ internal static string File_Content_Actions_MergeFiles_Dialog_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.MergeFiles.Dialog.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Merge files.
+ ///
+ internal static string File_Content_Actions_MergeFiles_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.MergeFiles.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to File content actions.
+ ///
+ internal static string File_Content_Actions_Title {
+ get {
+ return ResourceManager.GetString("File_Content_Actions.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enables actions related to the file properties.
+ ///
+ internal static string File_Properties_Description {
+ get {
+ return ResourceManager.GetString("File_Properties.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to File properties.
+ ///
+ internal static string File_Properties_Title {
+ get {
+ return ResourceManager.GetString("File_Properties.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unblock files.
+ ///
+ internal static string File_Properties_Unblock_Title {
+ get {
+ return ResourceManager.GetString("File_Properties.Unblock.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Decimal.
+ ///
+ internal static string Hashes_CRC_Decimal {
+ get {
+ return ResourceManager.GetString("Hashes.CRC.Decimal", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Hex.
+ ///
+ internal static string Hashes_CRC_Hex {
+ get {
+ return ResourceManager.GetString("Hashes.CRC.Hex", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Adds actions for generating and verifying checksums of files..
+ ///
+ internal static string Hashes_Description {
+ get {
+ return ResourceManager.GetString("Hashes.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Generating checksum.
+ ///
+ internal static string Hashes_Generate_Dialog_Title {
+ get {
+ return ResourceManager.GetString("Hashes.Generate.Dialog.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Checksums.
+ ///
+ internal static string Hashes_Generate_Filename {
+ get {
+ return ResourceManager.GetString("Hashes.Generate.Filename", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Generate checksums.
+ ///
+ internal static string Hashes_Generate_Title_P {
+ get {
+ return ResourceManager.GetString("Hashes.Generate.Title_P", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Generate checksum.
+ ///
+ internal static string Hashes_Generate_Title_S {
+ get {
+ return ResourceManager.GetString("Hashes.Generate.Title_S", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy checksum to clipboard.
+ ///
+ internal static string Hashes_InClipboard_Generate_Title {
+ get {
+ return ResourceManager.GetString("Hashes.InClipboard.Generate.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Compare with checksum in clipboard.
+ ///
+ internal static string Hashes_InClipboard_Verify_Title {
+ get {
+ return ResourceManager.GetString("Hashes.InClipboard.Verify.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace filename with the checksum.
+ ///
+ internal static string Hashes_InFilename_Generate_Title {
+ get {
+ return ResourceManager.GetString("Hashes.InFilename.Generate.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Compare checksum with filename.
+ ///
+ internal static string Hashes_InFilename_Verify_Title {
+ get {
+ return ResourceManager.GetString("Hashes.InFilename.Verify.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Save in multiple files.
+ ///
+ internal static string Hashes_MultipleFiles_Generate_Title {
+ get {
+ return ResourceManager.GetString("Hashes.MultipleFiles.Generate.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Compare checksum with content of same named files.
+ ///
+ internal static string Hashes_MultipleFiles_Verify_Title {
+ get {
+ return ResourceManager.GetString("Hashes.MultipleFiles.Verify.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Save checksums in one single file.
+ ///
+ internal static string Hashes_SingleFile_Generate_Title {
+ get {
+ return ResourceManager.GetString("Hashes.SingleFile.Generate.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Compare with checksums in file called "{0}".
+ ///
+ internal static string Hashes_SingleFile_Verify_Title {
+ get {
+ return ResourceManager.GetString("Hashes.SingleFile.Verify.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Checksum generator/checker.
+ ///
+ internal static string Hashes_Title {
+ get {
+ return ResourceManager.GetString("Hashes.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to One or more checksums are invalid.
+ ///
+ internal static string Hashes_Verify_Dialog_Fail {
+ get {
+ return ResourceManager.GetString("Hashes.Verify.Dialog.Fail", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The file {0} was not found, so the hash couldn't be verified..
+ ///
+ internal static string Hashes_Verify_Dialog_MissingFile {
+ get {
+ return ResourceManager.GetString("Hashes.Verify.Dialog.MissingFile", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to All checksums are valid.
+ ///
+ internal static string Hashes_Verify_Dialog_Success {
+ get {
+ return ResourceManager.GetString("Hashes.Verify.Dialog.Success", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Checksum validation.
+ ///
+ internal static string Hashes_Verify_Dialog_Title {
+ get {
+ return ResourceManager.GetString("Hashes.Verify.Dialog.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify checksums.
+ ///
+ internal static string Hashes_Verify_Title_P {
+ get {
+ return ResourceManager.GetString("Hashes.Verify.Title_P", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify checksum.
+ ///
+ internal static string Hashes_Verify_Title_S {
+ get {
+ return ResourceManager.GetString("Hashes.Verify.Title_S", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to clipboard_image.
+ ///
+ internal static string Image_Clipboard_Actions_CopyFromClipboard_FileName {
+ get {
+ return ResourceManager.GetString("Image_Clipboard_Actions.CopyFromClipboard.FileName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy image from clipboard into folder.
+ ///
+ internal static string Image_Clipboard_Actions_CopyFromClipboard_Title {
+ get {
+ return ResourceManager.GetString("Image_Clipboard_Actions.CopyFromClipboard.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy image to clipboard.
+ ///
+ internal static string Image_Clipboard_Actions_CopyToClipboard_Title {
+ get {
+ return ResourceManager.GetString("Image_Clipboard_Actions.CopyToClipboard.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Adds actions for copying/pasting images to/from the clipboard..
+ ///
+ internal static string Image_Clipboard_Actions_Description {
+ get {
+ return ResourceManager.GetString("Image_Clipboard_Actions.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Image clipboard actions.
+ ///
+ internal static string Image_Clipboard_Actions_Title {
+ get {
+ return ResourceManager.GetString("Image_Clipboard_Actions.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to File Actions menu can't be opened with the selected items..
+ ///
+ internal static string InvalidExplorerItem {
+ get {
+ return ResourceManager.GetString("InvalidExplorerItem", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Error loading plugin located at: .
+ ///
+ internal static string InvalidPlugin {
+ get {
+ return ResourceManager.GetString("InvalidPlugin", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PowerToys File Actions menu.
+ ///
+ internal static string ModuleName {
+ get {
+ return ResourceManager.GetString("ModuleName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copying files.
+ ///
+ internal static string Move_Copy_Actions_CopyTo_Progress {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.CopyTo.Progress", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy to.
+ ///
+ internal static string Move_Copy_Actions_CopyTo_Title {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.CopyTo.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Actions related to copying and moving files.
+ ///
+ internal static string Move_Copy_Actions_Description {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Moving files.
+ ///
+ internal static string Move_Copy_Actions_MoveTo_Progress {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.MoveTo.Progress", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Move to.
+ ///
+ internal static string Move_Copy_Actions_MoveTo_Title {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.MoveTo.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to New Folder.
+ ///
+ internal static string Move_Copy_Actions_NewFolder_FolderName {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.NewFolder.FolderName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to New folder with selection.
+ ///
+ internal static string Move_Copy_Actions_NewFolder_Title {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.NewFolder.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Saving file.
+ ///
+ internal static string Move_Copy_Actions_SaveAs_Progress {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.SaveAs.Progress", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Save file as.
+ ///
+ internal static string Move_Copy_Actions_SaveAs_Title {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.SaveAs.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Move & Copy actions.
+ ///
+ internal static string Move_Copy_Actions_Title {
+ get {
+ return ResourceManager.GetString("Move_Copy_Actions.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy part of path.
+ ///
+ internal static string Path_Copy_CopyPath_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPath.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Comma (",").
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Comma_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Comma.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cancel.
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Custom_Dialog_Cancel {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Custom.Dialog.Cancel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Ok.
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Custom_Dialog_Ok {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Custom.Dialog.Ok", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Choose a custom delimiter.
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Custom_Dialog_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Custom.Dialog.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Custom delimiter.
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Custom_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Custom.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Newline.
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Newline_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Newline.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Semicolon (";").
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Semicolon_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Semicolon.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Space (" ").
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Space_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Space.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy path of files separated by ....
+ ///
+ internal static string Path_Copy_CopyPathSeparatedBy_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.CopyPathSeparatedBy.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Adds the option to copy multiple files delimited by a delimiter or to copy certain parts of a path..
+ ///
+ internal static string Path_Copy_Description {
+ get {
+ return ResourceManager.GetString("Path_Copy.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy path of the containing folder.
+ ///
+ internal static string Path_Copy_DirectoryPath_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.DirectoryPath.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy WSL path of the containing folder.
+ ///
+ internal static string Path_Copy_DirectoryPathWSL_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.DirectoryPathWSL.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy file name.
+ ///
+ internal static string Path_Copy_FileName_File_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.FileName.File.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy folder name.
+ ///
+ internal static string Path_Copy_FileName_Folder_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.FileName.Folder.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy full path ({0}).
+ ///
+ internal static string Path_Copy_FullPath_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.FullPath.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy full WSL path.
+ ///
+ internal static string Path_Copy_FullPathWSL_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.FullPathWSL.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Perform on shortcut file (.lnk).
+ ///
+ internal static string Path_Copy_HandleShortcut_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.HandleShortcut.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Copy path.
+ ///
+ internal static string Path_Copy_Name {
+ get {
+ return ResourceManager.GetString("Path_Copy.Name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolve shortcut to destination file.
+ ///
+ internal static string Path_Copy_ResolveShortcut_Title {
+ get {
+ return ResourceManager.GetString("Path_Copy.ResolveShortcut.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Microsoft Corporation.
+ ///
+ internal static string PluginPublisher {
+ get {
+ return ResourceManager.GetString("PluginPublisher", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Adds entries for launching PowerToys modules..
+ ///
+ internal static string PowerToys_Description {
+ get {
+ return ResourceManager.GetString("PowerToys.Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unlock with File Locksmith.
+ ///
+ internal static string PowerToys_FileLocksmith_Title_P {
+ get {
+ return ResourceManager.GetString("PowerToys.FileLocksmith.Title_P", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unlock with File Locksmith.
+ ///
+ internal static string PowerToys_FileLocksmith_Title_S {
+ get {
+ return ResourceManager.GetString("PowerToys.FileLocksmith.Title_S", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resize with Image Resizer.
+ ///
+ internal static string PowerToys_ImageResizer_Title_P {
+ get {
+ return ResourceManager.GetString("PowerToys.ImageResizer.Title_P", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resize with Image Resizer.
+ ///
+ internal static string PowerToys_ImageResizer_Title_S {
+ get {
+ return ResourceManager.GetString("PowerToys.ImageResizer.Title_S", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PowerToys modules.
+ ///
+ internal static string PowerToys_Name {
+ get {
+ return ResourceManager.GetString("PowerToys.Name", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Rename with PowerRename.
+ ///
+ internal static string PowerToys_PowerRename_Title {
+ get {
+ return ResourceManager.GetString("PowerToys.PowerRename.Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cancel.
+ ///
+ internal static string Progress_Cancel {
+ get {
+ return ResourceManager.GetString("Progress.Cancel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Conflict: {0} already exists..
+ ///
+ internal static string Progress_Conflict_Content {
+ get {
+ return ResourceManager.GetString("Progress.Conflict.Content", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Ignore.
+ ///
+ internal static string Progress_Conflict_Ignore {
+ get {
+ return ResourceManager.GetString("Progress.Conflict.Ignore", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Replace.
+ ///
+ internal static string Progress_Conflict_Replace {
+ get {
+ return ResourceManager.GetString("Progress.Conflict.Replace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Conflict.
+ ///
+ internal static string Progress_Conflict_Title {
+ get {
+ return ResourceManager.GetString("Progress.Conflict.Title", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Properties/Resource.resx b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Properties/Resource.resx
new file mode 100644
index 000000000000..8b0c7390ef6b
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Properties/Resource.resx
@@ -0,0 +1,427 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Close menu
+
+
+ Adds actions for .exe and .dll files.
+ {Locked=".exe",".dll"}
+
+
+ Executable actions
+
+
+ Uninstall application
+
+
+ Collapse folder structure
+
+
+ Copy file content
+
+
+ As C escaped string
+ Subitem of "Copy file content" {Locked="C"}
+
+
+ As base64 encoded data url
+ Subitem of "Copy file content" {Locked="base64"}
+
+
+ As plain text
+ Subitem of "Copy file content"
+
+
+ As URI encoded string
+ Subitem of "Copy file content" {Locked="URI"}
+
+
+ As XML encoded string
+ Subitem of "Copy file content" {Locked="XML"}
+
+
+ Copy folder tree
+ Tree meaning the tree structure of a folder
+
+
+ Enables diverse actions for working with the contents of a file
+
+
+ All files
+
+
+ Save merged file
+
+
+ Merge files
+
+
+ File content actions
+
+
+ Enables actions related to the file properties
+
+
+ File properties
+
+
+ Unblock files
+ For reference see the Windows built-in File properties view for a file downloaded from the internet
+
+
+ Decimal
+
+
+ Hex
+ Acronym for Hexadecimal
+
+
+ Adds actions for generating and verifying checksums of files.
+
+
+ Generating checksum
+
+
+ Checksums
+ This string must be filename compliant
+
+
+ Generate checksums
+
+
+ Generate checksum
+
+
+ Copy checksum to clipboard
+
+
+ Compare with checksum in clipboard
+
+
+ Replace filename with the checksum
+
+
+ Compare checksum with filename
+
+
+ Save in multiple files
+
+
+ Compare checksum with content of same named files
+
+
+ Save checksums in one single file
+
+
+ Compare with checksums in file called "{0}"
+ {0} is a filename
+
+
+ Checksum generator/checker
+
+
+ One or more checksums are invalid
+
+
+ The file {0} was not found, so the hash couldn't be verified.
+
+
+ All checksums are valid
+
+
+ Checksum validation
+
+
+ Verify checksums
+
+
+ Verify checksum
+
+
+ clipboard_image
+ A filename
+
+
+ Copy image from clipboard into folder
+
+
+ Copy image to clipboard
+
+
+ Adds actions for copying/pasting images to/from the clipboard.
+
+
+ Image clipboard actions
+
+
+ PowerToys File Actions menu
+
+
+ Copying files
+
+
+ Copy to
+
+
+ Actions related to copying and moving files
+
+
+ Moving files
+
+
+ Move to
+
+
+ New Folder
+
+
+ New folder with selection
+
+
+ Save file as
+
+
+ Move & Copy actions
+
+
+ Copy part of path
+
+
+ Comma (",")
+
+
+ Cancel
+
+
+ Ok
+
+
+ Choose a custom delimiter
+
+
+ Custom delimiter
+
+
+ Newline
+
+
+ Semicolon (";")
+
+
+ Space (" ")
+
+
+ Copy path of files separated by ...
+
+
+ Adds the option to copy multiple files delimited by a delimiter or to copy certain parts of a path.
+
+
+ Copy path of the containing folder
+
+
+ Copy WSL path of the containing folder
+ WSL = Windows Subsystem for Linux
+
+
+ Copy file name
+
+
+ Copy folder name
+
+
+ Copy full path ({0})
+ {0} is a delimiter. Either "\", "/" or "\\"
+
+
+ Copy full WSL path
+
+
+ Perform on shortcut file (.lnk)
+ {Locked=".lnk"}
+
+
+ Copy path
+
+
+ Resolve shortcut to destination file
+
+
+ Microsoft Corporation
+
+
+ Adds entries for launching PowerToys modules.
+ {Locked="PowerToys"}
+
+
+ Unlock with File Locksmith
+
+
+ Unlock with File Locksmith
+
+
+ Resize with Image Resizer
+ Images meaning pictures, Resizer refers to resizing the scale of an image
+
+
+ Resize with Image Resizer
+ Image meaning a picture, Resizer refers to resizing the scale of an image
+
+
+ PowerToys modules
+ {Locked="PowerToys"}
+
+
+ Rename with PowerRename
+ Renaming files. {Locked="PowerRename"}
+
+
+ Cancel
+ To stop an action
+
+
+ Conflict: {0} already exists.
+ {Locked="{0}"} {0} is a file path
+
+
+ Ignore
+ Ignore a duplicate file
+
+
+ Replace
+ Replace a file
+
+
+ Conflict
+
+
+ Saving file
+
+
+ Error loading plugin located at:
+ Space at the end required. After this message comes a file path
+
+
+ File Actions menu can't be opened with the selected items.
+
+
+ Error
+
+
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/ResourceHelper.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/ResourceHelper.cs
new file mode 100644
index 000000000000..22725c7deebe
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/ResourceHelper.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using FileActionsMenu.Helpers.Properties;
+
+namespace FileActionsMenu.Helpers
+{
+ public static class ResourceHelper
+ {
+ ///
+ /// Gets a resource string from the resource file.
+ /// If the resource is not found, it returns a string with the resource name surrounded by '!!'.
+ ///
+ /// The name of the resource.
+ /// The requested resource.
+ public static string GetResource(string key)
+ {
+ return Resource.ResourceManager.GetString(key, CultureInfo.CurrentCulture) ?? $"!!{key}!!";
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/ShortcutHelper.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/ShortcutHelper.cs
new file mode 100644
index 000000000000..31d4fb31ed88
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/ShortcutHelper.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Shell32;
+
+namespace FileActionsMenu.Ui.Helpers
+{
+ public sealed class ShortcutHelper
+ {
+ ///
+ /// Gets the full path from a shortcut path.
+ ///
+ /// The path of the .lnk file.
+ /// The full path where the shortcut points to.
+ public static string GetFullPathFromShortcut(string shortcutPath)
+ {
+ if (!shortcutPath.EndsWith(".lnk", System.StringComparison.InvariantCulture))
+ {
+ return shortcutPath;
+ }
+
+ Shell shell = new();
+ Folder folder = shell.NameSpace(System.IO.Path.GetDirectoryName(shortcutPath));
+ FolderItem folderItem = folder.ParseName(System.IO.Path.GetFileName(shortcutPath));
+ if (folderItem != null)
+ {
+ ShellLinkObject link = (ShellLinkObject)folderItem.GetLink;
+ return link.Path;
+ }
+
+ return shortcutPath;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCollapseFolderActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCollapseFolderActionInvokedEvent.cs
new file mode 100644
index 000000000000..b2ebbd7116ec
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCollapseFolderActionInvokedEvent.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCollapseFolderActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int CollapsedFilesCount { get; set; }
+
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsCEscapedStringActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsCEscapedStringActionInvokedEvent.cs
new file mode 100644
index 000000000000..ff85d982bc78
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsCEscapedStringActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyContentAsCEscapedStringActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsDataUrlActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsDataUrlActionInvokedEvent.cs
new file mode 100644
index 000000000000..44a62c699812
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsDataUrlActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyContentAsDataUrlActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsPlaintextActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsPlaintextActionInvokedEvent.cs
new file mode 100644
index 000000000000..50704a91d700
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsPlaintextActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyContentAsPlaintextActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsUriEncodedActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsUriEncodedActionInvokedEvent.cs
new file mode 100644
index 000000000000..18f9218c3969
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsUriEncodedActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyContentAsUriEncodedActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsXmlEncodedActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsXmlEncodedActionInvokedEvent.cs
new file mode 100644
index 000000000000..b109f8f84bfa
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyContentAsXmlEncodedActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyContentAsXmlEncodedActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyDirectoryPathActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyDirectoryPathActionInvokedEvent.cs
new file mode 100644
index 000000000000..10e1302cd5a4
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyDirectoryPathActionInvokedEvent.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyDirectoryPathActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public bool IsWSLMode { get; set; }
+
+ public bool ResolveShortcut { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFilePathActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFilePathActionInvokedEvent.cs
new file mode 100644
index 000000000000..f7cfd188b6d9
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFilePathActionInvokedEvent.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyFilePathActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public bool IsWSLMode { get; set; }
+
+ public bool ResolveShortcut { get; set; }
+
+ public string? Delimiter { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent.cs
new file mode 100644
index 000000000000..d6b88fb54ded
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public string? Delimiter { get; set; }
+
+ public bool IsCustomSeparator { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFolderTreeActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFolderTreeActionInvokedEvent.cs
new file mode 100644
index 000000000000..babd6655fa3d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFolderTreeActionInvokedEvent.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyFolderTreeActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public bool IsDriveRoot { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFullPathActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFullPathActionInvokedEvent.cs
new file mode 100644
index 000000000000..b954603295f6
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyFullPathActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyFullPathActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyImageFromClipboardActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyImageFromClipboardActionInvokedEvent.cs
new file mode 100644
index 000000000000..7ecd0f0aee2d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyImageFromClipboardActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyImageFromClipboardActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyImageToClipboardActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyImageToClipboardActionInvokedEvent.cs
new file mode 100644
index 000000000000..e32a9fa36443
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuCopyImageToClipboardActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuCopyImageToClipboardActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuFileLocksmithActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuFileLocksmithActionInvokedEvent.cs
new file mode 100644
index 000000000000..256887bfc27a
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuFileLocksmithActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuFileLocksmithActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuGenerateHashesActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuGenerateHashesActionInvokedEvent.cs
new file mode 100644
index 000000000000..aef19fc9e698
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuGenerateHashesActionInvokedEvent.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuGenerateHashesActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public HashEnums.HashType HashType { get; set; }
+
+ public HashEnums.GenerateOrVerifyMode GenerateMode { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuImageResizerActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuImageResizerActionInvokedEvent.cs
new file mode 100644
index 000000000000..129906d83018
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuImageResizerActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuImageResizerActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuInvokedEvent.cs
new file mode 100644
index 000000000000..99c4f74629a9
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuInvokedEvent.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuInvokedEvent : EventBase, IEvent
+ {
+ public int LoadedPluginsCount { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuMergeContentActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuMergeContentActionInvokedEvent.cs
new file mode 100644
index 000000000000..a19eac0c8fb2
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuMergeContentActionInvokedEvent.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuMergeContentActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public bool HasDifferentExtensions { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuPowerRenameActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuPowerRenameActionInvokedEvent.cs
new file mode 100644
index 000000000000..b2f671fdee4d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuPowerRenameActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuPowerRenameActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuProgressConflictEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuProgressConflictEvent.cs
new file mode 100644
index 000000000000..8439983bddc9
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuProgressConflictEvent.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuProgressConflictEvent : EventBase, IEvent
+ {
+ public bool ReplaceChosen { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuUnblockFilesActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuUnblockFilesActionInvokedEvent.cs
new file mode 100644
index 000000000000..cc1ccf1d24e2
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuUnblockFilesActionInvokedEvent.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuUnblockFilesActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuUninstallActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuUninstallActionInvokedEvent.cs
new file mode 100644
index 000000000000..d8c9a2f8519b
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuUninstallActionInvokedEvent.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public sealed class FileActionsMenuUninstallActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public bool IsCalledFromDesktop { get; set; }
+
+ public bool IsCalledOnShortcut { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuVerifyHashesActionInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuVerifyHashesActionInvokedEvent.cs
new file mode 100644
index 000000000000..f716b9a8d4d8
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/FileActionsMenuVerifyHashesActionInvokedEvent.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ [EventData]
+ public class FileActionsMenuVerifyHashesActionInvokedEvent : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+
+ public HashEnums.HashType HashType { get; set; }
+
+ public HashEnums.GenerateOrVerifyMode VerifyMode { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/IFileActionsMenuItemInvokedEvent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/IFileActionsMenuItemInvokedEvent.cs
new file mode 100644
index 000000000000..fcd8f7a5212d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/IFileActionsMenuItemInvokedEvent.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ public interface IFileActionsMenuItemInvokedEvent : IEvent
+ {
+ public int ItemCount { get; set; }
+
+ public bool HasImagesSelected { get; set; }
+
+ public bool HasFilesSelected { get; set; }
+
+ public bool HasFoldersSelected { get; set; }
+
+ public bool HasExecutableFilesSelected { get; set; }
+
+ public new PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/TelemetryHelper.cs b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/TelemetryHelper.cs
new file mode 100644
index 000000000000..e77972690782
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/Telemetry/TelemetryHelper.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Linq;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FileActionsMenu.Helpers.Telemetry
+{
+ public sealed class TelemetryHelper
+ {
+ ///
+ /// Logs an event.
+ ///
+ /// The type of the event.
+ /// The event
+ /// A list of paths to the selected items.
+ public static void LogEvent(T e, string[] selectedItems)
+ where T : EventBase, IFileActionsMenuItemInvokedEvent
+ {
+ e.HasFilesSelected = selectedItems.Any(File.Exists);
+ e.HasFoldersSelected = selectedItems.Any(Directory.Exists);
+ e.HasExecutableFilesSelected = selectedItems.Any(item => item.EndsWith(".exe", System.StringComparison.InvariantCulture) || item.EndsWith(".dll", System.StringComparison.InvariantCulture));
+ e.HasImagesSelected = selectedItems.Any(item => item.IsImage());
+ e.ItemCount = selectedItems.Length;
+
+ PowerToysTelemetry.Log.WriteEvent(e);
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Helpers/app.manifest b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/app.manifest
new file mode 100644
index 000000000000..08aaa0b6080d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Helpers/app.manifest
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/CheckedMenuItemsDictionary.cs b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/CheckedMenuItemsDictionary.cs
new file mode 100644
index 000000000000..6aad4f800ca0
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/CheckedMenuItemsDictionary.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.UI.Xaml.Controls;
+
+namespace FileActionsMenu.Interfaces
+{
+ ///
+ /// A stub type that represents a dictionary of checked menu items. The key is the uuid of the group. The list contains the corresponding menu item and the action.
+ ///
+ public sealed class CheckedMenuItemsDictionary : Dictionary>
+ {
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/FileActionsMenu.Interfaces.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/FileActionsMenu.Interfaces.csproj
new file mode 100644
index 000000000000..a282acbffec8
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/FileActionsMenu.Interfaces.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Library
+ enable
+ ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps
+ false
+ false
+ true
+ true
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IAction.cs b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IAction.cs
new file mode 100644
index 000000000000..1d6a381953e7
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IAction.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace FileActionsMenu.Interfaces
+{
+ ///
+ /// Interface that represents an action.
+ ///
+ public interface IAction
+ {
+ ///
+ /// Gets or sets the SelectedItems property.
+ /// When the plugin is loaded, the selected items are passed to this property.
+ ///
+ public string[] SelectedItems { get; set; }
+
+ ///
+ /// Gets the title of the action
+ ///
+ public string Title { get; }
+
+ ///
+ /// Gets the type of the action.
+ ///
+ public ItemType Type { get; }
+
+ ///
+ /// Gets the sub menu items. Only has an effect if is .
+ ///
+ public IAction[]? SubMenuItems { get; }
+
+ ///
+ /// Gets the category of the action. Only applies for items in the top level menu.
+ ///
+ public int Category { get; }
+
+ ///
+ /// Gets the icon of the action displayed in the menu.
+ ///
+ public IconElement? Icon { get; }
+
+ ///
+ /// Gets a value indicating whether the action is visible or not.
+ ///
+ public bool IsVisible { get; }
+
+ ///
+ /// Executes the action.
+ ///
+ /// MenuItem that invoked the action.
+ /// EventArgs of the click event.
+ /// (Awaitable) task that indicates when the File Action Menu should close.
+ public Task Execute(object sender, RoutedEventArgs e);
+
+ public enum ItemType
+ {
+ ///
+ /// Single item action.
+ ///
+ SingleItem,
+
+ ///
+ /// Item with sub menu.
+ ///
+ HasSubMenu,
+
+ ///
+ /// Item is a separator. For simplicity should be used.
+ ///
+ Separator,
+
+ ///
+ /// Item is checkable. For simplicity should be used instead of .
+ ///
+ Checkable,
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IActionAndRequestCheckedMenuItems.cs b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IActionAndRequestCheckedMenuItems.cs
new file mode 100644
index 000000000000..6cb531190f48
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IActionAndRequestCheckedMenuItems.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace FileActionsMenu.Interfaces
+{
+ ///
+ /// In addition to the action, it also provides a dictionary of checked menu items.
+ ///
+ public interface IActionAndRequestCheckedMenuItems : IAction
+ {
+ ///
+ /// Gets or sets the dictionary of checked menu items.
+ ///
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get; set; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/ICheckableAction.cs b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/ICheckableAction.cs
new file mode 100644
index 000000000000..1fc1968fc319
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/ICheckableAction.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace FileActionsMenu.Interfaces
+{
+ ///
+ /// Abstract class that represents a checkable action.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "To inherit descriptions")]
+ public abstract class ICheckableAction : IAction
+ {
+ public IAction.ItemType Type => IAction.ItemType.Checkable;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the action is checked or not.
+ ///
+ public abstract bool IsChecked { get; set; }
+
+ ///
+ /// Gets a value indicating whether the action is checked by default. One and only one item in the same group should be checked by default.
+ ///
+ public abstract bool IsCheckedByDefault { get; }
+
+ ///
+ /// Gets a uuid that identifies the group of checkable actions. Only one item in the group can be checked at a time.
+ ///
+ public abstract string? CheckableGroupUUID { get; }
+
+ ///
+ ///
+ ///
+ public abstract string[] SelectedItems { get; set; }
+
+ ///
+ ///
+ ///
+ public abstract string Title { get; }
+
+ ///
+ ///
+ ///
+ public abstract IconElement? Icon { get; }
+
+ ///
+ ///
+ ///
+ public abstract bool IsVisible { get; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IFileActionsMenuPlugin.cs b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IFileActionsMenuPlugin.cs
new file mode 100644
index 000000000000..2df4f07914eb
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/IFileActionsMenuPlugin.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace FileActionsMenu.Interfaces
+{
+ public interface IFileActionsMenuPlugin
+ {
+ ///
+ /// Gets the name of the plugin
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the description of the plugin
+ ///
+ string Description { get; }
+
+ ///
+ /// Gets the author of the plugin
+ ///
+ string Author { get; }
+
+ ///
+ /// Gets the items that will be added to the top level menu
+ ///
+ IAction[] TopLevelMenuActions { get; }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/Separator.cs b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/Separator.cs
new file mode 100644
index 000000000000..325edb7b2957
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Interfaces/Separator.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace FileActionsMenu.Interfaces
+{
+ ///
+ /// A separator in the menu.
+ ///
+ public sealed class Separator : IAction
+ {
+ public string[] SelectedItems { get => []; set => _ = value; }
+
+ public string Title => string.Empty;
+
+ public IAction.ItemType Type => IAction.ItemType.Separator;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/FileActionsMenuPlugin.props b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/FileActionsMenuPlugin.props
new file mode 100644
index 000000000000..b83ca07a7c5f
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/FileActionsMenuPlugin.props
@@ -0,0 +1,27 @@
+
+
+
+
+ Library
+ enable
+ enable
+ ..\..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\FileActionsMenuPlugins\$(FileActionsMenuPluginName)
+ false
+ false
+ true
+ PowerToys.FileActionsMenu.Plugins.$(FileActionsMenuPluginName)
+ PowerToys.FileActionsMenu.Plugins.$(FileActionsMenuPluginName)
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/PluginMain.cs
new file mode 100644
index 000000000000..b3fcf388de3f
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/PluginMain.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.ExecutableActions
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("Executable_Actions.Name");
+
+ public string Description => ResourceHelper.GetResource("Executable_Actions.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new Uninstall(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/PowerToys.FileActionsMenu.Plugins.ExecutableActions.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/PowerToys.FileActionsMenu.Plugins.ExecutableActions.csproj
new file mode 100644
index 000000000000..49ca59f69421
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/PowerToys.FileActionsMenu.Plugins.ExecutableActions.csproj
@@ -0,0 +1,12 @@
+
+
+ ExecutableActions
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/Uninstall.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/Uninstall.cs
new file mode 100644
index 000000000000..d369a04e37fd
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ExecutableActions/Uninstall.cs
@@ -0,0 +1,117 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.Win32;
+
+namespace PowerToys.FileActionsMenu.Plugins.ExecutableActions
+{
+ internal sealed class Uninstall : IAction
+ {
+ private string[]? _selectedItems;
+ private string? _uninstallerPath;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Executable_Actions.Uninstall.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\ue74d" };
+
+ public bool IsVisible => SelectedItems.Length == 1
+ && (SelectedItems[0].EndsWith(".exe", StringComparison.InvariantCulture) || ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0])
+ .EndsWith(".exe", StringComparison.InvariantCulture) || SelectedItems[0].EndsWith(".dll", StringComparison.InvariantCulture) || ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]).EndsWith(".dll", StringComparison.InvariantCulture))
+ && ((_uninstallerPath = GetUninstallerPath(ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]))) is not null);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_uninstallerPath is null)
+ {
+ return Task.CompletedTask;
+ }
+
+ FileActionsMenuUninstallActionInvokedEvent telemetryEvent = new()
+ {
+ IsCalledFromDesktop = SelectedItems[0].Contains(Environment.GetFolderPath(Environment.SpecialFolder.Desktop)) || SelectedItems[0].Contains(Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory)) || SelectedItems[0].Contains(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)),
+ IsCalledOnShortcut = SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCulture),
+ };
+ TelemetryHelper.LogEvent(telemetryEvent, SelectedItems);
+
+ // Thank you Microsoft Copilot!
+ static string[] SplitCommandLine(string commandLine)
+ {
+ return commandLine.Split('"')
+ .Select((element, index) => index % 2 == 0
+ ? element.Split(' ', StringSplitOptions.RemoveEmptyEntries)
+ : [element])
+ .SelectMany(element => element)
+ .ToArray();
+ }
+
+ ProcessStartInfo processStartInfo = new()
+ {
+ FileName = SplitCommandLine(_uninstallerPath)[0],
+ Arguments = string.Join(" ", SplitCommandLine(_uninstallerPath).Skip(1)),
+ UseShellExecute = true,
+ };
+ Process.Start(processStartInfo);
+ return Task.CompletedTask;
+ }
+
+ public string? GetUninstallerPath(string exePath)
+ {
+ string uninstallKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
+ using RegistryKey? rk = Registry.LocalMachine.OpenSubKey(uninstallKey);
+ if (rk == null)
+ {
+ return null;
+ }
+
+ foreach (string skName in rk.GetSubKeyNames())
+ {
+ using RegistryKey? sk = rk.OpenSubKey(skName);
+ if (sk == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ string? value = sk.GetValue("InstallLocation")?.ToString();
+ if (value is null || value == string.Empty)
+ {
+ continue;
+ }
+
+ if ((Path.GetDirectoryName(exePath) ?? string.Empty).Contains(value.TrimEnd('\\')))
+ {
+ return sk.GetValue("UninstallString")?.ToString();
+ }
+ }
+ catch (Exception)
+ {
+ // Ignore exceptions
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsCString.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsCString.cs
new file mode 100644
index 000000000000..74aa5c6bab2a
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsCString.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using FontFamily = Microsoft.UI.Xaml.Media.FontFamily;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class AsCString : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyContentAsCString.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "C", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyContentAsCEscapedStringActionInvokedEvent(), SelectedItems);
+
+ Dictionary escapeSequences = new()
+ {
+ ["\x5C"] = "\\\\",
+ ["\x07"] = "\\a",
+ ["\x08"] = "\\b",
+ ["\x09"] = "\\t",
+ ["\x0A"] = "\\n",
+ ["\x0B"] = "\\v",
+ ["\x0C"] = "\\f",
+ ["\x0D"] = "\\r",
+ ["\x22"] = "\\\"",
+ ["\x27"] = "\\'",
+ };
+
+ string fileContent = File.ReadAllText(SelectedItems[0]);
+
+ foreach (var escapeSequence in escapeSequences)
+ {
+ fileContent = fileContent.Replace(escapeSequence.Key, escapeSequence.Value);
+ }
+
+ System.Windows.Clipboard.SetText(fileContent);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsDataUrl.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsDataUrl.cs
new file mode 100644
index 000000000000..36e5389c3d51
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsDataUrl.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Text.Json;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class AsDataUrl : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyContentAsDataUrl.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\ue71b" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ string mimeType = GetMimeType(Path.GetExtension(SelectedItems[0]));
+ byte[] fileContent = File.ReadAllBytes(SelectedItems[0]);
+ string base64fileContent = Convert.ToBase64String(fileContent);
+ System.Windows.Clipboard.SetText($"data:{mimeType};base64,{base64fileContent}");
+ return Task.CompletedTask;
+ }
+
+ private string GetMimeType(string extension)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyContentAsDataUrlActionInvokedEvent(), SelectedItems);
+
+ Dictionary imageTypes = new()
+ {
+ [".jpg"] = "image/jpeg",
+ [".jpeg"] = "image/jpeg",
+ [".png"] = "image/png",
+ [".gif"] = "image/gif",
+ [".bmp"] = "image/bmp",
+ [".svg"] = "image/svg+xml",
+ [".heic"] = "image/heic",
+ [".heif"] = "image/heif",
+ [".svgz"] = "image/svg+xml",
+ [".ico"] = "image/x-icon",
+ [".cur"] = "image/x-icon",
+ [".tif"] = "image/tiff",
+ [".tiff"] = "image/tiff",
+ [".webp"] = "image/webp",
+ [".avif"] = "image/avif",
+ [".apng"] = "image/apng",
+ [".jxl"] = "image/jxl",
+ [".jpe"] = "image/jpeg",
+ [".jfif"] = "image/jpeg",
+ [".pjpeg"] = "image/jpeg",
+ [".pjp"] = "image",
+ };
+
+ if (imageTypes.TryGetValue(extension, out string? value))
+ {
+ return value;
+ }
+
+ JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(GetType())!.Location)!, "monaco_languages.json")));
+ try
+ {
+ return jsonDocument.RootElement.GetProperty("list")
+ .EnumerateArray()
+ .First(predicate =>
+ {
+ return predicate.TryGetProperty("extensions", out JsonElement extensions) && extensions.EnumerateArray().Any(predicate => predicate.GetString() == extension);
+ })
+ .TryGetProperty("mimetypes", out JsonElement mimetypes)
+ ? mimetypes.EnumerateArray()
+ .FirstOrDefault()
+ .GetString() ?? "application/octet-stream"
+ : "application/octet-stream";
+ }
+ catch (InvalidOperationException)
+ {
+ return "application/octet-stream";
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsPlaintext.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsPlaintext.cs
new file mode 100644
index 000000000000..92466f1b9aa0
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsPlaintext.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class AsPlaintext : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyContentAsPlaintext.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\ue97e" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyContentAsPlaintextActionInvokedEvent(), SelectedItems);
+
+ System.Windows.Clipboard.SetText(File.ReadAllText(SelectedItems[0]));
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsURIEncoded.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsURIEncoded.cs
new file mode 100644
index 000000000000..60e071f43fe5
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsURIEncoded.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Web;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using FontFamily = Microsoft.UI.Xaml.Media.FontFamily;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class AsURIEncoded : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyContentAsUriEncoded.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "URI", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyContentAsUriEncodedActionInvokedEvent(), SelectedItems);
+
+ byte[] fileContent = File.ReadAllBytes(SelectedItems[0]);
+
+ System.Windows.Clipboard.SetText(HttpUtility.UrlEncode(fileContent));
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsXmlEncoded.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsXmlEncoded.cs
new file mode 100644
index 000000000000..ce211762d7f4
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/AsXmlEncoded.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Security;
+using System.Text.Json;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+using FontFamily = Microsoft.UI.Xaml.Media.FontFamily;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class AsXmlEncoded : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyContentAsXmlEncoded.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "XML", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyContentAsXmlEncodedActionInvokedEvent(), SelectedItems);
+
+ string fileContent = File.ReadAllText(SelectedItems[0]);
+
+ fileContent = SecurityElement.Escape(fileContent);
+
+ System.Windows.Clipboard.SetText(fileContent);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/CollapseFolderStructure.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/CollapseFolderStructure.cs
new file mode 100644
index 000000000000..a77693ec231a
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/CollapseFolderStructure.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class CollapseFolderStructure : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CollapseFolder.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon() { Glyph = "\uea3c" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && Directory.Exists(SelectedItems[0]);
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool cancelled = false;
+ int numberOfFiles = CountFilesInDirectory(SelectedItems[0]);
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCollapseFolderActionInvokedEvent() { CollapsedFilesCount = numberOfFiles }, SelectedItems);
+
+ FileActionProgressHelper fileActionProgressHelper = new(ResourceHelper.GetResource("File_Content_Actions.CollapseFolder.Title"), numberOfFiles, () => { cancelled = true; });
+
+ int i = 0;
+ foreach (string file in Directory.EnumerateFiles(SelectedItems[0], "*", SearchOption.AllDirectories))
+ {
+ if (cancelled)
+ {
+ break;
+ }
+
+ fileActionProgressHelper.UpdateProgress(i, Path.GetFileName(file));
+ i++;
+
+ if (Path.GetDirectoryName(file) == SelectedItems[0])
+ {
+ continue;
+ }
+
+ if (File.Exists(Path.Combine(SelectedItems[0], Path.GetFileName(file))))
+ {
+ await fileActionProgressHelper.Conflict(file, () => { File.Move(file, Path.Combine(SelectedItems[0], Path.GetFileName(file)), true); }, () => { });
+ }
+ else
+ {
+ File.Move(file, Path.Combine(SelectedItems[0], Path.GetFileName(file)));
+ }
+ }
+
+ foreach (string directory in Directory.GetDirectories(SelectedItems[0]))
+ {
+ if (CountFilesInDirectory(directory) == 0)
+ {
+ Directory.Delete(directory);
+ }
+ }
+ }
+
+ private int CountFilesInDirectory(string directory)
+ {
+ int count = 0;
+ foreach (string file in Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories))
+ {
+ count++;
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/CopyFileContent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/CopyFileContent.cs
new file mode 100644
index 000000000000..e33efda23010
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/CopyFileContent.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class CopyFileContent : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyContent.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.HasSubMenu;
+
+ public IAction[]? SubMenuItems =>
+ [
+ new AsPlaintext(),
+ new AsDataUrl(),
+ new AsCString(),
+ new AsXmlEncoded(),
+ new AsURIEncoded(),
+ ];
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon() { Glyph = "\ue8c8" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/DirectoryTree.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/DirectoryTree.cs
new file mode 100644
index 000000000000..b1ce1a111b59
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/DirectoryTree.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class DirectoryTree : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.CopyDirectoryTree.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon() { Glyph = "\ue8b7" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && Directory.Exists(SelectedItems[0]);
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFolderTreeActionInvokedEvent() { IsDriveRoot = SelectedItems[0].EndsWith(":/", StringComparison.InvariantCulture) }, SelectedItems);
+
+ Process process = new();
+
+ process.StartInfo.FileName = "cmd.exe";
+ process.StartInfo.Arguments = $"/c tree {SelectedItems[0]} /f";
+ process.StartInfo.UseShellExecute = false;
+ process.StartInfo.RedirectStandardOutput = true;
+
+ process.Start();
+
+ string output = process.StandardOutput.ReadToEnd();
+
+ process.WaitForExit();
+
+ System.Windows.Clipboard.SetText(output);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/MergeContent.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/MergeContent.cs
new file mode 100644
index 000000000000..c761c7debb16
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/MergeContent.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Forms;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ internal sealed class MergeContent : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Content_Actions.MergeFiles.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon() { Glyph = "\uea3c" };
+
+ public bool IsVisible => SelectedItems.Length > 1 && !SelectedItems.Any(Directory.Exists);
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool hasDifferentExtensions = false;
+ string extension = Path.GetExtension(SelectedItems[0]);
+ foreach (string item in SelectedItems[1..])
+ {
+ if (extension != Path.GetExtension(item))
+ {
+ hasDifferentExtensions = true;
+ break;
+ }
+ }
+
+ TelemetryHelper.LogEvent(new FileActionsMenuMergeContentActionInvokedEvent() { HasDifferentExtensions = hasDifferentExtensions }, SelectedItems);
+
+ SaveFileDialog saveFileDialog = new();
+ saveFileDialog.Title = ResourceHelper.GetResource("File_Content_Actions.MergeFiles.Dialog.Title");
+ saveFileDialog.InitialDirectory = Path.GetDirectoryName(SelectedItems[0]);
+ DialogResult result = saveFileDialog.ShowDialog();
+
+ if (result != DialogResult.OK)
+ {
+ return;
+ }
+
+ Stream fs = saveFileDialog.OpenFile();
+ foreach (var item in SelectedItems)
+ {
+ using Stream source = File.OpenRead(item);
+ await source.CopyToAsync(fs);
+ }
+
+ fs.Close();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/PluginMain.cs
new file mode 100644
index 000000000000..494df83ce2c4
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/PluginMain.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileContentActions
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("File_Content_Actions.Title");
+
+ public string Description => ResourceHelper.GetResource("File_Content_Actions.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new CopyFileContent(),
+ new DirectoryTree(),
+ new MergeContent(),
+ new CollapseFolderStructure(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/PowerToys.FileActionsMenu.Plugins.FileContentActions.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/PowerToys.FileActionsMenu.Plugins.FileContentActions.csproj
new file mode 100644
index 000000000000..62a917daad22
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileContentActions/PowerToys.FileActionsMenu.Plugins.FileContentActions.csproj
@@ -0,0 +1,13 @@
+
+
+ FileContentActions
+
+
+
+
+
+
+ PreserveNewest
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/PluginMain.cs
new file mode 100644
index 000000000000..c70f6bbd91ff
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/PluginMain.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileProperties
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("File_Properties.Title");
+
+ public string Description => ResourceHelper.GetResource("File_Properties.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new UnblockFiles(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/PowerToys.FileActionsMenu.Plugins.FileProperties.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/PowerToys.FileActionsMenu.Plugins.FileProperties.csproj
new file mode 100644
index 000000000000..d663f0588d34
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/PowerToys.FileActionsMenu.Plugins.FileProperties.csproj
@@ -0,0 +1,7 @@
+
+
+ FileProperties
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/UnblockFiles.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/UnblockFiles.cs
new file mode 100644
index 000000000000..f25741a95abb
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.FileProperties/UnblockFiles.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.FileProperties
+{
+ internal sealed class UnblockFiles : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("File_Properties.Unblock.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 8;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\ue785" };
+
+ public bool IsVisible => SelectedItems.Any(file => File.Exists(file + ":Zone.Identifier"));
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuUnblockFilesActionInvokedEvent(), SelectedItems);
+
+ foreach (string file in SelectedItems)
+ {
+ if (!File.Exists(file + ":Zone.Identifier"))
+ {
+ continue;
+ }
+
+ File.Delete(file + ":Zone.Identifier");
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32.cs
new file mode 100644
index 000000000000..8434594f8d53
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class CRC32(Hashes.HashCallingAction hashCallingAction) : IAction
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+
+ public string[] SelectedItems { get => []; set => _ = value; }
+
+ public string Title => "CRC32";
+
+ public IAction.ItemType Type => IAction.ItemType.HasSubMenu;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "CRC", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems =>
+ [
+ new CRC32Hex(_hashCallingAction),
+ new CRC32Decimal(_hashCallingAction),
+ ];
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32Decimal.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32Decimal.cs
new file mode 100644
index 000000000000..c30cbe2326e8
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32Decimal.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class CRC32Decimal(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => ResourceHelper.GetResource("Hashes.CRC.Decimal");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "10", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.CRC32Decimal, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.CRC32Decimal, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32Hex.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32Hex.cs
new file mode 100644
index 000000000000..64c62d198e7f
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC32Hex.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class CRC32Hex(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => ResourceHelper.GetResource("Hashes.CRC.Hex");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "16", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.CRC32Hex, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.CRC32Hex, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64.cs
new file mode 100644
index 000000000000..45432e729b17
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class CRC64(Hashes.HashCallingAction hashCallingAction) : IAction
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+
+ public string[] SelectedItems { get => []; set => _ = value; }
+
+ public string Title => "CRC64";
+
+ public IAction.ItemType Type => IAction.ItemType.HasSubMenu;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "CRC", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems =>
+ [
+ new CRC64Hex(_hashCallingAction),
+ new CRC64Decimal(_hashCallingAction),
+ ];
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64Decimal.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64Decimal.cs
new file mode 100644
index 000000000000..451919d5c334
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64Decimal.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class CRC64Decimal(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => ResourceHelper.GetResource("Hashes.CRC.Decimal");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "10", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.CRC64Decimal, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.CRC64Decimal, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64Hex.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64Hex.cs
new file mode 100644
index 000000000000..0efa8bb89bea
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/CRC64Hex.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class CRC64Hex(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => ResourceHelper.GetResource("Hashes.CRC.Hex");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "16", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.CRC64Hex, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.CRC64Hex, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/Hashes.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/Hashes.cs
new file mode 100644
index 000000000000..16f94f7bf3a7
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/Hashes.cs
@@ -0,0 +1,390 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.IO.Hashing;
+using System.Text;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using static FileActionsMenu.Helpers.HashEnums;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ public sealed class Hashes(Hashes.HashCallingAction hashCallingAction) : IAction
+ {
+ private readonly HashCallingAction _hashCallingAction = hashCallingAction;
+
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => _hashCallingAction == HashCallingAction.GENERATE ? (SelectedItems.Length == 1 ? ResourceHelper.GetResource("Hashes.Generate.Title_S") : ResourceHelper.GetResource("Hashes.Generate.Title_P")) : (SelectedItems.Length == 1 ? ResourceHelper.GetResource("Hashes.Verify.Title_S") : ResourceHelper.GetResource("Hashes.Verify.Title_P"));
+
+ public IAction.ItemType Type => IAction.ItemType.HasSubMenu;
+
+ public IAction[]? SubMenuItems =>
+ [
+ new SingleFile(_hashCallingAction),
+ new MultipleFiles(_hashCallingAction),
+ new InFilename(_hashCallingAction),
+ new InClipboard(_hashCallingAction),
+ new Separator(),
+ new MD5(_hashCallingAction),
+ new SHA1(_hashCallingAction),
+ new SHA256(_hashCallingAction),
+ new SHA384(_hashCallingAction),
+ new SHA512(_hashCallingAction),
+ new SHA3_256(_hashCallingAction),
+ new SHA3_384(_hashCallingAction),
+ new SHA3_512(_hashCallingAction),
+ new CRC32(_hashCallingAction),
+ new CRC64(_hashCallingAction),
+ ];
+
+ public int Category => 0;
+
+ public IconElement? Icon => _hashCallingAction == HashCallingAction.GENERATE ? new FontIcon { Glyph = "\uE73A" } : new FontIcon { Glyph = "\uE9D5" };
+
+ public bool IsVisible => !SelectedItems.Any(Directory.Exists);
+
+ public enum HashCallingAction
+ {
+ GENERATE,
+ VERIFY,
+ }
+
+ private static (Func HashGeneratorFunction, string FileExtension) GetHashProperties(HashType hashType)
+ {
+ Func hashGeneratorFunction;
+ string fileExtension;
+
+ switch (hashType)
+ {
+ case HashType.MD5:
+#pragma warning disable CA5351
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.MD5.Create().ComputeHash(fs));
+ };
+#pragma warning restore CA5351
+ fileExtension = ".md5";
+ break;
+ case HashType.SHA1:
+#pragma warning disable CA5350
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA1.Create().ComputeHash(fs));
+ };
+#pragma warning restore CA5350
+ fileExtension = ".sha1";
+ break;
+ case HashType.SHA256:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA256.Create().ComputeHash(fs));
+ };
+ fileExtension = ".sha256";
+ break;
+ case HashType.SHA384:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA384.Create().ComputeHash(fs));
+ };
+ fileExtension = ".sha384";
+ break;
+ case HashType.SHA512:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA512.Create().ComputeHash(fs));
+ };
+ fileExtension = ".sha512";
+ break;
+ case HashType.SHA3_256:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA3_256.Create().ComputeHash(fs));
+ };
+ fileExtension = ".sha3-256";
+ break;
+ case HashType.SHA3_384:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA3_384.Create().ComputeHash(fs));
+ };
+ fileExtension = ".sha3-384";
+ break;
+ case HashType.SHA3_512:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ return Convert.ToHexString(System.Security.Cryptography.SHA3_512.Create().ComputeHash(fs));
+ };
+ fileExtension = ".sha3-512";
+ break;
+ case HashType.CRC32Hex:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ Crc32 crc32 = new();
+ crc32.Append(fs);
+ return Convert.ToHexString(crc32.GetCurrentHash());
+ };
+ fileExtension = ".crc32";
+ break;
+ case HashType.CRC32Decimal:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ Crc32 crc32 = new();
+ crc32.Append(fs);
+ return crc32.GetCurrentHashAsUInt32().ToString(CultureInfo.InvariantCulture);
+ };
+ fileExtension = ".crc32";
+ break;
+ case HashType.CRC64Hex:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ Crc64 crc64 = new();
+ crc64.Append(fs);
+ return Convert.ToHexString(crc64.GetCurrentHash());
+ };
+ fileExtension = ".crc64";
+ break;
+ case HashType.CRC64Decimal:
+ hashGeneratorFunction = (filename) =>
+ {
+ using FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
+ Crc64 crc64 = new();
+ crc64.Append(fs);
+ return crc64.GetCurrentHashAsUInt64().ToString(CultureInfo.InvariantCulture);
+ };
+ fileExtension = ".crc64";
+ break;
+ default:
+ throw new InvalidOperationException("Unknown hash type");
+ }
+
+ return (hashGeneratorFunction, fileExtension);
+ }
+
+ public static Task VerifyHashes(HashType hashType, string[] selectedItems, CheckedMenuItemsDictionary checkedMenuItemsDictionary)
+ {
+ (Func hashGeneratorFunction, string fileExtension) = GetHashProperties(hashType);
+ List<(MenuFlyoutItemBase, IAction)> checkedMenuItems = checkedMenuItemsDictionary[GetUUID(HashCallingAction.VERIFY)];
+
+ IAction checkedMenuItemAction = checkedMenuItems.First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2;
+
+ bool valid = checkedMenuItemAction switch
+ {
+ SingleFile => VerifySingleFileWithHashes(selectedItems, hashGeneratorFunction, fileExtension),
+ MultipleFiles => VerifyMultipleFilesWithHashes(selectedItems, hashGeneratorFunction, fileExtension),
+ InFilename => VerifyHashesInFilenames(selectedItems, hashGeneratorFunction),
+ InClipboard => Clipboard.GetText() == hashGeneratorFunction(selectedItems[0]),
+ _ => throw new InvalidOperationException("Unknown checked menu item"),
+ };
+
+ GenerateOrVerifyMode verifyMode = checkedMenuItemAction switch
+ {
+ SingleFile => GenerateOrVerifyMode.SingleFile,
+ MultipleFiles => GenerateOrVerifyMode.MultipleFiles,
+ InFilename => GenerateOrVerifyMode.Filename,
+ InClipboard => GenerateOrVerifyMode.Clipboard,
+ _ => throw new InvalidOperationException("Unknown checked menu item"),
+ };
+
+ TelemetryHelper.LogEvent(new FileActionsMenuVerifyHashesActionInvokedEvent() { HashType = hashType, VerifyMode = verifyMode }, selectedItems);
+
+ if (valid)
+ {
+ MessageBox.Show(ResourceHelper.GetResource("Hashes.Verify.Dialog.Success"), ResourceHelper.GetResource("Hashes.Verify.Dialog.Title"), MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ else
+ {
+ MessageBox.Show(ResourceHelper.GetResource("Hashes.Verify.Dialog.Fail"), ResourceHelper.GetResource("Hashes.Verify.Dialog.Title"), MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public static async Task GenerateHashes(HashType hashType, string[] selectedItems, CheckedMenuItemsDictionary checkedMenuItemsDictionary)
+ {
+ (Func hashGeneratorFunction, string fileExtension) = GetHashProperties(hashType);
+ List<(MenuFlyoutItemBase, IAction)> checkedMenuItems = checkedMenuItemsDictionary[GetUUID(HashCallingAction.GENERATE)];
+
+ IAction checkedMenuItemAction = checkedMenuItems.First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2;
+
+ switch (checkedMenuItemAction)
+ {
+ case SingleFile:
+ await GenerateSingleFileWithHashes(selectedItems, hashGeneratorFunction, fileExtension);
+ break;
+ case MultipleFiles:
+ GenerateMultipleFilesWithHashes(selectedItems, hashGeneratorFunction, fileExtension);
+ break;
+ case InFilename:
+ GenerateHashesInFilenames(selectedItems, hashGeneratorFunction);
+ break;
+ case InClipboard:
+ Clipboard.SetText(hashGeneratorFunction(selectedItems[0]));
+ break;
+ default:
+ throw new InvalidOperationException("Unknown checked menu item");
+ }
+
+ GenerateOrVerifyMode generateMode = checkedMenuItemAction switch
+ {
+ SingleFile => GenerateOrVerifyMode.SingleFile,
+ MultipleFiles => GenerateOrVerifyMode.MultipleFiles,
+ InFilename => GenerateOrVerifyMode.Filename,
+ InClipboard => GenerateOrVerifyMode.Clipboard,
+ _ => throw new InvalidOperationException("Unknown checked menu item"),
+ };
+
+ TelemetryHelper.LogEvent(new FileActionsMenuGenerateHashesActionInvokedEvent() { HashType = hashType, GenerateMode = generateMode }, selectedItems);
+ }
+
+ private static void GenerateHashesInFilenames(string[] selectedItems, Func hashGeneratorFunction)
+ {
+ foreach (string filename in selectedItems)
+ {
+ string hash = hashGeneratorFunction(filename);
+
+ File.Move(filename, Path.Combine(Path.GetDirectoryName(filename) ?? string.Empty, hash + Path.GetExtension(filename)), true);
+ }
+ }
+
+ private static bool VerifyHashesInFilenames(string[] selectedItems, Func hashGeneratorFunction)
+ {
+ foreach (string filename in selectedItems)
+ {
+ string hash = hashGeneratorFunction(filename);
+
+ return Path.GetFileNameWithoutExtension(filename) == hash;
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private static async Task GenerateSingleFileWithHashes(string[] selectedItems, Func hashGeneratorFunction, string fileExtension)
+ {
+ FileActionProgressHelper fileActionProgressHelper = new(ResourceHelper.GetResource("Hashes.Generate.Dialog.Title"), 1, () => { });
+ fileActionProgressHelper.UpdateProgress(0, ResourceHelper.GetResource("Hashes.Generate.Filename") + fileExtension);
+
+ StringBuilder fileContent = new();
+
+ foreach (string filename in selectedItems)
+ {
+ fileContent.Append(filename + ":\n" + hashGeneratorFunction(filename) + "\n\n");
+ }
+
+ if (File.Exists(Path.GetDirectoryName(selectedItems[0]).GetOrArgumentNullException() + "\\" + ResourceHelper.GetResource("Hashes.Generate.Filename") + fileExtension))
+ {
+ await fileActionProgressHelper.Conflict(Path.GetDirectoryName(selectedItems[0]).GetOrArgumentNullException() + "\\Checksums" + fileExtension, () => File.WriteAllText(Path.GetDirectoryName(selectedItems[0]).GetOrArgumentNullException() + "\\" + ResourceHelper.GetResource("Hashes.Generate.Filename") + fileExtension, fileContent.ToString()), () => { });
+ }
+ else
+ {
+ File.WriteAllText(Path.GetDirectoryName(selectedItems[0]).GetOrArgumentNullException() + "\\" + ResourceHelper.GetResource("Hashes.Generate.Filename") + fileExtension, fileContent.ToString());
+ }
+ }
+
+ private static bool VerifySingleFileWithHashes(string[] selectedItems, Func hashGeneratorFunction, string fileExtension)
+ {
+ string checksumsFilename = Path.GetDirectoryName(selectedItems[0]).GetOrArgumentNullException() + "\\" + ResourceHelper.GetResource("Hashes.Generate.Filename") + fileExtension;
+
+ if (!File.Exists(checksumsFilename))
+ {
+#pragma warning disable CA1863 // Use "CompositeFormat"
+ MessageBox.Show(string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Hashes.Verify.Dialog.MissingFile"), checksumsFilename), ResourceHelper.GetResource("Hashes.Verify.Dialog.Title"), MessageBoxButton.OK);
+#pragma warning restore CA1863 // Use "CompositeFormat"
+
+ return false;
+ }
+
+ string[] checksums = File.ReadAllLines(checksumsFilename);
+
+ foreach (string filename in selectedItems)
+ {
+ string hash = hashGeneratorFunction(filename);
+
+ if (!checksums.Contains(filename + ":"))
+ {
+ return false;
+ }
+
+ if (checksums[Array.IndexOf(checksums, filename + ":") + 1] != hash)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static void GenerateMultipleFilesWithHashes(string[] selectedItems, Func hashGeneratorFunction, string fileExtension)
+ {
+ foreach (string filename in selectedItems)
+ {
+ string hash = hashGeneratorFunction(filename);
+
+ string hashFilename = filename + fileExtension;
+
+ if (File.Exists(hashFilename))
+ {
+ File.Delete(hashFilename);
+ }
+
+ File.WriteAllText(hashFilename, hash);
+ }
+ }
+
+ private static bool VerifyMultipleFilesWithHashes(string[] selectedItems, Func hashGeneratorFunction, string fileExtension)
+ {
+ foreach (string filename in selectedItems)
+ {
+ string hash = hashGeneratorFunction(filename);
+
+ string hashFilename = filename + fileExtension;
+
+ if (!File.Exists(hashFilename))
+ {
+#pragma warning disable CA1863 // Use "CompositeFormat"
+ MessageBox.Show(string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Hashes.Verify.Dialog.MissingFile"), hashFilename), ResourceHelper.GetResource("Hashes.Verify.Dialog.Title"), MessageBoxButton.OK);
+#pragma warning restore CA1863 // Use "CompositeFormat"
+
+ return false;
+ }
+
+ if (File.ReadAllText(hashFilename) != hash)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static string GetUUID(HashCallingAction hashCallingAction)
+ {
+ return hashCallingAction == HashCallingAction.GENERATE ? "2a89265d-a55a-4a48-b35f-a48f3e8bc2ea" : "2a89265d-a55a-4a48-b35f-a48f3e8bc2eb";
+ }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException("Inaccessible");
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/InClipboard.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/InClipboard.cs
new file mode 100644
index 000000000000..90d98ec7895d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/InClipboard.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class InClipboard(Hashes.HashCallingAction hashCallingAction) : ICheckableAction
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+
+ public override string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public override string Title => _hashCallingAction == Hashes.HashCallingAction.GENERATE ? ResourceHelper.GetResource("Hashes.InClipboard.Generate.Title") : ResourceHelper.GetResource("Hashes.InClipboard.Verify.Title");
+
+ public override IconElement? Icon => new FontIcon { Glyph = "\uf0e3" };
+
+ public override bool IsVisible => SelectedItems.Length == 1;
+
+ private bool _isChecked;
+
+ public override bool IsChecked { get => _isChecked; set => _isChecked = value; }
+
+ public override bool IsCheckedByDefault => false;
+
+ public override string? CheckableGroupUUID => Hashes.GetUUID(_hashCallingAction);
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/InFilename.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/InFilename.cs
new file mode 100644
index 000000000000..55c2253b6846
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/InFilename.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class InFilename(Hashes.HashCallingAction hashCallingAction) : ICheckableAction
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+
+ public override string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public override string Title => _hashCallingAction == Hashes.HashCallingAction.GENERATE ? ResourceHelper.GetResource("Hashes.InFilename.Generate.Title") : ResourceHelper.GetResource("Hashes.InFilename.Verify.Title");
+
+ public override IconElement? Icon => new FontIcon { Glyph = "\ue8ac" };
+
+ public override bool IsVisible => true;
+
+ private bool _isChecked;
+
+ public override bool IsChecked { get => _isChecked; set => _isChecked = value; }
+
+ public override bool IsCheckedByDefault => false;
+
+ public override string? CheckableGroupUUID => Hashes.GetUUID(_hashCallingAction);
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/MD5.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/MD5.cs
new file mode 100644
index 000000000000..99d6fc4dd711
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/MD5.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class MD5(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "MD5";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "MD5", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.MD5, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.MD5, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/MultipleFiles.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/MultipleFiles.cs
new file mode 100644
index 000000000000..b8cda84bbed0
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/MultipleFiles.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class MultipleFiles(Hashes.HashCallingAction hashCallingAction) : ICheckableAction
+ {
+ private string[]? _selectedItems;
+
+ public override string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public override string Title => hashCallingAction == Hashes.HashCallingAction.GENERATE ? ResourceHelper.GetResource("Hashes.MultipleFiles.Generate.Title") : ResourceHelper.GetResource("Hashes.MultipleFiles.Verify.Title");
+
+ public override IconElement? Icon => new FontIcon { Glyph = "\ued43" };
+
+ public override bool IsVisible => true;
+
+ private bool _isChecked;
+
+ public override bool IsChecked { get => _isChecked; set => _isChecked = value; }
+
+ public override bool IsCheckedByDefault => false;
+
+ public override string? CheckableGroupUUID => Hashes.GetUUID(hashCallingAction);
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/PluginMain.cs
new file mode 100644
index 000000000000..93bd940b0e67
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/PluginMain.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("Hashes.Title");
+
+ public string Description => ResourceHelper.GetResource("Hashes.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new Hashes(Hashes.HashCallingAction.GENERATE),
+ new Hashes(Hashes.HashCallingAction.VERIFY),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/PowerToys.FileActionsMenu.Plugins.Hashes.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/PowerToys.FileActionsMenu.Plugins.Hashes.csproj
new file mode 100644
index 000000000000..8ee4b7d1c76c
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/PowerToys.FileActionsMenu.Plugins.Hashes.csproj
@@ -0,0 +1,11 @@
+
+
+ Hashes
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA1.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA1.cs
new file mode 100644
index 000000000000..48ad9cd952a6
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA1.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA1(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA1";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA1, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA1, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA256.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA256.cs
new file mode 100644
index 000000000000..29982314b6fa
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA256.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA256(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA256";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA256, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA256, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA384.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA384.cs
new file mode 100644
index 000000000000..49d053e7a59c
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA384.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA384(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA384";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA384, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA384, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_256.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_256.cs
new file mode 100644
index 000000000000..960678ca0b53
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_256.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA3_256(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA3-256";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA3_256, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA3_256, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_384.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_384.cs
new file mode 100644
index 000000000000..8d2d450ead24
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_384.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA3_384(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA3-384";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA3_384, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA3_384, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_512.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_512.cs
new file mode 100644
index 000000000000..6765ea2e05c1
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA3_512.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA3_512(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA3-512";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.VERIFY)
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA3_512, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA3_512, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA512.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA512.cs
new file mode 100644
index 000000000000..926b5a3f1a5c
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SHA512.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SHA512(Hashes.HashCallingAction hashCallingAction) : IActionAndRequestCheckedMenuItems
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public string Title => "SHA512";
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public int Category => 0;
+
+ public IconElement? Icon => new FontIcon { Glyph = "SHA", FontFamily = FontFamily.XamlAutoFontFamily };
+
+ public bool IsVisible => true;
+
+ public IAction[]? SubMenuItems { get; }
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ if (_hashCallingAction == Hashes.HashCallingAction.GENERATE)
+ {
+ await Hashes.GenerateHashes(HashEnums.HashType.SHA512, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ else
+ {
+ await Hashes.VerifyHashes(HashEnums.HashType.SHA512, SelectedItems, CheckedMenuItemsDictionary);
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SingleFile.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SingleFile.cs
new file mode 100644
index 000000000000..5b485dd6dfae
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.Hashes/SingleFile.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.Hashes
+{
+ internal sealed class SingleFile(Hashes.HashCallingAction hashCallingAction) : ICheckableAction
+ {
+ private readonly Hashes.HashCallingAction _hashCallingAction = hashCallingAction;
+ private string[]? _selectedItems;
+
+ public override string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+#pragma warning disable CA1863 // Use 'CompositeFormat'
+ public override string Title => _hashCallingAction == Hashes.HashCallingAction.GENERATE ? ResourceHelper.GetResource("Hashes.SingleFile.Generate.Title") : string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Hashes.SingleFile.Verify.Title"), ResourceHelper.GetResource("Hashes.Generate.Filename"));
+#pragma warning restore CA1863 // Use 'CompositeFormat'
+
+ public override IconElement? Icon => new FontIcon { Glyph = "\ue8a5" };
+
+ public override bool IsVisible => true;
+
+ private bool _isChecked;
+
+ public override bool IsChecked { get => _isChecked; set => _isChecked = value; }
+
+ public override bool IsCheckedByDefault => true;
+
+ public override string? CheckableGroupUUID => Hashes.GetUUID(_hashCallingAction);
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/CopyImageFromClipboardToFolder.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/CopyImageFromClipboardToFolder.cs
new file mode 100644
index 000000000000..36cf9a8b7279
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/CopyImageFromClipboardToFolder.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using System.Windows.Media.Imaging;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.ImageClipboardActions
+{
+ internal sealed class CopyImageFromClipboardToFolder : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Image_Clipboard_Actions.CopyFromClipboard.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 4;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\ue8de" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && Directory.Exists(SelectedItems[0]) && Clipboard.ContainsImage();
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyImageFromClipboardActionInvokedEvent(), SelectedItems);
+
+ if (!Directory.Exists(SelectedItems[0]) || !Clipboard.ContainsImage())
+ {
+ return Task.CompletedTask;
+ }
+
+ string fileName = ResourceHelper.GetResource("Image_Clipboard_Actions.CopyFromClipboard.FileName");
+ string path = Path.Combine(SelectedItems[0], $"{fileName}.png");
+ int i = 1;
+ while (File.Exists(path))
+ {
+ path = Path.Combine(SelectedItems[0], $"{fileName} ({i}).png");
+ i++;
+ }
+
+ BitmapSource source = Clipboard.GetImage();
+ using var fileStream = new FileStream(path, FileMode.Create);
+ BitmapEncoder encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(source));
+ encoder.Save(fileStream);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/CopyImageToClipboard.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/CopyImageToClipboard.cs
new file mode 100644
index 000000000000..ac4584c1e97e
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/CopyImageToClipboard.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.ImageClipboardActions
+{
+ internal sealed class CopyImageToClipboard : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Image_Clipboard_Actions.CopyToClipboard.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 4;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\ue8e5" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && SelectedItems[0].IsImage();
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyImageToClipboardActionInvokedEvent(), SelectedItems);
+
+ Clipboard.SetImage(new System.Windows.Media.Imaging.BitmapImage(new Uri(SelectedItems[0])));
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/PluginMain.cs
new file mode 100644
index 000000000000..49ab115c69df
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/PluginMain.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.ImageClipboardActions
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("Image_Clipboard_Actions.Title");
+
+ public string Description => ResourceHelper.GetResource("Image_Clipboard_Actions.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new CopyImageToClipboard(),
+ new CopyImageFromClipboardToFolder(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions.csproj
new file mode 100644
index 000000000000..c60b8975dcf6
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions/PowerToys.FileActionsMenu.Plugins.ImageClipboardActions.csproj
@@ -0,0 +1,7 @@
+
+
+ ImageClipboardActions
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/CopyTo.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/CopyTo.cs
new file mode 100644
index 000000000000..f77886edd115
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/CopyTo.cs
@@ -0,0 +1,135 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Forms;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.MoveCopyActions
+{
+ internal sealed class CopyTo : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Move_Copy_Actions.CopyTo.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 1;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\uF413" };
+
+ public bool IsVisible => true;
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ FolderBrowserDialog dialog = new()
+ {
+ AddToRecent = false,
+ Description = ResourceHelper.GetResource("Move_Copy_Actions.CopyTo.Title"),
+ UseDescriptionForTitle = true,
+ AutoUpgradeEnabled = true,
+ ShowNewFolderButton = true,
+ SelectedPath = Path.GetDirectoryName(SelectedItems[0]) ?? Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
+ };
+
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ bool cancelled = false;
+ FileActionProgressHelper fileActionProgressHelper = new(ResourceHelper.GetResource("Move_Copy_Actions.CopyTo.Progress"), SelectedItems.Length, () => { cancelled = true; });
+
+ int i = -1;
+ foreach (string item in SelectedItems)
+ {
+ if (cancelled)
+ {
+ return;
+ }
+
+ i++;
+
+ if (File.Exists(item))
+ {
+ fileActionProgressHelper.UpdateProgress(i, Path.GetFileName(item));
+
+ string destination = Path.Combine(dialog.SelectedPath, Path.GetFileName(item));
+
+ if (item == destination)
+ {
+ continue;
+ }
+
+ if (File.Exists(destination))
+ {
+ await fileActionProgressHelper.Conflict(destination, () => File.Copy(item, destination, true), () => { });
+ }
+ else
+ {
+ File.Copy(item, destination);
+ }
+ }
+ else if (Directory.Exists(item))
+ {
+ fileActionProgressHelper.UpdateProgress(i, Path.GetFileName(item));
+
+ string destination = Path.Combine(dialog.SelectedPath, Path.GetFileName(item));
+
+ if (item == destination)
+ {
+ continue;
+ }
+
+ if (Directory.Exists(destination))
+ {
+ await fileActionProgressHelper.Conflict(item, () => DirectoryCopy(item, destination, true), () => { });
+ }
+ else
+ {
+ DirectoryCopy(item, destination, true);
+ }
+ }
+ }
+ }
+ }
+
+ private void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
+ {
+ DirectoryInfo dir = new(sourceDirName);
+ DirectoryInfo[] dirs = dir.GetDirectories();
+
+ if (!dir.Exists)
+ {
+ throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName);
+ }
+
+ if (!Directory.Exists(destDirName))
+ {
+ Directory.CreateDirectory(destDirName);
+ }
+
+ FileInfo[] files = dir.GetFiles();
+ foreach (FileInfo file in files)
+ {
+ string tempPath = Path.Combine(destDirName, file.Name);
+ file.CopyTo(tempPath, true);
+ }
+
+ if (copySubDirs)
+ {
+ foreach (DirectoryInfo subdir in dirs)
+ {
+ string tempPath = Path.Combine(destDirName, subdir.Name);
+ DirectoryCopy(subdir.FullName, tempPath, copySubDirs);
+ }
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/MoveTo.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/MoveTo.cs
new file mode 100644
index 000000000000..614d82ba7f08
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/MoveTo.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Forms;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.MoveCopyActions
+{
+ internal sealed class MoveTo : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Move_Copy_Actions.MoveTo.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 1;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\uE8DE" };
+
+ public bool IsVisible => true;
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ FolderBrowserDialog dialog = new()
+ {
+ AddToRecent = false,
+ Description = ResourceHelper.GetResource("Move_Copy_Actions.MoveTo.Title"),
+ UseDescriptionForTitle = true,
+ AutoUpgradeEnabled = true,
+ ShowNewFolderButton = true,
+ SelectedPath = Path.GetDirectoryName(SelectedItems[0]) ?? Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
+ };
+
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ bool cancelled = false;
+ FileActionProgressHelper fileActionProgressHelper = new(ResourceHelper.GetResource("Move_Copy_Actions.MoveTo.Progress"), SelectedItems.Length, () => { cancelled = true; });
+
+ int i = -1;
+ foreach (string item in SelectedItems)
+ {
+ if (cancelled)
+ {
+ return;
+ }
+
+ i++;
+
+ if (File.Exists(item))
+ {
+ fileActionProgressHelper.UpdateProgress(i, Path.GetFileName(item));
+ string destination = Path.Combine(dialog.SelectedPath, Path.GetFileName(item));
+
+ if (item == destination)
+ {
+ continue;
+ }
+
+ if (File.Exists(destination))
+ {
+ await fileActionProgressHelper.Conflict(destination, () => File.Move(item, destination, true), () => { });
+ }
+ else
+ {
+ File.Move(item, destination);
+ }
+ }
+ else if (Directory.Exists(item))
+ {
+ fileActionProgressHelper.UpdateProgress(i, Path.GetFileName(item));
+ string destination = Path.Combine(dialog.SelectedPath, Path.GetFileName(item));
+
+ if (item == destination)
+ {
+ continue;
+ }
+
+ if (Directory.Exists(destination))
+ {
+ await fileActionProgressHelper.Conflict(
+ item,
+ () =>
+ {
+ Directory.Delete(destination, true);
+ Directory.Move(item, destination);
+ },
+ () => { });
+ }
+ else
+ {
+ Directory.Move(item, destination);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/NewFolderWithSelection.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/NewFolderWithSelection.cs
new file mode 100644
index 000000000000..bae22b69532d
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/NewFolderWithSelection.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.MoveCopyActions
+{
+ internal sealed class NewFolderWithSelection : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Move_Copy_Actions.NewFolder.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 1;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\uE8F4" };
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ string path = Path.Combine(Path.GetDirectoryName(SelectedItems[0]) ?? Environment.GetFolderPath(Environment.SpecialFolder.Desktop), ResourceHelper.GetResource("Move_Copy_Actions.NewFolder.FolderName"));
+
+ int i = 0;
+ while (Directory.Exists(path))
+ {
+ if (path.EndsWith(')'))
+ {
+ path = path[..^(3 + i.ToString(CultureInfo.InvariantCulture).Length)];
+ }
+
+ i++;
+ path += " (" + i + ")";
+ }
+
+ Directory.CreateDirectory(path);
+
+ bool cancelled = false;
+ FileActionProgressHelper fileActionProgressHelper = new("Moving files to new folder", SelectedItems.Length, () => { cancelled = true; });
+
+ string append = string.Empty;
+
+ int count = 0;
+ foreach (string item in SelectedItems)
+ {
+ if (cancelled)
+ {
+ return Task.CompletedTask;
+ }
+
+ count++;
+ if (File.Exists(item))
+ {
+ fileActionProgressHelper.UpdateProgress(count, Path.GetFileName(item));
+
+ File.Move(item, Path.Combine(path, Path.GetFileName(item)));
+ }
+ else if (Directory.Exists(item))
+ {
+ fileActionProgressHelper.UpdateProgress(count, Path.GetFileName(item));
+
+ Directory.Move(item, Path.Combine(path, Path.GetFileName(item)));
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/PluginMain.cs
new file mode 100644
index 000000000000..9b30e84b7d18
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/PluginMain.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.MoveCopyActions
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("Move_Copy_Actions.Title");
+
+ public string Description => ResourceHelper.GetResource("Move_Copy_Actions.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new MoveTo(),
+ new CopyTo(),
+ new SaveAs(),
+ new NewFolderWithSelection(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/PowerToys.FileActionsMenu.Plugins.MoveCopyActions.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/PowerToys.FileActionsMenu.Plugins.MoveCopyActions.csproj
new file mode 100644
index 000000000000..b665f42a022c
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/PowerToys.FileActionsMenu.Plugins.MoveCopyActions.csproj
@@ -0,0 +1,7 @@
+
+
+ MoveCopyActions
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/SaveAs.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/SaveAs.cs
new file mode 100644
index 000000000000..9b6f1b1a1047
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.MoveCopyActions/SaveAs.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Forms;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.MoveCopyActions
+{
+ internal sealed class SaveAs : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Move_Copy_Actions.SaveAs.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 1;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\uE792" };
+
+ public bool IsVisible => SelectedItems.Length == 1 && !Directory.Exists(SelectedItems[0]);
+
+ public async Task Execute(object sender, RoutedEventArgs e)
+ {
+ SaveFileDialog dialog = new()
+ {
+ AddToRecent = false,
+ CheckPathExists = true,
+ CheckWriteAccess = true,
+ FileName = Path.GetFileName(SelectedItems[0]),
+ InitialDirectory = Path.GetDirectoryName(SelectedItems[0]) ?? Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
+ OverwritePrompt = false,
+ };
+
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ FileActionProgressHelper fileActionProgressHelper = new(ResourceHelper.GetResource("Move_Copy_Actions.SaveAs.Title"), 1, () => { });
+
+ fileActionProgressHelper.UpdateProgress(0, Path.GetFileName(SelectedItems[0]));
+
+ if (File.Exists(dialog.FileName))
+ {
+ await fileActionProgressHelper.Conflict(dialog.FileName, () => File.Move(SelectedItems[0], dialog.FileName, true), () => { });
+ }
+ else
+ {
+ File.Move(SelectedItems[0], dialog.FileName);
+ }
+
+ dialog.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyDirectoryPath.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyDirectoryPath.cs
new file mode 100644
index 000000000000..c9dc6dab81b9
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyDirectoryPath.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyDirectoryPath : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.DirectoryPath.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ if (Directory.Exists(SelectedItems[0]))
+ {
+ Clipboard.SetText(Directory.GetParent(SelectedItems[0])?.FullName ?? string.Empty);
+ }
+ else
+ {
+ Clipboard.SetText(Path.GetDirectoryName(SelectedItems[0]));
+ }
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyDirectoryPathActionInvokedEvent() { ResolveShortcut = resolveShortcut, IsWSLMode = false }, SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyDirectoryPathWSL.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyDirectoryPathWSL.cs
new file mode 100644
index 000000000000..d9e50cdbb66a
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyDirectoryPathWSL.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyDirectoryPathWSL : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.DirectoryPathWSL.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ string tmpPath = Directory.Exists(SelectedItems[0])
+ ? Directory.GetParent(SelectedItems[0])?.FullName ?? string.Empty
+ : Path.GetDirectoryName(SelectedItems[0]) ?? string.Empty;
+ Clipboard.SetText("/mnt/" + tmpPath[0].ToString().ToLowerInvariant() + tmpPath[1..].Replace("\\", "/").Replace(":/", "/"));
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyDirectoryPathActionInvokedEvent() { ResolveShortcut = resolveShortcut, IsWSLMode = true }, SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFileName.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFileName.cs
new file mode 100644
index 000000000000..63610ae3117b
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFileName.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyFileName : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.FileName." + (Directory.Exists(SelectedItems[0]) ? "Folder" : "File") + ".Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ Clipboard.SetText(Path.GetFileName(SelectedItems[0]));
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFullPathActionInvokedEvent(), SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathBackSlash.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathBackSlash.cs
new file mode 100644
index 000000000000..b168e7ac2944
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathBackSlash.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyFullPathBackSlash : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+#pragma warning disable CA1863 // Use 'CompositeFormat'
+ public string Title => string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Path_Copy.FullPath.Title"), "\\");
+#pragma warning restore CA1863 // Use 'CompositeFormat'
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ Clipboard.SetText(SelectedItems[0]);
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathActionInvokedEvent() { ResolveShortcut = resolveShortcut, IsWSLMode = false, Delimiter = "\\" }, SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathDoubleBackSlash.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathDoubleBackSlash.cs
new file mode 100644
index 000000000000..9764d150a61e
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathDoubleBackSlash.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyFullPathDoubleBackSlash : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+#pragma warning disable CA1863 // Use 'CompositeFormat'
+ public string Title => string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Path_Copy.FullPath.Title"), "\\\\");
+#pragma warning restore CA1863 // Use 'CompositeFormat'
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ Clipboard.SetText(SelectedItems[0].Replace("\\", "\\\\"));
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathActionInvokedEvent() { ResolveShortcut = resolveShortcut, IsWSLMode = false, Delimiter = "\\\\" }, SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathForwardSlash.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathForwardSlash.cs
new file mode 100644
index 000000000000..5bae552c0e53
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathForwardSlash.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyFullPathForwardSlash : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+#pragma warning disable CA1863 // Use 'CompositeFormat'
+ public string Title => string.Format(CultureInfo.InvariantCulture, ResourceHelper.GetResource("Path_Copy.FullPath.Title"), "/");
+#pragma warning restore CA1863 // Use 'CompositeFormat'
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ Clipboard.SetText(SelectedItems[0].Replace("\\", "/"));
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathActionInvokedEvent() { ResolveShortcut = resolveShortcut, IsWSLMode = false, Delimiter = "/" }, SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathWSL.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathWSL.cs
new file mode 100644
index 000000000000..44ca8ffd87cf
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyFullPathWSL.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyFullPathWSL : IActionAndRequestCheckedMenuItems
+ {
+ private string[]? _selectedItems;
+ private CheckedMenuItemsDictionary? _checkedMenuItemsDictionary;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.FullPathWSL.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public CheckedMenuItemsDictionary CheckedMenuItemsDictionary { get => _checkedMenuItemsDictionary.GetOrArgumentNullException(); set => _checkedMenuItemsDictionary = value; }
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ bool resolveShortcut = CheckedMenuItemsDictionary.ContainsKey("f2544fd5-13f7-4d52-b7b4-00a3c70923e6") && CheckedMenuItemsDictionary["f2544fd5-13f7-4d52-b7b4-00a3c70923e6"].First(checkedMenuItems => ((ToggleMenuFlyoutItem)checkedMenuItems.Item1).IsChecked).Item2 is ResolveShortcut;
+ if (SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && resolveShortcut)
+ {
+ SelectedItems[0] = ShortcutHelper.GetFullPathFromShortcut(SelectedItems[0]);
+ }
+
+ Clipboard.SetText("/mnt/" + SelectedItems[0][0].ToString().ToLowerInvariant() + SelectedItems[0][1..].Replace("\\", "/").Replace(":/", "/"));
+
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathActionInvokedEvent() { ResolveShortcut = resolveShortcut, IsWSLMode = true, Delimiter = "/" }, SelectedItems);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPath.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPath.cs
new file mode 100644
index 000000000000..73ba1852f6c7
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPath.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPath : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems
+ {
+ get => _selectedItems.GetOrArgumentNullException();
+ set => _selectedItems = value;
+ }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPath.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.HasSubMenu;
+
+ public IAction[]? SubMenuItems
+ {
+ get
+ {
+ IAction[] items = [
+ new CopyFileName(),
+ new CopyDirectoryPath(),
+ new CopyFullPathBackSlash(),
+ new CopyFullPathDoubleBackSlash(),
+ new CopyFullPathForwardSlash(),
+ new CopyFullPathWSL(),
+ new CopyDirectoryPathWSL(),
+ ];
+
+ if (SelectedItems.Length == 1 && SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase) && !Directory.Exists(SelectedItems[0]))
+ {
+ items = [new ResolveShortcut(), new HandleShortcut(), new Separator(), .. items];
+ }
+
+ return items;
+ }
+ }
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\uF0E3" };
+
+ public bool IsVisible => SelectedItems.Length == 1;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBy.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBy.cs
new file mode 100644
index 000000000000..321a2ae38f62
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBy.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text;
+using System.Windows;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPathSeparatedBy : IAction
+ {
+ private bool _isVisible;
+
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems
+ {
+ get => _selectedItems.GetOrArgumentNullException();
+ set
+ {
+ _selectedItems = value;
+
+ if (value.Length > 1)
+ {
+ _isVisible = true;
+ }
+ }
+ }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.HasSubMenu;
+
+ public IAction[]? SubMenuItems =>
+ [
+ new CopyPathSeparatedBySemicolon(),
+ new CopyPathSeparatedBySpace(),
+ new CopyPathSeparatedByComma(),
+ new CopyPathSeparatedByNewline(),
+ new CopyPathSeparatedByCustom()
+ ];
+
+ public int Category => 2;
+
+ public IconElement? Icon => new FontIcon { Glyph = "\uF0E3" };
+
+ public bool IsVisible => _isVisible;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public static void SeparateFilePathByDelimiterAndAddToClipboard(string delimiter, string[] items)
+ {
+ StringBuilder text = new();
+
+ foreach (string filename in items)
+ {
+ text.Append(filename);
+ text.Append(delimiter);
+ }
+
+ text.Length -= delimiter.Length;
+
+ Clipboard.SetText(text.ToString());
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByComma.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByComma.cs
new file mode 100644
index 000000000000..9d740c157bf6
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByComma.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPathSeparatedByComma : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems ?? throw new ArgumentNullException(nameof(SelectedItems)); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Comma.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent() { Delimiter = ",", IsCustomSeparator = false }, SelectedItems);
+
+ CopyPathSeparatedBy.SeparateFilePathByDelimiterAndAddToClipboard(",", SelectedItems);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByCustom.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByCustom.cs
new file mode 100644
index 000000000000..4dfa157d36e5
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByCustom.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Drawing;
+using System.Windows.Forms;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml.Controls;
+using Button = System.Windows.Forms.Button;
+using RoutedEventArgs = Microsoft.UI.Xaml.RoutedEventArgs;
+using TextBox = System.Windows.Forms.TextBox;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPathSeparatedByCustom : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems ?? throw new ArgumentNullException(nameof(SelectedItems)); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Custom.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TextBox customDelimiterTextBox = new() { Margin = new Padding(2), Location = new Point(0, 0) };
+ Button okButton = new() { Text = ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Custom.Dialog.Ok"), Location = new Point(0, 25), Margin = new Padding(2) };
+ Button cancelButton = new() { Text = ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Custom.Dialog.Cancel"), Margin = new Padding(2), Location = new Point(okButton.Width, 25) };
+ Form window = new()
+ {
+ Text = ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Custom.Dialog.Title"),
+ AcceptButton = okButton,
+ CancelButton = cancelButton,
+ Height = okButton.Height + customDelimiterTextBox.Height + 6,
+ Width = okButton.Width + cancelButton.Width + 18,
+ FormBorderStyle = FormBorderStyle.FixedDialog,
+ MinimizeBox = false,
+ MaximizeBox = false,
+ };
+ okButton.Click += (sender, e) =>
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent() { Delimiter = customDelimiterTextBox.Text, IsCustomSeparator = true }, SelectedItems);
+
+ CopyPathSeparatedBy.SeparateFilePathByDelimiterAndAddToClipboard(customDelimiterTextBox.Text, SelectedItems);
+ window.Close();
+ };
+ cancelButton.Click += (sender, e) => { window.Close(); };
+
+ window.Controls.Add(customDelimiterTextBox);
+ window.Controls.Add(okButton);
+ window.Controls.Add(cancelButton);
+
+ window.Anchor = AnchorStyles.None;
+
+ Rectangle screenRectangle = window.RectangleToScreen(window.ClientRectangle);
+ int titleHeight = screenRectangle.Top - window.Top;
+ window.Height += titleHeight + 10;
+
+ customDelimiterTextBox.Width = window.Width - 14;
+
+ window.ShowDialog();
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByNewline.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByNewline.cs
new file mode 100644
index 000000000000..a0413f1a0a5b
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedByNewline.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPathSeparatedByNewline : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems ?? throw new ArgumentNullException(nameof(SelectedItems)); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Newline.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent() { Delimiter = "\n", IsCustomSeparator = false }, SelectedItems);
+
+ CopyPathSeparatedBy.SeparateFilePathByDelimiterAndAddToClipboard("\n", SelectedItems);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBySemicolon.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBySemicolon.cs
new file mode 100644
index 000000000000..d4efda5126b3
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBySemicolon.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPathSeparatedBySemicolon : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems ?? throw new ArgumentNullException(nameof(SelectedItems)); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Semicolon.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent() { Delimiter = ";", IsCustomSeparator = false }, SelectedItems);
+
+ CopyPathSeparatedBy.SeparateFilePathByDelimiterAndAddToClipboard(";", SelectedItems);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBySpace.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBySpace.cs
new file mode 100644
index 000000000000..d51ea7403647
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/CopyPathSeparatedBySpace.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class CopyPathSeparatedBySpace : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems ?? throw new ArgumentNullException(nameof(SelectedItems)); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("Path_Copy.CopyPathSeparatedBy.Space.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 0;
+
+ public IconElement? Icon => null;
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuCopyFilePathsSeparatedByActionInvokedEvent() { Delimiter = " ", IsCustomSeparator = false }, SelectedItems);
+
+ CopyPathSeparatedBy.SeparateFilePathByDelimiterAndAddToClipboard(" ", SelectedItems);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/HandleShortcut.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/HandleShortcut.cs
new file mode 100644
index 000000000000..08109c2583dd
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/HandleShortcut.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class HandleShortcut : ICheckableAction
+ {
+ private string[]? _selectedItems;
+ private bool _isChecked;
+
+ public override string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public override string Title => ResourceHelper.GetResource("Path_Copy.HandleShortcut.Title");
+
+ public override IconElement? Icon => null;
+
+ public override bool IsVisible => SelectedItems.Length == 1 && SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase);
+
+ public override bool IsChecked { get => _isChecked; set => _isChecked = value; }
+
+ public override bool IsCheckedByDefault => false;
+
+ public override string? CheckableGroupUUID => "f2544fd5-13f7-4d52-b7b4-00a3c70923e6";
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/PluginMain.cs
new file mode 100644
index 000000000000..88f321469f99
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/PluginMain.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("Path_Copy.Name");
+
+ public string Description => ResourceHelper.GetResource("Path_Copy.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new CopyPath(),
+ new CopyPathSeparatedBy(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/PowerToys.FileActionsMenu.Plugins.PathCopy.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/PowerToys.FileActionsMenu.Plugins.PathCopy.csproj
new file mode 100644
index 000000000000..50e86339fc98
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/PowerToys.FileActionsMenu.Plugins.PathCopy.csproj
@@ -0,0 +1,7 @@
+
+
+ PathCopy
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/ResolveShortcut.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/ResolveShortcut.cs
new file mode 100644
index 000000000000..3f0337ac1e3b
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PathCopy/ResolveShortcut.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PathCopy
+{
+ internal sealed class ResolveShortcut : ICheckableAction
+ {
+ private string[]? _selectedItems;
+ private bool _isChecked;
+
+ public override string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public override string Title => ResourceHelper.GetResource("Path_Copy.ResolveShortcut.Title");
+
+ public override IconElement? Icon => null;
+
+ public override bool IsVisible => SelectedItems.Length == 1 && SelectedItems[0].EndsWith(".lnk", StringComparison.InvariantCultureIgnoreCase);
+
+ public override bool IsChecked { get => _isChecked; set => _isChecked = value; }
+
+ public override bool IsCheckedByDefault => true;
+
+ public override string? CheckableGroupUUID => "f2544fd5-13f7-4d52-b7b4-00a3c70923e6";
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/FileLocksmith.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/FileLocksmith.cs
new file mode 100644
index 000000000000..10f833d8854e
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/FileLocksmith.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PowerToys
+{
+ internal sealed class FileLocksmith : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => SelectedItems.Length == 1 ? ResourceHelper.GetResource("PowerToys.FileLocksmith.Title_S") : ResourceHelper.GetResource("PowerToys.FileLocksmith.Title_P");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 3;
+
+ public IconElement? Icon => IconHelper.GetIconFromModuleName("FileLocksmith");
+
+ public bool IsVisible => GPOWrapperProjection.GPOWrapper.GetConfiguredFileLocksmithEnabledValue() != GPOWrapperProjection.GpoRuleConfigured.Disabled && SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Enabled.FileLocksmith;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuFileLocksmithActionInvokedEvent(), SelectedItems);
+
+ SettingsUtils fileLocksmithSettings = new();
+
+ string paths = string.Join("\n", SelectedItems);
+ paths += "\n";
+
+ File.WriteAllText(fileLocksmithSettings.GetSettingsFilePath("File Locksmith", "last-run.log"), string.Empty);
+ using (BinaryWriter streamWriter = new(File.Open(fileLocksmithSettings.GetSettingsFilePath("File Locksmith", "last-run.log"), FileMode.OpenOrCreate), Encoding.ASCII))
+ {
+ foreach (char c in paths)
+ {
+ streamWriter.Write((ushort)c);
+ }
+ }
+
+ Process.Start("PowerToys.FileLocksmithUI.exe");
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/ImageResizer.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/ImageResizer.cs
new file mode 100644
index 000000000000..123ac6311ecd
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/ImageResizer.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace PowerToys.FileActionsMenu.Plugins.PowerToys
+{
+ internal sealed class ImageResizer : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => SelectedItems.Length == 1 ? ResourceHelper.GetResource("PowerToys.ImageResizer.Title_S") : ResourceHelper.GetResource("PowerToys.ImageResizer.Title_P");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 3;
+
+ public IconElement? Icon => IconHelper.GetIconFromModuleName("ImageResizer");
+
+ public bool IsVisible => SelectedItems.All(path => path.IsImage()) && GPOWrapperProjection.GPOWrapper.GetConfiguredImageResizerEnabledValue() != GPOWrapperProjection.GpoRuleConfigured.Disabled && SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Enabled.ImageResizer;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuImageResizerActionInvokedEvent(), SelectedItems);
+
+ StringBuilder arguments = new();
+
+ foreach (string item in SelectedItems)
+ {
+ arguments.Append(CultureInfo.InvariantCulture, $"\"{item}\" ");
+ }
+
+ ProcessStartInfo startInfo = new()
+ {
+ FileName = "PowerToys.ImageResizer.exe",
+ Arguments = arguments.ToString(),
+ UseShellExecute = true,
+ };
+ Process.Start(startInfo);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PluginMain.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PluginMain.cs
new file mode 100644
index 000000000000..105d1cfa641c
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PluginMain.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+
+namespace PowerToys.FileActionsMenu.Plugins.PowerToys
+{
+ public class PluginMain : IFileActionsMenuPlugin
+ {
+ public string Name => ResourceHelper.GetResource("PowerToys.Name");
+
+ public string Description => ResourceHelper.GetResource("PowerToys.Description");
+
+ public string Author => ResourceHelper.GetResource("PluginPublisher");
+
+ public IAction[] TopLevelMenuActions =>
+ [
+ new PowerRename(),
+ new ImageResizer(),
+ new FileLocksmith(),
+ ];
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PowerRename.cs b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PowerRename.cs
new file mode 100644
index 000000000000..547395640989
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PowerRename.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using FileActionsMenu.Ui.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Peek.Common.Models;
+
+namespace PowerToys.FileActionsMenu.Plugins.PowerToys
+{
+ internal sealed class PowerRename : IAction
+ {
+ private string[]? _selectedItems;
+
+ public string[] SelectedItems { get => _selectedItems.GetOrArgumentNullException(); set => _selectedItems = value; }
+
+ public string Title => ResourceHelper.GetResource("PowerToys.PowerRename.Title");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 3;
+
+ public IconElement? Icon => IconHelper.GetIconFromModuleName("PowerRename");
+
+ public bool IsVisible => GPOWrapperProjection.GPOWrapper.GetConfiguredPowerRenameEnabledValue() != GPOWrapperProjection.GpoRuleConfigured.Disabled && SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Enabled.PowerRename;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ TelemetryHelper.LogEvent(new FileActionsMenuPowerRenameActionInvokedEvent(), SelectedItems);
+ _ = RunPowerRename(CreateShellItemArrayFromPaths(SelectedItems));
+ return Task.CompletedTask;
+ }
+
+ [DllImport("\\..\\..\\PowerToys.PowerRenameContextMenu.dll", CharSet = CharSet.Unicode)]
+ public static extern int RunPowerRename(IShellItemArray psiItemArray);
+
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
+ private static extern void SHCreateShellItemArrayFromIDLists(uint cidl, IntPtr[] rgpidl, out IShellItemArray ppsia);
+
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
+ private static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string pszName, IntPtr pbc, out IntPtr ppidl, uint sfgaoIn, out uint psfgaoOut);
+
+ public static IShellItemArray CreateShellItemArrayFromPaths(string[] paths)
+ {
+ IntPtr[] pidls = new IntPtr[paths.Length];
+ for (int i = 0; i < paths.Length; i++)
+ {
+ uint psfgaoOut;
+ SHParseDisplayName(paths[i], IntPtr.Zero, out pidls[i], 0, out psfgaoOut);
+ }
+
+ IShellItemArray sia;
+ SHCreateShellItemArrayFromIDLists((uint)paths.Length, pidls, out sia);
+ return sia;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PowerToys.FileActionsMenu.Plugins.PowerToys.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PowerToys.FileActionsMenu.Plugins.PowerToys.csproj
new file mode 100644
index 000000000000..ec0508a35ca7
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Plugins/PowerToys.FileActionsMenu.Plugins.PowerToys/PowerToys.FileActionsMenu.Plugins.PowerToys.csproj
@@ -0,0 +1,13 @@
+
+
+ PowerToys
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/CloseAction.cs b/src/modules/FileActionsMenu/FileActionsMenu.Ui/CloseAction.cs
new file mode 100644
index 000000000000..80b40299c93a
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/CloseAction.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Interfaces;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace FileActionsMenu.Ui
+{
+ ///
+ /// Action that does nothing, so it just closes the menu.
+ ///
+ internal sealed class CloseAction : IAction
+ {
+ public string[] SelectedItems { get => []; set => _ = value; }
+
+ public string Title => ResourceHelper.GetResource("Close");
+
+ public IAction.ItemType Type => IAction.ItemType.SingleItem;
+
+ public IAction[]? SubMenuItems => null;
+
+ public int Category => 99;
+
+ public IconElement? Icon => new FontIcon() { Glyph = "\uE8BB" };
+
+ public bool IsVisible => true;
+
+ public Task Execute(object sender, RoutedEventArgs e)
+ {
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/ExplorerHelper.cs b/src/modules/FileActionsMenu/FileActionsMenu.Ui/ExplorerHelper.cs
new file mode 100644
index 000000000000..9d08b6d38684
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/ExplorerHelper.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Windows;
+using System.Windows.Navigation;
+using FileActionsMenu.Helpers;
+using Peek.Common.Models;
+using Peek.Helpers;
+using Peek.Helpers.Extensions;
+using Windows.Win32.Foundation;
+
+namespace FileActionsMenu.Ui.Helpers
+{
+ public sealed partial class ExplorerHelper
+ {
+ [LibraryImport("user32.dll")]
+ private static partial IntPtr GetForegroundWindow();
+
+ [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+ ///
+ /// Gets the selected items in the active Windows Explorer window.
+ ///
+ /// An array of paths of the selected items.
+ public static string[] GetSelectedItems()
+ {
+ List selected = [];
+ IShellItemArray? itemArray = default;
+ if (((HWND)GetForegroundWindow()).IsDesktopWindow())
+ {
+ itemArray = FileExplorerHelper.GetItemsFromDesktop((HWND)GetForegroundWindow(), true);
+ }
+ else
+ {
+ // Source: https://stackoverflow.com/questions/14193388/how-to-get-windows-explorers-selected-files-from-within-c
+ string filename;
+
+ foreach (SHDocVw.InternetExplorer window in new SHDocVw.ShellWindows())
+ {
+ filename = Path.GetFileNameWithoutExtension(window.FullName).ToLower(CultureInfo.InvariantCulture);
+ if (filename.Equals("explorer", StringComparison.OrdinalIgnoreCase) && window.HWND == GetForegroundWindow())
+ {
+ checked
+ {
+ itemArray = FileExplorerHelper.GetSelectedItems(new HWND((IntPtr)window.HWND));
+ }
+ }
+ }
+ }
+
+ checked
+ {
+ if (itemArray is null || itemArray.GetCount() == 0)
+ {
+ return [];
+ }
+ }
+
+ try
+ {
+ for (int i = 0; i < itemArray.GetCount(); i++)
+ {
+ IShellItem item = itemArray.GetItemAt(i);
+ selected.Add(item.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH));
+ }
+ }
+ catch (Exception)
+ {
+ MessageBox.Show(ResourceHelper.GetResource("InvalidExplorerItem"), ResourceHelper.GetResource("Error"), MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+
+ return [.. selected];
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenu.Ui.csproj b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenu.Ui.csproj
new file mode 100644
index 000000000000..3571e87a2361
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenu.Ui.csproj
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ WinExe
+ PowerToys.FileActionsMenu.Ui
+ PowerToys File Actions Menu
+ PowerToys File Actions Menu
+ enable
+ ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps
+ false
+ false
+ true
+ true
+ PowerToys.FileActionsMenu.Ui
+ true
+ true
+ app.manifest
+ true
+ None
+ true
+ true
+ PowerToys.FileActionsMenu.Ui.pri
+ FileActionsMenu.Ui
+
+
+
+
+
+
+
+
+
+ tlbimp
+ 1
+ 1
+ eab22ac0-30c1-11cf-a7eb-0000c05bae0b
+ 0
+ false
+ true
+
+
+ tlbimp
+ 0
+ 1
+ 50a7e9b0-70ef-11d1-b75a-00a0c90564fe
+ 0
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ win-x64
+
+
+ win-arm64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/App.xaml b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/App.xaml
new file mode 100644
index 000000000000..cf5fc1aae884
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/App.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/App.xaml.cs b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/App.xaml.cs
new file mode 100644
index 000000000000..c3a4d7fad2df
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/App.xaml.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using FileActionsMenu.Ui.Helpers;
+using Application = Microsoft.UI.Xaml.Application;
+
+namespace FileActionsMenu.Ui
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+ InitializeComponent();
+ if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredFileActionsMenuEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
+ {
+ Environment.Exit(0);
+ return;
+ }
+
+ string[] items = ExplorerHelper.GetSelectedItems();
+ if (items.Length == 0)
+ {
+ Environment.Exit(0);
+ return;
+ }
+
+ _ = new MainWindow(items);
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/MainWindow.xaml b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/MainWindow.xaml
new file mode 100644
index 000000000000..f1b33be61e41
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/MainWindow.xaml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/MainWindow.xaml.cs b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/MainWindow.xaml.cs
new file mode 100644
index 000000000000..a844540e25ab
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/FileActionsMenuXAML/MainWindow.xaml.cs
@@ -0,0 +1,326 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using FileActionsMenu.Helpers;
+using FileActionsMenu.Helpers.Telemetry;
+using FileActionsMenu.Interfaces;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using WinRT.Interop;
+using WinUIEx;
+
+namespace FileActionsMenu.Ui
+{
+ public partial class MainWindow : Window
+ {
+ private readonly CheckedMenuItemsDictionary _checkableMenuItemsIndex = [];
+
+ private readonly MenuFlyout _menu;
+
+ private IAction[] _actions =
+ [
+ new CloseAction(),
+ ];
+
+ private bool _actionStarted;
+ private bool _cancelClose;
+ private bool _cancelCheckableEvent;
+
+ public MainWindow(string[] selectedItems)
+ {
+ InitializeComponent();
+
+ this.SetWindowOpacity(0);
+
+ string[] ignoredDirectories = ["FileActionsMenu.Helpers", "FileActionsMenu.Interfaces", "runtimes", "Peek.Common", "PowerToys.FileActionsMenu.Plugins"];
+
+ string[] pluginPaths = Directory.EnumerateDirectories((Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException()) + "\\FileActionsMenuPlugins").Where(folderName => !ignoredDirectories.Contains(Path.GetFileName(folderName))).ToArray();
+ foreach (string pluginPath in pluginPaths)
+ {
+ try
+ {
+ Assembly plugin = Assembly.LoadFrom(Directory.EnumerateFiles(pluginPath).First(file => Path.GetFileName(file).StartsWith("PowerToys.FileActionsMenu.Plugins", StringComparison.InvariantCultureIgnoreCase) && Path.GetFileName(file).EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)));
+ plugin.GetExportedTypes().Where(type => type.GetInterfaces().Any(i => i.FullName?.EndsWith("IFileActionsMenuPlugin", StringComparison.InvariantCulture) ?? false)).ToList().ForEach(type =>
+ {
+ dynamic pluginInstance = Activator.CreateInstance(type)!;
+ Array.ForEach((IAction[])pluginInstance.TopLevelMenuActions, action => Array.Resize(ref _actions, _actions.Length + 1));
+ pluginInstance.TopLevelMenuActions.CopyTo(_actions, _actions.Length - pluginInstance.TopLevelMenuActions.Length);
+ });
+ }
+ catch (InvalidOperationException)
+ {
+ MessageBox.Show(ResourceHelper.GetResource("InvalidPlugin") + pluginPath, ResourceHelper.GetResource("Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ PowerToysTelemetry.Log.WriteEvent(new FileActionsMenuInvokedEvent()
+ {
+ LoadedPluginsCount = pluginPaths.Length,
+ });
+
+ _menu = new MenuFlyout();
+
+ _menu.Items.Add(new MenuFlyoutItem()
+ {
+ Text = ResourceHelper.GetResource("ModuleName"),
+ IsEnabled = false,
+ });
+ _menu.Items.Add(new MenuFlyoutSeparator());
+
+ Array.Sort(_actions, (a, b) => a.Category.CompareTo(b.Category));
+ int currentCategory = -1;
+
+ void HandleItems(IAction[] actions, object cm, bool firstLayer = true)
+ {
+ foreach (IAction action in actions)
+ {
+ action.SelectedItems = selectedItems;
+ if (action.IsVisible)
+ {
+ // Categories only apply to the first layer of items
+ if (firstLayer && action.Category != currentCategory)
+ {
+ currentCategory = action.Category;
+ (cm as MenuFlyout)!.Items.Add(new MenuFlyoutSeparator());
+ }
+
+ MenuFlyoutItemBase menuItem;
+ menuItem = new MenuFlyoutItem()
+ {
+ Text = action.Title,
+ };
+
+ if (action.Icon != null)
+ {
+ if (action.Icon is BitmapIcon bi)
+ {
+ bi.ShowAsMonochrome = false;
+ }
+
+ ((MenuFlyoutItem)menuItem).Icon = action.Icon;
+ }
+
+ if (action is IActionAndRequestCheckedMenuItems requestCheckedMenuItems)
+ {
+ requestCheckedMenuItems.CheckedMenuItemsDictionary = _checkableMenuItemsIndex;
+ }
+
+ if (action.Type == IAction.ItemType.SingleItem)
+ {
+ if (cm is MenuFlyout flyout)
+ {
+ ((MenuFlyoutItem)menuItem).Click += async (sender, args) =>
+ {
+ await ExecuteActionOfAction(action, sender, args);
+ };
+ flyout.Items.Add(menuItem);
+ }
+ else if (cm is MenuFlyoutSubItem item)
+ {
+ ((MenuFlyoutItem)menuItem).Click += async (sender, args) =>
+ {
+ await ExecuteActionOfAction(action, sender, args);
+ };
+ item.Items.Add(menuItem);
+ }
+ }
+ else if (action.Type == IAction.ItemType.HasSubMenu)
+ {
+ MenuFlyoutSubItem subItem = new()
+ {
+ Text = action.Title,
+ };
+ if (action.Icon != null)
+ {
+ if (action.Icon is BitmapIcon bi)
+ {
+ bi.ShowAsMonochrome = false;
+ }
+
+ subItem.Icon = action.Icon;
+ }
+
+ HandleItems(action.SubMenuItems!, subItem, false);
+
+ if (cm is MenuFlyout menuFlyout)
+ {
+ menuFlyout.Items.Add(subItem);
+ }
+ else if (cm is MenuFlyoutSubItem menuFlyoutSub)
+ {
+ menuFlyoutSub.Items.Add(subItem);
+ }
+ }
+ else if (action.Type == IAction.ItemType.Separator)
+ {
+ if (cm is MenuFlyout menuFlyout)
+ {
+ menuFlyout.Items.Add(new MenuFlyoutSeparator());
+ }
+ else if (cm is MenuFlyoutSubItem menuFlyoutSubItem)
+ {
+ menuFlyoutSubItem.Items.Add(new MenuFlyoutSeparator());
+ }
+ }
+ else if (action.Type == IAction.ItemType.Checkable)
+ {
+ if (action is not ICheckableAction checkableAction || checkableAction.CheckableGroupUUID == null)
+ {
+ throw new InvalidDataException("Action is checkable but does not implement ICheckableAction or ICheckableAction.CheckableGroupUUID is null");
+ }
+
+ ToggleMenuFlyoutItem toggleMenuItem = new()
+ {
+ Text = checkableAction.Title,
+ IsChecked = checkableAction.IsCheckedByDefault,
+ };
+
+ checkableAction.IsChecked = checkableAction.IsCheckedByDefault;
+
+ if (checkableAction.Icon != null)
+ {
+ if (action.Icon is BitmapIcon bi)
+ {
+ bi.ShowAsMonochrome = false;
+ }
+
+ toggleMenuItem.Icon = checkableAction.Icon;
+ }
+
+ if (!_checkableMenuItemsIndex.TryGetValue(checkableAction.CheckableGroupUUID, out List<(MenuFlyoutItemBase, IAction)>? value))
+ {
+ value = [];
+ _checkableMenuItemsIndex[checkableAction.CheckableGroupUUID] = value;
+ }
+
+ value.Add((toggleMenuItem, checkableAction));
+
+ toggleMenuItem.Click += (sender, args) =>
+ {
+ if (_cancelCheckableEvent)
+ {
+ return;
+ }
+
+ _cancelClose = true;
+ _cancelCheckableEvent = true;
+
+ checkableAction.IsChecked = toggleMenuItem.IsChecked;
+
+ foreach ((MenuFlyoutItemBase menuItem, IAction action) in _checkableMenuItemsIndex[checkableAction.CheckableGroupUUID])
+ {
+ if (menuItem is ToggleMenuFlyoutItem toggle)
+ {
+ if (toggle != toggleMenuItem)
+ {
+ toggle.IsChecked = false;
+ }
+ }
+ }
+
+ toggleMenuItem.IsChecked = true;
+ _cancelCheckableEvent = false;
+ };
+
+ if (cm is MenuFlyout menuFlyout)
+ {
+ menuFlyout.Items.Add(toggleMenuItem);
+ }
+ else if (cm is MenuFlyoutSubItem menuFlyoutSubItem)
+ {
+ menuFlyoutSubItem.Items.Add(toggleMenuItem);
+ }
+ }
+ else
+ {
+ throw new InvalidDataException("Unknown value for IAction.Type");
+ }
+ }
+ }
+ }
+
+ HandleItems(_actions, _menu);
+ }
+
+ private async Task ExecuteActionOfAction(IAction action, object sender, RoutedEventArgs e)
+ {
+ _actionStarted = true;
+ try
+ {
+ Task closeAction = action.Execute(sender, e);
+
+ Close();
+
+ await closeAction;
+
+ Environment.Exit(0);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("There was an error executing the action: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ [DllImport("user32.dll")]
+ private static extern bool GetCursorPos(out POINT lpPoint);
+
+ [DllImport("user32.dll")]
+ private static extern int GetDpiForWindow(IntPtr hwnd);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct POINT
+ {
+ public int X;
+ public int Y;
+ }
+
+ public void Window_Activated(object sender, RoutedEventArgs e)
+ {
+ this.Show();
+ this.SetForegroundWindow();
+ this.SetIsAlwaysOnTop(true);
+ this.SetIsShownInSwitchers(false);
+ this.Maximize();
+
+ if (GetCursorPos(out POINT p))
+ {
+ int dpi = GetDpiForWindow(WindowNative.GetWindowHandle(this));
+ double scaleFactor = dpi / 96.0;
+ p.X = (int)(p.X / scaleFactor);
+ p.Y = (int)(p.Y / scaleFactor);
+ _menu.ShowAt((UIElement)sender, new Windows.Foundation.Point(p.X, p.Y));
+ }
+ else
+ {
+ _menu.ShowAt((UIElement)sender, new Windows.Foundation.Point(0, 0));
+ }
+
+ _menu.Closing += (s, e) =>
+ {
+ // Keep open if user clicked on a checkable item
+ if (_cancelClose)
+ {
+ e.Cancel = true;
+ _cancelClose = false;
+ return;
+ }
+
+ if (!_actionStarted)
+ {
+ Close();
+ Environment.Exit(0);
+ }
+ };
+ }
+ }
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu.Ui/app.manifest b/src/modules/FileActionsMenu/FileActionsMenu.Ui/app.manifest
new file mode 100644
index 000000000000..49ee8bb71eed
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu.Ui/app.manifest
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true/PM
+ System
+ true
+
+
+
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.rc b/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.rc
new file mode 100644
index 000000000000..0bcdeca2ef58
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.rc
@@ -0,0 +1,40 @@
+#include
+#include "resource.h"
+#include "../../../../common/version/version.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+#include "winres.h"
+#undef APSTUDIO_READONLY_SYMBOLS
+
+1 VERSIONINFO
+FILEVERSION FILE_VERSION
+PRODUCTVERSION PRODUCT_VERSION
+FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+FILEFLAGS VS_FF_DEBUG
+#else
+FILEFLAGS 0x0L
+#endif
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_DLL
+FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
+ BEGIN
+ VALUE "CompanyName", COMPANY_NAME
+ VALUE "FileDescription", FILE_DESCRIPTION
+ VALUE "FileVersion", FILE_VERSION_STRING
+ VALUE "InternalName", INTERNAL_NAME
+ VALUE "LegalCopyright", COPYRIGHT_NOTE
+ VALUE "OriginalFilename", ORIGINAL_FILENAME
+ VALUE "ProductName", PRODUCT_NAME
+ VALUE "ProductVersion", PRODUCT_VERSION_STRING
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
+ END
+END
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.vcxproj b/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.vcxproj
new file mode 100644
index 000000000000..6f21b4bf779e
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.vcxproj
@@ -0,0 +1,85 @@
+
+
+
+
+ 15.0
+ {3A2398AB-D8F5-49C5-9EA7-483BED398BEC}
+ Win32Proj
+ FileActionsMenu
+ FileActionsMenu
+ 10.0.22621.0
+
+
+
+ DynamicLibrary
+ v143
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\
+ PowerToys.FileActionsMenu
+
+
+
+ EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
+
+
+ $(OutDir)$(TargetName)$(TargetExt)
+ Pathcch.lib;comctl32.lib;shcore.lib;%(AdditionalDependencies)
+ gdi32.dll;shell32.dll;ole32.dll;shlwapi.dll;oleaut32.dll;%(DelayLoadDLLs)
+
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+ {cc6e41ac-8174-4e8a-8d22-85dd7f4851df}
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.vcxproj.filters b/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.vcxproj.filters
new file mode 100644
index 000000000000..5b437a451006
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/FileActionsMenu.vcxproj.filters
@@ -0,0 +1,48 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {875a08c6-f610-4667-bd0f-80171ed96072}
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/dllmain.cpp b/src/modules/FileActionsMenu/FileActionsMenu/dllmain.cpp
new file mode 100644
index 000000000000..e673d4532dd3
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/dllmain.cpp
@@ -0,0 +1,425 @@
+#include "pch.h"
+#include
+#include
+#include
+#include "trace.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+BOOL APIENTRY DllMain(HMODULE /*hModule*/,
+ DWORD ul_reason_for_call,
+ LPVOID /*lpReserved*/)
+{
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ Trace::RegisterProvider();
+ break;
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ case DLL_PROCESS_DETACH:
+ Trace::UnregisterProvider();
+ break;
+ }
+ return TRUE;
+}
+
+namespace
+{
+ const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
+ const wchar_t JSON_KEY_WIN[] = L"win";
+ const wchar_t JSON_KEY_ALT[] = L"alt";
+ const wchar_t JSON_KEY_CTRL[] = L"ctrl";
+ const wchar_t JSON_KEY_SHIFT[] = L"shift";
+ const wchar_t JSON_KEY_CODE[] = L"code";
+ const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"file-actions-menu-shortcut-setting";
+}
+
+// The PowerToy name that will be shown in the settings.
+const static wchar_t* MODULE_NAME = L"FileActionsMenu";
+// Add a description that will we shown in the module settings page.
+const static wchar_t* MODULE_DESC = L"A module that adds an extra menu to Explorer.";
+
+// Implement the PowerToy Module Interface and all the required methods.
+class FileActionsMenu : public PowertoyModuleIface
+{
+private:
+ // The PowerToy state.
+ bool m_enabled = false;
+
+ Hotkey m_hotkey;
+
+ // If we should always try to run FileActionsMenu non-elevated.
+ bool m_alwaysRunNotElevated = true;
+
+ HANDLE m_hProcess = 0;
+ DWORD m_processPid = 0;
+
+ HANDLE m_hInvokeEvent;
+
+ // Load the settings file.
+ void init_settings()
+ {
+ try
+ {
+ // Load and parse the settings file for this PowerToy.
+ PowerToysSettings::PowerToyValues settings =
+ PowerToysSettings::PowerToyValues::load_from_settings_file(L"FileActionsMenu");
+
+ parse_settings(settings);
+ }
+ catch (std::exception&)
+ {
+ // Error while loading from the settings file. Let default values stay as they are.
+ }
+ }
+
+ void parse_settings(PowerToysSettings::PowerToyValues& settings)
+ {
+ auto settingsObject = settings.get_raw_json();
+ if (settingsObject.GetView().Size())
+ {
+ try
+ {
+ auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
+ parse_hotkey(jsonHotkeyObject);
+ }
+ catch (...)
+ {
+ Logger::error("Failed to initialize FileActionsMenu hotkey settings");
+
+ set_default_key_settings();
+ }
+ }
+ else
+ {
+ Logger::info("FileActionsMenu settings are empty");
+ set_default_key_settings();
+ }
+ }
+
+ void set_default_key_settings()
+ {
+ Logger::info("FileActionsMenu is going to use default key settings");
+ m_hotkey.win = false;
+ m_hotkey.alt = true;
+ m_hotkey.shift = false;
+ m_hotkey.ctrl = true;
+ m_hotkey.key = 'A';
+ }
+
+ void parse_hotkey(winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
+ {
+ try
+ {
+ m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
+ m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
+ m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
+ m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
+ m_hotkey.key = static_cast(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
+ }
+ catch (...)
+ {
+ Logger::error("Failed to initialize FileActionsMenu start shortcut");
+ }
+
+ if (!m_hotkey.key)
+ {
+ Logger::info("FileActionsMenu is going to use default shortcut");
+ m_hotkey.win = false;
+ m_hotkey.alt = true;
+ m_hotkey.shift = false;
+ m_hotkey.ctrl = true;
+ m_hotkey.key = 'A';
+ }
+ }
+
+ bool is_desktop_window(HWND windowHandle)
+ {
+ // Similar to the logic in IsDesktopWindow in FileActionsMenu UI. Keep logic synced.
+ // TODO: Refactor into same C++ class consumed by both.
+ wchar_t className[MAX_PATH];
+ if (GetClassName(windowHandle, className, MAX_PATH) == 0)
+ {
+ return false;
+ }
+ if (wcsncmp(className, L"Progman", MAX_PATH) !=0 && wcsncmp(className, L"WorkerW", MAX_PATH) != 0)
+ {
+ return false;
+ }
+ return FindWindowEx(windowHandle, NULL, L"SHELLDLL_DefView", NULL);
+ }
+
+ inline std::wstring GetErrorString(HRESULT handle)
+ {
+ _com_error err(handle);
+ return err.ErrorMessage();
+ }
+
+ bool is_explorer_window(HWND windowHandle)
+ {
+ CComPtr spShellWindows;
+ auto result = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
+ if (result != S_OK || spShellWindows == nullptr)
+ {
+ Logger::warn(L"Failed to create instance. {}", GetErrorString(result));
+ return true; // Might as well assume it's possible it's an explorer window.
+ }
+
+ // Enumerate all Shell Windows to compare the window handle against.
+ IUnknownPtr spEnum{};
+ result = spShellWindows->_NewEnum(&spEnum);
+ if (result != S_OK || spEnum == nullptr)
+ {
+ Logger::warn(L"Failed to list explorer Windows. {}", GetErrorString(result));
+ return true; // Might as well assume it's possible it's an explorer window.
+ }
+
+ IEnumVARIANTPtr spEnumVariant{};
+ result = spEnum.QueryInterface(__uuidof(spEnumVariant), &spEnumVariant);
+ if (result != S_OK || spEnumVariant == nullptr)
+ {
+ Logger::warn(L"Failed to enum explorer Windows. {}", GetErrorString(result));
+ spEnum->Release();
+ return true; // Might as well assume it's possible it's an explorer window.
+ }
+
+ variant_t variantElement{};
+ while (spEnumVariant->Next(1, &variantElement, NULL) == S_OK)
+ {
+ IWebBrowserApp* spWebBrowserApp;
+ result = variantElement.pdispVal->QueryInterface(IID_IWebBrowserApp, reinterpret_cast(&spWebBrowserApp));
+ if (result == S_OK)
+ {
+ HWND hwnd;
+ result = spWebBrowserApp->get_HWND(reinterpret_cast(&hwnd));
+ if (result == S_OK)
+ {
+ if (hwnd == windowHandle)
+ {
+ VariantClear(&variantElement);
+ spWebBrowserApp->Release();
+ spEnumVariant->Release();
+ spEnum->Release();
+ return true;
+ }
+ }
+ spWebBrowserApp->Release();
+ }
+ VariantClear(&variantElement);
+ }
+
+ spEnumVariant->Release();
+ spEnum->Release();
+ return false;
+ }
+
+ bool is_FileActionsMenu_or_explorer_or_desktop_window_focused()
+ {
+ HWND foregroundWindowHandle = GetForegroundWindow();
+ if (foregroundWindowHandle == NULL)
+ {
+ return false;
+ }
+
+ DWORD pid{};
+ if (GetWindowThreadProcessId(foregroundWindowHandle, &pid)!=0)
+ {
+ // If the foreground window is the FileActionsMenu window, send activation signal.
+ if (m_processPid != 0 && pid == m_processPid)
+ {
+ return true;
+ }
+ }
+
+ if (is_desktop_window(foregroundWindowHandle))
+ {
+ return true;
+ }
+
+ return is_explorer_window(foregroundWindowHandle);
+ }
+
+ void launch_process()
+ {
+ Logger::trace(L"Starting FileActionsMenu.UI process");
+
+ std::wstring executable_args = L"";
+
+ const auto modulePath = get_module_folderpath();
+ std::wstring runExecutablePath = modulePath;
+ runExecutablePath += L"\\WinUi3Apps\\PowerToys.FileActionsMenu.Ui.exe";
+ std::optional processStartedInfo = RunNonElevatedFailsafe(runExecutablePath, executable_args, modulePath, PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE);
+ if (processStartedInfo.has_value())
+ {
+ m_processPid = processStartedInfo.value().processID;
+ m_hProcess = processStartedInfo.value().processHandle.release();
+ }
+ else
+ {
+ Logger::error(L"FileActionsMenuViewer failed to start not elevated.");
+ }
+ }
+
+public:
+ FileActionsMenu()
+ {
+ init_settings();
+
+ m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_FILE_ACTIONS_MENU_SHARED_EVENT);
+ };
+
+ ~FileActionsMenu()
+ {
+ if (m_enabled)
+ {
+ }
+ m_enabled = false;
+ };
+
+ // Destroy the powertoy and free memory
+ virtual void destroy() override
+ {
+ delete this;
+ }
+
+ // Return the display name of the powertoy, this will be cached by the runner
+ virtual const wchar_t* get_name() override
+ {
+ return MODULE_NAME;
+ }
+
+ virtual const wchar_t* get_key() override
+ {
+ return MODULE_NAME;
+ }
+
+ // Return the configured status for the gpo policy for the module
+ virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
+ {
+ return powertoys_gpo::getConfiguredFileActionsMenuEnabledValue();
+ }
+
+ // Return JSON with the configuration options.
+ virtual bool get_config(wchar_t* buffer, int* buffer_size) override
+ {
+ HINSTANCE hinstance = reinterpret_cast(&__ImageBase);
+
+ // Create a Settings object.
+ PowerToysSettings::Settings settings(hinstance, get_name());
+ settings.set_description(MODULE_DESC);
+
+ return settings.serialize_to_buffer(buffer, buffer_size);
+ }
+
+ // Called by the runner to pass the updated settings values as a serialized JSON.
+ virtual void set_config(const wchar_t* config) override
+ {
+ try
+ {
+ // Parse the input JSON string.
+ PowerToysSettings::PowerToyValues values =
+ PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
+
+ parse_settings(values);
+
+ values.save_to_settings_file();
+ }
+ catch (std::exception&)
+ {
+ // Improper JSON.
+ }
+ }
+
+ // Enable the powertoy
+ virtual void enable()
+ {
+ Logger::trace("FileActionsMenu::enable()");
+ ResetEvent(m_hInvokeEvent);
+ m_enabled = true;
+ Trace::EnableFileActionsMenu(true);
+ }
+
+ // Disable the powertoy
+ virtual void disable()
+ {
+ Logger::trace("FileActionsMenu::disable()");
+ if (m_enabled)
+ {
+ ResetEvent(m_hInvokeEvent);
+ auto result = TerminateProcess(m_hProcess, 1);
+ if (result == 0)
+ {
+ int error = GetLastError();
+ Logger::trace("Couldn't terminate the process. Last error: {}", error);
+ }
+ }
+
+ m_enabled = false;
+ Trace::EnableFileActionsMenu(false);
+ }
+
+ // Returns if the powertoys is enabled
+ virtual bool is_enabled() override
+ {
+ return m_enabled;
+ }
+
+ virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
+ {
+ if (m_hotkey.key)
+ {
+ if (hotkeys && buffer_size >= 1)
+ {
+ hotkeys[0] = m_hotkey;
+ }
+
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ virtual bool on_hotkey(size_t /*hotkeyId*/) override
+ {
+ if (m_enabled)
+ {
+ Logger::trace(L"FileActionsMenu hotkey pressed");
+
+ // Only activate and consume the shortcut if a FileActionsMenu, explorer or desktop window is the foreground application.
+ if (is_FileActionsMenu_or_explorer_or_desktop_window_focused())
+ {
+ // TODO: fix VK_SPACE DestroyWindow in viewer app
+ launch_process();
+
+ SetEvent(m_hInvokeEvent);
+
+ Trace::FileActionsMenuInvoked();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ virtual void send_settings_telemetry() override
+ {
+ Logger::info("Send settings telemetry");
+ Trace::SettingsTelemetry(m_hotkey);
+ }
+};
+
+extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
+{
+ return new FileActionsMenu();
+}
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/packages.config b/src/modules/FileActionsMenu/FileActionsMenu/packages.config
new file mode 100644
index 000000000000..5ed22112fa7f
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/pch.cpp b/src/modules/FileActionsMenu/FileActionsMenu/pch.cpp
new file mode 100644
index 000000000000..a83d3bb2ccf7
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/pch.cpp
@@ -0,0 +1,2 @@
+#include "pch.h"
+#pragma comment(lib, "windowsapp")
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/pch.h b/src/modules/FileActionsMenu/FileActionsMenu/pch.h
new file mode 100644
index 000000000000..eddac0fdc1f3
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/pch.h
@@ -0,0 +1,7 @@
+#define WIN32_LEAN_AND_MEAN
+#include
+#include
+#include
+#include
+#include
+#include
\ No newline at end of file
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/resource.h b/src/modules/FileActionsMenu/FileActionsMenu/resource.h
new file mode 100644
index 000000000000..e1ea917e9470
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/resource.h
@@ -0,0 +1,13 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by FancyZonesModuleInterface.rc
+
+//////////////////////////////
+// Non-localizable
+
+#define FILE_DESCRIPTION "PowerToys FileActionsMenu Module"
+#define INTERNAL_NAME "PowerToys.FileActionsMenu"
+#define ORIGINAL_FILENAME "PowerToys.FileActionsMenu.dll"
+
+// Non-localizable
+//////////////////////////////
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/trace.cpp b/src/modules/FileActionsMenu/FileActionsMenu/trace.cpp
new file mode 100644
index 000000000000..1ab63b4dac2a
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/trace.cpp
@@ -0,0 +1,58 @@
+#include "pch.h"
+#include "trace.h"
+
+TRACELOGGING_DEFINE_PROVIDER(
+ g_hProvider,
+ "Microsoft.PowerToys",
+ // 0c3ce679-94d2-4a06-972d-3192f7f8eaea
+ (0x3c3ce679, 0x94d2, 0x4a06, 0x97, 0x2d, 0x31, 0x92, 0xf7, 0xf8, 0xea, 0xea),
+ TraceLoggingOptionProjectTelemetry());
+
+void Trace::RegisterProvider()
+{
+ TraceLoggingRegister(g_hProvider);
+}
+
+void Trace::UnregisterProvider()
+{
+ TraceLoggingUnregister(g_hProvider);
+}
+
+// Log if the user has FileActionsMenu enabled or disabled
+void Trace::EnableFileActionsMenu(const bool enabled) noexcept
+{
+ TraceLoggingWrite(
+ g_hProvider,
+ "FileActionsMenu_EnableFileActionsMenu",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingBoolean(enabled, "Enabled"));
+}
+
+// Log if the user has invoked FileActionsMenu
+void Trace::FileActionsMenuInvoked() noexcept
+{
+ TraceLoggingWrite(
+ g_hProvider,
+ "FileActionsMenu_InvokeFileActionsMenu",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
+}
+
+// Event to send settings telemetry.
+void Trace::SettingsTelemetry(PowertoyModuleIface::Hotkey& hotkey) noexcept
+{
+ std::wstring hotKeyStr =
+ std::wstring(hotkey.win ? L"Win + " : L"") +
+ std::wstring(hotkey.ctrl ? L"Ctrl + " : L"") +
+ std::wstring(hotkey.shift ? L"Shift + " : L"") +
+ std::wstring(hotkey.alt ? L"Alt + " : L"") +
+ std::wstring(L"VK ") + std::to_wstring(hotkey.key);
+
+ TraceLoggingWrite(
+ g_hProvider,
+ "FileActionsMenu_Settings",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingWideString(hotKeyStr.c_str(), "HotKey"));
+}
diff --git a/src/modules/FileActionsMenu/FileActionsMenu/trace.h b/src/modules/FileActionsMenu/FileActionsMenu/trace.h
new file mode 100644
index 000000000000..a61a5bad5119
--- /dev/null
+++ b/src/modules/FileActionsMenu/FileActionsMenu/trace.h
@@ -0,0 +1,19 @@
+#pragma once
+#include
+
+class Trace
+{
+public:
+ static void RegisterProvider();
+ static void UnregisterProvider();
+
+ // Log if the user has FileActionsMenu enabled or disabled
+ static void EnableFileActionsMenu(const bool enabled) noexcept;
+
+ // Log if the user has invoked FileActionsMenu
+ static void FileActionsMenuInvoked() noexcept;
+
+ // Event to send settings telemetry.
+ static void Trace::SettingsTelemetry(PowertoyModuleIface::Hotkey& hotkey) noexcept;
+
+};
diff --git a/src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs b/src/modules/peek/Peek.Helpers/DefaultAppHelper.cs
similarity index 96%
rename from src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs
rename to src/modules/peek/Peek.Helpers/DefaultAppHelper.cs
index e1652f3f7d99..073ebe56e384 100644
--- a/src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs
+++ b/src/modules/peek/Peek.Helpers/DefaultAppHelper.cs
@@ -7,9 +7,9 @@
using ManagedCommon;
using Peek.Common.Models;
-using Peek.UI.Native;
+using Peek.Helpers.Native;
-namespace Peek.UI.Helpers
+namespace Peek.Helpers
{
public static class DefaultAppHelper
{
diff --git a/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs b/src/modules/peek/Peek.Helpers/Extensions/HWNDExtensions.cs
similarity index 85%
rename from src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs
rename to src/modules/peek/Peek.Helpers/Extensions/HWNDExtensions.cs
index 2a29aadb70ab..e3776094ff16 100644
--- a/src/modules/peek/Peek.UI/Extensions/HWNDExtensions.cs
+++ b/src/modules/peek/Peek.Helpers/Extensions/HWNDExtensions.cs
@@ -3,14 +3,14 @@
// See the LICENSE file in the project root for more information.
using System.Text;
+using Peek.Helpers.Native;
-using Peek.UI.Native;
using Windows.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
-namespace Peek.UI.Extensions
+namespace Peek.Helpers.Extensions
{
public static class HWNDExtensions
{
@@ -27,7 +27,7 @@ internal static HWND GetActiveTab(this HWND windowHandle)
// Keep logic synced with the similar function in the C++ module interface.
// TODO: Refactor into same C++ class consumed by both.
- internal static bool IsDesktopWindow(this HWND windowHandle)
+ public static bool IsDesktopWindow(this HWND windowHandle)
{
StringBuilder strClassName = new StringBuilder(256);
var result = NativeMethods.GetClassName(windowHandle, strClassName, 256);
@@ -46,12 +46,12 @@ internal static bool IsDesktopWindow(this HWND windowHandle)
return windowHandle.FindChildWindow("SHELLDLL_DefView") != HWND.Null;
}
- internal static HWND FindChildWindow(this HWND windowHandle, string className)
+ public static HWND FindChildWindow(this HWND windowHandle, string className)
{
return PInvoke.FindWindowEx(windowHandle, HWND.Null, className, null);
}
- internal static Size GetMonitorSize(this HWND hwnd)
+ public static Size GetMonitorSize(this HWND hwnd)
{
var monitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
MONITORINFO info = default(MONITORINFO);
@@ -60,7 +60,7 @@ internal static Size GetMonitorSize(this HWND hwnd)
return new Size(info.rcMonitor.Size.Width, info.rcMonitor.Size.Height);
}
- internal static double GetMonitorScale(this HWND hwnd)
+ public static double GetMonitorScale(this HWND hwnd)
{
var dpi = PInvoke.GetDpiForWindow(hwnd);
var scalingFactor = dpi / 96d;
diff --git a/src/modules/peek/Peek.UI/Extensions/IShellItemExtensions.cs b/src/modules/peek/Peek.Helpers/Extensions/IShellItemExtensions.cs
similarity index 82%
rename from src/modules/peek/Peek.UI/Extensions/IShellItemExtensions.cs
rename to src/modules/peek/Peek.Helpers/Extensions/IShellItemExtensions.cs
index 59e0de147811..275e52e92db2 100644
--- a/src/modules/peek/Peek.UI/Extensions/IShellItemExtensions.cs
+++ b/src/modules/peek/Peek.Helpers/Extensions/IShellItemExtensions.cs
@@ -8,7 +8,7 @@
using ManagedCommon;
using Peek.Common.Models;
-namespace Peek.UI.Extensions;
+namespace Peek.Helpers.Extensions;
public static class IShellItemExtensions
{
@@ -20,16 +20,16 @@ public static IFileSystemItem ToIFileSystemItem(this IShellItem shellItem)
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name, shellItem.GetParsingName());
}
- private static string GetPath(this IShellItem shellItem) =>
+ public static string GetPath(this IShellItem shellItem) =>
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH, logError: false);
- private static string GetName(this IShellItem shellItem) =>
+ public static string GetName(this IShellItem shellItem) =>
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY, logError: true);
private static string GetParsingName(this IShellItem shellItem) =>
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, logError: true);
- private static string GetNameCore(this IShellItem shellItem, Windows.Win32.UI.Shell.SIGDN displayNameType, bool logError)
+ public static string GetNameCore(this IShellItem shellItem, Windows.Win32.UI.Shell.SIGDN displayNameType, bool logError)
{
try
{
diff --git a/src/modules/peek/Peek.UI/Extensions/SizeExtensions.cs b/src/modules/peek/Peek.Helpers/Extensions/SizeExtensions.cs
similarity index 97%
rename from src/modules/peek/Peek.UI/Extensions/SizeExtensions.cs
rename to src/modules/peek/Peek.Helpers/Extensions/SizeExtensions.cs
index 947144a91330..6d3f3608cdb0 100644
--- a/src/modules/peek/Peek.UI/Extensions/SizeExtensions.cs
+++ b/src/modules/peek/Peek.Helpers/Extensions/SizeExtensions.cs
@@ -4,7 +4,7 @@
using Windows.Foundation;
-namespace Peek.UI.Extensions
+namespace Peek.Helpers.Extensions
{
public static class SizeExtensions
{
diff --git a/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs b/src/modules/peek/Peek.Helpers/Extensions/WindowExtensions.cs
similarity index 94%
rename from src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs
rename to src/modules/peek/Peek.Helpers/Extensions/WindowExtensions.cs
index 0112a3877cdb..db2df33935eb 100644
--- a/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs
+++ b/src/modules/peek/Peek.Helpers/Extensions/WindowExtensions.cs
@@ -12,7 +12,7 @@
using Windows.Win32.UI.WindowsAndMessaging;
using WinUIEx;
-namespace Peek.UI.Extensions
+namespace Peek.Helpers.Extensions
{
public static class WindowExtensions
{
@@ -22,7 +22,7 @@ public static double GetMonitorScale(this Window window)
return hwnd.GetMonitorScale();
}
- internal static void CenterOnMonitor(this Window window, HWND hwndDesktop, double? width = null, double? height = null)
+ public static void CenterOnMonitor(this Window window, HWND hwndDesktop, double? width = null, double? height = null)
{
var hwndToCenter = new HWND(window.GetWindowHandle());
diff --git a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs b/src/modules/peek/Peek.Helpers/FileExplorerHelper.cs
similarity index 69%
rename from src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs
rename to src/modules/peek/Peek.Helpers/FileExplorerHelper.cs
index 466ae85028b8..94aba41255f4 100644
--- a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs
+++ b/src/modules/peek/Peek.Helpers/FileExplorerHelper.cs
@@ -6,7 +6,7 @@
using System.Runtime.InteropServices;
using Peek.Common.Models;
-using Peek.UI.Extensions;
+using Peek.Helpers.Extensions;
using SHDocVw;
using Windows.Win32;
using Windows.Win32.Foundation;
@@ -15,21 +15,21 @@
using IServiceProvider = Peek.Common.Models.IServiceProvider;
-namespace Peek.UI.Helpers
+namespace Peek.Helpers
{
public static class FileExplorerHelper
{
- internal static IShellItemArray? GetSelectedItems(HWND foregroundWindowHandle)
+ public static IShellItemArray? GetSelectedItems(HWND foregroundWindowHandle)
{
return GetItemsInternal(foregroundWindowHandle, onlySelectedFiles: true);
}
- internal static IShellItemArray? GetItems(HWND foregroundWindowHandle)
+ public static IShellItemArray? GetItems(HWND foregroundWindowHandle)
{
return GetItemsInternal(foregroundWindowHandle, onlySelectedFiles: false);
}
- private static IShellItemArray? GetItemsInternal(HWND foregroundWindowHandle, bool onlySelectedFiles)
+ public static IShellItemArray? GetItemsInternal(HWND foregroundWindowHandle, bool onlySelectedFiles)
{
// If the caret is visible, we assume the user is typing and we don't want to interfere with that
if (CaretVisible(foregroundWindowHandle))
@@ -46,7 +46,7 @@ public static class FileExplorerHelper
}
}
- private static IShellItemArray? GetItemsFromDesktop(HWND foregroundWindowHandle, bool onlySelectedFiles)
+ public static IShellItemArray? GetItemsFromDesktop(HWND foregroundWindowHandle, bool onlySelectedFiles)
{
const int SWC_DESKTOP = 8;
const int SWFO_NEEDDISPATCH = 1;
@@ -64,7 +64,7 @@ public static class FileExplorerHelper
return shellItemArray;
}
- private static IShellItemArray? GetItemsFromFileExplorer(HWND foregroundWindowHandle, bool onlySelectedFiles)
+ public static IShellItemArray? GetItemsFromFileExplorer(HWND foregroundWindowHandle, bool onlySelectedFiles)
{
IShellItemArray? shellItemArray = null;
@@ -74,29 +74,36 @@ public static class FileExplorerHelper
ShellWindows shellWindows = shell.Windows();
foreach (IWebBrowserApp webBrowserApp in shellWindows)
{
- if (webBrowserApp.Document is Shell32.IShellFolderViewDual2 shellFolderView)
+ try
{
- var folderTitle = shellFolderView.Folder.Title;
-
- if (webBrowserApp.HWND == foregroundWindowHandle)
+ if (webBrowserApp.Document is Shell32.IShellFolderViewDual2 shellFolderView)
{
- var serviceProvider = (IServiceProvider)webBrowserApp;
- var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke.SID_STopLevelBrowser, typeof(IShellBrowser).GUID);
- shellBrowser.GetWindow(out IntPtr shellBrowserHandle);
+ var folderTitle = shellFolderView.Folder.Title;
- if (activeTab == shellBrowserHandle)
+ if (webBrowserApp.HWND == foregroundWindowHandle)
{
- shellItemArray = GetShellItemArray(shellBrowser, onlySelectedFiles);
- return shellItemArray;
+ var serviceProvider = (IServiceProvider)webBrowserApp;
+ var shellBrowser = (IShellBrowser)serviceProvider.QueryService(PInvoke.SID_STopLevelBrowser, typeof(IShellBrowser).GUID);
+ shellBrowser.GetWindow(out IntPtr shellBrowserHandle);
+
+ if (activeTab == shellBrowserHandle)
+ {
+ shellItemArray = GetShellItemArray(shellBrowser, onlySelectedFiles);
+ return shellItemArray;
+ }
}
}
}
+ catch (COMException)
+ {
+ // Ignore the exception and continue to the next window
+ }
}
return shellItemArray;
}
- private static IShellItemArray? GetShellItemArray(IShellBrowser shellBrowser, bool onlySelectedFiles)
+ public static IShellItemArray? GetShellItemArray(IShellBrowser shellBrowser, bool onlySelectedFiles)
{
var shellViewObject = shellBrowser.QueryActiveShellView();
var shellView = shellViewObject as IFolderView;
diff --git a/src/modules/peek/Peek.UI/Native/NativeMethods.cs b/src/modules/peek/Peek.Helpers/Native/NativeMethods.cs
similarity index 98%
rename from src/modules/peek/Peek.UI/Native/NativeMethods.cs
rename to src/modules/peek/Peek.Helpers/Native/NativeMethods.cs
index 95badbae0323..e2fbe7819bea 100644
--- a/src/modules/peek/Peek.UI/Native/NativeMethods.cs
+++ b/src/modules/peek/Peek.Helpers/Native/NativeMethods.cs
@@ -8,7 +8,7 @@
using Peek.Common.Models;
-namespace Peek.UI.Native
+namespace Peek.Helpers.Native
{
public static class NativeMethods
{
diff --git a/src/modules/peek/Peek.Helpers/NativeMethods.txt b/src/modules/peek/Peek.Helpers/NativeMethods.txt
new file mode 100644
index 000000000000..908e8b9e5ed2
--- /dev/null
+++ b/src/modules/peek/Peek.Helpers/NativeMethods.txt
@@ -0,0 +1,23 @@
+MonitorFromWindow
+GetMonitorInfo
+GetDpiForWindow
+GetForegroundWindow
+SetForegroundWindow
+SetActiveWindow
+GetWindowThreadProcessId
+GetCurrentThreadId
+AttachThreadInput
+BringWindowToTop
+ShowWindow
+GetWindowTextLength
+FindWindowEx
+SID_STopLevelBrowser
+GetClassName
+_SVGIO
+MONITORINFO
+GetWindowRect
+GetWindowPlacement
+SetWindowPlacement
+GetGUIThreadInfo
+SET_WINDOW_POS_FLAGS
+SetWindowPos
\ No newline at end of file
diff --git a/src/modules/peek/Peek.Helpers/Peek.Helpers.csproj b/src/modules/peek/Peek.Helpers/Peek.Helpers.csproj
new file mode 100644
index 000000000000..f24d8f2aa124
--- /dev/null
+++ b/src/modules/peek/Peek.Helpers/Peek.Helpers.csproj
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+ Peek.Helpers
+ true
+ enable
+
+
+
+ tlbimp
+ 0
+ 1
+ 50a7e9b0-70ef-11d1-b75a-00a0c90564fe
+ 0
+ false
+ true
+
+
+ tlbimp
+ 1
+ 1
+ eab22ac0-30c1-11cf-a7eb-0000c05bae0b
+ 0
+ false
+ true
+
+
+ tlbimp
+ 0
+ 1
+ 420b2830-e718-11cf-893d-00a0c9054228
+ 0
+ false
+ true
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/peek/Peek.UI/Models/NeighboringItems.cs b/src/modules/peek/Peek.UI/Models/NeighboringItems.cs
index c528cc2b7ebc..752fd76f98f0 100644
--- a/src/modules/peek/Peek.UI/Models/NeighboringItems.cs
+++ b/src/modules/peek/Peek.UI/Models/NeighboringItems.cs
@@ -6,7 +6,7 @@
using System.Collections.Generic;
using Peek.Common.Models;
-using Peek.UI.Extensions;
+using Peek.Helpers.Extensions;
namespace Peek.UI.Models
{
diff --git a/src/modules/peek/Peek.UI/Peek.UI.csproj b/src/modules/peek/Peek.UI/Peek.UI.csproj
index cb643252f1ec..64957a114651 100644
--- a/src/modules/peek/Peek.UI/Peek.UI.csproj
+++ b/src/modules/peek/Peek.UI/Peek.UI.csproj
@@ -102,6 +102,7 @@
+
diff --git a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
index 7295dd1dcca6..903b0d983fe4 100644
--- a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
+++ b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
@@ -13,8 +13,8 @@
using Peek.Common.Constants;
using Peek.Common.Extensions;
using Peek.FilePreviewer.Models;
-using Peek.UI.Extensions;
-using Peek.UI.Helpers;
+using Peek.Helpers;
+using Peek.Helpers.Extensions;
using Peek.UI.Telemetry.Events;
using Windows.Foundation;
using WinUIEx;
diff --git a/src/modules/peek/Peek.UI/PeekXAML/Views/TitleBar.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/Views/TitleBar.xaml.cs
index 7e2759cc5dbc..7d4dc52c43f3 100644
--- a/src/modules/peek/Peek.UI/PeekXAML/Views/TitleBar.xaml.cs
+++ b/src/modules/peek/Peek.UI/PeekXAML/Views/TitleBar.xaml.cs
@@ -17,8 +17,8 @@
using Microsoft.UI.Xaml.Controls;
using Peek.Common.Helpers;
using Peek.Common.Models;
-using Peek.UI.Extensions;
-using Peek.UI.Helpers;
+using Peek.Helpers;
+using Peek.Helpers.Extensions;
using Peek.UI.Telemetry.Events;
using Windows.Graphics;
using Windows.Storage;
diff --git a/src/modules/peek/Peek.UI/Services/NeighboringItemsQuery.cs b/src/modules/peek/Peek.UI/Services/NeighboringItemsQuery.cs
index e8623d9e88aa..5e04f50ed847 100644
--- a/src/modules/peek/Peek.UI/Services/NeighboringItemsQuery.cs
+++ b/src/modules/peek/Peek.UI/Services/NeighboringItemsQuery.cs
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
-using Peek.UI.Helpers;
+using Peek.Helpers;
using Peek.UI.Models;
namespace Peek.UI
diff --git a/src/modules/powerrename/PowerRenameContextMenu/dllmain.cpp b/src/modules/powerrename/PowerRenameContextMenu/dllmain.cpp
index ea90a51ff131..ec6a4c4a5c3c 100644
--- a/src/modules/powerrename/PowerRenameContextMenu/dllmain.cpp
+++ b/src/modules/powerrename/PowerRenameContextMenu/dllmain.cpp
@@ -158,49 +158,6 @@ class __declspec(uuid("1861E28B-A1F0-4EF4-A1FE-4C8CA88E2174")) PowerRenameContex
}
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept { return m_site.CopyTo(riid, site); }
-protected:
- ComPtr m_site;
-
-private:
-
- HRESULT StartNamedPipeServerAndSendData(std::wstring pipe_name)
- {
- hPipe = CreateNamedPipe(
- pipe_name.c_str(),
- PIPE_ACCESS_DUPLEX |
- WRITE_DAC,
- PIPE_TYPE_MESSAGE |
- PIPE_READMODE_MESSAGE |
- PIPE_WAIT,
- PIPE_UNLIMITED_INSTANCES,
- BUFSIZE,
- BUFSIZE,
- 0,
- NULL);
-
- if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
- {
- return E_FAIL;
- }
-
- // This call blocks until a client process connects to the pipe
- BOOL connected = ConnectNamedPipe(hPipe, NULL);
- if (!connected)
- {
- if (GetLastError() == ERROR_PIPE_CONNECTED)
- {
- return S_OK;
- }
- else
- {
- CloseHandle(hPipe);
- }
- return E_FAIL;
- }
-
- return S_OK;
- }
-
HRESULT RunPowerRename(IShellItemArray* psiItemArray)
{
if (CSettingsInstance().GetEnabled())
@@ -267,6 +224,49 @@ class __declspec(uuid("1861E28B-A1F0-4EF4-A1FE-4C8CA88E2174")) PowerRenameContex
return S_OK;
}
+protected:
+ ComPtr m_site;
+
+private:
+
+ HRESULT StartNamedPipeServerAndSendData(std::wstring pipe_name)
+ {
+ hPipe = CreateNamedPipe(
+ pipe_name.c_str(),
+ PIPE_ACCESS_DUPLEX |
+ WRITE_DAC,
+ PIPE_TYPE_MESSAGE |
+ PIPE_READMODE_MESSAGE |
+ PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ BUFSIZE,
+ BUFSIZE,
+ 0,
+ NULL);
+
+ if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
+ {
+ return E_FAIL;
+ }
+
+ // This call blocks until a client process connects to the pipe
+ BOOL connected = ConnectNamedPipe(hPipe, NULL);
+ if (!connected)
+ {
+ if (GetLastError() == ERROR_PIPE_CONNECTED)
+ {
+ return S_OK;
+ }
+ else
+ {
+ CloseHandle(hPipe);
+ }
+ return E_FAIL;
+ }
+
+ return S_OK;
+ }
+
std::thread create_pipe_thread;
@@ -290,4 +290,10 @@ STDAPI DllCanUnloadNow()
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance)
{
return Module::GetModule().GetClassObject(rclsid, riid, instance);
+}
+
+extern "C" __declspec(dllexport) void RunPowerRename(IShellItemArray * psiItemArray)
+{
+ PowerRenameContextMenuCommand contextMenuCommand;
+ contextMenuCommand.RunPowerRename(psiItemArray);
}
\ No newline at end of file
diff --git a/src/runner/main.cpp b/src/runner/main.cpp
index 76a6f68d5c84..4e86320ba458 100644
--- a/src/runner/main.cpp
+++ b/src/runner/main.cpp
@@ -171,6 +171,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"WinUI3Apps/PowerToys.EnvironmentVariablesModuleInterface.dll",
L"PowerToys.MouseWithoutBordersModuleInterface.dll",
L"PowerToys.CropAndLockModuleInterface.dll",
+ L"WinUI3Apps/PowerToys.FileActionsMenu.dll",
L"PowerToys.CmdNotFoundModuleInterface.dll",
L"PowerToys.WorkspacesModuleInterface.dll",
L"PowerToys.ZoomItModuleInterface.dll",
diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs
index 2b582f396ad9..c571c29303bc 100644
--- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs
+++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs
@@ -39,6 +39,23 @@ public bool FancyZones
}
}
+ private bool fileActionsMenu = true;
+
+ [JsonPropertyName("FileActionsMenu")]
+ public bool FileActionsMenu
+ {
+ get => fileActionsMenu;
+ set
+ {
+ if (fileActionsMenu != value)
+ {
+ LogTelemetryEvent(value);
+ fileActionsMenu = value;
+ NotifyChange();
+ }
+ }
+ }
+
private bool imageResizer = true;
[JsonPropertyName("Image Resizer")]
diff --git a/src/settings-ui/Settings.UI.Library/FileActionsMenuProperties.cs b/src/settings-ui/Settings.UI.Library/FileActionsMenuProperties.cs
new file mode 100644
index 000000000000..bf612d3ba290
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/FileActionsMenuProperties.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class FileActionsMenuProperties : ISettingsConfig
+ {
+ public HotkeySettings DefaultFileActionsMenuShortcut => new(false, true, true, false, 65);
+
+ public FileActionsMenuProperties()
+ {
+ FileActionsMenuShortcut = DefaultFileActionsMenuShortcut;
+ }
+
+ private bool enableFileActionsMenu = true;
+
+ [JsonPropertyName("file-actions-menu-toggle-setting")]
+ [JsonConverter(typeof(BoolPropertyJsonConverter))]
+ public bool EnableFileActionsMenu
+ {
+ get => enableFileActionsMenu;
+ set
+ {
+ if (value != enableFileActionsMenu)
+ {
+ enableFileActionsMenu = value;
+ }
+ }
+ }
+
+ [JsonPropertyName("file-actions-menu-shortcut-setting")]
+ public HotkeySettings FileActionsMenuShortcut { get; set; }
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+
+ // This function is required to implement the ISettingsConfig interface and obtain the settings configurations.
+ public string GetModuleName()
+ {
+ string moduleName = FileActionsMenuSettings.ModuleName;
+ return moduleName;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Library/FileActionsMenuSettings.cs b/src/settings-ui/Settings.UI.Library/FileActionsMenuSettings.cs
new file mode 100644
index 000000000000..b0985e754ce2
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/FileActionsMenuSettings.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class FileActionsMenuSettings : BasePTModuleSettings, ISettingsConfig
+ {
+ public const string ModuleName = "FileActionsMenu";
+ public const string ModuleVersion = "1";
+
+ [JsonPropertyName("properties")]
+ public FileActionsMenuProperties Properties { get; set; }
+
+ public FileActionsMenuSettings()
+ {
+ Name = ModuleName;
+ Version = ModuleVersion;
+ Properties = new FileActionsMenuProperties();
+ }
+
+ public FileActionsMenuSettings(FileActionsMenuProperties localProperties)
+ {
+ ArgumentNullException.ThrowIfNull(localProperties);
+
+ Properties = new FileActionsMenuProperties();
+ Version = "1";
+ Name = ModuleName;
+ }
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Library/SndFileActionsMenuSettings.cs b/src/settings-ui/Settings.UI.Library/SndFileActionsMenuSettings.cs
new file mode 100644
index 000000000000..fdf4d1b2e904
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/SndFileActionsMenuSettings.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class SndFileActionsMenuSettings
+ {
+ [JsonPropertyName("FileActionsMenu")]
+ public FileActionsMenuSettings FileActionsMenu { get; set; }
+
+ public SndFileActionsMenuSettings()
+ {
+ }
+
+ public SndFileActionsMenuSettings(FileActionsMenuSettings settings)
+ {
+ FileActionsMenu = settings;
+ }
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/FileActionsMenu.png b/src/settings-ui/Settings.UI/Assets/Settings/Icons/FileActionsMenu.png
new file mode 100644
index 000000000000..c52852125d82
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Icons/FileActionsMenu.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/FileActionsMenu.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/FileActionsMenu.png
new file mode 100644
index 000000000000..95605ad531e6
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/FileActionsMenu.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/FileActionsMenu.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/FileActionsMenu.png
new file mode 100644
index 000000000000..fc6b5b96d448
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/FileActionsMenu.png differ
diff --git a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs
index 60de12a4c98b..8e0f8f0f0c62 100644
--- a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs
+++ b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs
@@ -53,6 +53,7 @@ public static bool GetIsModuleEnabled(Library.GeneralSettings generalSettingsCon
case ModuleType.CropAndLock: return generalSettingsConfig.Enabled.CropAndLock;
case ModuleType.EnvironmentVariables: return generalSettingsConfig.Enabled.EnvironmentVariables;
case ModuleType.FancyZones: return generalSettingsConfig.Enabled.FancyZones;
+ case ModuleType.FileActionsMenu: return generalSettingsConfig.Enabled.FileActionsMenu;
case ModuleType.FileLocksmith: return generalSettingsConfig.Enabled.FileLocksmith;
case ModuleType.FindMyMouse: return generalSettingsConfig.Enabled.FindMyMouse;
case ModuleType.Hosts: return generalSettingsConfig.Enabled.Hosts;
@@ -88,6 +89,10 @@ internal static void SetIsModuleEnabled(GeneralSettings generalSettingsConfig, M
case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break;
case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break;
case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break;
+ case ModuleType.FileActionsMenu:
+ SettingsRepository repository = SettingsRepository.GetInstance(new SettingsUtils());
+ repository.SettingsConfig.Properties.EnableFileActionsMenu = isEnabled;
+ break;
case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break;
case ModuleType.FindMyMouse: generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break;
case ModuleType.Hosts: generalSettingsConfig.Enabled.Hosts = isEnabled; break;
@@ -157,6 +162,7 @@ public static System.Type GetModulePageType(ModuleType moduleType)
ModuleType.CropAndLock => typeof(CropAndLockPage),
ModuleType.EnvironmentVariables => typeof(EnvironmentVariablesPage),
ModuleType.FancyZones => typeof(FancyZonesPage),
+ ModuleType.FileActionsMenu => typeof(FileActionsMenuPage),
ModuleType.FileLocksmith => typeof(FileLocksmithPage),
ModuleType.FindMyMouse => typeof(MouseUtilsPage),
ModuleType.Hosts => typeof(HostsPage),
diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
index f6e0f7c861ce..310a4f24940d 100644
--- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
+++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
@@ -15,6 +15,7 @@ public enum PowerToysModules
CropAndLock,
EnvironmentVariables,
FancyZones,
+ FileActionsMenu,
FileLocksmith,
FileExplorer,
ImageResizer,
diff --git a/src/settings-ui/Settings.UI/Services/ActivationService.cs b/src/settings-ui/Settings.UI/Services/ActivationService.cs
index 86ad2e4d7cb0..b72e001d0d4f 100644
--- a/src/settings-ui/Settings.UI/Services/ActivationService.cs
+++ b/src/settings-ui/Settings.UI/Services/ActivationService.cs
@@ -41,11 +41,13 @@ public async Task ActivateAsync(object activationArgs)
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
+#pragma warning disable WinUIEx1001 // The member will always be null.
if (Window.Current.Content == null)
{
// Create a Shell or Frame to act as the navigation context
Window.Current.Content = shell?.Value ?? new Frame();
}
+#pragma warning restore WinUIEx1001 // The member will always be null.
}
// Depending on activationArgs one of ActivationHandlers or DefaultActivationHandler
@@ -56,7 +58,9 @@ public async Task ActivateAsync(object activationArgs)
if (IsInteractive(activationArgs))
{
// Ensure the current window is active
+#pragma warning disable WinUIEx1001 // The member will always be null.
Window.Current.Activate();
+#pragma warning restore WinUIEx1001 // The member will always be null.
// Tasks after activation
await StartupAsync().ConfigureAwait(false);
diff --git a/src/settings-ui/Settings.UI/Services/NavigationService.cs b/src/settings-ui/Settings.UI/Services/NavigationService.cs
index b70976bd01c9..8f14676c472b 100644
--- a/src/settings-ui/Settings.UI/Services/NavigationService.cs
+++ b/src/settings-ui/Settings.UI/Services/NavigationService.cs
@@ -24,13 +24,7 @@ public static Frame Frame
{
get
{
- if (frame == null)
- {
- frame = Window.Current.Content as Frame;
- RegisterFrameEvents();
- }
-
- return frame;
+ return frame ?? throw new InvalidOperationException("Frame is not initialized.");
}
set
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileActionsMenu.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileActionsMenu.xaml
new file mode 100644
index 000000000000..b11b9545b600
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileActionsMenu.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileActionsMenu.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileActionsMenu.xaml.cs
new file mode 100644
index 000000000000..716b64c97d3f
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileActionsMenu.xaml.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
+using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class OobeFileActionsMenu : Page
+ {
+ public OobePowerToysModule ViewModel { get; set; }
+
+ public OobeFileActionsMenu()
+ {
+ this.InitializeComponent();
+ ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.FileActionsMenu]);
+ DataContext = ViewModel;
+ }
+
+ private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ if (OobeShellPage.OpenMainWindowCallback != null)
+ {
+ OobeShellPage.OpenMainWindowCallback(typeof(FileActionsMenuPage));
+ }
+
+ ViewModel.LogOpeningSettingsEvent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ ViewModel.LogOpeningModuleEvent();
+ HotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FileActionsMenuShortcut.GetKeysList();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ViewModel.LogClosingModuleEvent();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
index d419daf1ab4c..2ecdf078c154 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
@@ -97,6 +97,10 @@
x:Uid="Shell_FileLocksmith"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}"
Tag="FileLocksmith" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/FileActionsMenuPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/FileActionsMenuPage.xaml.cs
new file mode 100644
index 000000000000..31cf5be424da
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/FileActionsMenuPage.xaml.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.ViewModels;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class FileActionsMenuPage : Page
+ {
+ public FileActionsMenuViewModel ViewModel { get; set; }
+
+ public FileActionsMenuPage()
+ {
+ InitializeComponent();
+ var settingsUtils = new SettingsUtils();
+ ViewModel = new FileActionsMenuViewModel(SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
+ DataContext = ViewModel;
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
index f3f21292b562..bc33f560da1e 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
@@ -204,11 +204,14 @@
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileManagement.png}"
SelectsOnInvoked="False">
+
-
Not included in global results
+
+ File Actions Menu adds a menu to the Windows File Explorer, that enables you to execute certain actions. The menu can be opened via a shortcut.
+
+
+ File Actions Menu
+
+
+ Enable File Actions Menu
+
The size of result titles and the search query
@@ -4289,6 +4298,33 @@ Activate by holding the key for the character you want to add an accent to, then
The enabled state of some thumbnail handlers is managed by your organization.
+
+ File Actions Menu allows you to perform several actions on selected files and folders, such as generating checksums, copying paths separated by a delimiter or create a new folder with the selection.
+
+
+ File Actions Menu
+
+
+ Attribution
+
+
+ Learn more about File Actions Menu
+
+
+ File Actions Menu
+
+
+ Press shortcut to open a menu and perform actions on selected Files/Folders
+
+
+ File Actions Menu is a menu that allows you to perform several different actions on Files and Folders selected in Windows Explorer. These actions include generating hashes, copying the file paths of the selected objects delimited by a custom delimiter or copying the file content in different formats.
+
+
+ File Actions Menu
+
+
+ to open File Actions Menu
+
Enable sticky scroll
@@ -4867,4 +4903,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Project website
+
+ Activation
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
index e2b076228866..587c3b83e334 100644
--- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
@@ -176,6 +176,7 @@ private ObservableCollection GetModuleItems(ModuleType modu
ModuleType.CropAndLock => GetModuleItemsCropAndLock(),
ModuleType.EnvironmentVariables => GetModuleItemsEnvironmentVariables(),
ModuleType.FancyZones => GetModuleItemsFancyZones(),
+ ModuleType.FileActionsMenu => GetModuleItemsFileActionsMenu(),
ModuleType.FileLocksmith => GetModuleItemsFileLocksmith(),
ModuleType.FindMyMouse => GetModuleItemsFindMyMouse(),
ModuleType.Hosts => GetModuleItemsHosts(),
@@ -271,11 +272,14 @@ private ObservableCollection GetModuleItemsFancyZones()
return new ObservableCollection(list);
}
- private ObservableCollection GetModuleItemsFileLocksmith()
+ private ObservableCollection GetModuleItemsFileActionsMenu()
{
+ ISettingsRepository moduleSettingsRepository = SettingsRepository.GetInstance(new SettingsUtils());
+ var settings = moduleSettingsRepository.SettingsConfig;
var list = new List
{
- new DashboardModuleTextItem() { Label = resourceLoader.GetString("FileLocksmith_ShortDescription") },
+ new DashboardModuleTextItem() { Label = resourceLoader.GetString("FileActionsMenu_ShortDescription") },
+ new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("Activation_Shortcut_Title"), Shortcut = settings.Properties.FileActionsMenuShortcut.GetKeysList() },
};
return new ObservableCollection(list);
}
@@ -308,6 +312,15 @@ private ObservableCollection GetModuleItemsFindMyMouse()
return new ObservableCollection(list);
}
+ private ObservableCollection GetModuleItemsFileLocksmith()
+ {
+ var list = new List
+ {
+ new DashboardModuleTextItem() { Label = resourceLoader.GetString("FileLocksmith_ShortDescription") },
+ };
+ return new ObservableCollection(list);
+ }
+
private ObservableCollection GetModuleItemsHosts()
{
var list = new List
diff --git a/src/settings-ui/Settings.UI/ViewModels/FileActionsMenuViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/FileActionsMenuViewModel.cs
new file mode 100644
index 000000000000..45a61979b6ed
--- /dev/null
+++ b/src/settings-ui/Settings.UI/ViewModels/FileActionsMenuViewModel.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using global::PowerToys.GPOWrapper;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.Library.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+
+namespace Microsoft.PowerToys.Settings.UI.ViewModels
+{
+ public class FileActionsMenuViewModel : Observable
+ {
+ private const string ModuleName = FileActionsMenuSettings.ModuleName;
+
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
+ private FileActionsMenuSettings Settings { get; set; }
+
+ private Func SendConfigMSG { get; }
+
+ private GpoRuleConfigured _fileActionsMenuEnabledGpoRuleConfiguration;
+ private bool _fileActionsMenuEnabledStateIsGPOConfigured;
+ private bool _fileActionsMenuIsEnabled;
+ private HotkeySettings _fileActionsMenuShortcut;
+
+ public FileActionsMenuViewModel(ISettingsRepository moduleSettingsRepository, ISettingsRepository generalSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
+ {
+ // To obtain the general Settings configurations of PowerToys
+ ArgumentNullException.ThrowIfNull(generalSettingsRepository);
+
+ // To obtain the PowerPreview settings if it exists.
+ // If the file does not exist, to create a new one and return the default settings configurations.
+ ArgumentNullException.ThrowIfNull(moduleSettingsRepository);
+
+ GeneralSettingsConfig = generalSettingsRepository.SettingsConfig;
+
+ Settings = moduleSettingsRepository.SettingsConfig;
+
+ // set the callback functions value to handle outgoing IPC message.
+ SendConfigMSG = ipcMSGCallBackFunc;
+
+ _fileActionsMenuEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredFileActionsMenuEnabledValue();
+ if (_fileActionsMenuEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _fileActionsMenuEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
+ {
+ // Get the enabled state from GPO.
+ _fileActionsMenuEnabledStateIsGPOConfigured = true;
+ _fileActionsMenuIsEnabled = _fileActionsMenuEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
+ }
+ else
+ {
+ _fileActionsMenuIsEnabled = Settings.Properties.EnableFileActionsMenu;
+ }
+
+ _fileActionsMenuShortcut = Settings.Properties.FileActionsMenuShortcut;
+ }
+
+ public bool FileActionsMenuIsEnabled
+ {
+ get
+ {
+ return _fileActionsMenuIsEnabled;
+ }
+
+ set
+ {
+ if (_fileActionsMenuEnabledStateIsGPOConfigured)
+ {
+ // If it's GPO configured, shouldn't be able to change this state.
+ return;
+ }
+
+ if (value != _fileActionsMenuIsEnabled)
+ {
+ _fileActionsMenuIsEnabled = value;
+ Settings.Properties.EnableFileActionsMenu = value;
+
+ GeneralSettingsConfig.Enabled.FileActionsMenu = value;
+
+ OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
+ SendConfigMSG(outgoing.ToString());
+
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public HotkeySettings FileActionsMenuShortcut
+ {
+ get => _fileActionsMenuShortcut;
+ set
+ {
+ if (value != _fileActionsMenuShortcut)
+ {
+ _fileActionsMenuShortcut = value ?? Settings.Properties.DefaultFileActionsMenuShortcut;
+ Settings.Properties.FileActionsMenuShortcut = _fileActionsMenuShortcut;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public bool IsFileActionsMenuEnabledGpoConfigured
+ {
+ get => _fileActionsMenuEnabledStateIsGPOConfigured;
+ }
+
+ private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ // Notify UI of property change
+ OnPropertyChanged(propertyName);
+
+ if (SendConfigMSG != null)
+ {
+ SndFileActionsMenuSettings snd = new SndFileActionsMenuSettings(Settings);
+ SndModuleSettings ipcMessage = new(snd);
+ SendConfigMSG(ipcMessage.ToJsonString());
+ }
+ }
+ }
+}
diff --git a/tools/BugReportTool/BugReportTool/ProcessesList.cpp b/tools/BugReportTool/BugReportTool/ProcessesList.cpp
index 9665938e7e20..0c33bd4e94c3 100644
--- a/tools/BugReportTool/BugReportTool/ProcessesList.cpp
+++ b/tools/BugReportTool/BugReportTool/ProcessesList.cpp
@@ -13,6 +13,7 @@ std::vector processes =
L"PowerToys.FileLocksmithUI.exe",
L"PowerToys.KeyboardManagerEngine.exe",
L"PowerToys.KeyboardManagerEditor.exe",
+ L"PowerToys.FileActionsMenu.exe",
L"PowerToys.PowerAccent.exe",
L"PowerToys.PowerLauncher.exe",
L"PowerToys.PowerOCR.exe",
diff --git a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp
index 07842af5762f..3877d323a5cb 100644
--- a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp
+++ b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp
@@ -48,6 +48,7 @@ void ReportGPOValues(const std::filesystem::path &tmpDir)
report << "getConfiguredColorPickerEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredColorPickerEnabledValue()) << std::endl;
report << "getConfiguredCropAndLockEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredCropAndLockEnabledValue()) << std::endl;
report << "getConfiguredFancyZonesEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredFancyZonesEnabledValue()) << std::endl;
+ report << "getConfiguredFileActionsMenuEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredFileActionsMenuEnabledValue()) << std::endl;
report << "getConfiguredFileLocksmithEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredFileLocksmithEnabledValue()) << std::endl;
report << "getConfiguredSvgPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredSvgPreviewEnabledValue()) << std::endl;
report << "getConfiguredMarkdownPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMarkdownPreviewEnabledValue()) << std::endl;