diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index cf4af7fa8b..5c5b55762d 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -29,11 +29,14 @@ RUS AYUV bak Bcl +bgcode +Deflatealgorithm exa exabyte Gbits Gbps gcode +Heatshrink Mbits MBs mkv diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index 6870b3e86e..7ad88bde19 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -18,6 +18,7 @@ /TestFiles/ [^/]\.cur$ [^/]\.gcode$ +[^/]\.bgcode$ [^/]\.rgs$ \.a$ \.ai$ diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index eac48a607f..6f8f817492 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -66,6 +66,12 @@ "PowerToys.GcodeThumbnailProvider.dll", "PowerToys.GcodeThumbnailProvider.exe", "PowerToys.GcodeThumbnailProviderCpp.dll", + "PowerToys.BgcodePreviewHandler.dll", + "PowerToys.BgcodePreviewHandler.exe", + "PowerToys.BgcodePreviewHandlerCpp.dll", + "PowerToys.BgcodeThumbnailProvider.dll", + "PowerToys.BgcodeThumbnailProvider.exe", + "PowerToys.BgcodeThumbnailProviderCpp.dll", "PowerToys.ManagedTelemetry.dll", "PowerToys.MarkdownPreviewHandler.dll", "PowerToys.MarkdownPreviewHandler.exe", diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md index 92711f00dd..56a2eb9eee 100644 --- a/DATA_AND_PRIVACY.md +++ b/DATA_AND_PRIVACY.md @@ -383,6 +383,18 @@ _If you want to find diagnostic data events in the source code, these two links Microsoft.PowerToys.GcodeFilePreviewError Triggered when there is an error previewing a G-code file. + + Microsoft.PowerToys.BgcodeFileHandlerLoaded + Triggered when a Binary G-code file handler is loaded. + + + Microsoft.PowerToys.BgcodeFilePreviewed + Occurs when a Binary G-code file is previewed in File Explorer. + + + Microsoft.PowerToys.BgcodeFilePreviewError + Triggered when there is an error previewing a Binary G-code file. + Microsoft.PowerToys.MarkdownFileHandlerLoaded Occurs when a Markdown file handler is loaded. diff --git a/PowerToys.sln b/PowerToys.sln index 18b2dd569b..0775b28def 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -714,6 +714,18 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "sr EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodePreviewHandler", "src\modules\previewpane\BgcodePreviewHandler\BgcodePreviewHandler.csproj", "{9E0CBC06-F29A-4810-B93C-97D53863B95E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodePreviewHandlerCpp", "src\modules\previewpane\BgcodePreviewHandlerCpp\BgcodePreviewHandlerCpp.vcxproj", "{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodeThumbnailProviderCpp", "src\modules\previewpane\BgcodeThumbnailProviderCpp\BgcodeThumbnailProviderCpp.vcxproj", "{47B0678C-806B-4FE1-9F50-46BA88989532}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodeThumbnailProvider", "src\modules\previewpane\BgcodeThumbnailProvider\BgcodeThumbnailProvider.csproj", "{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-BgcodePreviewHandler", "src\modules\previewpane\UnitTests-BgcodePreviewHandler\UnitTests-BgcodePreviewHandler.csproj", "{99CA1509-FB73-456E-AFAF-AB89C017BD72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-BgcodeThumbnailProvider", "src\modules\previewpane\UnitTests-BgcodeThumbnailProvider\UnitTests-BgcodeThumbnailProvider.csproj", "{61CBF221-9452-4934-B685-146285E080D7}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseUtils.UITests", "src\modules\MouseUtils\MouseUtils.UITests\MouseUtils.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesEditorUITest", "src\modules\Workspaces\WorkspacesEditorUITest\WorkspacesEditorUITest.csproj", "{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}" @@ -2626,6 +2638,54 @@ Global {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|ARM64.ActiveCfg = Release|ARM64 {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.ActiveCfg = Release|x64 {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.Build.0 = Release|x64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|ARM64.Build.0 = Debug|ARM64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|x64.ActiveCfg = Debug|x64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|x64.Build.0 = Debug|x64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|ARM64.ActiveCfg = Release|ARM64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|ARM64.Build.0 = Release|ARM64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|x64.ActiveCfg = Release|x64 + {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|x64.Build.0 = Release|x64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|ARM64.Build.0 = Debug|ARM64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|x64.ActiveCfg = Debug|x64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|x64.Build.0 = Debug|x64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|ARM64.ActiveCfg = Release|ARM64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|ARM64.Build.0 = Release|ARM64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|x64.ActiveCfg = Release|x64 + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|x64.Build.0 = Release|x64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|ARM64.Build.0 = Debug|ARM64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|x64.ActiveCfg = Debug|x64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|x64.Build.0 = Debug|x64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|ARM64.ActiveCfg = Release|ARM64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|ARM64.Build.0 = Release|ARM64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|x64.ActiveCfg = Release|x64 + {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|x64.Build.0 = Release|x64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|ARM64.Build.0 = Debug|ARM64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|x64.ActiveCfg = Debug|x64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|x64.Build.0 = Debug|x64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|ARM64.ActiveCfg = Release|ARM64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|ARM64.Build.0 = Release|ARM64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|x64.ActiveCfg = Release|x64 + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|x64.Build.0 = Release|x64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|ARM64.Build.0 = Debug|ARM64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|x64.ActiveCfg = Debug|x64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|x64.Build.0 = Debug|x64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|ARM64.ActiveCfg = Release|ARM64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|ARM64.Build.0 = Release|ARM64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|x64.ActiveCfg = Release|x64 + {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|x64.Build.0 = Release|x64 + {61CBF221-9452-4934-B685-146285E080D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {61CBF221-9452-4934-B685-146285E080D7}.Debug|ARM64.Build.0 = Debug|ARM64 + {61CBF221-9452-4934-B685-146285E080D7}.Debug|x64.ActiveCfg = Debug|x64 + {61CBF221-9452-4934-B685-146285E080D7}.Debug|x64.Build.0 = Debug|x64 + {61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.ActiveCfg = Release|ARM64 + {61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64 + {61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64 + {61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64 {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64 {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64 {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64 @@ -2940,6 +3000,12 @@ Global {0217E86E-3476-9946-DE8E-9D200CEBD47A} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79} {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} + {9E0CBC06-F29A-4810-B93C-97D53863B95E} = {2F305555-C296-497E-AC20-5FA1B237996A} + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C} = {2F305555-C296-497E-AC20-5FA1B237996A} + {47B0678C-806B-4FE1-9F50-46BA88989532} = {2F305555-C296-497E-AC20-5FA1B237996A} + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A} + {99CA1509-FB73-456E-AFAF-AB89C017BD72} = {2F305555-C296-497E-AC20-5FA1B237996A} + {61CBF221-9452-4934-B685-146285E080D7} = {2F305555-C296-497E-AC20-5FA1B237996A} {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {322566EF-20DC-43A6-B9F8-616AF942579A} {43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482} diff --git a/doc/devdocs/tools/verification-scripts.md b/doc/devdocs/tools/verification-scripts.md index cff58f478f..d31ecd8e66 100644 --- a/doc/devdocs/tools/verification-scripts.md +++ b/doc/devdocs/tools/verification-scripts.md @@ -18,6 +18,7 @@ This script checks the preview handler registration for the following file types * .svgz * .pdf * .gcode +* .bgcode * .stl * .txt * .ini diff --git a/installer/PowerToysSetup/Resources.wxs b/installer/PowerToysSetup/Resources.wxs index 03a6cfba30..8f7ff494da 100644 --- a/installer/PowerToysSetup/Resources.wxs +++ b/installer/PowerToysSetup/Resources.wxs @@ -233,6 +233,15 @@ + + + + + + processesToTerminate = { + std::array processesToTerminate = { L"PowerToys.PowerLauncher.exe", L"PowerToys.Settings.exe", L"PowerToys.AdvancedPaste.exe", @@ -1186,12 +1186,14 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) L"PowerToys.PowerRename.exe", L"PowerToys.ImageResizer.exe", L"PowerToys.GcodeThumbnailProvider.exe", + L"PowerToys.BgcodeThumbnailProvider.exe", L"PowerToys.PdfThumbnailProvider.exe", L"PowerToys.MonacoPreviewHandler.exe", L"PowerToys.MarkdownPreviewHandler.exe", L"PowerToys.StlThumbnailProvider.exe", L"PowerToys.SvgThumbnailProvider.exe", L"PowerToys.GcodePreviewHandler.exe", + L"PowerToys.BgcodePreviewHandler.exe", L"PowerToys.QoiPreviewHandler.exe", L"PowerToys.PdfPreviewHandler.exe", L"PowerToys.QoiThumbnailProvider.exe", diff --git a/src/common/FilePreviewCommon/BgcodeBlockType.cs b/src/common/FilePreviewCommon/BgcodeBlockType.cs new file mode 100644 index 0000000000..296231e91c --- /dev/null +++ b/src/common/FilePreviewCommon/BgcodeBlockType.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. + +namespace Microsoft.PowerToys.FilePreviewCommon +{ + public enum BgcodeBlockType + { + FileMetadataBlock = 0, + GCodeBlock = 1, + SlicerMetadataBlock = 2, + PrinterMetadataBlock = 3, + PrintMetadataBlock = 4, + ThumbnailBlock = 5, + } +} diff --git a/src/common/FilePreviewCommon/BgcodeChecksumType.cs b/src/common/FilePreviewCommon/BgcodeChecksumType.cs new file mode 100644 index 0000000000..8d223568b7 --- /dev/null +++ b/src/common/FilePreviewCommon/BgcodeChecksumType.cs @@ -0,0 +1,12 @@ +// 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 Microsoft.PowerToys.FilePreviewCommon +{ + public enum BgcodeChecksumType + { + None = 0, + CRC32 = 1, + } +} diff --git a/src/common/FilePreviewCommon/BgcodeCompressionType.cs b/src/common/FilePreviewCommon/BgcodeCompressionType.cs new file mode 100644 index 0000000000..95f15705e6 --- /dev/null +++ b/src/common/FilePreviewCommon/BgcodeCompressionType.cs @@ -0,0 +1,14 @@ +// 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 Microsoft.PowerToys.FilePreviewCommon +{ + public enum BgcodeCompressionType + { + NoCompression = 0, + DeflateAlgorithm = 1, + HeatshrinkAlgorithm11 = 2, + HeatshrinkAlgorithm12 = 3, + } +} diff --git a/src/common/FilePreviewCommon/BgcodeHelper.cs b/src/common/FilePreviewCommon/BgcodeHelper.cs new file mode 100644 index 0000000000..0b1dca2d64 --- /dev/null +++ b/src/common/FilePreviewCommon/BgcodeHelper.cs @@ -0,0 +1,129 @@ +// 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 System.IO; +using System.IO.Compression; +using System.Linq; + +namespace Microsoft.PowerToys.FilePreviewCommon +{ + /// + /// Bgcode file helper class. + /// + public static class BgcodeHelper + { + private const uint MagicNumber = 'G' | 'C' << 8 | 'D' << 16 | 'E' << 24; + + /// + /// Gets any thumbnails found in a bgcode file. + /// + /// The instance to the bgcode file. + /// The thumbnails found in a bgcode file. + public static IEnumerable GetThumbnails(BinaryReader reader) + { + var magicNumber = reader.ReadUInt32(); + + if (magicNumber != MagicNumber) + { + throw new InvalidDataException("Invalid magic number."); + } + + var version = reader.ReadUInt32(); + + if (version != 1) + { + // Version 1 is the only one that exists + throw new InvalidDataException("Unsupported version."); + } + + var checksum = (BgcodeChecksumType)reader.ReadUInt16(); + + while (reader.BaseStream.Position < reader.BaseStream.Length) + { + var blockType = (BgcodeBlockType)reader.ReadUInt16(); + var compression = (BgcodeCompressionType)reader.ReadUInt16(); + var uncompressedSize = reader.ReadUInt32(); + + var size = compression == BgcodeCompressionType.NoCompression ? uncompressedSize : reader.ReadUInt32(); + + switch (blockType) + { + case BgcodeBlockType.FileMetadataBlock: + case BgcodeBlockType.PrinterMetadataBlock: + case BgcodeBlockType.PrintMetadataBlock: + case BgcodeBlockType.SlicerMetadataBlock: + case BgcodeBlockType.GCodeBlock: + reader.BaseStream.Seek(2 + size, SeekOrigin.Current); // Skip + + break; + + case BgcodeBlockType.ThumbnailBlock: + var format = (BgcodeThumbnailFormat)reader.ReadUInt16(); + + reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip width and height + + var data = ReadAndDecompressData(reader, compression, (int)size); + + if (data != null) + { + yield return new BgcodeThumbnail(format, data); + } + + break; + } + + if (checksum == BgcodeChecksumType.CRC32) + { + reader.BaseStream.Seek(4, SeekOrigin.Current); // Skip checksum + } + } + } + + /// + /// Gets the best thumbnail available in a bgcode file. + /// + /// The instance to the gcode file. + /// The best thumbnail available in the gcode file. + public static BgcodeThumbnail? GetBestThumbnail(BinaryReader reader) + { + return GetThumbnails(reader) + .OrderByDescending(x => x.Format switch + { + BgcodeThumbnailFormat.PNG => 2, + BgcodeThumbnailFormat.QOI => 1, + BgcodeThumbnailFormat.JPG => 0, + _ => 0, + }) + .ThenByDescending(x => x.Data.Length) + .FirstOrDefault(); + } + + private static byte[]? ReadAndDecompressData(BinaryReader reader, BgcodeCompressionType compression, int size) + { + // Though the spec doesn't actually mention it, the reference encoder code never applies compression to thumbnails data + // which makes complete sense as this data is PNG, JPEG or QOI encoded so already compressed as much as possible! + switch (compression) + { + case BgcodeCompressionType.NoCompression: + return reader.ReadBytes(size); + + case BgcodeCompressionType.DeflateAlgorithm: + var buffer = new byte[size]; + + using (var deflateStream = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true)) + { + deflateStream.ReadExactly(buffer, 0, size); + } + + return buffer; + + default: + reader.BaseStream.Seek(size, SeekOrigin.Current); // Skip unknown or unsupported compression types + + return null; + } + } + } +} diff --git a/src/common/FilePreviewCommon/BgcodeThumbnail.cs b/src/common/FilePreviewCommon/BgcodeThumbnail.cs new file mode 100644 index 0000000000..d3449556c5 --- /dev/null +++ b/src/common/FilePreviewCommon/BgcodeThumbnail.cs @@ -0,0 +1,66 @@ +// 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.IO; + +namespace Microsoft.PowerToys.FilePreviewCommon +{ + /// + /// Represents a bgcode thumbnail. + /// + public class BgcodeThumbnail + { + /// + /// Gets the bgcode thumbnail image format. + /// + public BgcodeThumbnailFormat Format { get; } + + /// + /// Gets the bgcode thumbnail image data. + /// + public byte[] Data { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The bgcode thumbnail image format. + /// The bgcode thumbnail image data. + public BgcodeThumbnail(BgcodeThumbnailFormat format, byte[] data) + { + Format = format; + Data = data; + } + + /// + /// Gets a representing this thumbnail. + /// + /// A representing this thumbnail. + public Bitmap? GetBitmap() + { + switch (Format) + { + case BgcodeThumbnailFormat.JPG: + case BgcodeThumbnailFormat.PNG: + return BitmapFromByteArray(); + + case BgcodeThumbnailFormat.QOI: + return BitmapFromQoiByteArray(); + + default: + return null; + } + } + + private Bitmap BitmapFromByteArray() + { + return new Bitmap(new MemoryStream(Data)); + } + + private Bitmap BitmapFromQoiByteArray() + { + return QoiImage.FromStream(new MemoryStream(Data)); + } + } +} diff --git a/src/common/FilePreviewCommon/BgcodeThumbnailFormat.cs b/src/common/FilePreviewCommon/BgcodeThumbnailFormat.cs new file mode 100644 index 0000000000..61219dae48 --- /dev/null +++ b/src/common/FilePreviewCommon/BgcodeThumbnailFormat.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. + +namespace Microsoft.PowerToys.FilePreviewCommon +{ + public enum BgcodeThumbnailFormat + { + /// + /// PNG image format. + /// + PNG = 0, + + /// + /// JPG image format. + /// + JPG = 1, + + /// + /// QOI image format. + /// + QOI = 2, + } +} diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index 62b5b49a9d..87ef1721b1 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -56,6 +56,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredGcodePreviewEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredBgcodePreviewEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredBgcodePreviewEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredSvgThumbnailsEnabledValue() { return static_cast(powertoys_gpo::getConfiguredSvgThumbnailsEnabledValue()); @@ -68,6 +72,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredGcodeThumbnailsEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredBgcodeThumbnailsEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredBgcodeThumbnailsEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredStlThumbnailsEnabledValue() { return static_cast(powertoys_gpo::getConfiguredStlThumbnailsEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 0d7783883b..33f90e15c9 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -21,9 +21,11 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredMouseWithoutBordersEnabledValue(); static GpoRuleConfigured GetConfiguredPdfPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredGcodePreviewEnabledValue(); + static GpoRuleConfigured GetConfiguredBgcodePreviewEnabledValue(); static GpoRuleConfigured GetConfiguredSvgThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredPdfThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredGcodeThumbnailsEnabledValue(); + static GpoRuleConfigured GetConfiguredBgcodeThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredStlThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredHostsFileEditorEnabledValue(); static GpoRuleConfigured GetConfiguredImageResizerEnabledValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 1e3c3a19f5..252b4d128a 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -24,9 +24,11 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredMonacoPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredPdfPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredGcodePreviewEnabledValue(); + static GpoRuleConfigured GetConfiguredBgcodePreviewEnabledValue(); static GpoRuleConfigured GetConfiguredSvgThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredPdfThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredGcodeThumbnailsEnabledValue(); + static GpoRuleConfigured GetConfiguredBgcodeThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredStlThumbnailsEnabledValue(); static GpoRuleConfigured GetConfiguredHostsFileEditorEnabledValue(); static GpoRuleConfigured GetConfiguredImageResizerEnabledValue(); diff --git a/src/common/interop/Constants.cpp b/src/common/interop/Constants.cpp index 62e6425f6f..4fc9fca6f6 100644 --- a/src/common/interop/Constants.cpp +++ b/src/common/interop/Constants.cpp @@ -127,6 +127,10 @@ namespace winrt::PowerToys::Interop::implementation { return CommonSharedConstants::GCODE_PREVIEW_RESIZE_EVENT; } + hstring Constants::BgcodePreviewResizeEvent() + { + return CommonSharedConstants::BGCODE_PREVIEW_RESIZE_EVENT; + } hstring Constants::QoiPreviewResizeEvent() { return CommonSharedConstants::QOI_PREVIEW_RESIZE_EVENT; diff --git a/src/common/interop/Constants.h b/src/common/interop/Constants.h index 1b3a0f556c..80834442c5 100644 --- a/src/common/interop/Constants.h +++ b/src/common/interop/Constants.h @@ -35,6 +35,7 @@ namespace winrt::PowerToys::Interop::implementation static hstring RegistryPreviewTriggerEvent(); static hstring MeasureToolTriggerEvent(); static hstring GcodePreviewResizeEvent(); + static hstring BgcodePreviewResizeEvent(); static hstring QoiPreviewResizeEvent(); static hstring DevFilesPreviewResizeEvent(); static hstring MarkdownPreviewResizeEvent(); diff --git a/src/common/interop/Constants.idl b/src/common/interop/Constants.idl index 1de4b849ab..6833b0d417 100644 --- a/src/common/interop/Constants.idl +++ b/src/common/interop/Constants.idl @@ -32,6 +32,7 @@ namespace PowerToys static String RegistryPreviewTriggerEvent(); static String MeasureToolTriggerEvent(); static String GcodePreviewResizeEvent(); + static String BgcodePreviewResizeEvent(); static String QoiPreviewResizeEvent(); static String DevFilesPreviewResizeEvent(); static String MarkdownPreviewResizeEvent(); diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index d240297f39..aa6306d7ba 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -92,6 +92,9 @@ namespace CommonSharedConstants // Path to the event used by GcodePreviewHandler const wchar_t GCODE_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysGcodePreviewResizeEvent-6ff1f9bd-ccbd-4b24-a79f-40a34fb0317d"; + // Path to the event used by BgcodePreviewHandler + const wchar_t BGCODE_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysBgcodePreviewResizeEvent-1a76a553-919a-49e0-8179-776582d8e476"; + // Path to the event used by QoiPreviewHandler const wchar_t QOI_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysQoiPreviewResizeEvent-579518d1-8c8b-494f-8143-04f43d761ead"; diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index c880614237..00cde3b485 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -19,6 +19,10 @@ struct LogSettings inline const static std::wstring gcodePrevLogPath = L"logs\\FileExplorer_localLow\\GcodePreviewHandler\\gcode-prev-handler-log.log"; inline const static std::string gcodeThumbLoggerName = "GcodeThumbnailProvider"; inline const static std::wstring gcodeThumbLogPath = L"logs\\FileExplorer_localLow\\GcodeThumbnailProvider\\gcode-thumbnail-provider-log.log"; + inline const static std::string bgcodePrevLoggerName = "bgcodePrevHandler"; + inline const static std::wstring bgcodePrevLogPath = L"logs\\FileExplorer_localLow\\BgcodePreviewHandler\\bgcode-prev-handler-log.log"; + inline const static std::string bgcodeThumbLoggerName = "BgcodeThumbnailProvider"; + inline const static std::wstring bgcodeThumbLogPath = L"logs\\FileExplorer_localLow\\BgcodeThumbnailProvider\\bgcode-thumbnail-provider-log.log"; inline const static std::string mdPrevLoggerName = "MDPrevHandler"; inline const static std::wstring mdPrevLogPath = L"logs\\FileExplorer_localLow\\MDPrevHandler\\md-prev-handler-log.log"; inline const static std::string monacoPrevLoggerName = "MonacoPrevHandler"; diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index 4185cf43b5..ed60bc1a37 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -37,9 +37,11 @@ namespace powertoys_gpo const std::wstring POLICY_CONFIGURE_ENABLED_MONACO_PREVIEW = L"ConfigureEnabledUtilityFileExplorerMonacoPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_PDF_PREVIEW = L"ConfigureEnabledUtilityFileExplorerPDFPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_GCODE_PREVIEW = L"ConfigureEnabledUtilityFileExplorerGcodePreview"; + const std::wstring POLICY_CONFIGURE_ENABLED_BGCODE_PREVIEW = L"ConfigureEnabledUtilityFileExplorerBgcodePreview"; const std::wstring POLICY_CONFIGURE_ENABLED_SVG_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerSVGThumbnails"; const std::wstring POLICY_CONFIGURE_ENABLED_PDF_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerPDFThumbnails"; const std::wstring POLICY_CONFIGURE_ENABLED_GCODE_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerGcodeThumbnails"; + const std::wstring POLICY_CONFIGURE_ENABLED_BGCODE_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerBgcodeThumbnails"; const std::wstring POLICY_CONFIGURE_ENABLED_STL_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerSTLThumbnails"; const std::wstring POLICY_CONFIGURE_ENABLED_HOSTS_FILE_EDITOR = L"ConfigureEnabledUtilityHostsFileEditor"; const std::wstring POLICY_CONFIGURE_ENABLED_IMAGE_RESIZER = L"ConfigureEnabledUtilityImageResizer"; @@ -328,6 +330,11 @@ namespace powertoys_gpo return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_GCODE_PREVIEW); } + inline gpo_rule_configured_t getConfiguredBgcodePreviewEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_BGCODE_PREVIEW); + } + inline gpo_rule_configured_t getConfiguredSvgThumbnailsEnabledValue() { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_SVG_THUMBNAILS); @@ -343,6 +350,11 @@ namespace powertoys_gpo return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_GCODE_THUMBNAILS); } + inline gpo_rule_configured_t getConfiguredBgcodeThumbnailsEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_BGCODE_THUMBNAILS); + } + inline gpo_rule_configured_t getConfiguredStlThumbnailsEnabledValue() { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_STL_THUMBNAILS); diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h index 8b957bd5b9..be6c04a969 100644 --- a/src/common/utils/modulesRegistry.h +++ b/src/common/utils/modulesRegistry.h @@ -17,6 +17,7 @@ namespace NonLocalizable const static std::vector ExtMarkdown = { L".md", L".markdown", L".mdown", L".mkdn", L".mkd", L".mdwn", L".mdtxt", L".mdtext" }; const static std::vector ExtPDF = { L".pdf" }; const static std::vector ExtGCode = { L".gcode" }; + const static std::vector ExtBGCode = { L".bgcode" }; const static std::vector ExtSTL = { L".stl" }; const static std::vector ExtQOI = { L".qoi" }; const static std::vector ExtNoNoNo = { @@ -146,6 +147,19 @@ inline registry::ChangeSet getGcodePreviewHandlerChangeSet(const std::wstring in NonLocalizable::ExtGCode); } +inline registry::ChangeSet getBgcodePreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{0e6d5bdd-d5f8-4692-a089-8bb88cdd37f4}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(PowerToys.BgcodePreviewHandlerCpp.dll)d").wstring(), + L"BgcodePreviewHandler", + L"Binary G-code Preview Handler", + NonLocalizable::ExtBGCode); +} + inline registry::ChangeSet getQoiPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser) { using namespace registry::shellex; @@ -200,6 +214,19 @@ inline registry::ChangeSet getGcodeThumbnailHandlerChangeSet(const std::wstring NonLocalizable::ExtGCode); } +inline registry::ChangeSet getBgcodeThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{5c93a1e4-99d0-4fb3-991c-6c296a27be21}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(PowerToys.BgcodeThumbnailProviderCpp.dll)d").wstring(), + L"BgcodeThumbnailProvider", + L"Binary G-code Thumbnail Provider", + NonLocalizable::ExtBGCode); +} + inline registry::ChangeSet getStlThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) { using namespace registry::shellex; @@ -275,9 +302,11 @@ inline std::vector getAllOnByDefaultModulesChangeSets(const getMdPreviewHandlerChangeSet(installationDir, PER_USER), getMonacoPreviewHandlerChangeSet(installationDir, PER_USER), getGcodePreviewHandlerChangeSet(installationDir, PER_USER), + getBgcodePreviewHandlerChangeSet(installationDir, PER_USER), getQoiPreviewHandlerChangeSet(installationDir, PER_USER), getSvgThumbnailHandlerChangeSet(installationDir, PER_USER), getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER), + getBgcodeThumbnailHandlerChangeSet(installationDir, PER_USER), getStlThumbnailHandlerChangeSet(installationDir, PER_USER), getQoiThumbnailHandlerChangeSet(installationDir, PER_USER), getRegistryPreviewChangeSet(installationDir, PER_USER) }; @@ -291,10 +320,12 @@ inline std::vector getAllModulesChangeSets(const std::wstri getMonacoPreviewHandlerChangeSet(installationDir, PER_USER), getPdfPreviewHandlerChangeSet(installationDir, PER_USER), getGcodePreviewHandlerChangeSet(installationDir, PER_USER), + getBgcodePreviewHandlerChangeSet(installationDir, PER_USER), getQoiPreviewHandlerChangeSet(installationDir, PER_USER), getSvgThumbnailHandlerChangeSet(installationDir, PER_USER), getPdfThumbnailHandlerChangeSet(installationDir, PER_USER), getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER), + getBgcodeThumbnailHandlerChangeSet(installationDir, PER_USER), getStlThumbnailHandlerChangeSet(installationDir, PER_USER), getQoiThumbnailHandlerChangeSet(installationDir, PER_USER), getRegistryPreviewChangeSet(installationDir, PER_USER), diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.winget b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.winget index 92986e8107..6b64e2114d 100644 --- a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.winget +++ b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.winget @@ -54,6 +54,8 @@ properties: EnablePdfThumbnail: false EnableGcodePreview: false EnableGcodeThumbnail: false + EnableBgcodePreview: false + EnableBgcodeThumbnail: false EnableStlThumbnail: false EnableQoiPreview: false EnableQoiThumbnail: false diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.winget b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.winget index 5ff4dcfe71..e3b9c56c09 100644 --- a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.winget +++ b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.winget @@ -54,6 +54,8 @@ properties: EnablePdfThumbnail: true EnableGcodePreview: true EnableGcodeThumbnail: true + EnableBgcodePreview: true + EnableBgcodeThumbnail: true EnableStlThumbnail: true EnableQoiPreview: true EnableQoiThumbnail: true diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 9bbd0d7ff3..685eeaf350 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -217,6 +217,16 @@ + + + + + + + + + + @@ -247,6 +257,16 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index 95deba2bd6..7fe996abcc 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -253,9 +253,11 @@ If you don't configure this policy, the user will be able to control the setting Source code file preview: Configure enabled state PDF file preview: Configure enabled state Gcode file preview: Configure enabled state + BGcode file preview: Configure enabled state SVG file thumbnail: Configure enabled state PDF file thumbnail: Configure enabled state Gcode file thumbnail: Configure enabled state + BGcode file thumbnail: Configure enabled state STL file thumbnail: Configure enabled state Hosts file editor: Configure enabled state Image Resizer: Configure enabled state diff --git a/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandler.csproj b/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandler.csproj new file mode 100644 index 0000000000..2042920cdc --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandler.csproj @@ -0,0 +1,65 @@ + + + + + + + enable + true + PowerToys.BgcodePreviewHandler + PowerToys BgcodePreviewHandler + PowerToys BgcodePreviewHandler + ..\..\..\..\$(Platform)\$(Configuration)\BgcodePreviewPaneDocumentation.xml + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + true + PowerToys.BgcodePreviewHandler + {9E0CBC06-F29A-4810-B93C-97D53863B95E} + Microsoft.PowerToys.PreviewHandler.Bgcode + PerMonitorV2 + + + + + + True + True + Resource.resx + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + $(NoWarn);1591 + WinExe + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resource.Designer.cs + + + + diff --git a/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandlerControl.cs b/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandlerControl.cs new file mode 100644 index 0000000000..7dc205bed3 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandlerControl.cs @@ -0,0 +1,179 @@ +// 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 Common; +using Microsoft.PowerToys.FilePreviewCommon; +using Microsoft.PowerToys.PreviewHandler.Bgcode.Telemetry.Events; +using Microsoft.PowerToys.Telemetry; + +namespace Microsoft.PowerToys.PreviewHandler.Bgcode +{ + /// + /// Implementation of Control for Bgcode Preview Handler. + /// + public class BgcodePreviewHandlerControl : FormHandlerControl + { + /// + /// Picture box control to display the Binary G-code thumbnail. + /// + private PictureBox _pictureBox; + + /// + /// Text box to display the information about blocked elements from Svg. + /// + private RichTextBox _textBox; + + /// + /// Represent if a text box info bar is added for showing message. + /// + private bool _infoBarAdded; + + /// + /// Initializes a new instance of the class. + /// + public BgcodePreviewHandlerControl() + { + SetBackgroundColor(Settings.BackgroundColor); + } + + /// + /// Start the preview on the Control. + /// + /// Stream reference to access source file. + public override void DoPreview(T dataSource) + { + if (global::PowerToys.GPOWrapper.GPOWrapper.GetConfiguredBgcodePreviewEnabledValue() == global::PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + // GPO is disabling this utility. Show an error message instead. + _infoBarAdded = true; + AddTextBoxControl(Properties.Resource.GpoDisabledErrorText); + Resize += FormResized; + base.DoPreview(dataSource); + + return; + } + + try + { + Bitmap thumbnail = null; + + if (!(dataSource is string filePath)) + { + throw new ArgumentException($"{nameof(dataSource)} for {nameof(BgcodePreviewHandlerControl)} must be a string but was a '{typeof(T)}'"); + } + + FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); + + using (var reader = new BinaryReader(fs)) + { + var bgcodeThumbnail = BgcodeHelper.GetBestThumbnail(reader); + + thumbnail = bgcodeThumbnail?.GetBitmap(); + } + + _infoBarAdded = false; + + if (thumbnail == null) + { + _infoBarAdded = true; + AddTextBoxControl(Properties.Resource.BgcodeWithoutEmbeddedThumbnails); + } + else + { + AddPictureBoxControl(thumbnail); + } + + Resize += FormResized; + base.DoPreview(fs); + try + { + PowerToysTelemetry.Log.WriteEvent(new BgcodeFilePreviewed()); + } + catch + { // Should not crash if sending telemetry is failing. Ignore the exception. + } + } + catch (Exception ex) + { + PreviewError(ex, dataSource); + } + } + + /// + /// Occurs when RichtextBox is resized. + /// + /// Reference to resized control. + /// Provides data for the ContentsResized event. + private void RTBContentsResized(object sender, ContentsResizedEventArgs e) + { + var richTextBox = sender as RichTextBox; + richTextBox.Height = e.NewRectangle.Height + 5; + } + + /// + /// Occurs when form is resized. + /// + /// Reference to resized control. + /// Provides data for the resize event. + private void FormResized(object sender, EventArgs e) + { + if (_infoBarAdded) + { + _textBox.Width = Width; + } + } + + /// + /// Adds a PictureBox Control to Control Collection. + /// + /// Image to display on PictureBox Control. + private void AddPictureBoxControl(Image image) + { + _pictureBox = new PictureBox(); + _pictureBox.BackgroundImage = image; + _pictureBox.BackgroundImageLayout = Width >= image.Width && Height >= image.Height ? ImageLayout.Center : ImageLayout.Zoom; + _pictureBox.Dock = DockStyle.Fill; + Controls.Add(_pictureBox); + } + + /// + /// Adds a Text Box in Controls for showing information about blocked elements. + /// + /// Message to be displayed in textbox. + private void AddTextBoxControl(string message) + { + _textBox = new RichTextBox(); + _textBox.Text = message; + _textBox.BackColor = Color.LightYellow; + _textBox.Multiline = true; + _textBox.Dock = DockStyle.Top; + _textBox.ReadOnly = true; + _textBox.ContentsResized += RTBContentsResized; + _textBox.ScrollBars = RichTextBoxScrollBars.None; + _textBox.BorderStyle = BorderStyle.None; + Controls.Add(_textBox); + } + + /// + /// Called when an error occurs during preview. + /// + /// The exception which occurred. + /// Stream reference to access source file. + private void PreviewError(Exception exception, T dataSource) + { + try + { + PowerToysTelemetry.Log.WriteEvent(new BgcodeFilePreviewError { Message = exception.Message }); + } + catch + { // Should not crash if sending telemetry is failing. Ignore the exception. + } + + Controls.Clear(); + _infoBarAdded = true; + AddTextBoxControl(Properties.Resource.BgcodeNotPreviewedError); + base.DoPreview(dataSource); + } + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandlerControl.resx b/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandlerControl.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/BgcodePreviewHandlerControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodePreviewHandler/Program.cs b/src/modules/previewpane/BgcodePreviewHandler/Program.cs new file mode 100644 index 0000000000..f1f1d0ed35 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Program.cs @@ -0,0 +1,80 @@ +// 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.Threading; + +using Common.UI; +using Microsoft.PowerToys.Telemetry; +using PowerToys.Interop; + +namespace Microsoft.PowerToys.PreviewHandler.Bgcode +{ + internal static class Program + { + private static CancellationTokenSource _tokenSource = new CancellationTokenSource(); + + private static BgcodePreviewHandlerControl _previewHandlerControl; + + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main(string[] args) + { + ApplicationConfiguration.Initialize(); + if (args != null) + { + if (args.Length == 6) + { + ETWTrace etwTrace = new ETWTrace(Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "LocalLow", "Microsoft", "PowerToys", "etw")); + + string filePath = args[0]; + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); + + int left = Convert.ToInt32(args[2], 10); + int right = Convert.ToInt32(args[3], 10); + int top = Convert.ToInt32(args[4], 10); + int bottom = Convert.ToInt32(args[5], 10); + Rectangle s = new Rectangle(left, top, right - left, bottom - top); + + _previewHandlerControl = new BgcodePreviewHandlerControl(); + + if (!_previewHandlerControl.SetWindow(hwnd, s)) + { + return; + } + + _previewHandlerControl.DoPreview(filePath); + + NativeEventWaiter.WaitForEventLoop( + Constants.GcodePreviewResizeEvent(), + () => + { + Rectangle s = default; + if (!_previewHandlerControl.SetRect(s)) + { + etwTrace?.Dispose(); + + // When the parent HWND became invalid, the application won't respond to Application.Exit(). + Environment.Exit(0); + } + }, + Dispatcher.CurrentDispatcher, + _tokenSource.Token); + + etwTrace?.Dispose(); + } + else + { + MessageBox.Show("Wrong number of args: " + args.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + Application.Run(); + } + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandler/Properties/Resource.Designer.cs b/src/modules/previewpane/BgcodePreviewHandler/Properties/Resource.Designer.cs new file mode 100644 index 0000000000..ecb8017305 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Properties/Resource.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// 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 Microsoft.PowerToys.PreviewHandler.Bgcode.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("Microsoft.PowerToys.PreviewHandler.Bgcode.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 This Binary G-code could not be previewed due to an internal error.. + /// + internal static string BgcodeNotPreviewedError { + get { + return ResourceManager.GetString("BgcodeNotPreviewedError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This Binary G-code does not contain any embedded thumbnails.. + /// + internal static string BgcodeWithoutEmbeddedThumbnails { + get { + return ResourceManager.GetString("BgcodeWithoutEmbeddedThumbnails", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.. + /// + internal static string GpoDisabledErrorText { + get { + return ResourceManager.GetString("GpoDisabledErrorText", resourceCulture); + } + } + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandler/Properties/Resource.resx b/src/modules/previewpane/BgcodePreviewHandler/Properties/Resource.resx new file mode 100644 index 0000000000..62a1cdc0dd --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Properties/Resource.resx @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + This Binary G-code could not be previewed due to an internal error. + This text is displayed if Binary G-code fails to preview + + + This Binary G-code does not contain any embedded thumbnails. + + + Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator. + GPO stands for the Windows Group Policy Object feature. + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodePreviewHandler/Settings.cs b/src/modules/previewpane/BgcodePreviewHandler/Settings.cs new file mode 100644 index 0000000000..20815d160b --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Settings.cs @@ -0,0 +1,38 @@ +// 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 Microsoft.PowerToys.PreviewHandler.Bgcode +{ + internal sealed class Settings + { + /// + /// Gets the color of the window background. + /// Even though this is not a setting yet, it's retrieved from a "Settings" class to be aligned with other preview handlers that contain this setting. + /// It's possible it can be converted into a setting in the future. + /// + public static Color BackgroundColor + { + get + { + if (GetTheme() == "dark") + { + return Color.FromArgb(30, 30, 30); // #1e1e1e + } + else + { + return Color.White; + } + } + } + + /// + /// Returns the theme. + /// + /// Theme that should be used. + public static string GetTheme() + { + return Common.UI.ThemeManager.GetWindowsBaseColor().ToLowerInvariant(); + } + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFileHandlerLoaded.cs b/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFileHandlerLoaded.cs new file mode 100644 index 0000000000..f28f358953 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFileHandlerLoaded.cs @@ -0,0 +1,22 @@ +// 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.CodeAnalysis; +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Bgcode.Telemetry.Events +{ + /// + /// A telemetry event to be raised when a bgcode file has been viewed in the preview pane. + /// + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class BgcodeFileHandlerLoaded : EventBase, IEvent + { + /// + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFilePreviewError.cs b/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFilePreviewError.cs new file mode 100644 index 0000000000..e71f2c3f3f --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFilePreviewError.cs @@ -0,0 +1,27 @@ +// 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.CodeAnalysis; +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Bgcode.Telemetry.Events +{ + /// + /// A telemetry event to be raised when an error has occurred in the preview pane. + /// + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class BgcodeFilePreviewError : EventBase, IEvent + { + /// + /// Gets or sets the error message to log as part of the telemetry event. + /// + public string Message { get; set; } + + /// + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance; + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFilePreviewed.cs b/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFilePreviewed.cs new file mode 100644 index 0000000000..8083b5d7e5 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandler/Telemetry/Events/BgcodeFilePreviewed.cs @@ -0,0 +1,22 @@ +// 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.CodeAnalysis; +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Bgcode.Telemetry.Events +{ + /// + /// A telemetry event to be raised when a bgcode file has been viewed in the preview pane. + /// + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class BgcodeFilePreviewed : EventBase, IEvent + { + /// + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandler.cpp b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandler.cpp new file mode 100644 index 0000000000..2091db6c34 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandler.cpp @@ -0,0 +1,284 @@ +#include "pch.h" +#include "BgcodePreviewHandler.h" +#include "../powerpreview/powerpreviewConstants.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +BgcodePreviewHandler::BgcodePreviewHandler() : + m_cRef(1), m_hwndParent(NULL), m_rcParent(), m_punkSite(NULL), m_process(NULL) +{ + m_resizeEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::BGCODE_PREVIEW_RESIZE_EVENT); + + std::filesystem::path logFilePath(PTSettingsHelper::get_local_low_folder_location()); + logFilePath.append(LogSettings::bgcodePrevLogPath); + Logger::init(LogSettings::bgcodePrevLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + InterlockedIncrement(&g_cDllRef); +} + +BgcodePreviewHandler::~BgcodePreviewHandler() +{ + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +IFACEMETHODIMP BgcodePreviewHandler::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = { + QITABENT(BgcodePreviewHandler, IPreviewHandler), + QITABENT(BgcodePreviewHandler, IInitializeWithFile), + QITABENT(BgcodePreviewHandler, IPreviewHandlerVisuals), + QITABENT(BgcodePreviewHandler, IOleWindow), + QITABENT(BgcodePreviewHandler, IObjectWithSite), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) +BgcodePreviewHandler::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) +BgcodePreviewHandler::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +#pragma endregion + +#pragma region IInitializationWithFile + +IFACEMETHODIMP BgcodePreviewHandler::Initialize(LPCWSTR pszFilePath, DWORD grfMode) +{ + m_filePath = pszFilePath; + return S_OK; +} + +#pragma endregion + +#pragma region IPreviewHandler + +IFACEMETHODIMP BgcodePreviewHandler::SetWindow(HWND hwnd, const RECT* prc) +{ + if (hwnd && prc) + { + m_hwndParent = hwnd; + m_rcParent = *prc; + } + return S_OK; +} + +IFACEMETHODIMP BgcodePreviewHandler::SetFocus() +{ + return S_OK; +} + +IFACEMETHODIMP BgcodePreviewHandler::QueryFocus(HWND* phwnd) +{ + HRESULT hr = E_INVALIDARG; + if (phwnd) + { + *phwnd = ::GetFocus(); + if (*phwnd) + { + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + return hr; +} + +IFACEMETHODIMP BgcodePreviewHandler::TranslateAccelerator(MSG* pmsg) +{ + HRESULT hr = S_FALSE; + IPreviewHandlerFrame* pFrame = NULL; + if (m_punkSite && SUCCEEDED(m_punkSite->QueryInterface(&pFrame))) + { + hr = pFrame->TranslateAccelerator(pmsg); + + pFrame->Release(); + } + return hr; +} + +IFACEMETHODIMP BgcodePreviewHandler::SetRect(const RECT* prc) +{ + HRESULT hr = E_INVALIDARG; + if (prc != NULL) + { + if (m_rcParent.left == 0 && m_rcParent.top == 0 && m_rcParent.right == 0 && m_rcParent.bottom == 0 && (prc->left != 0 || prc->top != 0 || prc->right != 0 || prc->bottom != 0)) + { + // BgcodePreviewHandler position first initialisation, do the first preview + m_rcParent = *prc; + DoPreview(); + } + if (!m_resizeEvent) + { + Logger::error(L"Failed to create resize event for BgcodePreviewHandler"); + } + else + { + if (m_rcParent.right != prc->right || m_rcParent.left != prc->left || m_rcParent.top != prc->top || m_rcParent.bottom != prc->bottom) + { + if (!SetEvent(m_resizeEvent)) + { + Logger::error(L"Failed to signal resize event for BgcodePreviewHandler"); + } + } + } + m_rcParent = *prc; + hr = S_OK; + } + return hr; +} + +IFACEMETHODIMP BgcodePreviewHandler::DoPreview() +{ + try + { + if (m_hwndParent == NULL || (m_rcParent.left == 0 && m_rcParent.top == 0 && m_rcParent.right == 0 && m_rcParent.bottom == 0)) + { + // Postponing Start BgcodePreviewHandler.exe, parent and position not yet initialized. Preview will be done after initialisation. + return S_OK; + } + Logger::info(L"Starting BgcodePreviewHandler.exe"); + + STARTUPINFO info = { sizeof(info) }; + std::wstring cmdLine{ L"\"" + m_filePath + L"\"" }; + cmdLine += L" "; + std::wostringstream ss; + ss << std::hex << m_hwndParent; + + cmdLine += ss.str(); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.left); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.right); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.top); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.bottom); + std::wstring appPath = get_module_folderpath(g_hInst) + L"\\PowerToys.BgcodePreviewHandler.exe"; + + SHELLEXECUTEINFO sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = appPath.c_str(); + sei.lpParameters = cmdLine.c_str(); + sei.nShow = SW_SHOWDEFAULT; + ShellExecuteEx(&sei); + + // Prevent to leak processes: preview is called multiple times when minimizing and restoring Explorer window + if (m_process) + { + TerminateProcess(m_process, 0); + } + + m_process = sei.hProcess; + } + catch (std::exception& e) + { + std::wstring errorMessage = std::wstring{ winrt::to_hstring(e.what()) }; + Logger::error(L"Failed to start BgcodePreviewHandler.exe. Error: {}", errorMessage); + } + + return S_OK; +} + +IFACEMETHODIMP BgcodePreviewHandler::Unload() +{ + Logger::info(L"Unload and terminate .exe"); + + m_hwndParent = NULL; + TerminateProcess(m_process, 0); + return S_OK; +} + +#pragma endregion + +#pragma region IPreviewHandlerVisuals + +IFACEMETHODIMP BgcodePreviewHandler::SetBackgroundColor(COLORREF color) +{ + HBRUSH brush = CreateSolidBrush(WindowsColors::is_dark_mode() ? powerpreviewConstants::DARK_THEME_COLOR : powerpreviewConstants::LIGHT_THEME_COLOR); + SetClassLongPtr(m_hwndParent, GCLP_HBRBACKGROUND, reinterpret_cast(brush)); + return S_OK; +} + +IFACEMETHODIMP BgcodePreviewHandler::SetFont(const LOGFONTW* plf) +{ + return S_OK; +} + +IFACEMETHODIMP BgcodePreviewHandler::SetTextColor(COLORREF color) +{ + return S_OK; +} + +#pragma endregion + +#pragma region IOleWindow + +IFACEMETHODIMP BgcodePreviewHandler::GetWindow(HWND* phwnd) +{ + HRESULT hr = E_INVALIDARG; + if (phwnd) + { + *phwnd = m_hwndParent; + hr = S_OK; + } + return hr; +} + +IFACEMETHODIMP BgcodePreviewHandler::ContextSensitiveHelp(BOOL fEnterMode) +{ + return E_NOTIMPL; +} + +#pragma endregion + +#pragma region IObjectWithSite + +IFACEMETHODIMP BgcodePreviewHandler::SetSite(IUnknown* punkSite) +{ + if (m_punkSite) + { + m_punkSite->Release(); + m_punkSite = NULL; + } + return punkSite ? punkSite->QueryInterface(&m_punkSite) : S_OK; +} + +IFACEMETHODIMP BgcodePreviewHandler::GetSite(REFIID riid, void** ppv) +{ + *ppv = NULL; + return m_punkSite ? m_punkSite->QueryInterface(riid, ppv) : E_FAIL; +} + +#pragma endregion + +#pragma region Helper Functions + +#pragma endregion diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandler.h b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandler.h new file mode 100644 index 0000000000..481eedec09 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandler.h @@ -0,0 +1,69 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include + +class BgcodePreviewHandler : + public IInitializeWithFile, + public IPreviewHandler, + public IPreviewHandlerVisuals, + public IOleWindow, + public IObjectWithSite +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IInitializeWithFile + IFACEMETHODIMP Initialize(LPCWSTR pszFilePath, DWORD grfMode); + + // IPreviewHandler + IFACEMETHODIMP SetWindow(HWND hwnd, const RECT* prc); + IFACEMETHODIMP SetFocus(); + IFACEMETHODIMP QueryFocus(HWND* phwnd); + IFACEMETHODIMP TranslateAccelerator(MSG* pmsg); + IFACEMETHODIMP SetRect(const RECT* prc); + IFACEMETHODIMP DoPreview(); + IFACEMETHODIMP Unload(); + + // IPreviewHandlerVisuals + IFACEMETHODIMP SetBackgroundColor(COLORREF color); + IFACEMETHODIMP SetFont(const LOGFONTW* plf); + IFACEMETHODIMP SetTextColor(COLORREF color); + + // IOleWindow + IFACEMETHODIMP GetWindow(HWND* phwnd); + IFACEMETHODIMP ContextSensitiveHelp(BOOL fEnterMode); + + // IObjectWithSite + IFACEMETHODIMP SetSite(IUnknown* punkSite); + IFACEMETHODIMP GetSite(REFIID riid, void** ppv); + + BgcodePreviewHandler(); +protected: + ~BgcodePreviewHandler(); + +private: + // Reference count of component. + long m_cRef; + + // Provided during initialization. + std::wstring m_filePath; + + // Parent window that hosts the previewer window. + // Note: do NOT DestroyWindow this. + HWND m_hwndParent; + // Bounding rect of the parent window. + RECT m_rcParent; + + // Site pointer from host, used to get IPreviewHandlerFrame. + IUnknown* m_punkSite; + + HANDLE m_process; + HANDLE m_resizeEvent; +}; \ No newline at end of file diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.rc b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.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/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj new file mode 100644 index 0000000000..7679cccbc0 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj @@ -0,0 +1,119 @@ + + + + + 16.0 + Win32Proj + {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C} + BgcodePreviewHandlerCpp + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + PowerToys.$(ProjectName) + + + + Level3 + true + _DEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + ../../.. + + + Windows + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + ../../.. + + + Windows + true + true + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + + + + + + + + + + Create + + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {98537082-0fdb-40de-abd8-0dc5a4269bab} + + + + + + + + + + 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/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj.filters b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj.filters new file mode 100644 index 0000000000..9d6a4997b2 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/BgcodePreviewHandlerCpp.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {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 + + + + + Header Files + + + Header Files + + + Header Files + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/ClassFactory.cpp b/src/modules/previewpane/BgcodePreviewHandlerCpp/ClassFactory.cpp new file mode 100644 index 0000000000..13b07c0c71 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/ClassFactory.cpp @@ -0,0 +1,84 @@ +#include "pch.h" +#include "ClassFactory.h" +#include "BgcodePreviewHandler.h" + +#include +#include + +extern long g_cDllRef; + +ClassFactory::ClassFactory() : + m_cRef(1) +{ + InterlockedIncrement(&g_cDllRef); +} + +ClassFactory::~ClassFactory() +{ + InterlockedDecrement(&g_cDllRef); +} + +// +// IUnknown +// + +IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void **ppv) +{ + static const QITAB qit[] = { + QITABENT(ClassFactory, IClassFactory), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +// +// IClassFactory +// + +IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + HRESULT hr = CLASS_E_NOAGGREGATION; + + if (pUnkOuter == NULL) + { + hr = E_OUTOFMEMORY; + + BgcodePreviewHandler* pExt = new (std::nothrow) BgcodePreviewHandler(); + if (pExt) + { + hr = pExt->QueryInterface(riid, ppv); + pExt->Release(); + } + } + + return hr; +} + +IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock) +{ + if (fLock) + { + InterlockedIncrement(&g_cDllRef); + } + else + { + InterlockedDecrement(&g_cDllRef); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/ClassFactory.h b/src/modules/previewpane/BgcodePreviewHandlerCpp/ClassFactory.h new file mode 100644 index 0000000000..b393c3916e --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/ClassFactory.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class ClassFactory : public IClassFactory +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IClassFactory + IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv); + IFACEMETHODIMP LockServer(BOOL fLock); + + ClassFactory(); + +protected: + ~ClassFactory(); + +private: + long m_cRef; +}; diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/GlobalExportFunctions.def b/src/modules/previewpane/BgcodePreviewHandlerCpp/GlobalExportFunctions.def new file mode 100644 index 0000000000..76fc66cac3 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/GlobalExportFunctions.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/dllmain.cpp b/src/modules/previewpane/BgcodePreviewHandlerCpp/dllmain.cpp new file mode 100644 index 0000000000..49ca61cdf9 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/dllmain.cpp @@ -0,0 +1,73 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include "ClassFactory.h" + +HINSTANCE g_hInst = NULL; +long g_cDllRef = 0; + +// {0e6d5bdd-d5f8-4692-a089-8bb88cdd37f4} +static const GUID CLSID_BgcodePreviewHandler = { 0x0e6d5bdd, 0xd5f8, 0x4692, { 0xa0, 0x89, 0x8b, 0xb8, 0x8c, 0xdd, 0x37, 0xf4 } }; + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hInst = hModule; + DisableThreadLibraryCalls(hModule); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +// +// FUNCTION: DllGetClassObject +// +// PURPOSE: Create the class factory and query to the specific interface. +// +// PARAMETERS: +// * rclsid - The CLSID that will associate the correct data and code. +// * riid - A reference to the identifier of the interface that the caller +// is to use to communicate with the class object. +// * ppv - The address of a pointer variable that receives the interface +// pointer requested in riid. Upon successful return, *ppv contains the +// requested interface pointer. If an error occurs, the interface pointer +// is NULL. +// +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) +{ + HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; + + if (IsEqualCLSID(CLSID_BgcodePreviewHandler, rclsid)) + { + hr = E_OUTOFMEMORY; + + ClassFactory* pClassFactory = new ClassFactory(); + if (pClassFactory) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + } + + return hr; +} + +// +// FUNCTION: DllCanUnloadNow +// +// PURPOSE: Check if we can unload the component from the memory. +// +// NOTE: The component can be unloaded from the memory when its reference +// count is zero (i.e. nobody is still using the component). +// +STDAPI DllCanUnloadNow(void) +{ + return g_cDllRef > 0 ? S_FALSE : S_OK; +} diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/packages.config b/src/modules/previewpane/BgcodePreviewHandlerCpp/packages.config new file mode 100644 index 0000000000..09bfc449e2 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/pch.cpp b/src/modules/previewpane/BgcodePreviewHandlerCpp/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/pch.h b/src/modules/previewpane/BgcodePreviewHandlerCpp/pch.h new file mode 100644 index 0000000000..125ddcdf24 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/pch.h @@ -0,0 +1,14 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + +#endif //PCH_H diff --git a/src/modules/previewpane/BgcodePreviewHandlerCpp/resource.h b/src/modules/previewpane/BgcodePreviewHandlerCpp/resource.h new file mode 100644 index 0000000000..446d0b8185 --- /dev/null +++ b/src/modules/previewpane/BgcodePreviewHandlerCpp/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AlwaysOnTopModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Bgcode Preview Handler Module" +#define INTERNAL_NAME "PowerToys.BgcodePreviewHandlerCpp" +#define ORIGINAL_FILENAME "PowerToys.BgcodePreviewHandlerCpp.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.cs b/src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.cs new file mode 100644 index 0000000000..8ec7b6e631 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.cs @@ -0,0 +1,146 @@ +// 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.Drawing2D; +using System.Drawing.Imaging; + +using Microsoft.PowerToys.FilePreviewCommon; + +namespace Microsoft.PowerToys.ThumbnailHandler.Bgcode +{ + /// + /// Binary G-code Thumbnail Provider. + /// + public class BgcodeThumbnailProvider + { + public BgcodeThumbnailProvider(string filePath) + { + FilePath = filePath; + Stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + } + + /// + /// Gets the file path to the file creating thumbnail for. + /// + public string FilePath { get; private set; } + + /// + /// Gets the stream object to access file. + /// + public Stream Stream { get; private set; } + + /// + /// The maximum dimension (width or height) thumbnail we will generate. + /// + private const uint MaxThumbnailSize = 10000; + + /// + /// Reads the Binary G-code content searching for thumbnails and returns the largest. + /// + /// The BinaryReader instance for the Binary G-code content. + /// The maximum thumbnail size, in pixels. + /// A thumbnail extracted from the Binary G-code content. + public static Bitmap GetThumbnail(BinaryReader reader, uint cx) + { + if (cx > MaxThumbnailSize || reader == null || reader.BaseStream.Length == 0) + { + return null; + } + + Bitmap thumbnail = null; + + try + { + var bgcodeThumbnail = BgcodeHelper.GetBestThumbnail(reader); + + thumbnail = bgcodeThumbnail?.GetBitmap(); + } + catch (Exception) + { + // TODO: add logger + } + + if (thumbnail != null && ( + ((thumbnail.Width != cx || thumbnail.Height > cx) && (thumbnail.Height != cx || thumbnail.Width > cx)) || + thumbnail.PixelFormat != PixelFormat.Format32bppArgb)) + { + // We are not the appropriate size for caller. Resize now while + // respecting the aspect ratio. + float scale = Math.Min((float)cx / thumbnail.Width, (float)cx / thumbnail.Height); + int scaleWidth = (int)(thumbnail.Width * scale); + int scaleHeight = (int)(thumbnail.Height * scale); + thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight); + } + + return thumbnail; + } + + /// + /// Resize the image with high quality to the specified width and height. + /// + /// The image to resize. + /// The width to resize to. + /// The height to resize to. + /// The resized image. + public static Bitmap ResizeImage(Image image, int width, int height) + { + if (width <= 0 || + height <= 0 || + width > MaxThumbnailSize || + height > MaxThumbnailSize || + image == null) + { + return null; + } + + Bitmap destImage = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (var graphics = Graphics.FromImage(destImage)) + { + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + graphics.DrawImage(image, 0, 0, width, height); + } + + image.Dispose(); + + return destImage; + } + + /// + /// Generate thumbnail bitmap for provided Bgcode file/stream. + /// + /// Maximum thumbnail size, in pixels. + /// Generated bitmap + public Bitmap GetThumbnail(uint cx) + { + if (cx == 0 || cx > MaxThumbnailSize) + { + return null; + } + + if (global::PowerToys.GPOWrapper.GPOWrapper.GetConfiguredBgcodeThumbnailsEnabledValue() == global::PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + // GPO is disabling this utility. + return null; + } + + using (var reader = new BinaryReader(this.Stream)) + { + Bitmap thumbnail = GetThumbnail(reader, cx); + if (thumbnail != null && thumbnail.Size.Width > 0 && thumbnail.Size.Height > 0) + { + return thumbnail; + } + } + + return null; + } + } +} diff --git a/src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.csproj b/src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.csproj new file mode 100644 index 0000000000..b17ac59a18 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProvider/BgcodeThumbnailProvider.csproj @@ -0,0 +1,43 @@ + + + + + + + enable + true + {9BC1C986-1E97-4D07-A7B1-CE226C239EFA} + Microsoft.PowerToys.ThumbnailHandler.Bgcode + PowerToys.BgcodeThumbnailProvider + PowerToys.BgcodeThumbnailProvider + PowerToys BgcodePreviewHandler + true + PowerToys BgcodePreviewHandler + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + + $(NoWarn);1591 + WinExe + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodeThumbnailProvider/Program.cs b/src/modules/previewpane/BgcodeThumbnailProvider/Program.cs new file mode 100644 index 0000000000..debc9c298b --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProvider/Program.cs @@ -0,0 +1,42 @@ +// 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; + +namespace Microsoft.PowerToys.ThumbnailHandler.Bgcode +{ + internal static class Program + { + private static BgcodeThumbnailProvider _thumbnailProvider; + + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main(string[] args) + { + ApplicationConfiguration.Initialize(); + if (args != null) + { + if (args.Length == 2) + { + string filePath = args[0]; + uint cx = Convert.ToUInt32(args[1], 10); + + _thumbnailProvider = new BgcodeThumbnailProvider(filePath); + Bitmap thumbnail = _thumbnailProvider.GetThumbnail(cx); + if (thumbnail != null) + { + filePath = filePath.Replace(".bgcode", ".bmp"); + thumbnail.Save(filePath, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + else + { + MessageBox.Show("Bgcode thumbnail - wrong number of args: " + args.Length.ToString(CultureInfo.InvariantCulture)); + } + } + } + } +} diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProvider.cpp b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProvider.cpp new file mode 100644 index 0000000000..b0f2992fe7 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProvider.cpp @@ -0,0 +1,201 @@ +#include "pch.h" +#include "BgcodeThumbnailProvider.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +BgcodeThumbnailProvider::BgcodeThumbnailProvider() : + m_cRef(1), m_pStream(NULL), m_process(NULL) +{ + std::filesystem::path logFilePath(PTSettingsHelper::get_local_low_folder_location()); + logFilePath.append(LogSettings::bgcodeThumbLogPath); + Logger::init(LogSettings::bgcodeThumbLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + InterlockedIncrement(&g_cDllRef); +} + +BgcodeThumbnailProvider::~BgcodeThumbnailProvider() +{ + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +IFACEMETHODIMP BgcodeThumbnailProvider::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = { + QITABENT(BgcodeThumbnailProvider, IThumbnailProvider), + QITABENT(BgcodeThumbnailProvider, IInitializeWithStream), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) +BgcodeThumbnailProvider::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) +BgcodeThumbnailProvider::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +#pragma endregion + +#pragma region IInitializationWithStream + +IFACEMETHODIMP BgcodeThumbnailProvider::Initialize(IStream* pStream, DWORD grfMode) +{ + HRESULT hr = E_INVALIDARG; + if (pStream) + { + // Initialize can be called more than once, so release existing valid + // m_pStream. + if (m_pStream) + { + m_pStream->Release(); + m_pStream = NULL; + } + + m_pStream = pStream; + m_pStream->AddRef(); + hr = S_OK; + } + return hr; +} + +#pragma endregion + +#pragma region IThumbnailProvider + +IFACEMETHODIMP BgcodeThumbnailProvider::GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha) +{ + // Read stream into the buffer + char buffer[4096]; + ULONG cbRead; + + Logger::trace(L"Begin"); + + GUID guid; + if (CoCreateGuid(&guid) == S_OK) + { + wil::unique_cotaskmem_string guidString; + if (SUCCEEDED(StringFromCLSID(guid, &guidString))) + { + Logger::info(L"Read stream and save to tmp file."); + + // {CLSID} -> CLSID + std::wstring guid = std::wstring(guidString.get()).substr(1, std::wstring(guidString.get()).size() - 2); + std::wstring filePath = PTSettingsHelper::get_local_low_folder_location() + L"\\BgcodeThumbnail-Temp\\"; + if (!std::filesystem::exists(filePath)) + { + std::filesystem::create_directories(filePath); + } + + std::wstring fileName = filePath + guid + L".bgcode"; + + // Write data to tmp file + std::fstream file; + file.open(fileName, std::ios_base::out | std::ios_base::binary); + + if (!file.is_open()) + { + return 0; + } + + while (true) + { + auto result = m_pStream->Read(buffer, 4096, &cbRead); + + file.write(buffer, cbRead); + if (result == S_FALSE) + { + break; + } + } + file.close(); + + m_pStream->Release(); + m_pStream = NULL; + + try + { + Logger::info(L"Start BgcodeThumbnailProvider.exe"); + + STARTUPINFO info = { sizeof(info) }; + std::wstring cmdLine{ L"\"" + fileName + L"\"" }; + cmdLine += L" "; + cmdLine += std::to_wstring(cx); + + std::wstring appPath = get_module_folderpath(g_hInst) + L"\\PowerToys.BgcodeThumbnailProvider.exe"; + + SHELLEXECUTEINFO sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = appPath.c_str(); + sei.lpParameters = cmdLine.c_str(); + sei.nShow = SW_SHOWDEFAULT; + ShellExecuteEx(&sei); + m_process = sei.hProcess; + WaitForSingleObject(m_process, INFINITE); + std::filesystem::remove(fileName); + + + std::wstring fileNameBmp = filePath + guid + L".bmp"; + if (std::filesystem::exists(fileNameBmp)) + { + *phbmp = static_cast(LoadImage(NULL, fileNameBmp.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)); + *pdwAlpha = WTS_ALPHATYPE::WTSAT_ARGB; + std::filesystem::remove(fileNameBmp); + } + else + { + Logger::info(L"Bmp file not generated."); + return E_FAIL; + } + } + catch (std::exception& e) + { + std::wstring errorMessage = std::wstring{ winrt::to_hstring(e.what()) }; + Logger::error(L"Failed to start BgcodeThumbnailProvider.exe. Error: {}", errorMessage); + } + } + } + + // ensure releasing the stream (not all if branches contain it) + if (m_pStream) + { + m_pStream->Release(); + m_pStream = NULL; + } + + return S_OK; +} + + +#pragma endregion + +#pragma region Helper Functions + +#pragma endregion diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProvider.h b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProvider.h new file mode 100644 index 0000000000..56bac9b1fc --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProvider.h @@ -0,0 +1,37 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include + +class BgcodeThumbnailProvider : + public IInitializeWithStream, + public IThumbnailProvider +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IInitializeWithStream + IFACEMETHODIMP Initialize(IStream* pstream, DWORD grfMode); + + // IThumbnailProvider + IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha); + + BgcodeThumbnailProvider(); +protected: + ~BgcodeThumbnailProvider(); + +private: + // Reference count of component. + long m_cRef; + + // Provided during initialization. + IStream* m_pStream; + + HANDLE m_process; +}; \ No newline at end of file diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.rc b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.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/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj new file mode 100644 index 0000000000..f561cf53be --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj @@ -0,0 +1,118 @@ + + + + + 16.0 + Win32Proj + {47B0678C-806B-4FE1-9F50-46BA88989532} + BgcodeThumbnailProviderCpp + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + PowerToys.$(ProjectName) + + + + Level3 + true + _DEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + ../../.. + + + Windows + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + ../../.. + + + Windows + true + true + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + + + + + + + + + + Create + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + + + + 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/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj.filters b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj.filters new file mode 100644 index 0000000000..3ce6ff0db6 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/BgcodeThumbnailProviderCpp.vcxproj.filters @@ -0,0 +1,59 @@ + + + + + {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 + + + + + Header Files + + + Header Files + + + Header Files + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/ClassFactory.cpp b/src/modules/previewpane/BgcodeThumbnailProviderCpp/ClassFactory.cpp new file mode 100644 index 0000000000..9196db6c19 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/ClassFactory.cpp @@ -0,0 +1,84 @@ +#include "pch.h" +#include "ClassFactory.h" +#include "BgcodeThumbnailProvider.h" + +#include +#include + +extern long g_cDllRef; + +ClassFactory::ClassFactory() : + m_cRef(1) +{ + InterlockedIncrement(&g_cDllRef); +} + +ClassFactory::~ClassFactory() +{ + InterlockedDecrement(&g_cDllRef); +} + +// +// IUnknown +// + +IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void **ppv) +{ + static const QITAB qit[] = { + QITABENT(ClassFactory, IClassFactory), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +// +// IClassFactory +// + +IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + HRESULT hr = CLASS_E_NOAGGREGATION; + + if (pUnkOuter == NULL) + { + hr = E_OUTOFMEMORY; + + BgcodeThumbnailProvider* pExt = new (std::nothrow) BgcodeThumbnailProvider(); + if (pExt) + { + hr = pExt->QueryInterface(riid, ppv); + pExt->Release(); + } + } + + return hr; +} + +IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock) +{ + if (fLock) + { + InterlockedIncrement(&g_cDllRef); + } + else + { + InterlockedDecrement(&g_cDllRef); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/ClassFactory.h b/src/modules/previewpane/BgcodeThumbnailProviderCpp/ClassFactory.h new file mode 100644 index 0000000000..b393c3916e --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/ClassFactory.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class ClassFactory : public IClassFactory +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IClassFactory + IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv); + IFACEMETHODIMP LockServer(BOOL fLock); + + ClassFactory(); + +protected: + ~ClassFactory(); + +private: + long m_cRef; +}; diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/GlobalExportFunctions.def b/src/modules/previewpane/BgcodeThumbnailProviderCpp/GlobalExportFunctions.def new file mode 100644 index 0000000000..76fc66cac3 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/GlobalExportFunctions.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/dllmain.cpp b/src/modules/previewpane/BgcodeThumbnailProviderCpp/dllmain.cpp new file mode 100644 index 0000000000..4c28da5c6d --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/dllmain.cpp @@ -0,0 +1,73 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include "ClassFactory.h" + +HINSTANCE g_hInst = NULL; +long g_cDllRef = 0; + +// {5c93a1e4-99d0-4fb3-991c-6c296a27be21} +static const GUID CLSID_BgcodeThumbnailProvider = { 0x5c93a1e4, 0x99d0, 0x4fb3, { 0x99, 0x1c, 0x6c, 0x29, 0x6a, 0x27, 0xbe, 0x21 } }; + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hInst = hModule; + DisableThreadLibraryCalls(hModule); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +// +// FUNCTION: DllGetClassObject +// +// PURPOSE: Create the class factory and query to the specific interface. +// +// PARAMETERS: +// * rclsid - The CLSID that will associate the correct data and code. +// * riid - A reference to the identifier of the interface that the caller +// is to use to communicate with the class object. +// * ppv - The address of a pointer variable that receives the interface +// pointer requested in riid. Upon successful return, *ppv contains the +// requested interface pointer. If an error occurs, the interface pointer +// is NULL. +// +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) +{ + HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; + + if (IsEqualCLSID(CLSID_BgcodeThumbnailProvider, rclsid)) + { + hr = E_OUTOFMEMORY; + + ClassFactory* pClassFactory = new ClassFactory(); + if (pClassFactory) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + } + + return hr; +} + +// +// FUNCTION: DllCanUnloadNow +// +// PURPOSE: Check if we can unload the component from the memory. +// +// NOTE: The component can be unloaded from the memory when its reference +// count is zero (i.e. nobody is still using the component). +// +STDAPI DllCanUnloadNow(void) +{ + return g_cDllRef > 0 ? S_FALSE : S_OK; +} diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/packages.config b/src/modules/previewpane/BgcodeThumbnailProviderCpp/packages.config new file mode 100644 index 0000000000..ff4b059648 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/pch.cpp b/src/modules/previewpane/BgcodeThumbnailProviderCpp/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/pch.h b/src/modules/previewpane/BgcodeThumbnailProviderCpp/pch.h new file mode 100644 index 0000000000..8a0d004247 --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/pch.h @@ -0,0 +1,15 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include +#include + +#endif //PCH_H diff --git a/src/modules/previewpane/BgcodeThumbnailProviderCpp/resource.h b/src/modules/previewpane/BgcodeThumbnailProviderCpp/resource.h new file mode 100644 index 0000000000..911c12832c --- /dev/null +++ b/src/modules/previewpane/BgcodeThumbnailProviderCpp/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AlwaysOnTopModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Bgcode Thumbnail Provider Module" +#define INTERNAL_NAME "PowerToys.BgcodeThumbnailProviderCpp" +#define ORIGINAL_FILENAME "PowerToys.BgcodeThumbnailProviderCpp.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/previewpane/UnitTests-BgcodePreviewHandler/BgcodePreviewHandlerTest.cs b/src/modules/previewpane/UnitTests-BgcodePreviewHandler/BgcodePreviewHandlerTest.cs new file mode 100644 index 0000000000..fcc15c134d --- /dev/null +++ b/src/modules/previewpane/UnitTests-BgcodePreviewHandler/BgcodePreviewHandlerTest.cs @@ -0,0 +1,93 @@ +// 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.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; + +using Microsoft.PowerToys.PreviewHandler.Bgcode; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BgcodePreviewHandlerUnitTests +{ + [STATestClass] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")] + public class BgcodePreviewHandlerTest + { + [DataTestMethod] + [DataRow("HelperFiles/sample.bgcode")] + public void BgcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled(string filePath) + { + // Arrange + using (var bgcodePreviewHandlerControl = new BgcodePreviewHandlerControl()) + { + // Act + var file = File.ReadAllBytes(filePath); + + bgcodePreviewHandlerControl.DoPreview(GetMockStream(file)); + + var flowLayoutPanel = bgcodePreviewHandlerControl.Controls[0] as FlowLayoutPanel; + + // Assert + Assert.AreEqual(1, bgcodePreviewHandlerControl.Controls.Count); + } + } + + [TestMethod] + public void BgcodePreviewHandlerControlShouldAddValidInfoBarIfBgcodePreviewThrows() + { + // Arrange + using (var bgcodePreviewHandlerControl = new BgcodePreviewHandlerControl()) + { + var mockStream = new Mock(); + mockStream + .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(new Exception()); + + // Act + bgcodePreviewHandlerControl.DoPreview(mockStream.Object); + var textBox = bgcodePreviewHandlerControl.Controls[0] as RichTextBox; + + // Assert + Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text)); + Assert.AreEqual(1, bgcodePreviewHandlerControl.Controls.Count); + Assert.AreEqual(DockStyle.Top, textBox.Dock); + Assert.AreEqual(Color.LightYellow, textBox.BackColor); + Assert.IsTrue(textBox.Multiline); + Assert.IsTrue(textBox.ReadOnly); + Assert.AreEqual(RichTextBoxScrollBars.None, textBox.ScrollBars); + Assert.AreEqual(BorderStyle.None, textBox.BorderStyle); + } + } + + private static IStream GetMockStream(byte[] sourceArray) + { + var streamMock = new Mock(); + int bytesRead = 0; + + streamMock + .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((buffer, countToRead, bytesReadPtr) => + { + int actualCountToRead = Math.Min(sourceArray.Length - bytesRead, countToRead); + if (actualCountToRead > 0) + { + Array.Copy(sourceArray, bytesRead, buffer, 0, actualCountToRead); + Marshal.WriteInt32(bytesReadPtr, actualCountToRead); + bytesRead += actualCountToRead; + } + else + { + Marshal.WriteInt32(bytesReadPtr, 0); + } + }); + + return streamMock.Object; + } + } +} diff --git a/src/modules/previewpane/UnitTests-BgcodePreviewHandler/HelperFiles/sample.bgcode b/src/modules/previewpane/UnitTests-BgcodePreviewHandler/HelperFiles/sample.bgcode new file mode 100644 index 0000000000..64ec331f7f Binary files /dev/null and b/src/modules/previewpane/UnitTests-BgcodePreviewHandler/HelperFiles/sample.bgcode differ diff --git a/src/modules/previewpane/UnitTests-BgcodePreviewHandler/UnitTests-BgcodePreviewHandler.csproj b/src/modules/previewpane/UnitTests-BgcodePreviewHandler/UnitTests-BgcodePreviewHandler.csproj new file mode 100644 index 0000000000..ca3cbbd4f6 --- /dev/null +++ b/src/modules/previewpane/UnitTests-BgcodePreviewHandler/UnitTests-BgcodePreviewHandler.csproj @@ -0,0 +1,36 @@ + + + + + + UnitTests-BgcodePreviewHandler + PowerToys UnitTests-BgcodePreviewHandler + PowerToys UnitTests-BgcodePreviewHandler + {99CA1509-FB73-456E-AFAF-AB89C017BD72} + BgcodePreviewHandlerUnitTests + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + + + + + + + + + + + + + + + + + + + Always + + + diff --git a/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/BgcodeThumbnailProviderTests.cs b/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/BgcodeThumbnailProviderTests.cs new file mode 100644 index 0000000000..6c15a3fc3c --- /dev/null +++ b/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/BgcodeThumbnailProviderTests.cs @@ -0,0 +1,71 @@ +// 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.IO; + +using Microsoft.PowerToys.ThumbnailHandler.Bgcode; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BgcodeThumbnailProviderUnitTests +{ + [STATestClass] + public class BgcodeThumbnailProviderTests + { + [DataTestMethod] + [DataRow("HelperFiles/sample.bgcode")] + public void GetThumbnailValidStreamBgcode(string filePath) + { + // Act + BgcodeThumbnailProvider provider = new BgcodeThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(256); + + Assert.IsTrue(bitmap != null); + } + + [TestMethod] + public void GetThumbnailInValidSizeBgcode() + { + // Act + var filePath = "HelperFiles/sample.bgcode"; + + BgcodeThumbnailProvider provider = new BgcodeThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(0); + + Assert.IsTrue(bitmap == null); + } + + [TestMethod] + public void GetThumbnailToBigBgcode() + { + // Act + var filePath = "HelperFiles/sample.bgcode"; + + BgcodeThumbnailProvider provider = new BgcodeThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(10001); + + Assert.IsTrue(bitmap == null); + } + + [TestMethod] + public void CheckNoBgcodeEmptyDataShouldReturnNullBitmap() + { + using (var reader = new BinaryReader(new MemoryStream())) + { + Bitmap thumbnail = BgcodeThumbnailProvider.GetThumbnail(reader, 256); + Assert.IsTrue(thumbnail == null); + } + } + + [TestMethod] + public void CheckNoBgcodeNullStringShouldReturnNullBitmap() + { + Bitmap thumbnail = BgcodeThumbnailProvider.GetThumbnail(null, 256); + Assert.IsTrue(thumbnail == null); + } + } +} diff --git a/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/HelperFiles/sample.bgcode b/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/HelperFiles/sample.bgcode new file mode 100644 index 0000000000..64ec331f7f Binary files /dev/null and b/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/HelperFiles/sample.bgcode differ diff --git a/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/UnitTests-BgcodeThumbnailProvider.csproj b/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/UnitTests-BgcodeThumbnailProvider.csproj new file mode 100644 index 0000000000..5a44d479d5 --- /dev/null +++ b/src/modules/previewpane/UnitTests-BgcodeThumbnailProvider/UnitTests-BgcodeThumbnailProvider.csproj @@ -0,0 +1,38 @@ + + + + + + UnitTests-BgcodeThumbnailProvider + PowerToys UnitTests-BgcodeThumbnailProvider + UnitTests-BgcodeThumbnailProvider + PowerToys UnitTests-BgcodeThumbnailProvider + {61CBF221-9452-4934-B685-146285E080D7} + BgcodeThumbnailProviderUnitTests + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + + + + + + + + + + + + + + Always + + + diff --git a/src/modules/previewpane/powerpreview/CLSID.h b/src/modules/previewpane/powerpreview/CLSID.h index 0c75ed23f4..4c866a6b80 100644 --- a/src/modules/previewpane/powerpreview/CLSID.h +++ b/src/modules/previewpane/powerpreview/CLSID.h @@ -37,6 +37,12 @@ const CLSID CLSID_SHIMActivateGcodePreviewHandler = { 0x516cb24f, 0x562f, 0x422f // ec52dea8-7c9f-4130-a77b-1737d0418507 const CLSID CLSID_GcodePreviewHandler = { 0xec52dea8, 0x7c9f, 0x4130, { 0xa7, 0x7b, 0x17, 0x37, 0xd0, 0x41, 0x85, 0x07 } }; +// 8fae8d5d-6bd1-46b4-a74f-cbebba4c7b62 +const CLSID CLSID_SHIMActivateBgcodePreviewHandler = { 0x8fae8d5d, 0x6bd1, 0x46b4, { 0xa7, 0x4f, 0xcb, 0xeb, 0xba, 0x4c, 0x7b, 0x62 } }; + +// dd8de316-7b01-48e7-ba21-e92c646704af +const GUID CLSID_BgcodePreviewHandler = { 0xdd8de316, 0x7b01, 0x48e7, { 0xba, 0x21, 0xe9, 0x2c, 0x64, 0x67, 0x04, 0xaf } }; + // F498BE36-5C94-4EC9-A65A-AD1CF4C38271 const GUID CLSID_SHIMActivateQoiPreviewHandler = { 0xf498be36, 0x5c94, 0x4ec9, { 0xa6, 0x5a, 0xad, 0x1c, 0xf4, 0xc3, 0x82, 0x71 } }; @@ -46,6 +52,9 @@ const GUID CLSID_QoiPreviewHandler = { 0x8aa07897, 0xc30b, 0x4543, { 0x86, 0x5b, // BFEE99B4-B74D-4348-BCA5-E757029647FF const GUID CLSID_GcodeThumbnailProvider = { 0xbfee99b4, 0xb74d, 0x4348, { 0xbc, 0xa5, 0xe7, 0x57, 0x02, 0x96, 0x47, 0xff } }; +// c28761a0-8420-43ad-bff3-40400543e2d4 +const GUID CLSID_BgcodeThumbnailProvider = {0xc28761a0, 0x8420, 0x43ad, {0xbf, 0xf3, 0x40, 0x40, 0x05, 0x43, 0xe2, 0xd4}}; + // 8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF const GUID CLSID_StlThumbnailProvider = { 0x8bc8afc2, 0x4e7c, 0x4695, { 0x81, 0x8e, 0x8c, 0x1f, 0xfd, 0xce, 0xa2, 0xaf } }; diff --git a/src/modules/previewpane/powerpreview/Resources.resx b/src/modules/previewpane/powerpreview/Resources.resx index 6703e476ef..38a95c38ce 100644 --- a/src/modules/previewpane/powerpreview/Resources.resx +++ b/src/modules/previewpane/powerpreview/Resources.resx @@ -143,7 +143,7 @@ Developer files Previewer - + Markdown Previewer @@ -198,4 +198,13 @@ Qoi Thumbnail Provider + + Binary G-code Previewer + + + Binary G-code Previewer + + + Binary G-code Thumbnail Provider + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/powerpreview.cpp b/src/modules/previewpane/powerpreview/powerpreview.cpp index 84d2a590dc..9df07ced0a 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -50,6 +50,11 @@ PowerPreviewModule::PowerPreviewModule() : .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredGcodePreviewEnabledValue, .registryChanges = getGcodePreviewHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"bgcode-previewer-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_PREVPANE_BGCODE_SETTINGS_DESCRIPTION), + .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredBgcodePreviewEnabledValue, + .registryChanges = getBgcodePreviewHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"svg-thumbnail-toggle-setting", .settingDescription = GET_RESOURCE_STRING(IDS_SVG_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredSvgThumbnailsEnabledValue, @@ -65,6 +70,11 @@ PowerPreviewModule::PowerPreviewModule() : .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredGcodeThumbnailsEnabledValue, .registryChanges = getGcodeThumbnailHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"bgcode-thumbnail-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_BGCODE_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), + .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredBgcodeThumbnailsEnabledValue, + .registryChanges = getBgcodeThumbnailHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"stl-thumbnail-toggle-setting", .settingDescription = GET_RESOURCE_STRING(IDS_STL_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredStlThumbnailsEnabledValue, diff --git a/src/modules/previewpane/powerpreview/powerpreview.vcxproj b/src/modules/previewpane/powerpreview/powerpreview.vcxproj index 7ea86fc440..9b32f385c4 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.vcxproj +++ b/src/modules/previewpane/powerpreview/powerpreview.vcxproj @@ -86,7 +86,9 @@ - + + Designer + diff --git a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs index f6dd527015..72113e67fc 100644 --- a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs +++ b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs @@ -223,6 +223,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool enableBgcodePreview = true; + + [JsonPropertyName("bgcode-previewer-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableBgcodePreview + { + get => enableBgcodePreview; + set + { + if (value != enableBgcodePreview) + { + LogTelemetryEvent(value); + enableBgcodePreview = value; + } + } + } + private bool enableGcodeThumbnail = true; [JsonPropertyName("gcode-thumbnail-toggle-setting")] @@ -240,6 +257,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool enableBgcodeThumbnail = true; + + [JsonPropertyName("bgcode-thumbnail-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableBgcodeThumbnail + { + get => enableBgcodeThumbnail; + set + { + if (value != enableBgcodeThumbnail) + { + LogTelemetryEvent(value); + enableBgcodeThumbnail = value; + } + } + } + private bool enableStlThumbnail = true; [JsonPropertyName("stl-thumbnail-toggle-setting")] diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs index 373b9a3580..636d96aec6 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs @@ -61,11 +61,13 @@ namespace ViewModelTests Assert.AreEqual(originalSettings.Properties.EnableMonacoPreview, viewModel.MonacoRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnablePdfPreview, viewModel.PDFRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableGcodePreview, viewModel.GCODERenderIsEnabled); + Assert.AreEqual(originalSettings.Properties.EnableBgcodePreview, viewModel.BGCODERenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableQoiPreview, viewModel.QOIRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableSvgPreview, viewModel.SVGRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableSvgThumbnail, viewModel.SVGThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnablePdfThumbnail, viewModel.PDFThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableGcodeThumbnail, viewModel.GCODEThumbnailIsEnabled); + Assert.AreEqual(originalSettings.Properties.EnableBgcodeThumbnail, viewModel.BGCODEThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableStlThumbnail, viewModel.STLThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableQoiThumbnail, viewModel.QOIThumbnailIsEnabled); @@ -146,6 +148,24 @@ namespace ViewModelTests viewModel.GCODEThumbnailIsEnabled = true; } + [TestMethod] + public void BGCODEThumbnailIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableBgcodeThumbnail); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.BGCODEThumbnailIsEnabled = true; + } + [TestMethod] public void STLThumbnailIsEnabledShouldPrevHandlerWhenSuccessful() { @@ -254,6 +274,24 @@ namespace ViewModelTests viewModel.GCODERenderIsEnabled = true; } + [TestMethod] + public void BGCODERenderIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableBgcodePreview); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.BGCODERenderIsEnabled = true; + } + [TestMethod] public void QOIRenderIsEnabledShouldPrevHandlerWhenSuccessful() { diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml index 301cf7c51e..c5867fff74 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml @@ -147,8 +147,18 @@ + + + + + + + + - + diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index a1abec7e4a..f9b971816c 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -2002,7 +2002,7 @@ Made with 💗 by Microsoft and the PowerToys community. File Locksmith lists which processes are using the selected files or directories and allows closing those processes. - PowerToys introduces add-ons to the Windows File Explorer that will enable files like Markdown (.md), PDF (.pdf), SVG (.svg), STL (.stl), G-code (.gcode) and developer files to be viewed in the preview pane. It introduces File Explorer thumbnail support for a number of these file types as well. + PowerToys introduces add-ons to the Windows File Explorer that will enable files like Markdown (.md), PDF (.pdf), SVG (.svg), STL (.stl), G-code (.gcode), Binary G-code (.bgcode), and developer files to be viewed in the preview pane. It introduces File Explorer thumbnail support for a number of these file types as well. Image Resizer is a Windows shell extension for simple bulk image-resizing. @@ -5015,6 +5015,18 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Bug report package is being created + + Binary Geometric Code + + + .bgcode + + + .bgcode + + + Binary Geometric Code + Highlight the cursor or dim the screen to spotlight it diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs index 86089a2d5d..75c22dd855 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs @@ -125,6 +125,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _gcodeRenderIsEnabled = Settings.Properties.EnableGcodePreview; } + _bgcodeRenderEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredBgcodePreviewEnabledValue(); + if (_bgcodeRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _bgcodeRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) + { + // Get the enabled state from GPO. + _bgcodeRenderEnabledStateIsGPOConfigured = true; + _bgcodeRenderIsEnabled = _bgcodeRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + _bgcodeRenderIsGpoEnabled = _bgcodeRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + _bgcodeRenderIsGpoDisabled = _bgcodeRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled; + } + else + { + _bgcodeRenderIsEnabled = Settings.Properties.EnableBgcodePreview; + } + _qoiRenderEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredQoiPreviewEnabledValue(); if (_qoiRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _qoiRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) { @@ -181,6 +195,20 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _gcodeThumbnailIsEnabled = Settings.Properties.EnableGcodeThumbnail; } + _bgcodeThumbnailEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredBgcodeThumbnailsEnabledValue(); + if (_bgcodeThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _bgcodeThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) + { + // Get the enabled state from GPO. + _bgcodeThumbnailEnabledStateIsGPOConfigured = true; + _bgcodeThumbnailIsEnabled = _bgcodeThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + _bgcodeThumbnailIsGpoEnabled = _bgcodeThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + _bgcodeThumbnailIsGpoDisabled = _bgcodeThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled; + } + else + { + _bgcodeThumbnailIsEnabled = Settings.Properties.EnableBgcodeThumbnail; + } + _stlThumbnailEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredStlThumbnailsEnabledValue(); if (_stlThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _stlThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) { @@ -251,6 +279,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _gcodeRenderIsGpoDisabled; private bool _gcodeRenderIsEnabled; + private GpoRuleConfigured _bgcodeRenderEnabledGpoRuleConfiguration; + private bool _bgcodeRenderEnabledStateIsGPOConfigured; + private bool _bgcodeRenderIsGpoEnabled; + private bool _bgcodeRenderIsGpoDisabled; + private bool _bgcodeRenderIsEnabled; + private GpoRuleConfigured _qoiRenderEnabledGpoRuleConfiguration; private bool _qoiRenderEnabledStateIsGPOConfigured; private bool _qoiRenderIsGpoEnabled; @@ -275,6 +309,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _gcodeThumbnailIsGpoDisabled; private bool _gcodeThumbnailIsEnabled; + private GpoRuleConfigured _bgcodeThumbnailEnabledGpoRuleConfiguration; + private bool _bgcodeThumbnailEnabledStateIsGPOConfigured; + private bool _bgcodeThumbnailIsGpoEnabled; + private bool _bgcodeThumbnailIsGpoDisabled; + private bool _bgcodeThumbnailIsEnabled; + private GpoRuleConfigured _stlThumbnailEnabledGpoRuleConfiguration; private bool _stlThumbnailEnabledStateIsGPOConfigured; private bool _stlThumbnailIsGpoEnabled; @@ -294,7 +334,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { return _svgRenderEnabledStateIsGPOConfigured || _mdRenderEnabledStateIsGPOConfigured || _monacoRenderEnabledStateIsGPOConfigured || _pdfRenderEnabledStateIsGPOConfigured - || _gcodeRenderEnabledStateIsGPOConfigured || _qoiRenderEnabledStateIsGPOConfigured; + || _gcodeRenderEnabledStateIsGPOConfigured || _qoiRenderEnabledStateIsGPOConfigured + || _bgcodeRenderEnabledStateIsGPOConfigured; } } @@ -304,7 +345,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { return _svgThumbnailEnabledStateIsGPOConfigured || _pdfThumbnailEnabledStateIsGPOConfigured || _gcodeThumbnailEnabledStateIsGPOConfigured || _stlThumbnailEnabledStateIsGPOConfigured - || _qoiThumbnailEnabledStateIsGPOConfigured; + || _qoiThumbnailEnabledStateIsGPOConfigured || _bgcodeThumbnailEnabledStateIsGPOConfigured; } } @@ -778,6 +819,48 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool BGCODERenderIsEnabled + { + get + { + return _bgcodeRenderIsEnabled; + } + + set + { + if (_bgcodeRenderEnabledStateIsGPOConfigured) + { + // If it's GPO configured, shouldn't be able to change this state. + return; + } + + if (value != _bgcodeRenderIsEnabled) + { + _bgcodeRenderIsEnabled = value; + Settings.Properties.EnableBgcodePreview = value; + RaisePropertyChanged(); + } + } + } + + // Used to only disable enabled button on forced enabled state. (With this users still able to change the utility properties.) + public bool BGCODERenderIsGpoEnabled + { + get + { + return _bgcodeRenderIsGpoEnabled; + } + } + + // Used to disable the settings card on forced disabled state. + public bool BGCODERenderIsGpoDisabled + { + get + { + return _bgcodeRenderIsGpoDisabled; + } + } + public bool GCODEThumbnailIsEnabled { get @@ -820,6 +903,48 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool BGCODEThumbnailIsEnabled + { + get + { + return _bgcodeThumbnailIsEnabled; + } + + set + { + if (_bgcodeThumbnailEnabledStateIsGPOConfigured) + { + // If it's GPO configured, shouldn't be able to change this state. + return; + } + + if (value != _bgcodeThumbnailIsEnabled) + { + _bgcodeThumbnailIsEnabled = value; + Settings.Properties.EnableBgcodeThumbnail = value; + RaisePropertyChanged(); + } + } + } + + // Used to only disable enabled button on forced enabled state. (With this users still able to change the utility properties.) + public bool BGCODEThumbnailIsGpoEnabled + { + get + { + return _bgcodeThumbnailIsGpoEnabled; + } + } + + // Used to disable the settings card on forced disabled state. + public bool BGCODEThumbnailIsGpoDisabled + { + get + { + return _bgcodeThumbnailIsGpoDisabled; + } + } + public bool STLThumbnailIsEnabled { get diff --git a/tools/BugReportTool/BugReportTool/ProcessesList.cpp b/tools/BugReportTool/BugReportTool/ProcessesList.cpp index 9665938e7e..3b5062f908 100644 --- a/tools/BugReportTool/BugReportTool/ProcessesList.cpp +++ b/tools/BugReportTool/BugReportTool/ProcessesList.cpp @@ -27,6 +27,8 @@ std::vector processes = L"PowerToys.Hosts.exe", L"PowerToys.GcodePreviewHandler.exe", L"PowerToys.GcodeThumbnailProvider.exe", + L"PowerToys.BgcodePreviewHandler.exe", + L"PowerToys.BgcodeThumbnailProvider.exe", L"PowerToys.MarkdownPreviewHandler.exe", L"PowerToys.MonacoPreviewHandler.exe", L"PowerToys.PdfPreviewHandler.exe", diff --git a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp index 5fed9f4fa6..eb576690e8 100644 --- a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp +++ b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp @@ -17,9 +17,11 @@ namespace { HKEY_CLASSES_ROOT, L"AppID\\{CF142243-F059-45AF-8842-DBBE9783DB14}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{07665729-6243-4746-95b7-79579308d1b2}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{ec52dea8-7c9f-4130-a77b-1737d0418507}" }, + { HKEY_CLASSES_ROOT, L"CLSID\\{dd8de316-7b01-48e7-ba21-e92c646704af}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{8AA07897-C30B-4543-865B-00A0E5A1B32D}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{BCC13D15-9720-4CC4-8371-EA74A274741E}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{BFEE99B4-B74D-4348-BCA5-E757029647FF}" }, + { HKEY_CLASSES_ROOT, L"CLSID\\{c28761a0-8420-43ad-bff3-40400543e2d4}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\\InprocServer32" }, { HKEY_CLASSES_ROOT, L"CLSID\\{0440049F-D1DC-4E46-B27B-98393D79486B}" }, @@ -41,6 +43,7 @@ namespace { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", L"{45769bcc-e8fd-42d0-947e-02beef77a1f5}" }, { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", L"{07665729-6243-4746-95b7-79579308d1b2}" }, { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", L"{ec52dea8-7c9f-4130-a77b-1737d0418507}" }, + { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", L"{dd8de316-7b01-48e7-ba21-e92c646704af}" }, { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", L"{8AA07897-C30B-4543-865B-00A0E5A1B32D}" }, { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION", L"prevhost.exe" }, { HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION", L"dllhost.exe" } diff --git a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp index 02e04a2c5e..3c042ee06d 100644 --- a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp +++ b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp @@ -55,9 +55,11 @@ void ReportGPOValues(const std::filesystem::path &tmpDir) report << "getConfiguredMonacoPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredMonacoPreviewEnabledValue()) << std::endl; report << "getConfiguredPdfPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredPdfPreviewEnabledValue()) << std::endl; report << "getConfiguredGcodePreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredGcodePreviewEnabledValue()) << std::endl; + report << "getConfiguredBgcodePreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredBgcodePreviewEnabledValue()) << std::endl; report << "getConfiguredSvgThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredSvgThumbnailsEnabledValue()) << std::endl; report << "getConfiguredPdfThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredPdfThumbnailsEnabledValue()) << std::endl; report << "getConfiguredGcodeThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredGcodeThumbnailsEnabledValue()) << std::endl; + report << "getConfiguredBgcodeThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredBgcodeThumbnailsEnabledValue()) << std::endl; report << "getConfiguredStlThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredStlThumbnailsEnabledValue()) << std::endl; report << "getConfiguredHostsFileEditorEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredHostsFileEditorEnabledValue()) << std::endl; report << "getConfiguredImageResizerEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredImageResizerEnabledValue()) << std::endl; diff --git a/tools/Verification scripts/Check preview handler registration.ps1 b/tools/Verification scripts/Check preview handler registration.ps1 index e2b5eee30e..d4b54313b6 100644 --- a/tools/Verification scripts/Check preview handler registration.ps1 +++ b/tools/Verification scripts/Check preview handler registration.ps1 @@ -15,12 +15,13 @@ function PublicStaticVoidMain { [String] $MachineWideHandler } - [String[]]$TypesToCheck = @(".markdown", ".mdtext", ".mdtxt", ".mdown", ".mkdn", ".mdwn", ".mkd", ".md", ".svg", ".svgz", ".pdf", ".gcode", ".stl", ".txt", ".ini", ".qoi") + [String[]]$TypesToCheck = @(".markdown", ".mdtext", ".mdtxt", ".mdown", ".mkdn", ".mdwn", ".mkd", ".md", ".svg", ".svgz", ".pdf", ".gcode", ".bgcode", ".stl", ".txt", ".ini", ".qoi") $IPREVIEW_HANDLER_CLSID = '{8895b1c6-b41f-4c1c-a562-0d564250836f}' $PowerToysHandlers = @{ '{07665729-6243-4746-95b7-79579308d1b2}' = "PowerToys PDF handler" '{ddee2b8a-6807-48a6-bb20-2338174ff779}' = "PowerToys SVG handler" '{ec52dea8-7c9f-4130-a77b-1737d0418507}' = "PowerToys GCode handler" + '{dd8de316-7b01-48e7-ba21-e92c646704af}' = "PowerToys BGCode handler" '{8AA07897-C30B-4543-865B-00A0E5A1B32D}' = "PowerToys QOI handler" '{45769bcc-e8fd-42d0-947e-02beef77a1f5}' = "PowerToys Markdown handler" '{afbd5a44-2520-4ae0-9224-6cfce8fe4400}' = "PowerToys Monaco fallback handler"