From 0990724e445fae8319aeec5a1eb92f238c088f37 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Tue, 14 Nov 2023 15:41:09 +0000 Subject: [PATCH] [FileExplorer]Add QoiThumbnailProvider and QoiPreviewHandler (#29735) * [FileExplorer]QoiThumbnailProvider * Corrects code documentation * Adds QoiPreviewHandler * Corrects GUIDs * Ensure returned thumbnail image is 32bit ARGB * Updates following review comments * More updates following review comments * Updates following PR comments * Fix dark theme background in QoiPreviewHandler * Updates attribution text --- .pipelines/ESRPSigning_core.json | 6 + .../ci/templates/build-powertoys-steps.yml | 2 + PowerToys.sln | 92 +++++- installer/PowerToysSetup/Resources.wxs | 9 + .../CustomAction.cpp | 4 +- src/common/GPOWrapper/GPOWrapper.cpp | 8 + src/common/GPOWrapper/GPOWrapper.h | 2 + src/common/GPOWrapper/GPOWrapper.idl | 2 + src/common/interop/interop.cpp | 4 + src/common/interop/shared_constants.h | 3 + src/common/logger/logger_settings.h | 4 + src/common/utils/gpo.h | 14 +- src/common/utils/modulesRegistry.h | 33 ++- src/gpo/assets/PowerToys.admx | 23 +- src/gpo/assets/en-US/PowerToys.adml | 5 +- .../previewpane/QoiPreviewHandler/Program.cs | 63 +++++ .../Properties/Resource.Designer.cs | 81 ++++++ .../Properties/Resource.resx | 128 +++++++++ .../QoiPreviewHandler.csproj | 77 +++++ .../QoiPreviewHandlerControl.cs | 166 +++++++++++ .../previewpane/QoiPreviewHandler/Settings.cs | 38 +++ .../Telemetry/Events/QoiFilePreviewError.cs | 25 ++ .../Telemetry/Events/QoiFilePreviewed.cs | 20 ++ .../QoiPreviewHandlerCpp/ClassFactory.cpp | 84 ++++++ .../QoiPreviewHandlerCpp/ClassFactory.h | 24 ++ .../GlobalExportFunctions.def | 3 + .../QoiPreviewHandler.cpp | 266 ++++++++++++++++++ .../QoiPreviewHandlerCpp/QoiPreviewHandler.h | 69 +++++ .../QoiPreviewHandlerCpp.rc | 40 +++ .../QoiPreviewHandlerCpp.vcxproj | 121 ++++++++ .../QoiPreviewHandlerCpp.vcxproj.filters | 56 ++++ .../QoiPreviewHandlerCpp/dllmain.cpp | 73 +++++ .../QoiPreviewHandlerCpp/packages.config | 4 + .../previewpane/QoiPreviewHandlerCpp/pch.cpp | 5 + .../previewpane/QoiPreviewHandlerCpp/pch.h | 14 + .../QoiPreviewHandlerCpp/resource.h | 13 + .../QoiThumbnailProvider/Program.cs | 42 +++ .../QoiThumbnailProvider.cs | 139 +++++++++ .../QoiThumbnailProvider.csproj | 54 ++++ .../QoiThumbnailProviderCpp/ClassFactory.cpp | 84 ++++++ .../QoiThumbnailProviderCpp/ClassFactory.h | 24 ++ .../GlobalExportFunctions.def | 3 + .../QoiThumbnailProvider.cpp | 195 +++++++++++++ .../QoiThumbnailProvider.h | 37 +++ .../QoiThumbnailProviderCpp.rc | 40 +++ .../QoiThumbnailProviderCpp.vcxproj | 120 ++++++++ .../QoiThumbnailProviderCpp.vcxproj.filters | 59 ++++ .../QoiThumbnailProviderCpp/dllmain.cpp | 73 +++++ .../QoiThumbnailProviderCpp/packages.config | 5 + .../QoiThumbnailProviderCpp/pch.cpp | 5 + .../previewpane/QoiThumbnailProviderCpp/pch.h | 15 + .../QoiThumbnailProviderCpp/resource.h | 13 + .../HelperFiles/sample.qoi | Bin 0 -> 16488 bytes .../QoiPreviewHandlerTest.cs | 92 ++++++ .../UnitTests-QoiPreviewHandler.csproj | 47 ++++ .../HelperFiles/sample.qoi | Bin 0 -> 16488 bytes .../QoiThumbnailProviderTests.cs | 61 ++++ .../UnitTests-QoiThumbnailProvider.csproj | 48 ++++ src/modules/previewpane/powerpreview/CLSID.h | 10 + .../previewpane/powerpreview/Resources.resx | 6 + .../previewpane/powerpreview/powerpreview.cpp | 10 + .../PowerPreviewProperties.cs | 34 +++ .../ViewModelTests/PowerPreview.cs | 38 +++ .../SettingsXAML/Views/PowerPreviewPage.xaml | 27 +- .../Settings.UI/Strings/en-us/Resources.resw | 15 + .../ViewModels/PowerPreviewViewModel.cs | 90 ++++++ .../BugReportTool/ProcessesList.cpp | 2 + .../BugReportTool/RegistryUtils.cpp | 4 + .../BugReportTool/ReportGPOValues.cpp | 2 + .../Check preview handler registration.ps1 | 3 +- 70 files changed, 2970 insertions(+), 8 deletions(-) create mode 100644 src/modules/previewpane/QoiPreviewHandler/Program.cs create mode 100644 src/modules/previewpane/QoiPreviewHandler/Properties/Resource.Designer.cs create mode 100644 src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx create mode 100644 src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj create mode 100644 src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs create mode 100644 src/modules/previewpane/QoiPreviewHandler/Settings.cs create mode 100644 src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs create mode 100644 src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.h create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/GlobalExportFunctions.def create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.h create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.rc create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj.filters create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/dllmain.cpp create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/packages.config create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/pch.cpp create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/pch.h create mode 100644 src/modules/previewpane/QoiPreviewHandlerCpp/resource.h create mode 100644 src/modules/previewpane/QoiThumbnailProvider/Program.cs create mode 100644 src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs create mode 100644 src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.h create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/GlobalExportFunctions.def create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.rc create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj.filters create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/dllmain.cpp create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/packages.config create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/pch.cpp create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/pch.h create mode 100644 src/modules/previewpane/QoiThumbnailProviderCpp/resource.h create mode 100644 src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi create mode 100644 src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs create mode 100644 src/modules/previewpane/UnitTests-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj create mode 100644 src/modules/previewpane/UnitTests-QoiThumbnailProvider/HelperFiles/sample.qoi create mode 100644 src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs create mode 100644 src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 24f1fda402..73bc45ed03 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -73,6 +73,12 @@ "PowerToys.PdfThumbnailProviderCpp.dll", "PowerToys.powerpreview.dll", "PowerToys.PreviewHandlerCommon.dll", + "PowerToys.QoiPreviewHandler.dll", + "PowerToys.QoiPreviewHandler.exe", + "PowerToys.QoiPreviewHandlerCpp.dll", + "PowerToys.QoiThumbnailProvider.dll", + "PowerToys.QoiThumbnailProvider.exe", + "PowerToys.QoiThumbnailProviderCpp.dll", "PowerToys.StlThumbnailProvider.dll", "PowerToys.StlThumbnailProvider.exe", "PowerToys.StlThumbnailProviderCpp.dll", diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index ba7aec6f36..23cd60f191 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -195,8 +195,10 @@ steps: **\UnitTests-GcodeThumbnailProvider.dll **\UnitTests-StlThumbnailProvider.dll **\UnitTests-PdfThumbnailProvider.dll + **\UnitTests-QoiThumbnailProvider.dll **\Settings.UI.UnitTests.dll **\UnitTests-GcodePreviewHandler.dll + **\UnitTests-QoiPreviewHandler.dll **\UnitTests-FancyZonesEditor.dll **\UnitTests-PdfPreviewHandler.dll **\UnitTests-PreviewHandlerCommon.dll diff --git a/PowerToys.sln b/PowerToys.sln index 81257cd738..c3a17c1804 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -538,7 +538,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLock", "src\modules\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLockModuleInterface", "src\modules\CropAndLock\CropAndLockModuleInterface\CropAndLockModuleInterface.vcxproj", "{3157FA75-86CF-4EE2-8F62-C43F776493C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-FancyZonesEditor", "src\modules\fancyzones\UnitTests-FancyZonesEditor\UnitTests-FancyZonesEditor.csproj", "{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-FancyZonesEditor", "src\modules\fancyzones\UnitTests-FancyZonesEditor\UnitTests-FancyZonesEditor.csproj", "{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvironmentVariables", "EnvironmentVariables", "{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}" EndProject @@ -546,6 +546,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariables", "src EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnvironmentVariablesModuleInterface", "src\modules\EnvironmentVariables\EnvironmentVariablesModuleInterface\EnvironmentVariablesModuleInterface.vcxproj", "{B9420661-B0E4-4241-ABD4-4A27A1F64250}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QoiThumbnailProviderCpp", "src\modules\previewpane\QoiThumbnailProviderCpp\QoiThumbnailProviderCpp.vcxproj", "{CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QoiThumbnailProvider", "src\modules\previewpane\QoiThumbnailProvider\QoiThumbnailProvider.csproj", "{D949EC7D-48A9-4279-95D5-078E7FD1F048}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QoiPreviewHandlerCpp", "src\modules\previewpane\QoiPreviewHandlerCpp\QoiPreviewHandlerCpp.vcxproj", "{3BAF9C81-A194-4925-A035-5E24A5D1E542}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QoiPreviewHandler", "src\modules\previewpane\QoiPreviewHandler\QoiPreviewHandler.csproj", "{6B04803D-B418-4833-A67E-B0FC966636A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiPreviewHandler", "src\modules\previewpane\UnitTests-QoiPreviewHandler\UnitTests-QoiPreviewHandler.csproj", "{3940AD4D-F748-4BE4-9083-85769CD553EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiThumbnailProvider", "src\modules\previewpane\UnitTests-QoiThumbnailProvider\UnitTests-QoiThumbnailProvider.csproj", "{F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2352,6 +2364,78 @@ Global {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x64.Build.0 = Release|x64 {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x86.ActiveCfg = Release|x64 {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x86.Build.0 = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|ARM64.Build.0 = Debug|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x64.ActiveCfg = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x64.Build.0 = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x86.ActiveCfg = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x86.Build.0 = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|ARM64.ActiveCfg = Release|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|ARM64.Build.0 = Release|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x64.ActiveCfg = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x64.Build.0 = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x86.ActiveCfg = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x86.Build.0 = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|ARM64.Build.0 = Debug|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x64.ActiveCfg = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x64.Build.0 = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x86.ActiveCfg = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x86.Build.0 = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|ARM64.ActiveCfg = Release|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|ARM64.Build.0 = Release|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x64.ActiveCfg = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x64.Build.0 = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x86.ActiveCfg = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x86.Build.0 = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|ARM64.Build.0 = Debug|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x64.ActiveCfg = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x64.Build.0 = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x86.ActiveCfg = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x86.Build.0 = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|ARM64.ActiveCfg = Release|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|ARM64.Build.0 = Release|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x64.ActiveCfg = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x64.Build.0 = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x86.ActiveCfg = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x86.Build.0 = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|ARM64.Build.0 = Debug|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x64.ActiveCfg = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x64.Build.0 = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x86.ActiveCfg = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x86.Build.0 = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|ARM64.ActiveCfg = Release|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|ARM64.Build.0 = Release|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x64.ActiveCfg = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x64.Build.0 = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x86.ActiveCfg = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x86.Build.0 = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|ARM64.Build.0 = Debug|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x64.ActiveCfg = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x64.Build.0 = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x86.ActiveCfg = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x86.Build.0 = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|ARM64.ActiveCfg = Release|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|ARM64.Build.0 = Release|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x64.ActiveCfg = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x64.Build.0 = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x86.ActiveCfg = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x86.Build.0 = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|ARM64.Build.0 = Debug|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x64.ActiveCfg = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x64.Build.0 = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x86.ActiveCfg = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x86.Build.0 = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|ARM64.ActiveCfg = Release|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|ARM64.Build.0 = Release|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.ActiveCfg = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.Build.0 = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x86.ActiveCfg = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2550,6 +2634,12 @@ Global {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} {B9420661-B0E4-4241-ABD4-4A27A1F64250} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7} = {2F305555-C296-497E-AC20-5FA1B237996A} + {D949EC7D-48A9-4279-95D5-078E7FD1F048} = {2F305555-C296-497E-AC20-5FA1B237996A} + {3BAF9C81-A194-4925-A035-5E24A5D1E542} = {2F305555-C296-497E-AC20-5FA1B237996A} + {6B04803D-B418-4833-A67E-B0FC966636A5} = {2F305555-C296-497E-AC20-5FA1B237996A} + {3940AD4D-F748-4BE4-9083-85769CD553EF} = {2F305555-C296-497E-AC20-5FA1B237996A} + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} = {2F305555-C296-497E-AC20-5FA1B237996A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Resources.wxs b/installer/PowerToysSetup/Resources.wxs index 9627af3724..50369a505e 100644 --- a/installer/PowerToysSetup/Resources.wxs +++ b/installer/PowerToysSetup/Resources.wxs @@ -402,6 +402,15 @@ + + + + + + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 4374d2403d..c76e2203f8 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -1005,7 +1005,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) } processes.resize(bytes / sizeof(processes[0])); - std::array processesToTerminate = { + std::array processesToTerminate = { L"PowerToys.PowerLauncher.exe", L"PowerToys.Settings.exe", L"PowerToys.Awake.exe", @@ -1026,7 +1026,9 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) L"PowerToys.StlThumbnailProvider.exe", L"PowerToys.SvgThumbnailProvider.exe", L"PowerToys.GcodePreviewHandler.exe", + L"PowerToys.QoiPreviewHandler.exe", L"PowerToys.PdfPreviewHandler.exe", + L"PowerToys.QoiThumbnailProvider.exe", L"PowerToys.SvgPreviewHandler.exe", L"PowerToys.Peek.UI.exe", L"PowerToys.MouseWithoutBorders.exe", diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index 378e3dcb52..77d81e8516 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -152,4 +152,12 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredEnvironmentVariablesEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredQoiPreviewEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredQoiPreviewEnabledValue()); + } + GpoRuleConfigured GPOWrapper::GetConfiguredQoiThumbnailsEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue()); + } } diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index c60f5e78ef..c29fac95b6 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -44,6 +44,8 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetAllowExperimentationValue(); static GpoRuleConfigured GetRunPluginEnabledValue(winrt::hstring const& pluginID); static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue(); + static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue(); + static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue(); }; } diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 748f63db91..d0e3ebbd4f 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -48,6 +48,8 @@ namespace PowerToys static GpoRuleConfigured GetAllowExperimentationValue(); static GpoRuleConfigured GetRunPluginEnabledValue(String pluginID); static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue(); + static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue(); + static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue(); } } } diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp index ebf6c8be22..9a8801fa14 100644 --- a/src/common/interop/interop.cpp +++ b/src/common/interop/interop.cpp @@ -235,6 +235,10 @@ public return gcnew String(CommonSharedConstants::GCODE_PREVIEW_RESIZE_EVENT); } + static String ^ QoiPreviewResizeEvent() { + return gcnew String(CommonSharedConstants::QOI_PREVIEW_RESIZE_EVENT); + } + static String ^ DevFilesPreviewResizeEvent() { return gcnew String(CommonSharedConstants::DEV_FILES_PREVIEW_RESIZE_EVENT); } diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 3d71b5fdba..441621ecb2 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -62,6 +62,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 QoiPreviewHandler + const wchar_t QOI_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysQoiPreviewResizeEvent-579518d1-8c8b-494f-8143-04f43d761ead"; + // Path to the event used by DevFilesPreviewHandler const wchar_t DEV_FILES_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysDevFilesPreviewResizeEvent-5707a22c-2cac-4ea2-82f0-27c03ef0b5f3"; diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index 038a9a8e20..91dd5faae8 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -27,6 +27,10 @@ struct LogSettings inline const static std::wstring pdfPrevLogPath = L"logs\\FileExplorer_localLow\\PdfPrevHandler\\pdf-prev-handler-log.txt"; inline const static std::string pdfThumbLoggerName = "PdfThumbnailProvider"; inline const static std::wstring pdfThumbLogPath = L"logs\\FileExplorer_localLow\\PdfThumbnailProvider\\pdf-thumbnail-provider-log.txt"; + inline const static std::string qoiPrevLoggerName = "QoiPrevHandler"; + inline const static std::wstring qoiPrevLogPath = L"logs\\FileExplorer_localLow\\QoiPreviewHandler\\qoi-prev-handler-log.txt"; + inline const static std::string qoiThumbLoggerName = "QoiThumbnailProvider"; + inline const static std::wstring qoiThumbLogPath = L"logs\\FileExplorer_localLow\\QoiThumbnailProvider\\qoi-thumbnail-provider-log.txt"; inline const static std::string stlThumbLoggerName = "StlThumbnailProvider"; inline const static std::wstring stlThumbLogPath = L"logs\\FileExplorer_localLow\\StlThumbnailProvider\\stl-thumbnail-provider-log.txt"; inline const static std::string svgPrevLoggerName = "SvgPrevHandler"; diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index 840fa266f0..be88e0d22d 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -56,6 +56,8 @@ namespace powertoys_gpo { const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_WITHOUT_BORDERS = L"ConfigureEnabledUtilityMouseWithoutBorders"; const std::wstring POLICY_CONFIGURE_ENABLED_PEEK = L"ConfigureEnabledUtilityPeek"; const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables"; + const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview"; + const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails"; // The registry value names for PowerToys installer and update policies. const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled"; @@ -366,7 +368,7 @@ namespace powertoys_gpo { { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_MOUSE_WITHOUT_BORDERS); } - + inline gpo_rule_configured_t getConfiguredPeekEnabledValue() { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_PEEK); @@ -446,4 +448,14 @@ namespace powertoys_gpo { return getConfiguredValue(POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS); } } + + inline gpo_rule_configured_t getConfiguredQoiPreviewEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_QOI_PREVIEW); + } + + inline gpo_rule_configured_t getConfiguredQoiThumbnailsEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS); + } } diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h index f4a6564bae..f736f43f7a 100644 --- a/src/common/utils/modulesRegistry.h +++ b/src/common/utils/modulesRegistry.h @@ -18,7 +18,8 @@ namespace NonLocalizable const static std::vector ExtPDF = { L".pdf" }; const static std::vector ExtGCode = { L".gcode" }; const static std::vector ExtSTL = { L".stl" }; - const static std::vector ExtNoNoNo = { + const static std::vector ExtQOI = { L".qoi" }; + const static std::vector ExtNoNoNo = { L".svgz" //Monaco cannot handle this file type at all; it's a binary file. }; } @@ -145,6 +146,19 @@ inline registry::ChangeSet getGcodePreviewHandlerChangeSet(const std::wstring in NonLocalizable::ExtGCode); } +inline registry::ChangeSet getQoiPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::preview, + perUser, + L"{729B72CD-B72E-4FE9-BCBF-E954B33FE699}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(PowerToys.QoiPreviewHandlerCpp.dll)d").wstring(), + L"QoiPreviewHandler", + L"Qoi Preview Handler", + NonLocalizable::ExtQOI); +} + inline registry::ChangeSet getSvgThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) { using namespace registry::shellex; @@ -198,6 +212,19 @@ inline registry::ChangeSet getStlThumbnailHandlerChangeSet(const std::wstring in NonLocalizable::ExtSTL); } +inline registry::ChangeSet getQoiThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{AD856B15-D25E-4008-AFB7-AFAA55586188}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(PowerToys.QoiThumbnailProviderCpp.dll)d").wstring(), + L"QoiThumbnailProvider", + L"Qoi Thumbnail Provider", + NonLocalizable::ExtQOI); +} + inline registry::ChangeSet getRegistryPreviewSetDefaultAppChangeSet(const std::wstring installationDir, const bool perUser) { const HKEY scope = perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; @@ -245,9 +272,11 @@ inline std::vector getAllOnByDefaultModulesChangeSets(const getMdPreviewHandlerChangeSet(installationDir, PER_USER), getMonacoPreviewHandlerChangeSet(installationDir, PER_USER), getGcodePreviewHandlerChangeSet(installationDir, PER_USER), + getQoiPreviewHandlerChangeSet(installationDir, PER_USER), getSvgThumbnailHandlerChangeSet(installationDir, PER_USER), getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER), getStlThumbnailHandlerChangeSet(installationDir, PER_USER), + getQoiThumbnailHandlerChangeSet(installationDir, PER_USER), getRegistryPreviewChangeSet(installationDir, PER_USER) }; } @@ -259,10 +288,12 @@ inline std::vector getAllModulesChangeSets(const std::wstri getMonacoPreviewHandlerChangeSet(installationDir, PER_USER), getPdfPreviewHandlerChangeSet(installationDir, PER_USER), getGcodePreviewHandlerChangeSet(installationDir, PER_USER), + getQoiPreviewHandlerChangeSet(installationDir, PER_USER), getSvgThumbnailHandlerChangeSet(installationDir, PER_USER), getPdfThumbnailHandlerChangeSet(installationDir, PER_USER), getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER), getStlThumbnailHandlerChangeSet(installationDir, PER_USER), + getQoiThumbnailHandlerChangeSet(installationDir, PER_USER), getRegistryPreviewChangeSet(installationDir, PER_USER), getRegistryPreviewSetDefaultAppChangeSet(installationDir, PER_USER) }; } diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 9687634134..0b135f665e 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -5,7 +5,7 @@ - + @@ -14,6 +14,7 @@ + @@ -188,6 +189,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index aff38e7d7a..de04d77c8b 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -1,7 +1,7 @@ - + PowerToys PowerToys @@ -16,6 +16,7 @@ PowerToys version 0.70.0 or later PowerToys version 0.73.0 or later PowerToys version 0.75.0 or later + PowerToys version 0.76.0 or later This policy configures the enabled state for all PowerToys utilities. @@ -149,6 +150,8 @@ Note: Changes require a restart of PowerToys Run. Allow Experimentation Configure enabled state for all plugins Configure enabled state for individual plugins + QOI file preview: Configure enabled state + QOI file thumbnail: Configure enabled state diff --git a/src/modules/previewpane/QoiPreviewHandler/Program.cs b/src/modules/previewpane/QoiPreviewHandler/Program.cs new file mode 100644 index 0000000000..4a012bef5a --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Program.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Windows.Threading; +using Common.UI; +using interop; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi +{ + internal static class Program + { + private static CancellationTokenSource _tokenSource = new CancellationTokenSource(); + + private static QoiPreviewHandlerControl _previewHandlerControl; + + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main(string[] args) + { + ApplicationConfiguration.Initialize(); + if (args != null) + { + if (args.Length == 6) + { + string filePath = args[0]; + int hwnd = Convert.ToInt32(args[1], 16); + + Rectangle s = default(Rectangle); + 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); + + _previewHandlerControl = new QoiPreviewHandlerControl(); + _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.DoPreview(filePath); + + NativeEventWaiter.WaitForEventLoop( + Constants.QoiPreviewResizeEvent(), + () => + { + Rectangle s = default(Rectangle); + _previewHandlerControl.SetRect(s); + }, + Dispatcher.CurrentDispatcher, + _tokenSource.Token); + } + 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/QoiPreviewHandler/Properties/Resource.Designer.cs b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.Designer.cs new file mode 100644 index 0000000000..f00aa4da78 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// 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.Qoi.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.Qoi.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 Qoi could not be previewed due to an internal error.. + /// + internal static string QoiNotPreviewedError { + get { + return ResourceManager.GetString("QoiNotPreviewedError", resourceCulture); + } + } + + /// + /// Looks up a localized string for an error when Gpo has the utility disabled. + /// + internal static string GpoDisabledErrorText { + get { + return ResourceManager.GetString("GpoDisabledErrorText", resourceCulture); + } + } + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx new file mode 100644 index 0000000000..ed37e8f252 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 Qoi could not be previewed due to an internal error. + This text is displayed if Qoi fails to preview + + + 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/QoiPreviewHandler/QoiPreviewHandler.csproj b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj new file mode 100644 index 0000000000..8ba906c5e0 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj @@ -0,0 +1,77 @@ + + + enable + true + PowerToys.QoiPreviewHandler + PowerToys QoiPreviewHandler + PowerToys QoiPreviewHandler + ..\..\..\..\$(Platform)\$(Configuration)\QoiPreviewPaneDocumentation.xml + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + true + PowerToys.QoiPreviewHandler + true + + + + + win10-x64 + + + win10-arm64 + + + + {6B04803D-B418-4833-A67E-B0FC966636A5} + Microsoft.PowerToys.PreviewHandler.Qoi + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + + + + + + + + True + True + Resource.resx + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + $(NoWarn);1591 + WinExe + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resource.Designer.cs + + + + diff --git a/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs new file mode 100644 index 0000000000..95d606ce82 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs @@ -0,0 +1,166 @@ +// 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.PreviewHandler.Qoi.Telemetry.Events; +using Microsoft.PowerToys.Telemetry; +using PreviewHandlerCommon.Utilities; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi +{ + /// + /// Implementation of Control for Qoi Preview Handler. + /// + public class QoiPreviewHandlerControl : FormHandlerControl + { + /// + /// Picture box control to display the Qoi thumbnail. + /// + private PictureBox _pictureBox; + + /// + /// Text box to display errors. + /// + private RichTextBox _textBox; + + /// + /// Represent if an text box info bar is added for showing message. + /// + private bool _infoBarAdded; + + /// + /// Initializes a new instance of the class. + /// + public QoiPreviewHandlerControl() + { + 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.GetConfiguredQoiPreviewEnabledValue() == 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(QoiPreviewHandlerControl)} must be a string but was a '{typeof(T)}'"); + } + + FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); + + thumbnail = QoiImage.FromStream(fs); + + _infoBarAdded = false; + + AddPictureBoxControl(thumbnail); + + Resize += FormResized; + base.DoPreview(fs); + try + { + PowerToysTelemetry.Log.WriteEvent(new QoiFilePreviewed()); + } + 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 to display errors. + /// + /// 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 QoiFilePreviewError { Message = exception.Message }); + } + catch + { // Should not crash if sending telemetry is failing. Ignore the exception. + } + + Controls.Clear(); + _infoBarAdded = true; + AddTextBoxControl(Properties.Resource.QoiNotPreviewedError); + base.DoPreview(dataSource); + } + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Settings.cs b/src/modules/previewpane/QoiPreviewHandler/Settings.cs new file mode 100644 index 0000000000..779f18a48f --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/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.Qoi +{ + 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/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs new file mode 100644 index 0000000000..71aa4f575a --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events +{ + /// + /// A telemetry event to be raised when an error has occurred in the preview pane. + /// + [EventData] + public class QoiFilePreviewError : 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/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs new file mode 100644 index 0000000000..b38727ebbb --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events +{ + /// + /// A telemetry event to be raised when a Qoi file has been viewed in the preview pane. + /// + [EventData] + public class QoiFilePreviewed : EventBase, IEvent + { + /// + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp new file mode 100644 index 0000000000..0d6a9934cc --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp @@ -0,0 +1,84 @@ +#include "pch.h" +#include "ClassFactory.h" +#include "QoiPreviewHandler.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; + + QoiPreviewHandler* pExt = new (std::nothrow) QoiPreviewHandler(); + 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/QoiPreviewHandlerCpp/ClassFactory.h b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.h new file mode 100644 index 0000000000..b393c3916e --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/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/QoiPreviewHandlerCpp/GlobalExportFunctions.def b/src/modules/previewpane/QoiPreviewHandlerCpp/GlobalExportFunctions.def new file mode 100644 index 0000000000..76fc66cac3 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/GlobalExportFunctions.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp new file mode 100644 index 0000000000..c5d9816478 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp @@ -0,0 +1,266 @@ +#include "pch.h" +#include "QoiPreviewHandler.h" +#include "../powerpreview/powerpreviewConstants.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +QoiPreviewHandler::QoiPreviewHandler() : + m_cRef(1), m_hwndParent(NULL), m_rcParent(), m_punkSite(NULL), m_process(NULL) +{ + m_resizeEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::QOI_PREVIEW_RESIZE_EVENT); + + std::filesystem::path logFilePath(PTSettingsHelper::get_local_low_folder_location()); + logFilePath.append(LogSettings::qoiPrevLogPath); + Logger::init(LogSettings::qoiPrevLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + InterlockedIncrement(&g_cDllRef); +} + +QoiPreviewHandler::~QoiPreviewHandler() +{ + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +IFACEMETHODIMP QoiPreviewHandler::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = { + QITABENT(QoiPreviewHandler, IPreviewHandler), + QITABENT(QoiPreviewHandler, IInitializeWithFile), + QITABENT(QoiPreviewHandler, IPreviewHandlerVisuals), + QITABENT(QoiPreviewHandler, IOleWindow), + QITABENT(QoiPreviewHandler, IObjectWithSite), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) +QoiPreviewHandler::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) +QoiPreviewHandler::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +#pragma endregion + +#pragma region IInitializationWithFile + +IFACEMETHODIMP QoiPreviewHandler::Initialize(LPCWSTR pszFilePath, DWORD grfMode) +{ + m_filePath = pszFilePath; + return S_OK; +} + +#pragma endregion + +#pragma region IPreviewHandler + +IFACEMETHODIMP QoiPreviewHandler::SetWindow(HWND hwnd, const RECT* prc) +{ + if (hwnd && prc) + { + m_hwndParent = hwnd; + m_rcParent = *prc; + } + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::SetFocus() +{ + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::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 QoiPreviewHandler::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 QoiPreviewHandler::SetRect(const RECT* prc) +{ + HRESULT hr = E_INVALIDARG; + if (prc != NULL) + { + if (!m_resizeEvent) + { + Logger::error(L"Failed to create resize event for QoiPreviewHandler"); + } + 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 QoiPreviewHandler"); + } + } + } + m_rcParent = *prc; + hr = S_OK; + } + return hr; +} + +IFACEMETHODIMP QoiPreviewHandler::DoPreview() +{ + try + { + Logger::info(L"Starting QoiPreviewHandler.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.QoiPreviewHandler.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; + } + catch (std::exception& e) + { + std::wstring errorMessage = std::wstring{ winrt::to_hstring(e.what()) }; + Logger::error(L"Failed to start QoiPreviewHandler.exe. Error: {}", errorMessage); + } + + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::Unload() +{ + Logger::info(L"Unload and terminate .exe"); + + m_hwndParent = NULL; + TerminateProcess(m_process, 0); + return S_OK; +} + +#pragma endregion + +#pragma region IPreviewHandlerVisuals + +IFACEMETHODIMP QoiPreviewHandler::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 QoiPreviewHandler::SetFont(const LOGFONTW* plf) +{ + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::SetTextColor(COLORREF color) +{ + return S_OK; +} + +#pragma endregion + +#pragma region IOleWindow + +IFACEMETHODIMP QoiPreviewHandler::GetWindow(HWND* phwnd) +{ + HRESULT hr = E_INVALIDARG; + if (phwnd) + { + *phwnd = m_hwndParent; + hr = S_OK; + } + return hr; +} + +IFACEMETHODIMP QoiPreviewHandler::ContextSensitiveHelp(BOOL fEnterMode) +{ + return E_NOTIMPL; +} + +#pragma endregion + +#pragma region IObjectWithSite + +IFACEMETHODIMP QoiPreviewHandler::SetSite(IUnknown* punkSite) +{ + if (m_punkSite) + { + m_punkSite->Release(); + m_punkSite = NULL; + } + return punkSite ? punkSite->QueryInterface(&m_punkSite) : S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::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/QoiPreviewHandlerCpp/QoiPreviewHandler.h b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.h new file mode 100644 index 0000000000..0534f7cd55 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.h @@ -0,0 +1,69 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include + +class QoiPreviewHandler : + 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); + + QoiPreviewHandler(); +protected: + ~QoiPreviewHandler(); + +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/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.rc b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.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/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj new file mode 100644 index 0000000000..28307d2d30 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj @@ -0,0 +1,121 @@ + + + + + 16.0 + Win32Proj + {3BAF9C81-A194-4925-A035-5E24A5D1E542} + QoiPreviewHandlerCpp + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + PowerToys.$(ProjectName) + + + + Level3 + true + _DEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + Windows + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + 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/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj.filters b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj.filters new file mode 100644 index 0000000000..5878befcdc --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.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/QoiPreviewHandlerCpp/dllmain.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/dllmain.cpp new file mode 100644 index 0000000000..444d42f1a9 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/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; + +// {729B72CD-B72E-4FE9-BCBF-E954B33FE699} +static const GUID CLSID_QoiPreviewHandler = { 0x729b72cd, 0xb72e, 0x4fe9, { 0xbc, 0xbf, 0xe9, 0x54, 0xb3, 0x3f, 0xe6, 0x99 } }; + +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_QoiPreviewHandler, 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/QoiPreviewHandlerCpp/packages.config b/src/modules/previewpane/QoiPreviewHandlerCpp/packages.config new file mode 100644 index 0000000000..47bae1882f --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/pch.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/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/QoiPreviewHandlerCpp/pch.h b/src/modules/previewpane/QoiPreviewHandlerCpp/pch.h new file mode 100644 index 0000000000..125ddcdf24 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/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/QoiPreviewHandlerCpp/resource.h b/src/modules/previewpane/QoiPreviewHandlerCpp/resource.h new file mode 100644 index 0000000000..7adebf4efe --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AlwaysOnTopModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Qoi Preview Handler Module" +#define INTERNAL_NAME "PowerToys.QoiPreviewHandlerCpp" +#define ORIGINAL_FILENAME "PowerToys.QoiPreviewHandlerCpp.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/previewpane/QoiThumbnailProvider/Program.cs b/src/modules/previewpane/QoiThumbnailProvider/Program.cs new file mode 100644 index 0000000000..7d0e898212 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProvider/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.Qoi +{ + internal static class Program + { + private static QoiThumbnailProvider _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 QoiThumbnailProvider(filePath); + Bitmap thumbnail = _thumbnailProvider.GetThumbnail(cx); + if (thumbnail != null) + { + filePath = filePath.Replace(".qoi", ".bmp"); + thumbnail.Save(filePath, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + else + { + MessageBox.Show("Qoi thumbnail - wrong number of args: " + args.Length.ToString(CultureInfo.InvariantCulture)); + } + } + } + } +} diff --git a/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs new file mode 100644 index 0000000000..b055a26d0b --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs @@ -0,0 +1,139 @@ +// 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 PreviewHandlerCommon.Utilities; + +namespace Microsoft.PowerToys.ThumbnailHandler.Qoi +{ + /// + /// Qoi Thumbnail Provider. + /// + public class QoiThumbnailProvider + { + public QoiThumbnailProvider(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; + + /// + /// Generate thumbnail bitmap for provided Qoi stream. + /// + /// The Stream instance for the Qoi bitmap. + /// The maximum thumbnail size, in pixels. + /// A thumbnail rendered from the Qoi bitmap. + public static Bitmap GetThumbnail(Stream stream, uint cx) + { + if (cx > MaxThumbnailSize || stream == null || stream.Length == 0) + { + return null; + } + + Bitmap thumbnail = null; + try + { + thumbnail = QoiImage.FromStream(stream); + } + 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 Qoi 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.GetConfiguredQoiThumbnailsEnabledValue() == global::PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + // GPO is disabling this utility. + return null; + } + + Bitmap thumbnail = GetThumbnail(this.Stream, cx); + if (thumbnail != null && thumbnail.Size.Width > 0 && thumbnail.Size.Height > 0) + { + return thumbnail; + } + + return null; + } + } +} diff --git a/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj new file mode 100644 index 0000000000..e78b84b355 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj @@ -0,0 +1,54 @@ + + + enable + true + {D949EC7D-48A9-4279-95D5-078E7FD1F048} + Microsoft.PowerToys.ThumbnailHandler.Qoi + PowerToys.QoiThumbnailProvider + PowerToys.QoiThumbnailProvider + PowerToys QoiPreviewHandler + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + true + PowerToys QoiPreviewHandler + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + true + + + + + win10-x64 + + + win10-arm64 + + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + $(NoWarn);1591 + WinExe + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp new file mode 100644 index 0000000000..1765e8d101 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp @@ -0,0 +1,84 @@ +#include "pch.h" +#include "ClassFactory.h" +#include "QoiThumbnailProvider.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; + + QoiThumbnailProvider* pExt = new (std::nothrow) QoiThumbnailProvider(); + 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/QoiThumbnailProviderCpp/ClassFactory.h b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.h new file mode 100644 index 0000000000..b393c3916e --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/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/QoiThumbnailProviderCpp/GlobalExportFunctions.def b/src/modules/previewpane/QoiThumbnailProviderCpp/GlobalExportFunctions.def new file mode 100644 index 0000000000..76fc66cac3 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/GlobalExportFunctions.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp new file mode 100644 index 0000000000..50781f5d29 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp @@ -0,0 +1,195 @@ +#include "pch.h" +#include "QoiThumbnailProvider.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +QoiThumbnailProvider::QoiThumbnailProvider() : + m_cRef(1), m_pStream(NULL), m_process(NULL) +{ + std::filesystem::path logFilePath(PTSettingsHelper::get_local_low_folder_location()); + logFilePath.append(LogSettings::qoiThumbLogPath); + Logger::init(LogSettings::qoiThumbLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + InterlockedIncrement(&g_cDllRef); +} + +QoiThumbnailProvider::~QoiThumbnailProvider() +{ + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +IFACEMETHODIMP QoiThumbnailProvider::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = { + QITABENT(QoiThumbnailProvider, IThumbnailProvider), + QITABENT(QoiThumbnailProvider, IInitializeWithStream), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) +QoiThumbnailProvider::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) +QoiThumbnailProvider::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +#pragma endregion + +#pragma region IInitializationWithStream + +IFACEMETHODIMP QoiThumbnailProvider::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 QoiThumbnailProvider::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"\\QoiThumbnail-Temp\\"; + if (!std::filesystem::exists(filePath)) + { + std::filesystem::create_directories(filePath); + } + + std::wstring fileName = filePath + guid + L".qoi"; + + // 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 QoiThumbnailProvider.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.QoiThumbnailProvider.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 QoiThumbnailProvider.exe. Error: {}", errorMessage); + } + } + } + + + return S_OK; +} + + +#pragma endregion + +#pragma region Helper Functions + +#pragma endregion diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h new file mode 100644 index 0000000000..ad959d707b --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h @@ -0,0 +1,37 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include + +class QoiThumbnailProvider : + 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); + + QoiThumbnailProvider(); +protected: + ~QoiThumbnailProvider(); + +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/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.rc b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.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/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj new file mode 100644 index 0000000000..2167c7cecf --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj @@ -0,0 +1,120 @@ + + + + + 16.0 + Win32Proj + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7} + QoiThumbnailProviderCpp + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + PowerToys.$(ProjectName) + + + + Level3 + true + _DEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + Windows + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + 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/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj.filters b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj.filters new file mode 100644 index 0000000000..7ff177923e --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.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/QoiThumbnailProviderCpp/dllmain.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/dllmain.cpp new file mode 100644 index 0000000000..c39cf8fb7f --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/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; + +// {AD856B15-D25E-4008-AFB7-AFAA55586188} +static const GUID CLSID_QoiThumbnailProvider = { 0xad856b15, 0xd25e, 0x4008, { 0xaf, 0xb7, 0xaf, 0xaa, 0x55, 0x58, 0x61, 0x88 } }; + +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_QoiThumbnailProvider, 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/QoiThumbnailProviderCpp/packages.config b/src/modules/previewpane/QoiThumbnailProviderCpp/packages.config new file mode 100644 index 0000000000..e11b462529 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/pch.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/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/QoiThumbnailProviderCpp/pch.h b/src/modules/previewpane/QoiThumbnailProviderCpp/pch.h new file mode 100644 index 0000000000..8a0d004247 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/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/QoiThumbnailProviderCpp/resource.h b/src/modules/previewpane/QoiThumbnailProviderCpp/resource.h new file mode 100644 index 0000000000..e6392a085f --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AlwaysOnTopModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Qoi Thumbnail Provider Module" +#define INTERNAL_NAME "PowerToys.QoiThumbnailProviderCpp" +#define ORIGINAL_FILENAME "PowerToys.QoiThumbnailProviderCpp.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi b/src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi new file mode 100644 index 0000000000000000000000000000000000000000..90eef44febf90aaa1628194d75ba8802d5604c5d GIT binary patch literal 16488 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 literal 0 HcmV?d00001 diff --git a/src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs b/src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs new file mode 100644 index 0000000000..417e739a05 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; +using Microsoft.PowerToys.PreviewHandler.Qoi; +using Microsoft.PowerToys.STATestExtension; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace QoiPreviewHandlerUnitTests +{ + [STATestClass] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")] + public class QoiPreviewHandlerTest + { + [TestMethod] + public void QoiPreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled() + { + // Arrange + using (var qoiPreviewHandlerControl = new QoiPreviewHandlerControl()) + { + // Act + var file = File.ReadAllBytes("HelperFiles/sample.qoi"); + + qoiPreviewHandlerControl.DoPreview(GetMockStream(file)); + + var flowLayoutPanel = qoiPreviewHandlerControl.Controls[0] as FlowLayoutPanel; + + // Assert + Assert.AreEqual(1, qoiPreviewHandlerControl.Controls.Count); + } + } + + [TestMethod] + public void QoiPreviewHandlerControlShouldAddValidInfoBarIfQoiPreviewThrows() + { + // Arrange + using (var qoiPreviewHandlerControl = new QoiPreviewHandlerControl()) + { + var mockStream = new Mock(); + mockStream + .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(new Exception()); + + // Act + qoiPreviewHandlerControl.DoPreview(mockStream.Object); + var textBox = qoiPreviewHandlerControl.Controls[0] as RichTextBox; + + // Assert + Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text)); + Assert.AreEqual(1, qoiPreviewHandlerControl.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-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj b/src/modules/previewpane/UnitTests-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj new file mode 100644 index 0000000000..6f48cfca68 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj @@ -0,0 +1,47 @@ + + + UnitTests-QoiPreviewHandler + PowerToys UnitTests-QoiPreviewHandler + UnitTests-QoiPreviewHandler + PowerToys UnitTests-QoiPreviewHandler + + + + {3940AD4D-F748-4BE4-9083-85769CD553EF} + PdfPreviewHandlerUnitTests + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + {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-QoiThumbnailProvider/HelperFiles/sample.qoi b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/HelperFiles/sample.qoi new file mode 100644 index 0000000000000000000000000000000000000000..90eef44febf90aaa1628194d75ba8802d5604c5d GIT binary patch literal 16488 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 literal 0 HcmV?d00001 diff --git a/src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs new file mode 100644 index 0000000000..bb1516d8d1 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Drawing; +using Microsoft.PowerToys.STATestExtension; +using Microsoft.PowerToys.ThumbnailHandler.Qoi; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace QoiThumbnailProviderUnitTests +{ + [STATestClass] + public class QoiThumbnailProviderTests + { + [TestMethod] + public void GetThumbnailValidStreamQoi() + { + // Act + var filePath = "HelperFiles/sample.qoi"; + + QoiThumbnailProvider provider = new QoiThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(256); + + Assert.IsTrue(bitmap != null); + } + + [TestMethod] + public void GetThumbnailInValidSizeQoi() + { + // Act + var filePath = "HelperFiles/sample.qoi"; + + QoiThumbnailProvider provider = new QoiThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(0); + + Assert.IsTrue(bitmap == null); + } + + [TestMethod] + public void GetThumbnailToBigQoi() + { + // Act + var filePath = "HelperFiles/sample.qoi"; + + QoiThumbnailProvider provider = new QoiThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(10001); + + Assert.IsTrue(bitmap == null); + } + + [TestMethod] + public void CheckNoQoiNullStringShouldReturnNullBitmap() + { + Bitmap thumbnail = QoiThumbnailProvider.GetThumbnail(null, 256); + Assert.IsTrue(thumbnail == null); + } + } +} diff --git a/src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj new file mode 100644 index 0000000000..6bdeabad59 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj @@ -0,0 +1,48 @@ + + + UnitTests-QoiThumbnailProvider + PowerToys UnitTests-QoiThumbnailProvider + UnitTests-QoiThumbnailProvider + PowerToys UnitTests-QoiThumbnailProvider + + + + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} + QoiThumbnailProviderUnitTests + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + {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 a33d9709f2..0c75ed23f4 100644 --- a/src/modules/previewpane/powerpreview/CLSID.h +++ b/src/modules/previewpane/powerpreview/CLSID.h @@ -37,17 +37,27 @@ 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 } }; +// F498BE36-5C94-4EC9-A65A-AD1CF4C38271 +const GUID CLSID_SHIMActivateQoiPreviewHandler = { 0xf498be36, 0x5c94, 0x4ec9, { 0xa6, 0x5a, 0xad, 0x1c, 0xf4, 0xc3, 0x82, 0x71 } }; + +// 8AA07897-C30B-4543-865B-00A0E5A1B32D +const GUID CLSID_QoiPreviewHandler = { 0x8aa07897, 0xc30b, 0x4543, { 0x86, 0x5b, 0x0, 0xa0, 0xe5, 0xa1, 0xb3, 0x2d } }; + // BFEE99B4-B74D-4348-BCA5-E757029647FF const GUID CLSID_GcodeThumbnailProvider = { 0xbfee99b4, 0xb74d, 0x4348, { 0xbc, 0xa5, 0xe7, 0x57, 0x02, 0x96, 0x47, 0xff } }; // 8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF const GUID CLSID_StlThumbnailProvider = { 0x8bc8afc2, 0x4e7c, 0x4695, { 0x81, 0x8e, 0x8c, 0x1f, 0xfd, 0xce, 0xa2, 0xaf } }; +// 907B7E38-38ED-42E7-A276-9EF0ECABB003 +const GUID CLSID_QoiThumbnailProvider = { 0x907b7e38, 0x38ed, 0x42e7, { 0xa2, 0x76, 0x9e, 0xf0, 0xec, 0xab, 0xb0, 0x3 } }; + // Pairs of NativeClsid vs ManagedClsid used for preview handlers. const std::vector> NativeToManagedClsid({ { CLSID_SHIMActivateMdPreviewHandler, CLSID_MdPreviewHandler }, { CLSID_SHIMActivatePdfPreviewHandler, CLSID_PdfPreviewHandler }, { CLSID_SHIMActivateGcodePreviewHandler, CLSID_GcodePreviewHandler }, + { CLSID_SHIMActivateQoiPreviewHandler, CLSID_QoiPreviewHandler }, { CLSID_SHIMActivateSvgPreviewHandler, CLSID_SvgPreviewHandler }, { CLSID_SHIMActivateSvgThumbnailProvider, CLSID_SvgThumbnailProvider } }); diff --git a/src/modules/previewpane/powerpreview/Resources.resx b/src/modules/previewpane/powerpreview/Resources.resx index f21c3ba477..6703e476ef 100644 --- a/src/modules/previewpane/powerpreview/Resources.resx +++ b/src/modules/previewpane/powerpreview/Resources.resx @@ -192,4 +192,10 @@ Stl Thumbnail Provider + + Qoi Previewer + + + Qoi 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 eade6a3a43..1eef1f0398 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -71,6 +71,16 @@ PowerPreviewModule::PowerPreviewModule() : .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredStlThumbnailsEnabledValue, .registryChanges = getStlThumbnailHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"qoi-previewer-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_PREVPANE_QOI_SETTINGS_DESCRIPTION), + .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredQoiPreviewEnabledValue, + .registryChanges = getQoiPreviewHandlerChangeSet(installationDir, installPerUser) }); + + m_fileExplorerModules.push_back({ .settingName = L"qoi-thumbnail-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_QOI_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), + .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue, + .registryChanges = getQoiThumbnailHandlerChangeSet(installationDir, installPerUser) }); + try { PowerToysSettings::PowerToyValues settings = diff --git a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs index 1167325cce..d015f049c9 100644 --- a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs +++ b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs @@ -221,6 +221,40 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("stl-thumbnail-color-setting")] public StringProperty StlThumbnailColor { get; set; } + private bool enableQoiPreview = true; + + [JsonPropertyName("qoi-previewer-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableQoiPreview + { + get => enableQoiPreview; + set + { + if (value != enableQoiPreview) + { + LogTelemetryEvent(value); + enableQoiPreview = value; + } + } + } + + private bool enableQoiThumbnail = true; + + [JsonPropertyName("qoi-thumbnail-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableQoiThumbnail + { + get => enableQoiThumbnail; + set + { + if (value != enableQoiThumbnail) + { + LogTelemetryEvent(value); + enableQoiThumbnail = value; + } + } + } + public PowerPreviewProperties() { SvgBackgroundColorMode = new IntProperty(DefaultSvgBackgroundColorMode); diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs index 07869ede8e..f70357293e 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs @@ -60,11 +60,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.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.EnableStlThumbnail, viewModel.STLThumbnailIsEnabled); + Assert.AreEqual(originalSettings.Properties.EnableQoiThumbnail, viewModel.QOIThumbnailIsEnabled); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -161,6 +163,24 @@ namespace ViewModelTests viewModel.STLThumbnailIsEnabled = true; } + [TestMethod] + public void QOIThumbnailIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableQoiThumbnail); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.QOIThumbnailIsEnabled = true; + } + [TestMethod] public void MDRenderIsEnabledShouldPrevHandlerWhenSuccessful() { @@ -232,5 +252,23 @@ namespace ViewModelTests // act viewModel.GCODERenderIsEnabled = true; } + + [TestMethod] + public void QOIRenderIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableQoiPreview); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.QOIRenderIsEnabled = true; + } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml index 2596a8991c..1ffb2ae1e8 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml @@ -131,6 +131,18 @@ IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.IsGCODERenderEnabledGpoConfigured}" IsTabStop="{x:Bind Mode=OneWay, Path=ViewModel.IsGCODERenderEnabledGpoConfigured}" Severity="Informational" /> + + + + @@ -201,6 +213,19 @@ IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.IsSTLThumbnailEnabledGpoConfigured}" IsTabStop="{x:Bind Mode=OneWay, Path=ViewModel.IsSTLThumbnailEnabledGpoConfigured}" Severity="Informational" /> + + + + + @@ -210,7 +235,7 @@ - + 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 5521174208..2983a5aa05 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -1135,6 +1135,21 @@ Color + + Quite Ok Image + File type, do not translate + + + .qoi + + + Quite OK Image + File type, do not translate + + + .qoi + File extension, should not be altered + Portable Document Format File type, do not translate diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs index be013954a7..d5a190c554 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerPreviewViewModel.cs @@ -129,6 +129,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _gcodeRenderIsEnabled = Settings.Properties.EnableGcodePreview; } + _qoiRenderEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredQoiPreviewEnabledValue(); + if (_qoiRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _qoiRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) + { + // Get the enabled state from GPO. + _qoiRenderEnabledStateIsGPOConfigured = true; + _qoiRenderIsEnabled = _qoiRenderEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + } + else + { + _qoiRenderIsEnabled = Settings.Properties.EnableQoiPreview; + } + _pdfThumbnailEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredPdfThumbnailsEnabledValue(); if (_pdfThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _pdfThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) { @@ -166,6 +178,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } _stlThumbnailColor = Settings.Properties.StlThumbnailColor.Value; + + _qoiThumbnailEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredQoiThumbnailsEnabledValue(); + if (_qoiThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _qoiThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) + { + // Get the enabled state from GPO. + _qoiThumbnailEnabledStateIsGPOConfigured = true; + _qoiThumbnailIsEnabled = _qoiThumbnailEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; + } + else + { + _qoiThumbnailIsEnabled = Settings.Properties.EnableQoiThumbnail; + } } private GpoRuleConfigured _svgRenderEnabledGpoRuleConfiguration; @@ -194,6 +218,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _gcodeRenderEnabledStateIsGPOConfigured; private bool _gcodeRenderIsEnabled; + private GpoRuleConfigured _qoiRenderEnabledGpoRuleConfiguration; + private bool _qoiRenderEnabledStateIsGPOConfigured; + private bool _qoiRenderIsEnabled; + private GpoRuleConfigured _svgThumbnailEnabledGpoRuleConfiguration; private bool _svgThumbnailEnabledStateIsGPOConfigured; private bool _svgThumbnailIsEnabled; @@ -211,6 +239,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _stlThumbnailIsEnabled; private string _stlThumbnailColor; + private GpoRuleConfigured _qoiThumbnailEnabledGpoRuleConfiguration; + private bool _qoiThumbnailEnabledStateIsGPOConfigured; + private bool _qoiThumbnailIsEnabled; + public bool SVGRenderIsEnabled { get @@ -616,6 +648,64 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool QOIRenderIsEnabled + { + get + { + return _qoiRenderIsEnabled; + } + + set + { + if (_qoiRenderEnabledStateIsGPOConfigured) + { + // If it's GPO configured, shouldn't be able to change this state. + return; + } + + if (value != _qoiRenderIsEnabled) + { + _qoiRenderIsEnabled = value; + Settings.Properties.EnableQoiPreview = value; + RaisePropertyChanged(); + } + } + } + + public bool IsQOIRenderEnabledGpoConfigured + { + get => _qoiRenderEnabledStateIsGPOConfigured; + } + + public bool QOIThumbnailIsEnabled + { + get + { + return _qoiThumbnailIsEnabled; + } + + set + { + if (_qoiThumbnailEnabledStateIsGPOConfigured) + { + // If it's GPO configured, shouldn't be able to change this state. + return; + } + + if (value != _qoiThumbnailIsEnabled) + { + _qoiThumbnailIsEnabled = value; + Settings.Properties.EnableQoiThumbnail = value; + RaisePropertyChanged(); + } + } + } + + public bool IsQOIThumbnailEnabledGpoConfigured + { + get => _qoiThumbnailEnabledStateIsGPOConfigured; + } + public string GetSettingsSubPath() { return _settingsConfigFileFolder + "\\" + ModuleName; diff --git a/tools/BugReportTool/BugReportTool/ProcessesList.cpp b/tools/BugReportTool/BugReportTool/ProcessesList.cpp index 340e8e7c02..4e1b1d8bfe 100644 --- a/tools/BugReportTool/BugReportTool/ProcessesList.cpp +++ b/tools/BugReportTool/BugReportTool/ProcessesList.cpp @@ -30,6 +30,8 @@ std::vector processes = L"PowerToys.MonacoPreviewHandler.exe", L"PowerToys.PdfPreviewHandler.exe", L"PowerToys.PdfThumbnailProvider.exe", + L"PowerToys.QoiPreviewHandler.exe", + L"PowerToys.QoiThumbnailProvider.exe", L"PowerToys.StlThumbnailProvider.exe", L"PowerToys.SvgPreviewHandler.exe", L"PowerToys.SvgThumbnailProvider.exe", diff --git a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp index dba32b9b2b..5fed9f4fa6 100644 --- a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp +++ b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp @@ -17,6 +17,7 @@ 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\\{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\\{8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF}" }, @@ -28,6 +29,8 @@ namespace { HKEY_CLASSES_ROOT, L".md\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }, { HKEY_CLASSES_ROOT, L".pdf\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }, { HKEY_CLASSES_ROOT, L".pdf\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" }, + { HKEY_CLASSES_ROOT, L".qoi\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }, + { HKEY_CLASSES_ROOT, L".qoi\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" }, { HKEY_CLASSES_ROOT, L".gcode\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }, { HKEY_CLASSES_ROOT, L".gcode\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" }, { HKEY_CLASSES_ROOT, L".stl\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" } @@ -38,6 +41,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"{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 a776af32d4..5ca46b1b26 100644 --- a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp +++ b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp @@ -64,5 +64,7 @@ void ReportGPOValues(const std::filesystem::path& tmpDir) report << "getSuspendNewUpdateToastValue: " << gpo_rule_configured_to_string(powertoys_gpo::getSuspendNewUpdateToastValue()) << std::endl; report << "getDisablePeriodicUpdateCheckValue: " << gpo_rule_configured_to_string(powertoys_gpo::getDisablePeriodicUpdateCheckValue()) << std::endl; report << "getAllowExperimentationValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowExperimentationValue()) << std::endl; + report << "getConfiguredQoiPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredQoiPreviewEnabledValue()) << std::endl; + report << "getConfiguredQoiThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue()) << std::endl; } diff --git a/tools/Verification scripts/Check preview handler registration.ps1 b/tools/Verification scripts/Check preview handler registration.ps1 index 8790acd1ce..e2b5eee30e 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") + [String[]]$TypesToCheck = @(".markdown", ".mdtext", ".mdtxt", ".mdown", ".mkdn", ".mdwn", ".mkd", ".md", ".svg", ".svgz", ".pdf", ".gcode", ".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" + '{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" '{DC6EFB56-9CFA-464D-8880-44885D7DC193}' = "Adobe Acrobat DC"