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"