Enable PDF files in preview pane (#9088)

## Summary of the Pull Request
This PR enables user to preview PDF files in the Explorer preview pane
and in Outlook. 

**What is this about:**
Windows does not support out of the box experience for previewing PDF
files in the preview pane. Users need to install third-party software
like Adobe Acrobat reader. The PdfPreviewHandler module enbales the user
to preview PDF files.

**How does someone test / validate:** 
Run the installer, open Explorer and select a PDF file, enable the
preview pane. Maybe need to remove third-party PDF software.

## Quality Checklist

- [X] **Linked issue:** #3548
- [ ] **Communication:** I've discussed this with core contributors in the issue. 
- [X] **Tests:** Added/updated and all pass
- [X] **Installer:** Added/updated and all pass
- [X] **Localization:** All end user facing strings can be localized
- [ ] **Docs:** Added/ updated
- [x] **Binaries:** Any new files are added to WXS / YML
   - [ ] No new binaries
   - [x] YML for signing
   - [x] WXS for installer
This commit is contained in:
R. de Veen 2021-08-26 23:43:26 +02:00 committed by GitHub
parent da46b90457
commit 4177708e49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1073 additions and 14 deletions

View File

@ -1527,6 +1527,7 @@ PCWSTR
pdb
pdbonly
pdf
pdfpreviewhandler
pdo
pdto
pdtobj

View File

@ -122,6 +122,8 @@ steps:
testAssemblyVer2: |
**\UnitTests-SvgThumbnailProvider.dll
**\Microsoft.PowerToys.Settings.UI.UnitTests.dll
**\UnitTests-MarkdownPreviewHandler.dll
**\UnitTests-PdfPreviewHandler.dll
**\UnitTests-SvgPreviewHandler.dll
**\UnitTests-PreviewHandlerCommon.dll
**\PreviewPaneUnitTests.dll

View File

@ -108,6 +108,8 @@ build:
- 'modules\FileExplorerPreview\ManagedTelemetry.dll'
- 'modules\FileExplorerPreview\MarkdownPreviewHandler.dll'
- 'modules\FileExplorerPreview\MarkdownPreviewHandler.comhost.dll'
- 'modules\FileExplorerPreview\PdfPreviewHandler.dll'
- 'modules\FileExplorerPreview\PdfPreviewHandler.comhost.dll'
- 'modules\FileExplorerPreview\powerpreview.dll'
- 'modules\FileExplorerPreview\PreviewHandlerCommon.dll'
- 'modules\FileExplorerPreview\SvgPreviewHandler.dll'

View File

@ -166,7 +166,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PreviewHandlerCommon", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkdownPreviewHandler", "src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj", "{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-MarkdownPreviewHandler", "src\modules\previewpane\PreviewPaneUnitTests\UnitTests-MarkdownPreviewHandler.csproj", "{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-MarkdownPreviewHandler", "src\modules\previewpane\UnitTests-MarkdownPreviewHandler\UnitTests-MarkdownPreviewHandler.csproj", "{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SvgPreviewHandler", "src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj", "{DA425894-6E13-404F-8DCB-78584EC0557A}"
EndProject
@ -305,6 +305,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Common.UI", "src\common\Microsoft.PowerToys.Common.UI\Microsoft.PowerToys.Common.UI.csproj", "{C3A17DCA-217B-462C-BB0C-BE086AF80081}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfPreviewHandler", "src\modules\previewpane\PdfPreviewHandler\PdfPreviewHandler.csproj", "{69E1EE8D-143A-4060-9129-4658ACF14AAF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-PdfPreviewHandler", "src\modules\previewpane\UnitTests-PdfPreviewHandler\UnitTests-PdfPreviewHandler.csproj", "{ECC20689-002A-4354-95A6-B58DF089C6FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Registry", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\Microsoft.PowerToys.Run.Plugin.Registry.csproj", "{4BABF3FE-3451-42FD-873F-3C332E18DCEF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Registry.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry.UnitTest\Microsoft.PowerToys.Run.Plugin.Registry.UnitTests.csproj", "{0648DF05-5DDA-4BE1-B5F2-584926EBDB65}"
@ -796,6 +800,18 @@ Global
{C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.ActiveCfg = Release|x64
{C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.Build.0 = Release|x64
{C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x86.ActiveCfg = Release|x64
{69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|x64.ActiveCfg = Debug|x64
{69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|x64.Build.0 = Debug|x64
{69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|x86.ActiveCfg = Debug|x64
{69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|x64.ActiveCfg = Release|x64
{69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|x64.Build.0 = Release|x64
{69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|x86.ActiveCfg = Release|x64
{ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|x64.ActiveCfg = Debug|x64
{ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|x64.Build.0 = Debug|x64
{ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|x86.ActiveCfg = Debug|x64
{ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|x64.ActiveCfg = Release|x64
{ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|x64.Build.0 = Release|x64
{ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|x86.ActiveCfg = Release|x64
{4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.ActiveCfg = Debug|x64
{4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.Build.0 = Debug|x64
{4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x86.ActiveCfg = Debug|x64
@ -1019,6 +1035,8 @@ Global
{B39DC643-4663-475E-B329-03F0C9918D48} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{8F62026A-294B-41C6-8839-87463613F216} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{C3A17DCA-217B-462C-BB0C-BE086AF80081} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{69E1EE8D-143A-4060-9129-4658ACF14AAF} = {2F305555-C296-497E-AC20-5FA1B237996A}
{ECC20689-002A-4354-95A6-B58DF089C6FF} = {2F305555-C296-497E-AC20-5FA1B237996A}
{4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{0648DF05-5DDA-4BE1-B5F2-584926EBDB65} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{6ED2F4FC-E122-4CEE-90F1-97E4CCC8BC7A} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}

View File

@ -79,11 +79,20 @@
<desktop2:DesktopPreviewHandler Clsid="74619BDA-A66B-451D-864C-A7726F5FE650"/>
</uap3:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap3:FileTypeAssociation Name="pdfpreviewhandler" desktop2:AllowSilentDefaultTakeOver="true">
<uap:SupportedFileTypes>
<uap:FileType>.pdf</uap:FileType>
</uap:SupportedFileTypes>
<desktop2:DesktopPreviewHandler Clsid="4F6D533B-4185-43A6-AD75-9B20034B14CA"/>
</uap3:FileTypeAssociation>
</uap:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Preview Handler" AppId="E39A92FE-D89A-417B-9B9D-F0B6BD564B36" SystemSurrogate="PreviewHost">
<com:Class Id="74619BDA-A66B-451D-864C-A7726F5FE650" Path="modules\powerpreview.dll" ThreadingModel="Both"/>
<com:Class Id="E0907A95-6F9A-4D1B-A97A-7D9D2648881E" Path="modules\powerpreview.dll" ThreadingModel="Both"/>
<com:Class Id="4F6D533B-4185-43A6-AD75-9B20034B14CA" Path="modules\powerpreview.dll" ThreadingModel="Both"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>

View File

@ -239,7 +239,6 @@
</Directory>
</Directory>
<!-- KBM -->
<Directory Id="KeyboardManagerInstallFolder" Name="$(var.KeyboardManagerProjectName)">
<Directory Id="KeyboardManagerEditorInstallFolder" Name="KeyboardManagerEditor" />
@ -499,6 +498,19 @@
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Assembly" Value="MarkdownPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Class" Value="Microsoft.PowerToys.PreviewHandler.Markdown.MarkdownPreviewHandler" />
</RegistryKey>
<!-- Registry Key for Class Registration of Pdf Preview Handler -->
<RegistryKey Root="HKCR" Key="CLSID\{07665729-6243-4746-95b7-79579308d1b2}">
<RegistryValue Type="string" Value="Microsoft.PowerToys.PreviewHandler.Pdf.PdfPreviewHandler" />
<RegistryValue Type="string" Name="DisplayName" Value="Pdf Preview Handler" />
<RegistryValue Type="string" Name="AppID" Value="{CF142243-F059-45AF-8842-DBBE9783DB14}" />
<RegistryValue Type="string" Key="Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[FileExplorerPreviewInstallFolder]PdfPreviewHandler.comhost.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="Assembly" Value="PdfPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32" Name="Class" Value="Microsoft.PowerToys.PreviewHandler.Pdf.PdfPreviewHandler" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Both" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Assembly" Value="PdfPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Class" Value="Microsoft.PowerToys.PreviewHandler.Pdf.PdfPreviewHandler" />
</RegistryKey>
<!-- Registry Key for AppID registration -->
<RegistryKey Root="HKCR" Key="AppID\{CF142243-F059-45AF-8842-DBBE9783DB14}">
<RegistryValue Type="expandable" Name="DllSurrogate" Value="%SystemRoot%\system32\prevhost.exe" />
@ -511,6 +523,10 @@
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\PreviewHandlers">
<RegistryValue Type="string" Name="{45769bcc-e8fd-42d0-947e-02beef77a1f5}" Value="Markdown Preview Handler" />
</RegistryKey>
<!-- Add Pdf preview handler to preview handlers list -->
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\PreviewHandlers">
<RegistryValue Type="string" Name="{07665729-6243-4746-95b7-79579308d1b2}" Value="Pdf Preview Handler" />
</RegistryKey>
<!-- Add file type association for Svg Preview Handler -->
<RegistryKey Root="HKCR" Key=".svg\shellex">
<RegistryValue Type="string" Key="{8895b1c6-b41f-4c1c-a562-0d564250836f}" Value="{ddee2b8a-6807-48a6-bb20-2338174ff779}" />
@ -523,6 +539,10 @@
<RegistryKey Root="HKCR" Key=".md\shellex">
<RegistryValue Type="string" Key="{8895b1c6-b41f-4c1c-a562-0d564250836f}" Value="{45769bcc-e8fd-42d0-947e-02beef77a1f5}" />
</RegistryKey>
<!-- Add file type association for Pdf Preview Handler -->
<RegistryKey Root="HKCR" Key=".pdf\shellex">
<RegistryValue Type="string" Key="{8895b1c6-b41f-4c1c-a562-0d564250836f}" Value="{07665729-6243-4746-95b7-79579308d1b2}" />
</RegistryKey>
<!-- Update Key to use IE11 for prevhost.exe -->
<RegistryKey Root="HKLM" Key="Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION">
<RegistryValue Type="integer" Name="prevhost.exe" Value="11000" />
@ -784,6 +804,11 @@
<File Source="$(var.BinX64Dir)modules\FileExplorerPreview\Markdig.Signed.dll" />
<File Source="$(var.BinX64Dir)modules\FileExplorerPreview\HtmlAgilityPack.dll" />
<File Id="FileExplorerPreview_System.IO.Abstractions.dll" Source="$(var.BinX64Dir)modules\FileExplorerPreview\System.IO.Abstractions.dll" />
<!-- File to include dll for Pdf Preview Handler -->
<File Source="$(var.BinX64Dir)modules\FileExplorerPreview\PdfPreviewHandler.dll" />
<File Source="$(var.BinX64Dir)modules\FileExplorerPreview\PdfPreviewHandler.comhost.dll" />
<File Source="$(var.BinX64Dir)modules\FileExplorerPreview\PdfPreviewHandler.runtimeconfig.json" />
<File Source="$(var.BinX64Dir)modules\FileExplorerPreview\PdfPreviewHandler.deps.json" />
</Component>
</DirectoryRef>
@ -1022,7 +1047,10 @@
<File Id="MarkdownPreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\FileExplorerPreview\$(var.Language)\MarkdownPreviewHandler.resources.dll" />
</Component>
<Component Id="SVGPreviewHandler_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)FileExplorerPreviewInstallFolder">
<File Id="SVGPreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\FileExplorerPreview\$(var.Language)\SvgPreviewHandler.resources.dll" />
<File Id="SVGPreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\FileExplorerPreview\$(var.Language)\SvgPreviewHandler.resources.dll" />
</Component>
<Component Id="PDFPreviewHandler_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)FileExplorerPreviewInstallFolder">
<File Id="PDFPreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\FileExplorerPreview\$(var.Language)\PdfPreviewHandler.resources.dll" />
</Component>
<!-- PowerToys Run aka Launcher plugin resources -->
<Component Id="Launcher_Calculator_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)CalculatorPluginFolder">

View File

@ -0,0 +1,14 @@
{
"Projects": [
{
"LanguageSet": "Azure_Languages",
"LocItems": [
{
"SourceFile": "src\\modules\\previewpane\\PdfPreviewHandler\\Properties\\Resources.resx",
"CopyOption": "LangIDOnName",
"OutputPath": "src\\modules\\previewpane\\PdfPreviewHandler\\Properties"
}
]
}
]
}

View File

@ -0,0 +1,73 @@
// 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.Runtime.InteropServices;
using Common;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.PowerToys.PreviewHandler.Pdf
{
/// <summary>
/// Implementation of preview handler for pdf files.
/// </summary>
[Guid("07665729-6243-4746-95b7-79579308d1b2")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class PdfPreviewHandler : StreamBasedPreviewHandler, IDisposable
{
private PdfPreviewHandlerControl _pdfPreviewHandlerControl;
private bool _disposedValue;
/// <summary>
/// Initializes a new instance of the <see cref="PdfPreviewHandler"/> class.
/// </summary>
public PdfPreviewHandler()
{
Initialize();
}
/// <inheritdoc />
public override void DoPreview()
{
_pdfPreviewHandlerControl.DoPreview(Stream);
}
/// <inheritdoc />
protected override IPreviewHandlerControl CreatePreviewHandlerControl()
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.Events.PdfFileHandlerLoaded());
_pdfPreviewHandlerControl = new PdfPreviewHandlerControl();
return _pdfPreviewHandlerControl;
}
/// <summary>
/// Disposes objects
/// </summary>
/// <param name="disposing">Is Disposing</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_pdfPreviewHandlerControl.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposedValue = true;
}
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,72 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<Platforms>x64</Platforms>
<UseWindowsForms>true</UseWindowsForms>
<AssemblyTitle>PdfPreviewHandler</AssemblyTitle>
<AssemblyDescription>PowerToys PdfPreviewHandler</AssemblyDescription>
<AssemblyCompany>Microsoft Corp.</AssemblyCompany>
<AssemblyCopyright>Copyright (C) 2020 Microsoft Corporation</AssemblyCopyright>
<AssemblyProduct>PowerToys</AssemblyProduct>
<AssemblyTitle>PdfPreviewHandler</AssemblyTitle>
<Company>Microsoft Corp.</Company>
<Product>PowerToys</Product>
<NeutralLanguage>en-US</NeutralLanguage>
<Description>PowerToys PdfPreviewHandler</Description>
<Copyright>Copyright (C) 2020 Microsoft Corporation</Copyright>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>$(SolutionDir)$(Platform)\$(Configuration)\modules\FileExplorerPreview\PdfPreviewPaneDocumentation.xml</DocumentationFile>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\FileExplorerPreview\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup>
<ProjectGuid>{69E1EE8D-143A-4060-9129-4658ACF14AAF}</ProjectGuid>
<RootNamespace>Microsoft.PowerToys.PreviewHandler.Pdf</RootNamespace>
<TargetFramework>netcoreapp3.1</TargetFramework>
<EnableComHosting>true</EnableComHosting>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
</PropertyGroup>
<Import Project="..\..\..\Version.props" />
<ItemGroup>
<Compile Include="..\..\..\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
<Compile Update="PdfPreviewHandlerControl.cs" />
<Compile Update="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.IO.Abstractions" Version="12.2.5" />
<PackageReference Include="System.Runtime.WindowsRuntime" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\common\PreviewHandlerCommon.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Windows">
<HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\10.0.17134.0\Windows.winmd</HintPath>
<IsWinMDFile>true</IsWinMDFile>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,299 @@
// 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.ComTypes;
using System.Windows.Forms;
using Common;
using Common.Utilities;
using Microsoft.PowerToys.PreviewHandler.Pdf.Properties;
using Microsoft.PowerToys.PreviewHandler.Pdf.Telemetry.Events;
using Microsoft.PowerToys.Telemetry;
using Windows.Data.Pdf;
using Windows.Storage.Streams;
using Windows.UI.ViewManagement;
namespace Microsoft.PowerToys.PreviewHandler.Pdf
{
/// <summary>
/// Win Form Implementation for Pdf Preview Handler.
/// </summary>
public class PdfPreviewHandlerControl : FormHandlerControl
{
/// <summary>
/// RichTextBox control to display error message.
/// </summary>
private RichTextBox _infoBar;
/// <summary>
/// FlowLayoutPanel control to display the image of the pdf.
/// </summary>
private FlowLayoutPanel _flowLayoutPanel;
/// <summary>
/// Use UISettings to get system colors and scroll bar size.
/// </summary>
private static UISettings _uISettings = new UISettings();
/// <summary>
/// Initializes a new instance of the <see cref="PdfPreviewHandlerControl"/> class.
/// </summary>
public PdfPreviewHandlerControl()
{
SetBackgroundColor(GetBackgroundColor());
}
/// <summary>
/// Start the preview on the Control.
/// </summary>
/// <param name="dataSource">Stream reference to access source file.</param>
public override void DoPreview<T>(T dataSource)
{
this.SuspendLayout();
try
{
using (var dataStream = new ReadonlyStream(dataSource as IStream))
{
var memStream = new MemoryStream();
dataStream.CopyTo(memStream);
memStream.Position = 0;
try
{
// AsRandomAccessStream() extension method from System.Runtime.WindowsRuntime
var pdf = PdfDocument.LoadFromStreamAsync(memStream.AsRandomAccessStream()).GetAwaiter().GetResult();
if (pdf.PageCount > 0)
{
InvokeOnControlThread(() =>
{
_flowLayoutPanel = new FlowLayoutPanel
{
AutoScroll = true,
AutoSize = true,
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown,
WrapContents = false,
};
_flowLayoutPanel.Resize += FlowLayoutPanel_Resize;
// Only show first 10 pages.
for (uint i = 0; i < pdf.PageCount && i < 10; i++)
{
using (var page = pdf.GetPage(i))
{
var image = PageToImage(page);
var picturePanel = new Panel()
{
Name = "picturePanel",
Margin = new Padding(6, 6, 6, 0),
Size = CalculateSize(image),
BorderStyle = BorderStyle.FixedSingle,
};
var picture = new PictureBox
{
Dock = DockStyle.Fill,
Image = image,
SizeMode = PictureBoxSizeMode.Zoom,
};
picturePanel.Controls.Add(picture);
_flowLayoutPanel.Controls.Add(picturePanel);
}
}
if (pdf.PageCount > 10)
{
var messageBox = new RichTextBox
{
Name = "messageBox",
Text = Resources.PdfMorePagesMessage,
BackColor = Color.LightYellow,
Dock = DockStyle.Fill,
Multiline = true,
ReadOnly = true,
ScrollBars = RichTextBoxScrollBars.None,
BorderStyle = BorderStyle.None,
};
messageBox.ContentsResized += RTBContentsResized;
_flowLayoutPanel.Controls.Add(messageBox);
}
Controls.Add(_flowLayoutPanel);
});
}
}
#pragma warning disable CA1031 // Password protected files throws an generic Exception
catch (Exception ex)
#pragma warning restore CA1031
{
if (ex.Message.Contains("Unable to update the password. The value provided as the current password is incorrect.", StringComparison.Ordinal))
{
InvokeOnControlThread(() =>
{
Controls.Clear();
_infoBar = GetTextBoxControl(Resources.PdfPasswordProtectedError);
Controls.Add(_infoBar);
});
}
else
{
throw;
}
}
finally
{
memStream.Dispose();
}
}
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewed());
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewError { Message = ex.Message });
InvokeOnControlThread(() =>
{
Controls.Clear();
_infoBar = GetTextBoxControl(Resources.PdfNotPreviewedError);
Controls.Add(_infoBar);
});
}
finally
{
base.DoPreview(dataSource);
}
this.ResumeLayout(false);
this.PerformLayout();
}
/// <summary>
/// Resize the Panels on FlowLayoutPanel resize based on the size of the image.
/// </summary>
/// <param name="sender">sender (not used)</param>
/// <param name="e">args (not used)</param>
private void FlowLayoutPanel_Resize(object sender, EventArgs e)
{
this.SuspendLayout();
_flowLayoutPanel.SuspendLayout();
foreach (Panel panel in _flowLayoutPanel.Controls.Find("picturePanel", false))
{
var pictureBox = panel.Controls[0] as PictureBox;
var image = pictureBox.Image;
panel.Size = CalculateSize(image);
}
_flowLayoutPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
/// <summary>
/// Transform the PdfPage to an Image.
/// </summary>
/// <param name="page">The page to transform to an Image.</param>
/// <returns>An object of type <see cref="Image"/></returns>
private Image PageToImage(PdfPage page)
{
Image imageOfPage;
using (var stream = new InMemoryRandomAccessStream())
{
page.RenderToStreamAsync(stream, new PdfPageRenderOptions()
{
DestinationWidth = (uint)this.ClientSize.Width,
}).GetAwaiter().GetResult();
imageOfPage = Image.FromStream(stream.AsStream());
}
return imageOfPage;
}
/// <summary>
/// Calculate the size of the control based on the size of the image/pdf page.
/// </summary>
/// <param name="pdfImage">Image of pdf page.</param>
/// <returns>New size off the panel.</returns>
private Size CalculateSize(Image pdfImage)
{
var hasScrollBar = _flowLayoutPanel.VerticalScroll.Visible;
// Add 12px margin to the image by making it 12px smaller.
int width = this.ClientSize.Width - 12;
// If the vertical scroll bar is visible, make the image smaller.
var scrollBarSizeWidth = (int)_uISettings.ScrollBarSize.Width;
if (hasScrollBar && width > scrollBarSizeWidth)
{
width -= scrollBarSizeWidth;
}
int originalWidth = pdfImage.Width;
int originalHeight = pdfImage.Height;
float percentWidth = (float)width / originalWidth;
int newHeight = (int)(originalHeight * percentWidth);
return new Size(width, newHeight);
}
/// <summary>
/// Get the system background color, based on the selected theme.
/// </summary>
/// <returns>An object of type <see cref="Color"/>.</returns>
private static Color GetBackgroundColor()
{
var systemBackgroundColor = _uISettings.GetColorValue(UIColorType.Background);
return Color.FromArgb(systemBackgroundColor.A, systemBackgroundColor.R, systemBackgroundColor.G, systemBackgroundColor.B);
}
/// <summary>
/// Gets a textbox control.
/// </summary>
/// <param name="message">Message to be displayed in textbox.</param>
/// <returns>An object of type <see cref="RichTextBox"/>.</returns>
private RichTextBox GetTextBoxControl(string message)
{
var textBox = new RichTextBox
{
Text = message,
BackColor = Color.LightYellow,
Multiline = true,
Dock = DockStyle.Top,
ReadOnly = true,
ScrollBars = RichTextBoxScrollBars.None,
BorderStyle = BorderStyle.None,
};
textBox.ContentsResized += RTBContentsResized;
return textBox;
}
/// <summary>
/// Callback when RichTextBox is resized.
/// </summary>
/// <param name="sender">Reference to resized control.</param>
/// <param name="e">Provides data for the resize event.</param>
private void RTBContentsResized(object sender, ContentsResizedEventArgs e)
{
var richTextBox = (RichTextBox)sender;
// Add 5px extra height to the textbox.
richTextBox.Height = e.NewRectangle.Height + 5;
}
}
}

View File

@ -0,0 +1,104 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.PowerToys.PreviewHandler.Pdf.Properties
{
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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.Pdf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to This pdf could not be preview due to an internal error..
/// </summary>
internal static string PdfNotPreviewedError
{
get
{
return ResourceManager.GetString("PdfNotPreviewedError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Can't preview pdf. This pdf is password protected..
/// </summary>
internal static string PdfPasswordProtectedError
{
get
{
return ResourceManager.GetString("PdfPasswordProtectedError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This PDF contains more pages, this preview only shows the first 10 pages. Open PDF to view all pages..
/// </summary>
internal static string PdfMorePagesMessage
{
get
{
return ResourceManager.GetString("PdfMorePagesMessage", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="PdfMorePagesMessage" xml:space="preserve">
<value>This PDF contains more than 10 pages. Open the document to view all pages.</value>
<comment>This text is displayed if PDF has more than 10 pages.</comment>
</data>
<data name="PdfNotPreviewedError" xml:space="preserve">
<value>This PDF could not be previewed due to an internal error.</value>
<comment>This text is displayed if PDF fails to preview</comment>
</data>
<data name="PdfPasswordProtectedError" xml:space="preserve">
<value>Can't preview file. This PDF is password protected.</value>
<comment>This text is displayed if PDF is password protected.</comment>
</data>
</root>

View File

@ -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.Pdf.Telemetry.Events
{
/// <summary>
/// A telemetry event that is triggered when a pdf file is viewed in the preview pane.
/// </summary>
[EventData]
public class PdfFileHandlerLoaded : EventBase, IEvent
{
/// <inheritdoc/>
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@ -0,0 +1,23 @@
// 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 Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerToys.PreviewHandler.Pdf.Telemetry.Events
{
/// <summary>
/// A telemetry event that is triggered when an error occurs while attempting to view a markdown file in the preview pane.
/// </summary>
public class PdfFilePreviewError : EventBase, IEvent
{
/// <summary>
/// Gets or sets the error message.
/// </summary>
public string Message { get; set; }
/// <inheritdoc/>
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
}
}

View File

@ -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.Pdf.Telemetry.Events
{
/// <summary>
/// A telemetry event that is triggered when a markdown file is viewed in the preview pane.
/// </summary>
[EventData]
public class PdfFilePreviewed : EventBase, IEvent
{
/// <inheritdoc/>
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@ -1,2 +1,2 @@
![Minion](https://octodex.github.com/images/minion.png)
![Minion](https://octodex.github.com/images/minion.png)
<script>alert("hello")</script>

View File

@ -9,7 +9,7 @@ using Microsoft.PowerToys.STATestExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PreviewHandlerCommon;
namespace PreviewPaneUnitTests
namespace MarkdownPreviewHandlerUnitTests
{
[STATestClass]
public class MarkdownPreviewHandlerTest

View File

@ -0,0 +1,89 @@
// 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.Pdf;
using Microsoft.PowerToys.STATestExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace PdfPreviewHandlerUnitTests
{
[STATestClass]
public class PdfPreviewHandlerTest
{
[TestMethod]
public void PdfPreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled()
{
// Arrange
using (var pdfPreviewHandlerControl = new PdfPreviewHandlerControl())
{
// Act
var file = File.ReadAllBytes("HelperFiles/sample.pdf");
pdfPreviewHandlerControl.DoPreview<IStream>(GetMockStream(file));
var flowLayoutPanel = pdfPreviewHandlerControl.Controls[0] as FlowLayoutPanel;
// Assert
Assert.AreEqual(1, pdfPreviewHandlerControl.Controls.Count);
}
}
[TestMethod]
public void PdfPreviewHandlerControlShouldAddValidInfoBarIfPdfPreviewThrows()
{
// Arrange
using (var pdfPreviewHandlerControl = new PdfPreviewHandlerControl())
{
var mockStream = new Mock<IStream>();
mockStream
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
.Throws(new Exception());
// Act
pdfPreviewHandlerControl.DoPreview(mockStream.Object);
var textBox = pdfPreviewHandlerControl.Controls[0] as RichTextBox;
// Assert
Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text));
Assert.AreEqual(1, pdfPreviewHandlerControl.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<IStream>();
var firstCall = true;
streamMock
.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<IntPtr>()))
.Callback<byte[], int, IntPtr>((buffer, countToRead, bytesReadPtr) =>
{
if (firstCall)
{
Array.Copy(sourceArray, 0, buffer, 0, sourceArray.Length);
Marshal.WriteInt32(bytesReadPtr, sourceArray.Length);
firstCall = false;
}
else
{
Marshal.WriteInt32(bytesReadPtr, 0);
}
});
return streamMock.Object;
}
}
}

View File

@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Platforms>x64</Platforms>
<AssemblyTitle>UnitTests-PdfPreviewHandler</AssemblyTitle>
<AssemblyDescription>PowerToys UnitTests-PdfPreviewHandler</AssemblyDescription>
<AssemblyCompany>Microsoft Corp.</AssemblyCompany>
<AssemblyCopyright>Copyright (C) 2020 Microsoft Corp.</AssemblyCopyright>
<AssemblyProduct>PowerToys</AssemblyProduct>
<AssemblyTitle>UnitTests-PdfPreviewHandler</AssemblyTitle>
<Company>Microsoft Corp.</Company>
<Product>PowerToys</Product>
<NeutralLanguage>en-US</NeutralLanguage>
<Description>PowerToys UnitTests-PdfPreviewHandler</Description>
<Copyright>Copyright (C) 2020 Microsoft Corp.</Copyright>
</PropertyGroup>
<PropertyGroup>
<ProjectGuid>{ECC20689-002A-4354-95A6-B58DF089C6FF}</ProjectGuid>
<RootNamespace>PreviewPaneUnitTests</RootNamespace>
<AssemblyName>PreviewPaneUnitTests</AssemblyName>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
</PropertyGroup>
<Import Project="..\..\..\Version.props" />
<ItemGroup>
<None Remove="HelperFiles\dummy-password.pdf" />
<None Remove="HelperFiles\dummy.pdf" />
<None Remove="HelperFiles\sample.pdf" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.14.4" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\common\PreviewHandlerCommon.csproj" />
<ProjectReference Include="..\PdfPreviewHandler\PdfPreviewHandler.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\..\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
<Content Include="HelperFiles\sample.pdf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Compile Include="..\STATestClassAttribute.cs" Link="STATestClassAttribute.cs" />
<Compile Include="..\STATestMethodAttribute.cs" Link="STATestMethodAttribute.cs" />
<AdditionalFiles Include="..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -16,6 +16,12 @@ const CLSID CLSID_SHIMActivateMdPreviewHandler = { 0xE0907A95, 0x6F9A, 0x4D1B, {
// 45769bcc-e8fd-42d0-947e-02beef77a1f5
const CLSID CLSID_MdPreviewHandler = { 0x45769bcc, 0xe8fd, 0x42d0, { 0x94, 0x7e, 0x02, 0xbe, 0xef, 0x77, 0xa1, 0xf5 } };
// 4F6D533B-4185-43A6-AD75-9B20034B14CA
const CLSID CLSID_SHIMActivatePdfPreviewHandler = { 0x4f6d533b, 0x4185, 0x43a6, { 0xad, 0x75, 0x9b, 0x20, 0x3, 0x4b, 0x14, 0xca } };
// 07665729-6243-4746-95b7-79579308d1b2
const CLSID CLSID_PdfPreviewHandler = { 0x07665729, 0x6243, 0x4746, { 0x95, 0xb7, 0x79, 0x57, 0x93, 0x08, 0xd1, 0xb2 } };
// 9C723B8C-4F5C-4147-9DE4-C2808F9AF66B
const CLSID CLSID_SHIMActivateSvgThumbnailProvider = { 0x9C723B8C, 0x4F5C, 0x4147, { 0x9D, 0xE4, 0xC2, 0x80, 0x8F, 0x9A, 0xF6, 0x6B } };
@ -23,8 +29,9 @@ const CLSID CLSID_SHIMActivateSvgThumbnailProvider = { 0x9C723B8C, 0x4F5C, 0x414
const CLSID CLSID_SvgThumbnailProvider = { 0x36B27788, 0xA8BB, 0x4698, { 0xA7, 0x56, 0xDF, 0x9F, 0x11, 0xF6, 0x4F, 0x84 } };
// Pairs of NativeClsid vs ManagedClsid used for preview handlers.
const std::vector<std::pair<CLSID, CLSID>> NativeToManagedClsid({
const std::vector<std::pair<CLSID, CLSID>> NativeToManagedClsid({
{ CLSID_SHIMActivateMdPreviewHandler, CLSID_MdPreviewHandler },
{ CLSID_SHIMActivatePdfPreviewHandler, CLSID_PdfPreviewHandler },
{ CLSID_SHIMActivateSvgPreviewHandler, CLSID_SvgPreviewHandler },
{ CLSID_SHIMActivateSvgThumbnailProvider, CLSID_SvgThumbnailProvider }
});

View File

@ -167,5 +167,11 @@
</data>
<data name="FileExplorer_Admin_Restart_Warning_Dont_Show_Again" xml:space="preserve">
<value>Don't show again</value>
</data>
</data>
<data name="Prevpane_Pdf_Settings_Description" xml:space="preserve">
<value>PDF Previewer</value>
</data>
<data name="Prevpane_Pdf_Settings_Displayname" xml:space="preserve">
<value>PDF Previewer</value>
</data>
</root>

View File

@ -33,6 +33,14 @@ PowerPreviewModule::PowerPreviewModule() :
L"Markdown Preview Handler",
std::make_unique<RegistryWrapper>()));
m_fileExplorerModules.emplace_back(std::make_unique<PreviewHandlerSettings>(
true,
L"pdf-previewer-toggle-setting",
GET_RESOURCE_STRING(IDS_PREVPANE_PDF_SETTINGS_DESCRIPTION),
L"{07665729-6243-4746-95b7-79579308d1b2}",
L"PDF Preview Handler",
std::make_unique<RegistryWrapper>()));
m_fileExplorerModules.emplace_back(std::make_unique<ThumbnailProviderSettings>(
true,
L"svg-thumbnail-toggle-setting",

View File

@ -63,6 +63,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool enablePdfPreview = true;
[JsonPropertyName("pdf-previewer-toggle-setting")]
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool EnablePdfPreview
{
get => enablePdfPreview;
set
{
if (value != enablePdfPreview)
{
LogTelemetryEvent(value);
enablePdfPreview = value;
}
}
}
public PowerPreviewProperties()
{
}

View File

@ -49,10 +49,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
_svgRenderIsEnabled = Settings.Properties.EnableSvgPreview;
_svgThumbnailIsEnabled = Settings.Properties.EnableSvgThumbnail;
_mdRenderIsEnabled = Settings.Properties.EnableMdPreview;
_pdfRenderIsEnabled = Settings.Properties.EnablePdfPreview;
}
private bool _svgRenderIsEnabled;
private bool _mdRenderIsEnabled;
private bool _pdfRenderIsEnabled;
private bool _svgThumbnailIsEnabled;
public bool SVGRenderIsEnabled
@ -109,6 +111,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
}
}
public bool PDFRenderIsEnabled
{
get
{
return _pdfRenderIsEnabled;
}
set
{
if (value != _pdfRenderIsEnabled)
{
_pdfRenderIsEnabled = value;
Settings.Properties.EnablePdfPreview = value;
RaisePropertyChanged();
}
}
}
public string GetSettingsSubPath()
{
return _settingsConfigFileFolder + "\\" + ModuleName;

View File

@ -57,6 +57,7 @@ namespace ViewModelTests
// Verify that the old settings persisted
Assert.AreEqual(originalGeneralSettings.IsElevated, viewModel.IsElevated);
Assert.AreEqual(originalSettings.Properties.EnableMdPreview, viewModel.MDRenderIsEnabled);
Assert.AreEqual(originalSettings.Properties.EnablePdfPreview, viewModel.PDFRenderIsEnabled);
Assert.AreEqual(originalSettings.Properties.EnableSvgPreview, viewModel.SVGRenderIsEnabled);
Assert.AreEqual(originalSettings.Properties.EnableSvgThumbnail, viewModel.SVGThumbnailIsEnabled);
@ -118,5 +119,23 @@ namespace ViewModelTests
// act
viewModel.MDRenderIsEnabled = true;
}
[TestMethod]
public void PDFRenderIsEnabledShouldPrevHandlerWhenSuccessful()
{
// Assert
Func<string, int> sendMockIPCConfigMSG = msg =>
{
SndModuleSettings<SndPowerPreviewSettings> snd = JsonSerializer.Deserialize<SndModuleSettings<SndPowerPreviewSettings>>(msg);
Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnablePdfPreview);
return 0;
};
// arrange
PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository<PowerPreviewSettings>.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName);
// act
viewModel.PDFRenderIsEnabled = true;
}
}
}

View File

@ -58,7 +58,6 @@
Foreground="{ThemeResource CardPrimaryForegroundBrush}"
VerticalAlignment="Center"/>
<StackPanel
VerticalAlignment="Center"
Grid.Column="1"

View File

@ -581,6 +581,10 @@
<value>Enable SVG (.svg) preview</value>
<comment>Do you want this feature on / off</comment>
</data>
<data name="FileExplorerPreview_ToggleSwitch_Preview_PDF.Header" xml:space="preserve">
<value>Enable PDF (.pdf) preview</value>
<comment>Do you want this feature on / off</comment>
</data>
<data name="FileExplorerPreview_ToggleSwitch_SVG_Thumbnail.Header" xml:space="preserve">
<value>Enable SVG (.svg) thumbnails</value>
<comment>Do you want this feature on / off</comment>

View File

@ -18,7 +18,6 @@
ModuleImageSource="ms-appx:///Assets/Modules/PowerPreview.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel Orientation="Vertical">
<muxc:InfoBar Severity="Warning"
@ -47,10 +46,16 @@
IsEnabled="{Binding Mode=OneWay, Path=IsElevated}"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="FileExplorerPreview_ToggleSwitch_Preview_PDF" Icon="&#xEA90;">
<controls:Setting.ActionContent>
<ToggleSwitch IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.PDFRenderIsEnabled}"
IsEnabled="{Binding Mode=OneWay, Path=IsElevated}"/>
</controls:Setting.ActionContent>
</controls:Setting>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="FileExplorerPreview_IconThumbnail_GroupSettings">
<muxc:InfoBar Severity="Informational"
x:Uid="FileExplorerPreview_RebootRequired"
IsOpen="True"
@ -62,9 +67,8 @@
IsEnabled="{Binding Mode=OneWay, Path=IsElevated}"/>
</controls:Setting.ActionContent>
</controls:Setting>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>

View File

@ -19,12 +19,14 @@ namespace
{ HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\AppUserModelId\\PowerToysRun" },
{ HKEY_CLASSES_ROOT, L".svg\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" },
{ HKEY_CLASSES_ROOT, L".svg\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" },
{ HKEY_CLASSES_ROOT, L".md\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }
{ HKEY_CLASSES_ROOT, L".md\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" },
{ HKEY_CLASSES_ROOT, L".pdf\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }
};
vector<tuple<HKEY, wstring, wstring>> registryValues = {
{ HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", L"{ddee2b8a-6807-48a6-bb20-2338174ff779}" },
{ 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\\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" }
};