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 0000000000..90eef44feb Binary files /dev/null and b/src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi differ 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 0000000000..90eef44feb Binary files /dev/null and b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/HelperFiles/sample.qoi differ 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"