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"