diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index e45a8fb517..9c97c8dba7 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -423,6 +423,7 @@ DARKPURPLE DARKTEAL DARKYELLOW datareader +datatracker Datavalue dataversion DATAW @@ -519,7 +520,7 @@ dllexport dllhost dllmain DNLEN -Dns +dns DONOTROUND DONTVALIDATEPATH dotnet @@ -790,7 +791,10 @@ graphql GSM gtm gui +guiddata guiddef +guidgenerator +GUIDv GUITHREADINFO GValue gwl @@ -862,6 +866,7 @@ HOLDESC HOMEPATH homljgmgpmcbpjbnjpfijnhipfkiclkd HOOKPROC +Horvalds Hostbackdropbrush hotkeycontrol hotkeys @@ -916,6 +921,7 @@ IDR idx IDXGI IEnum +ietf IExec IEXPLORE Iface @@ -956,6 +962,7 @@ INPUTHARDWARE INPUTKEYBOARD INPUTLANGCHANGED INPUTMOUSE +inputparser INPUTSINK INPUTTYPE INSTALLDESKTOPSHORTCUT @@ -1428,6 +1435,7 @@ odbccp Oem officehubintl ofs +oid oldcolor olditem oldnewthing @@ -1760,6 +1768,7 @@ roslyn roundf ROUNDSMALL Rpc +rpcrt RRF rrr RSAT @@ -2172,6 +2181,7 @@ uxtheme UYVY vabdq validmodulename +valuegenerator Vanara variantassignment vcamp diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 7d793997df..7bcafc06bc 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -131,6 +131,7 @@ "modules\\launcher\\Plugins\\TimeDate\\Microsoft.PowerToys.Run.Plugin.TimeDate.dll", "modules\\launcher\\Plugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll", "modules\\launcher\\Plugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll", + "modules\\launcher\\Plugins\\ValueGenerator\\Community.PowerToys.Run.Plugin.ValueGenerator.dll", "modules\\MeasureTool\\PowerToys.MeasureToolModuleInterface.dll", "modules\\MeasureTool\\PowerToys.MeasureToolCore.dll", diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index 0da849f2bb..9fb7023624 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -231,6 +231,7 @@ steps: **\Microsoft.Interop.Tests.dll **\ImageResizer.Test.dll **\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.dll + **\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.dll **\Microsoft.Plugin.Folder.UnitTests.dll **\Microsoft.Plugin.Program.UnitTests.dll **\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.dll diff --git a/PowerToys.sln b/PowerToys.sln index d82533f297..f636803aa1 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -140,6 +140,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} + {D095BE44-1F2E-463E-A494-121892A75EA2} = {D095BE44-1F2E-463E-A494-121892A75EA2} {F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820} {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} @@ -525,6 +526,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilePreviewCommon", "src\co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.PowerToys", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.csproj", "{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.csproj", "{D095BE44-1F2E-463E-A494-121892A75EA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj", "{90F9FA90-2C20-4004-96E6-F3B78151F5A5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2247,6 +2252,30 @@ Global {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x64.Build.0 = Release|x64 {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x86.ActiveCfg = Release|x64 {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x86.Build.0 = Release|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|ARM64.Build.0 = Debug|ARM64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x64.ActiveCfg = Debug|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x64.Build.0 = Debug|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x86.ActiveCfg = Debug|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x86.Build.0 = Debug|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|ARM64.ActiveCfg = Release|ARM64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|ARM64.Build.0 = Release|ARM64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x64.ActiveCfg = Release|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x64.Build.0 = Release|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x86.ActiveCfg = Release|x64 + {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x86.Build.0 = Release|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|ARM64.Build.0 = Debug|ARM64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x64.ActiveCfg = Debug|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x64.Build.0 = Debug|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x86.ActiveCfg = Debug|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x86.Build.0 = Debug|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|ARM64.ActiveCfg = Release|ARM64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|ARM64.Build.0 = Release|ARM64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.ActiveCfg = Release|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.Build.0 = Release|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x86.ActiveCfg = Release|x64 + {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2436,6 +2465,8 @@ Global {929C1324-22E8-4412-A9A8-80E85F3985A5} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {9EBAA524-0EDA-470B-95D4-39383285CBB2} = {1AFB6476-670D-4E80-A464-657E01DFF482} {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {D095BE44-1F2E-463E-A494-121892A75EA2} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {90F9FA90-2C20-4004-96E6-F3B78151F5A5} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md b/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md new file mode 100644 index 0000000000..ddecae0bee --- /dev/null +++ b/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md @@ -0,0 +1,52 @@ +# Value Generator Plugin + +The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings and to generate GUIDs versions 1, 3, 4 and 5. + +![Image of Value Generator plugin](/doc/images/launcher/plugin/community.valuegenerator.png) + +### [`IComputeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/IComputeRequest.cs) +- Interface for a request for computation +- the `bool Compute()` method must populate the `IsSuccessful` and one of the `Result` and `ErrorMessage` fields +- The result of `string ResultToString()` will be used for the Result's title +- The `Description` field will be used for the Result's subtitle + +### [`HashRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Hashing/HashRequest.cs) +- Implements IComputeRequest +- Supports the hashing algorithms from System.Security.Cryptography: + - MD5 + - SHA1 + - SHA256 + - SHA384 + - SHA512 +- If other algorithms are added to System.Security.Cryptography, they can be added to the `_algorithms` dictionary. [`InputParser.ParseInput()`](#inputparser) will need to return a `HashRequest` for the algorithm in the query + +### [`Base64Request`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64Request.cs) +- Implements IComputeRequest +- `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor + +### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs) +- Implements IComputeRequest +- Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID + +### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDGenerator.cs) +- Utility class for generating or calculating GUIDs +- Generating GUID versions 1 and 4 is done using builtin APIs. [`UuidCreateSequential`](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential) for version 1 and `System.Guid.NewGuid()` for version 4 +- Versions 3 and 5 take two parameters, a namespace and a name +- The namespace must be a valid GUID or one of the [predefined ones](https://datatracker.ietf.org/doc/html/rfc4122#appendix-C) +- The `PredefinedNamespaces` dictionary contains aliases for the predefined namespaces +- The name can be any string + +### [`InputParser`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs) +- It is responsible only for parsing the query from the user +- Based on the user query, the `ParseInput()` method must return an object that implements the `IComputeRequest` interface or it must throw one of `FormatException` or `ArgumentException` +- Throwing an `ArgumentException` should signal the fact the query contains a mistake that the user can fix (eg. an unsupported hash function, an invalid GUID version, an invalid namespace, etc) +> The error message will be shown to the user and no log message will be created +- Throwing a `FormatException` should signal either: + - that the query may become valid, and so it does not make sense to show an error just yet (eg. the query does not contain a request yet, a hash request without a string to hash) + - that the query is completely invalid +> The error message will not be shown to the user but a log message will be created + +### Adding a new value generator +1. To add a new value generator, create a folder under `/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/` and inside it add a class that implements `IComputeRequest`. +2. Add any utility classes that are specific to the new generator inside the same folder to keep them separated from the other generators. +3. Modify the `InputParser.ParseInput()` to handle a request for the new generator and return an instance of the class you created in step 1 \ No newline at end of file diff --git a/doc/images/launcher/plugins/community.valuegenerator.png b/doc/images/launcher/plugins/community.valuegenerator.png new file mode 100644 index 0000000000..37d564565a Binary files /dev/null and b/doc/images/launcher/plugins/community.valuegenerator.png differ diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 61a48a7e08..721ef997c6 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -597,6 +597,9 @@ + + + diff --git a/installer/PowerToysSetup/Resources.wxs b/installer/PowerToysSetup/Resources.wxs index a4dd7067c3..2724e73992 100644 --- a/installer/PowerToysSetup/Resources.wxs +++ b/installer/PowerToysSetup/Resources.wxs @@ -11,7 +11,7 @@ - + @@ -393,6 +393,15 @@ + + + + + + @@ -474,6 +483,7 @@ + diff --git a/installer/PowerToysSetup/Run.wxs b/installer/PowerToysSetup/Run.wxs index 3186c17c31..6cf803e36e 100644 --- a/installer/PowerToysSetup/Run.wxs +++ b/installer/PowerToysSetup/Run.wxs @@ -105,6 +105,11 @@ + + + + + @@ -319,6 +324,16 @@ + + + + + + + + + + @@ -365,6 +380,8 @@ + + diff --git a/installer/PowerToysSetup/generateAllFileComponents.ps1 b/installer/PowerToysSetup/generateAllFileComponents.ps1 index e435ff8bdb..108d725f12 100644 --- a/installer/PowerToysSetup/generateAllFileComponents.ps1 +++ b/installer/PowerToysSetup/generateAllFileComponents.ps1 @@ -203,6 +203,11 @@ Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson ""$ Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName PowerToysImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\modules\launcher\Plugins\PowerToys\Images""" Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""PowerToysCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot" Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""PowerToysImagesCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot" +###ValueGenerator +Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson ""$PSScriptRoot..\..\..\$platform\Release\modules\launcher\Plugins\ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.deps.json"" -fileListName ValueGeneratorCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1" +Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName ValueGeneratorImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\modules\launcher\Plugins\ValueGenerator\Images""" +Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""ValueGeneratorCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot" +Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""ValueGeneratorImagesCmpFiles"" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot" ## Plugins #ShortcutGuide diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj new file mode 100644 index 0000000000..ba684821bd --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj @@ -0,0 +1,19 @@ + + + + net7.0-windows + enable + false + + + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/GUIDGeneratorTests.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/GUIDGeneratorTests.cs new file mode 100644 index 0000000000..f15fcc9b33 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/GUIDGeneratorTests.cs @@ -0,0 +1,140 @@ +// 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.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests +{ + [TestClass] + public class GUIDGeneratorTests + { + [TestMethod] + public void GUIDv1Generator() + { + var guidRequest = new GUID.GUIDRequest(1); + guidRequest.Compute(); + var guid = guidRequest.Result; + + Assert.IsNotNull(guid); + Assert.AreEqual(0x1000, GetGUIDVersion(guid)); + } + + [TestMethod] + public void GUIDv3Generator() + { + var guidRequest = new GUID.GUIDRequest(3, "ns:DNS", "abc"); + guidRequest.Compute(); + var guid = guidRequest.Result; + + Assert.IsNotNull(guid); + Assert.AreEqual(0x3000, GetGUIDVersion(guid)); + } + + [TestMethod] + public void GUIDv4Generator() + { + var guidRequest = new GUID.GUIDRequest(4); + guidRequest.Compute(); + var guid = guidRequest.Result; + + Assert.IsNotNull(guid); + Assert.AreEqual(0x4000, GetGUIDVersion(guid)); + } + + [TestMethod] + public void GUIDv5Generator() + { + var guidRequest = new GUID.GUIDRequest(5, "ns:DNS", "abc"); + guidRequest.Compute(); + var guid = guidRequest.Result; + + Assert.IsNotNull(guid); + Assert.AreEqual(0x5000, GetGUIDVersion(guid)); + } + + [DataTestMethod] + [DataRow(3, "ns:DNS", "abc", "5bd670ce-29c8-3369-a8a1-10ce44c7259e")] + [DataRow(3, "ns:URL", "abc", "874a8cb4-4e91-3055-a476-3d3e2ffe375f")] + [DataRow(3, "ns:OID", "abc", "5557cd36-6b67-38ac-83fe-825f5905fc15")] + [DataRow(3, "ns:X500", "abc", "589392cb-93e1-392c-a846-367c45ed1ecc")] + [DataRow(3, "589392cb-93e1-392c-a846-367c45ed1ecc", "abc", "f55f77d2-feed-378e-aa47-2f716b07aaad")] + [DataRow(3, "abc", "abc", null)] + [DataRow(3, "", "abc", null)] + [DataRow(3, null, "abc", null)] + [DataRow(3, "abc", "", null)] + [DataRow(3, "abc", null, null)] + [DataRow(5, "ns:DNS", "abc", "6cb8e707-0fc5-5f55-88d4-d4fed43e64a8")] + [DataRow(5, "ns:URL", "abc", "68661508-f3c4-55b4-945d-ae2b4dfe5db4")] + [DataRow(5, "ns:OID", "abc", "7697a46f-b283-5da3-8e7c-62c11c03dd9e")] + [DataRow(5, "ns:X500", "abc", "53e882a6-63b1-578b-8bf1-8f0878cfa6b7")] + [DataRow(5, "589392cb-93e1-392c-a846-367c45ed1ecc", "abc", "396466f5-96f4-57e2-aea1-78e1bb1bacc6")] + [DataRow(5, "abc", "abc", null)] + [DataRow(5, "", "abc", null)] + [DataRow(5, null, "abc", null)] + [DataRow(5, "abc", "", null)] + [DataRow(5, "abc", null, null)] + public void GUIDv3Andv5(int version, string namespaceName, string name, string expectedResult) + { + var expectException = false; + if (namespaceName == null) + { + expectException = true; + } + + string[] predefinedNamespaces = new string[] { "ns:DNS", "ns:URL", "ns:OID", "ns:X500" }; + if (namespaceName != null && + !Guid.TryParse(namespaceName, out _) && + !predefinedNamespaces.Contains(namespaceName)) + { + expectException = true; + } + + if (name == null) + { + expectException = true; + } + + try + { + var guidRequest = new GUID.GUIDRequest(version, namespaceName, name); + + guidRequest.Compute(); + + if (expectException) + { + Assert.Fail("GUID generator should have thrown an exception"); + } + + if (namespaceName != string.Empty) + { + Assert.IsTrue(guidRequest.IsSuccessful); + Assert.AreEqual(expectedResult, guidRequest.ResultToString()); + } + else + { + Assert.IsFalse(guidRequest.IsSuccessful); + } + } + catch (AssertFailedException) + { + throw; + } + catch + { + if (!expectException) + { + throw; + } + } + } + + private static short GetGUIDVersion(byte[] guid) + { + var time_hi_and_version = BitConverter.ToInt16(guid.AsSpan()[6..8]); + return (short)(time_hi_and_version & 0xF000); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs new file mode 100644 index 0000000000..e17437556d --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs @@ -0,0 +1,92 @@ +// 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.Linq; +using System.Text.RegularExpressions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Wox.Plugin; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests +{ + [TestClass] + public class InputParserTests + { + [DataTestMethod] + [DataRow("md5 abc", typeof(Hashing.HashRequest))] + [DataRow("sha1 abc", typeof(Hashing.HashRequest))] + [DataRow("sha256 abc", typeof(Hashing.HashRequest))] + [DataRow("sha384 abc", typeof(Hashing.HashRequest))] + [DataRow("sha512 abc", typeof(Hashing.HashRequest))] + [DataRow("sha1111 abc", null)] + [DataRow("uuid", typeof(GUID.GUIDRequest))] + [DataRow("guidv3 ns:DNS abc", typeof(GUID.GUIDRequest))] + [DataRow("guidv2 ns:DNS abc", null)] + [DataRow("uUiD5 ns:URL abc", typeof(GUID.GUIDRequest))] + [DataRow("Guidvv ns:DNS abc", null)] + [DataRow("guidv4", typeof(GUID.GUIDRequest))] + [DataRow("base64 abc", typeof(Base64.Base64Request))] + [DataRow("base99 abc", null)] + [DataRow("base64s abc", null)] + public void ParserTest(string input, Type? expectedRequestType) + { + var parser = new InputParser(); + var query = new Query(input); + + var expectException = false; + string? command = null; + if (query.Terms.Count == 0) + { + expectException = true; + } + else + { + command = query.Terms[0]; + } + + if (command != null && !CommandIsKnown(command)) + { + expectException = true; + } + + try + { + IComputeRequest request = parser.ParseInput(query); + if (expectException) + { + Assert.Fail("Parser should have thrown an exception"); + } + + Assert.IsNotNull(request); + + Assert.AreEqual(expectedRequestType, request.GetType()); + } + catch (AssertFailedException) + { + throw; + } + catch + { + if (!expectException) + { + throw; + } + } + } + + private static bool CommandIsKnown(string command) + { + string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64" }; + + if (hashes.Contains(command.ToLowerInvariant())) + { + return true; + } + + Regex regex = new Regex("^(guid|uuid)([1345]{0,1}|v[1345]{1})$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + return regex.IsMatch(command); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64Request.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64Request.cs new file mode 100644 index 0000000000..b613e48606 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64Request.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; +using System.Text; +using Wox.Plugin.Logger; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64 +{ + public class Base64Request : IComputeRequest + { + public byte[] Result { get; set; } + + public string Description => "Base64 Encoding"; + + public bool IsSuccessful { get; set; } + + public string ErrorMessage { get; set; } + + private byte[] DataToEncode { get; set; } + + public Base64Request(byte[] dataToEncode) + { + DataToEncode = dataToEncode ?? throw new ArgumentNullException(nameof(dataToEncode)); + } + + public bool Compute() + { + IsSuccessful = true; + try + { + Result = Encoding.UTF8.GetBytes(System.Convert.ToBase64String(DataToEncode)); + } + catch (Exception e) + { + Log.Exception(e.Message, e, GetType()); + ErrorMessage = e.Message; + IsSuccessful = false; + } + + return IsSuccessful; + } + + public string ResultToString() + { + return Encoding.UTF8.GetString(Result); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Community.PowerToys.Run.Plugin.ValueGenerator.csproj b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Community.PowerToys.Run.Plugin.ValueGenerator.csproj new file mode 100644 index 0000000000..0298e66f7a --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Community.PowerToys.Run.Plugin.ValueGenerator.csproj @@ -0,0 +1,74 @@ + + + + + net7.0-windows + {D095BE44-1F2E-463E-A494-121892A75EA2} + true + $(Version).0 + Community.PowerToys.Run.Plugin.ValueGenerator + Community.PowerToys.Run.Plugin.ValueGenerator + false + false + true + ..\..\..\..\..\$(Platform)\$(Configuration)\modules\launcher\Plugins\ValueGenerator\ + + + + true + DEBUG;TRACE + full + false + + + + TRACE + true + pdbonly + + + + + false + + + false + + + + + + PreserveNewest + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDGenerator.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDGenerator.cs new file mode 100644 index 0000000000..6479972f13 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDGenerator.cs @@ -0,0 +1,119 @@ +// 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.Net; +using System.Security.Cryptography; +using System.Text; +using Wox.Plugin.Common.Win32; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID +{ + internal sealed class GUIDGenerator + { + // As defined in https://datatracker.ietf.org/doc/html/rfc4122#appendix-C + public static readonly Dictionary PredefinedNamespaces = new Dictionary() + { + { "ns:dns", new Guid(0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) }, + { "ns:url", new Guid(0x6ba7b811, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) }, + { "ns:oid", new Guid(0x6ba7b812, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) }, + { "ns:x500", new Guid(0x6ba7b814, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) }, + }; + + public static Guid V1() + { + GUIDDATA guiddata; + + int uuidCreateResult = NativeMethods.UuidCreateSequential(out guiddata); + + if (uuidCreateResult != Win32Constants.RPC_S_OK && uuidCreateResult != Win32Constants.RPC_S_UUID_LOCAL_ONLY) + { + throw new InvalidOperationException("Failed to create GUID version 1"); + } + + return new Guid(guiddata.Data1, guiddata.Data2, guiddata.Data3, guiddata.Data4); + } + + public static Guid V3(Guid uuidNamespace, string uuidName) + { + return V3AndV5(uuidNamespace, uuidName, 3); + } + + public static Guid V4() + { + return Guid.NewGuid(); + } + + public static Guid V5(Guid uuidNamespace, string uuidName) + { + return V3AndV5(uuidNamespace, uuidName, 5); + } + + private static Guid V3AndV5(Guid uuidNamespace, string uuidName, short version) + { + byte[] namespaceBytes = uuidNamespace.ToByteArray(); + byte[] networkEndianNamespaceBytes = namespaceBytes; + + // Convert time_low, time_mid and time_hi_and_version to network order + int time_low = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(networkEndianNamespaceBytes.AsSpan()[0..4])); + short time_mid = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkEndianNamespaceBytes.AsSpan()[4..6])); + short time_hi_and_version = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkEndianNamespaceBytes.AsSpan()[6..8])); + + Buffer.BlockCopy(BitConverter.GetBytes(time_low), 0, networkEndianNamespaceBytes, 0, 4); + Buffer.BlockCopy(BitConverter.GetBytes(time_mid), 0, networkEndianNamespaceBytes, 4, 2); + Buffer.BlockCopy(BitConverter.GetBytes(time_hi_and_version), 0, networkEndianNamespaceBytes, 6, 2); + + byte[] nameBytes = Encoding.ASCII.GetBytes(uuidName); + + byte[] namespaceAndNameBytes = new byte[networkEndianNamespaceBytes.Length + nameBytes.Length]; + Buffer.BlockCopy(networkEndianNamespaceBytes, 0, namespaceAndNameBytes, 0, namespaceBytes.Length); + Buffer.BlockCopy(nameBytes, 0, namespaceAndNameBytes, networkEndianNamespaceBytes.Length, nameBytes.Length); + + byte[] hash; + if (version == 3) + { +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + hash = MD5.HashData(namespaceAndNameBytes); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms + } + else if (version == 5) + { +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms + hash = SHA1.HashData(namespaceAndNameBytes); +#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms + } + else + { + throw new InvalidOperationException($"GUID version {version} does not exist"); + } + + byte[] result = new byte[16]; + + // Copy first 16-bytes of the hash into our Uuid result + Buffer.BlockCopy(hash, 0, result, 0, 16); + + // Convert put time_low, time_mid and time_hi_and_version back to host order + time_low = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(result.AsSpan()[0..4])); + time_mid = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(result.AsSpan()[4..6])); + time_hi_and_version = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(result.AsSpan()[6..8])); + + // Set version 'version' in time_hi_and_version field according to https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.3 + time_hi_and_version &= 0x0FFF; +#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand + time_hi_and_version = (short)(time_hi_and_version | (version << 12)); +#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand + + Buffer.BlockCopy(BitConverter.GetBytes(time_low), 0, result, 0, 4); + Buffer.BlockCopy(BitConverter.GetBytes(time_mid), 0, result, 4, 2); + Buffer.BlockCopy(BitConverter.GetBytes(time_hi_and_version), 0, result, 6, 2); + + // Set upper two bits to "10" + result[8] &= 0x3F; + result[8] |= 0x80; + + return new Guid(result); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs new file mode 100644 index 0000000000..00d30bba90 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs @@ -0,0 +1,148 @@ +// 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.Security.Cryptography; +using Wox.Plugin.Logger; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID +{ + public class GUIDRequest : IComputeRequest + { + public byte[] Result { get; set; } + + public bool IsSuccessful { get; set; } + + public string ErrorMessage { get; set; } + + private int Version { get; set; } + + public string Description + { + get + { + switch (Version) + { + case 1: + return "Version 1: Time base GUID"; + case 3: + case 5: + string hashAlgorithm; + if (Version == 3) + { + hashAlgorithm = HashAlgorithmName.MD5.ToString(); + } + else + { + hashAlgorithm = HashAlgorithmName.SHA1.ToString(); + } + + return $"Version {Version} ({hashAlgorithm}): Namespace and name based GUID."; + case 4: + return "Version 4: Randomly generated GUID"; + default: + return string.Empty; + } + } + } + + private Guid? GuidNamespace { get; set; } + + private string GuidName { get; set; } + + private Guid GuidResult { get; set; } + + private static readonly string NullNamespaceError = $"The first parameter needs to be a valid GUID or one of: {string.Join(", ", GUIDGenerator.PredefinedNamespaces.Keys)}"; + + public GUIDRequest(int version, string guidNamespace = null, string name = null) + { + Version = version; + + if (Version < 1 || Version > 5 || Version == 2) + { + throw new ArgumentException("Unsupported GUID version. Supported versions are 1, 3, 4 and 5"); + } + + if (version == 3 || version == 5) + { + if (guidNamespace == null) + { + throw new ArgumentNullException(null, NullNamespaceError); + } + + Guid guid; + if (GUIDGenerator.PredefinedNamespaces.TryGetValue(guidNamespace.ToLowerInvariant(), out guid)) + { + GuidNamespace = guid; + } + else if (Guid.TryParse(guidNamespace, out guid)) + { + GuidNamespace = guid; + } + else + { + throw new ArgumentNullException(null, NullNamespaceError); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + else + { + GuidName = name; + } + } + else + { + GuidNamespace = null; + } + + ErrorMessage = null; + } + + public bool Compute() + { + IsSuccessful = true; + try + { + switch (Version) + { + case 1: + GuidResult = GUIDGenerator.V1(); + break; + case 3: + GuidResult = GUIDGenerator.V3(GuidNamespace.Value, GuidName); + break; + case 4: + GuidResult = GUIDGenerator.V4(); + break; + case 5: + GuidResult = GUIDGenerator.V5(GuidNamespace.Value, GuidName); + break; + } + + Result = GuidResult.ToByteArray(); + } + catch (InvalidOperationException e) + { + Log.Exception(e.Message, e, GetType()); + ErrorMessage = e.Message; + IsSuccessful = false; + } + + return IsSuccessful; + } + + public string ResultToString() + { + if (!IsSuccessful) + { + return ErrorMessage; + } + + return GuidResult.ToString(); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Hashing/HashRequest.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Hashing/HashRequest.cs new file mode 100644 index 0000000000..352372285f --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Hashing/HashRequest.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; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using Wox.Plugin.Logger; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.Hashing +{ + public class HashRequest : IComputeRequest + { + public byte[] Result { get; set; } + + public bool IsSuccessful { get; set; } + + public string ErrorMessage { get; set; } + + public string Description + { + get + { + return $"{AlgorithmName}({Encoding.UTF8.GetString(DataToHash)})"; + } + } + + public HashAlgorithmName AlgorithmName { get; set; } + + private byte[] DataToHash { get; set; } + + private static Dictionary _algorithms = new Dictionary() + { +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + { HashAlgorithmName.MD5, MD5.Create() }, +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms + +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms + { HashAlgorithmName.SHA1, SHA1.Create() }, +#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms + { HashAlgorithmName.SHA256, SHA256.Create() }, + { HashAlgorithmName.SHA384, SHA384.Create() }, + { HashAlgorithmName.SHA512, SHA512.Create() }, + }; + + public HashRequest(HashAlgorithmName algorithmName, byte[] dataToHash) + { + AlgorithmName = algorithmName; + DataToHash = dataToHash ?? throw new ArgumentNullException(nameof(dataToHash)); + } + + public bool Compute() + { + if (DataToHash == null) + { + ErrorMessage = "Null data passed to hash request"; + Log.Exception(ErrorMessage, new InvalidOperationException(ErrorMessage), GetType()); + IsSuccessful = false; + } + else + { + Result = _algorithms[AlgorithmName].ComputeHash(DataToHash); + IsSuccessful = true; + } + + return IsSuccessful; + } + + public string ResultToString() + { + StringBuilder sb = new StringBuilder(); + foreach (var b in Result) + { + sb.Append(b.ToString("X2", null)); + } + + return sb.ToString(); + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/IComputeRequest.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/IComputeRequest.cs new file mode 100644 index 0000000000..c24d041ac7 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/IComputeRequest.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; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator +{ + public interface IComputeRequest + { + public byte[] Result { get; set; } + + public string Description { get; } + + public bool IsSuccessful { get; set; } + + public string ErrorMessage { get; set; } + + bool Compute(); + + public string ResultToString(); + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/ValueGenerator.dark.png b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/ValueGenerator.dark.png new file mode 100644 index 0000000000..b94d83c300 Binary files /dev/null and b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/ValueGenerator.dark.png differ diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/ValueGenerator.light.png b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/ValueGenerator.light.png new file mode 100644 index 0000000000..e63953a560 Binary files /dev/null and b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/ValueGenerator.light.png differ diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/Warning.dark.png b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/Warning.dark.png new file mode 100644 index 0000000000..f033c78fda Binary files /dev/null and b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/Warning.dark.png differ diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/Warning.light.png b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/Warning.light.png new file mode 100644 index 0000000000..37dd08c87e Binary files /dev/null and b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Images/Warning.light.png differ diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs new file mode 100644 index 0000000000..394fbf8752 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs @@ -0,0 +1,132 @@ +// 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.Security.Cryptography; +using System.Text; +using Community.PowerToys.Run.Plugin.ValueGenerator.Base64; +using Community.PowerToys.Run.Plugin.ValueGenerator.GUID; +using Community.PowerToys.Run.Plugin.ValueGenerator.Hashing; +using Wox.Plugin; +using Wox.Plugin.Logger; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator +{ + public class InputParser + { + public IComputeRequest ParseInput(Query query) + { + IComputeRequest request; + + if (query.Terms.Count == 0) + { + throw new FormatException("Empty request"); + } + + string command = query.Terms[0]; + + if (command.ToLower(null) == "md5") + { + int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase); + string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim(); + + if (content == string.Empty) + { + throw new FormatException("Empty hash request"); + } + + Log.Debug($"Will calculate MD5 hash for: {content}", GetType()); + request = new HashRequest(HashAlgorithmName.MD5, Encoding.UTF8.GetBytes(content)); + } + else if (command.StartsWith("sha", StringComparison.InvariantCultureIgnoreCase)) + { + int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase); + string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim(); + HashAlgorithmName algorithmName; + + switch (command.Substring(3)) + { + case "1": + algorithmName = HashAlgorithmName.SHA1; + break; + + case "256": + algorithmName = HashAlgorithmName.SHA256; + break; + + case "384": + algorithmName = HashAlgorithmName.SHA384; + break; + + case "512": + algorithmName = HashAlgorithmName.SHA512; + break; + default: + throw new ArgumentException("Unknown SHA variant. Supported variants: SHA1, SHA256, SHA384, SHA512"); + } + + if (content == string.Empty) + { + throw new FormatException("Empty hash request"); + } + + Log.Debug($"Will calculate {algorithmName} hash for: {content}", GetType()); + request = new HashRequest(algorithmName, Encoding.UTF8.GetBytes(content)); + } + else if (command.StartsWith("guid", StringComparison.InvariantCultureIgnoreCase) || + command.StartsWith("uuid", StringComparison.InvariantCultureIgnoreCase)) + { + string content = query.Search.Substring(command.Length).Trim(); + + // Default to version 4 + int version = 4; + string versionQuery = command.Substring(4); + + if (versionQuery.Length > 0) + { + if (versionQuery.StartsWith("v", StringComparison.InvariantCultureIgnoreCase)) + { + versionQuery = versionQuery.Substring(1); + } + + if (!int.TryParse(versionQuery, null, out version)) + { + throw new ArgumentException("Could not determine requested GUID version"); + } + } + + if (version == 3 || version == 5) + { + string[] sParameters = content.Split(" "); + + if (sParameters.Length != 2) + { + throw new ArgumentException("GUID versions 3 and 5 require 2 parameters - a namespace GUID and a name"); + } + + string namespaceParameter = sParameters[0]; + string nameParameter = sParameters[1]; + + request = new GUIDRequest(version, namespaceParameter, nameParameter); + } + else + { + request = new GUIDRequest(version); + } + } + else if (command.ToLower(null) == "base64") + { + int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase); + string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim(); + request = new Base64Request(Encoding.UTF8.GetBytes(content)); + } + else + { + throw new FormatException($"Invalid Query: {query.RawUserQuery}"); + } + + return request; + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs new file mode 100644 index 0000000000..50d304c8d6 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs @@ -0,0 +1,156 @@ +// 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.Runtime.InteropServices; +using System.Threading; +using System.Windows; +using Community.PowerToys.Run.Plugin.ValueGenerator.Properties; +using ManagedCommon; +using Wox.Plugin; +using Wox.Plugin.Logger; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator +{ + public class Main : IPlugin, IPluginI18n, IDisposable + { + public string Name => Resources.plugin_name; + + public string Description => Resources.plugin_description; + + private PluginInitContext _context; + private static bool _isLightTheme = true; + private bool _disposed; + private static InputParser _inputParser = new InputParser(); + + public void Init(PluginInitContext context) + { + context = context ?? throw new ArgumentNullException(paramName: nameof(context)); + + _context = context; + _context.API.ThemeChanged += OnThemeChanged; + UpdateIconPath(_context.API.GetCurrentTheme()); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void OnThemeChanged(Theme currentTheme, Theme newTheme) + { + UpdateIconPath(newTheme); + } + + private static void UpdateIconPath(Theme theme) + { + if (theme == Theme.Light || theme == Theme.HighContrastWhite) + { + _isLightTheme = true; + } + else + { + _isLightTheme = false; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_context != null && _context.API != null) + { + _context.API.ThemeChanged -= OnThemeChanged; + } + + _disposed = true; + } + } + } + + public string GetTranslatedPluginDescription() + { + return Resources.plugin_description; + } + + public string GetTranslatedPluginTitle() + { + return Resources.plugin_name; + } + + public List Query(Query query) + { + if (query == null) + { + throw new ArgumentNullException(paramName: nameof(query)); + } + + var results = new List(); + try + { + IComputeRequest computeRequest = _inputParser.ParseInput(query); + results.Add(GetResult(computeRequest)); + } + catch (ArgumentException e) + { + results.Add(GetErrorResult(e.Message)); + } + catch (FormatException e) + { + Log.Debug(GetTranslatedPluginTitle() + ": " + e.Message, GetType()); + } + + return results; + } + + private Result GetResult(IComputeRequest request) + { + request.Compute(); + + return new Result + { + ContextData = request.Result, + Title = request.ResultToString(), + IcoPath = _isLightTheme ? "Images/ValueGenerator.dark.png" : "Images/ValueGenerator.light.png", + Score = 300, + SubTitle = request.Description, + Action = c => + { + var ret = false; + var thread = new Thread(() => + { + try + { + Clipboard.SetText(request.ResultToString()); + ret = true; + } + catch (ExternalException) + { + MessageBox.Show(Properties.Resources.copy_failed); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + return ret; + }, + }; + } + + private Result GetErrorResult(string errorMessage) + { + return new Result + { + Title = Resources.error_title, + SubTitle = errorMessage, + IcoPath = _isLightTheme ? "Images/Warning.light.png" : "Images/Warning.dark.png", + Action = _ => { return true; }, + }; + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..88e011f1bf --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// 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 Community.PowerToys.Run.Plugin.ValueGenerator.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()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Community.PowerToys.Run.Plugin.ValueGenerator.Properties.Resources", typeof(Resources).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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Copy failed. + /// + public static string copy_failed { + get { + return ResourceManager.GetString("copy_failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy {0} to clipboard. + /// + public static string copy_to_clipboard { + get { + return ResourceManager.GetString("copy_to_clipboard", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value Generator Error. + /// + public static string error_title { + get { + return ResourceManager.GetString("error_title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A plugin to calculate hashes and generate values.. + /// + public static string plugin_description { + get { + return ResourceManager.GetString("plugin_description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value Generator. + /// + public static string plugin_name { + get { + return ResourceManager.GetString("plugin_name", resourceCulture); + } + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx new file mode 100644 index 0000000000..5c07f6d5b8 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Copy failed + + + Copy {0} to clipboard + + + Value Generator Error + + + A plugin to calculate hashes and generate values. + + + Value Generator + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/plugin.json b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/plugin.json new file mode 100644 index 0000000000..085efe787b --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/plugin.json @@ -0,0 +1,14 @@ +{ + "ID": "a26b1bb4dbd911edafa10242ac120002", + "ActionKeyword": "#", + "isGlobal": false, + "Name": "ValueGenerator", + "Description": "A plugin to calculate hashes and generate values", + "Author": "IHorvalds", + "Version": "1.0.0", + "Language": "csharp", + "Website": "https://github/com/IHorvalds", + "IcoPathDark": "Images\\ValueGenerator.dark.png", + "IcoPathLight": "Images\\ValueGenerator.light.png", + "ExecuteFileName": "Community.PowerToys.Run.Plugin.ValueGenerator.dll" +} \ No newline at end of file diff --git a/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs b/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs index 432d7ea5f5..4e34eac2cc 100644 --- a/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs +++ b/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs @@ -118,6 +118,9 @@ namespace Wox.Plugin.Common.Win32 [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); + + [DllImport("rpcrt4.dll")] + public static extern int UuidCreateSequential(out GUIDDATA Uuid); } [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "These are the names used by win32.")] @@ -143,6 +146,16 @@ namespace Wox.Plugin.Common.Win32 /// Closes the window /// public const int SC_CLOSE = 0xF060; + + /// + /// RPC call succeeded + /// + public const int RPC_S_OK = 0; + + /// + /// The UUID is guaranteed to be unique to this computer only. + /// + public const int RPC_S_UUID_LOCAL_ONLY = 0x720; } public static class ShellItemTypeConstants @@ -618,6 +631,16 @@ namespace Wox.Plugin.Common.Win32 AllAccess = StandardRightsRequired | Synchronize | 0xFFFF, } + [StructLayout(LayoutKind.Sequential)] + public struct GUIDDATA + { + public int Data1; + public short Data2; + public short Data3; + [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Data4; + } + /// /// Contains information about the placement of a window on the screen. /// diff --git a/src/modules/launcher/Wox.Plugin/Query.cs b/src/modules/launcher/Wox.Plugin/Query.cs index b4752e2892..c8bc934508 100644 --- a/src/modules/launcher/Wox.Plugin/Query.cs +++ b/src/modules/launcher/Wox.Plugin/Query.cs @@ -44,6 +44,18 @@ namespace Wox.Plugin } } + /// + /// Gets the query as entered by the user. + /// You should only use this property if you need to process the raw text directly. + /// + public string RawUserQuery + { + get + { + return _query; + } + } + private string _search; ///