Compare commits
27 Commits
dev/featur
...
samchaps/w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf72adc942 | ||
|
|
60bf86825b | ||
|
|
4ef3f23897 | ||
|
|
5bd9dd5935 | ||
|
|
5590eb9484 | ||
|
|
6f06f76784 | ||
|
|
5981d0e81e | ||
|
|
539a4e5678 | ||
|
|
aea217ddca | ||
|
|
d001a4c0e0 | ||
|
|
5712123598 | ||
|
|
e504653323 | ||
|
|
b46b8d176f | ||
|
|
b98f233b75 | ||
|
|
083ec27a45 | ||
|
|
bca780f886 | ||
|
|
d4e618cdc9 | ||
|
|
e1cb01d188 | ||
|
|
bddfe42d39 | ||
|
|
30d346c93f | ||
|
|
899b5016b9 | ||
|
|
0d05089316 | ||
|
|
6295ed86ca | ||
|
|
73925885dd | ||
|
|
437d2dd5f7 | ||
|
|
a4bd09a2c8 | ||
|
|
c2aae52bba |
@@ -76,6 +76,13 @@
|
||||
"modules\\FileLocksmith\\PowerToys.FileLocksmithUI.exe",
|
||||
"modules\\FileLocksmith\\PowerToys.FileLocksmithUI.dll",
|
||||
|
||||
"modules\\Peek\\Peek.Common.dll",
|
||||
"modules\\Peek\\Peek.FilePreviewer.dll",
|
||||
"modules\\Peek\\Powertoys.Peek.UI.dll",
|
||||
"modules\\Peek\\Powertoys.Peek.UI.exe",
|
||||
"modules\\Peek\\WIC.dll",
|
||||
"modules\\Peek\\Powertoys.Peek.dll",
|
||||
|
||||
"modules\\ImageResizer\\PowerToys.ImageResizer.exe",
|
||||
"modules\\ImageResizer\\PowerToys.ImageResizer.dll",
|
||||
"modules\\ImageResizer\\PowerToys.ImageResizerExt.dll",
|
||||
|
||||
101
PowerToys.sln
@@ -468,7 +468,21 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLibInterop", "
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GPOWrapperProjection", "src\common\GPOWrapperProjection\GPOWrapperProjection.csproj", "{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GPOWrapperProjection", "src\common\GPOWrapperProjection\GPOWrapperProjection.csproj", "{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Peek", "Peek", "{17B4FA70-001E-4D33-BBBB-0D142DBC2E20}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.UI.WPF", "src\modules\peek\Peek.UI.WPF\Peek.UI.WPF.csproj", "{C0240BC3-95AF-4B38-811A-76E3FD56B576}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Peek", "src\modules\peek\peek\peek.vcxproj", "{A1425B53-3D61-4679-8623-E64A0D3D0A48}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.UI", "src\modules\peek\Peek.UI\Peek.UI.csproj", "{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Common", "src\modules\peek\Peek.Common\Peek.Common.csproj", "{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.FilePreviewer", "src\modules\peek\Peek.FilePreviewer\Peek.FilePreviewer.csproj", "{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WIC", "src\modules\peek\WIC\WIC.csproj", "{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -1909,6 +1923,84 @@ Global
|
||||
{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|x64.Build.0 = Release|x64
|
||||
{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|x86.ActiveCfg = Release|x64
|
||||
{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|x86.Build.0 = Release|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Debug|x64.Build.0 = Debug|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Debug|x86.Build.0 = Debug|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Release|x64.ActiveCfg = Release|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Release|x64.Build.0 = Release|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Release|x86.ActiveCfg = Release|x64
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576}.Release|x86.Build.0 = Release|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|x86.Build.0 = Debug|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|x64.Build.0 = Release|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|x86.ActiveCfg = Release|x64
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|x86.Build.0 = Release|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x64.Build.0 = Debug|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x86.Build.0 = Debug|x86
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x64.ActiveCfg = Release|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x64.Build.0 = Release|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x64.Deploy.0 = Release|x64
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x86.ActiveCfg = Release|x86
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x86.Build.0 = Release|x86
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x86.Deploy.0 = Release|x86
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|x64.Build.0 = Debug|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|x86.Build.0 = Debug|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|x64.ActiveCfg = Release|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|x64.Build.0 = Release|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|x86.ActiveCfg = Release|x64
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|x86.Build.0 = Release|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|x64.Build.0 = Debug|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|x86.Build.0 = Debug|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.ActiveCfg = Release|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.Build.0 = Release|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x86.ActiveCfg = Release|x64
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x86.Build.0 = Release|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Debug|x64.Build.0 = Debug|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Debug|x86.Build.0 = Debug|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Release|x64.ActiveCfg = Release|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Release|x64.Build.0 = Release|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Release|x86.ActiveCfg = Release|x64
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF}.Release|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -2071,6 +2163,13 @@ Global
|
||||
{C604B37E-9D0E-4484-8778-E8B31B0E1B3A} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
|
||||
{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{17B4FA70-001E-4D33-BBBB-0D142DBC2E20} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{C0240BC3-95AF-4B38-811A-76E3FD56B576} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{A1425B53-3D61-4679-8623-E64A0D3D0A48} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
{21B69DE5-59FD-4C5D-A142-EF1C1C430EAF} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<?define PowerAccentProjectName="PowerAccent"?>
|
||||
<?define PowerRenameProjectName="PowerRename"?>
|
||||
<?define FileLocksmithProjectName="FileLocksmith"?>
|
||||
<?define PeekProjectName="Peek"?>
|
||||
<?define ColorPickerProjectName="ColorPicker"?>
|
||||
<?define PowerOCRProjectName="PowerOCR"?>
|
||||
<?define VideoConferenceProjectName="VideoConference"?>
|
||||
@@ -53,7 +54,7 @@
|
||||
|
||||
<?define SettingsV2Files=backup_restore_settings.json;Ijwhost.dll;ColorCode.Core.dll;ColorCode.WinUI.dll;CommunityToolkit.Common.dll;CommunityToolkit.Labs.WinUI.SettingsControls.dll;CommunityToolkit.WinUI.dll;CommunityToolkit.WinUI.UI.Controls.Core.dll;CommunityToolkit.WinUI.UI.Controls.DataGrid.dll;CommunityToolkit.WinUI.UI.Controls.Input.dll;CommunityToolkit.WinUI.UI.Controls.Layout.dll;CommunityToolkit.WinUI.UI.Controls.Markdown.dll;CommunityToolkit.WinUI.UI.Controls.Media.dll;CommunityToolkit.WinUI.UI.Controls.Primitives.dll;CommunityToolkit.WinUI.UI.dll;icon.ico;Microsoft.Graphics.Canvas.Interop.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.Settings.deps.json;PowerToys.Settings.dll;PowerToys.Settings.exe;PowerToys.Settings.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;resources.pri;System.CodeDom.dll;System.IO.Abstractions.dll;WinRT.Runtime.dll;Microsoft.Graphics.Canvas.dll;System.Management.dll;PowerToys.GPOWrapper.dll?>
|
||||
|
||||
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PowerAccent.png;PowerOCR.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ScreenRuler.png;ShortcutGuide.png;VideoConference.png?>
|
||||
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PowerAccent.png;PowerOCR.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ScreenRuler.png;ShortcutGuide.png;VideoConference.png;Peek.png?>
|
||||
|
||||
<?define SettingsV2OOBEAssetsModulesFiles=ColorPicker.gif;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;FancyZones.gif;FileExplorer.png;FileLocksmith.gif;ImageResizer.gif;KBM.gif;MouseUtils.gif;PowerAccent.gif;PowerOCR.gif;PowerRename.gif;Run.gif;ScreenRuler.gif;OOBEShortcutGuide.png;VideoConferenceMute.png;OOBEPTHero.png?>
|
||||
|
||||
@@ -121,6 +122,10 @@
|
||||
|
||||
<?define FileLocksmithAssetsFiles=Icon.ico?>
|
||||
|
||||
<?define PeekFiles=CommunityToolkit.Mvvm.dll;Microsoft.Win32.SystemEvents.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;Peek.Common.dll;Peek.FilePreviewer.dll;Powertoys.Peek.UI.dll;Powertoys.Peek.UI.exe;Powertoys.Peek.UI.deps.json;Powertoys.Peek.UI.runtimeconfig.json;resources.pri;System.CodeDom.dll;System.Drawing.Common.dll;System.Management.dll;WIC.dll;WinUIEx.dll?>
|
||||
|
||||
<?define PeekAssetsFiles=Icon.ico?>
|
||||
|
||||
<?define PowerRenameSparsePackageAssets=LargeTile.png;SmallTile.png;SplashScreen.png;Square150x150Logo.png;Square44x44Logo.png;storelogo.png;Wide310x150Logo.png?>
|
||||
|
||||
<?define ImageResizerSparsePackageAssets=LargeTile.png;SmallTile.png;SplashScreen.png;Square150x150Logo.png;Square44x44Logo.png;storelogo.png;Wide310x150Logo.png?>
|
||||
@@ -564,8 +569,13 @@
|
||||
<Directory Id="HostsMicrosoftUIXamlAssetsInstallFolder" Name="Assets" />
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<!-- Launcher -->
|
||||
|
||||
<!-- Hosts -->
|
||||
<Directory Id="PeekInstallFolder" Name="$(var.PeekProjectName)">
|
||||
<Directory Id="PeekAssetsInstallFolder" Name="Assets" />
|
||||
</Directory>
|
||||
|
||||
<!-- Launcher -->
|
||||
<Directory Id="LauncherInstallFolder" Name="launcher">
|
||||
<Directory Id="LauncherImagesFolder" Name="Images" />
|
||||
<Directory Id="LauncherPropertiesFolder" Name="Properties" />
|
||||
@@ -1032,6 +1042,43 @@
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- Peek -->
|
||||
<DirectoryRef Id="PeekInstallFolder" FileSource="$(var.BinDir)modules\$(var.PeekProjectName)">
|
||||
<Component Id="Module_Peek" Win64="yes">
|
||||
<File Source="$(var.BinDir)modules\$(var.PeekProjectName)\PowerToys.Peek.dll" />
|
||||
</Component>
|
||||
|
||||
<?foreach File in $(var.PeekFiles)?>
|
||||
<Component Id="Peek_$(var.File)" Win64="yes">
|
||||
<File Id="PeekFile_$(var.File)" Source="$(var.BinDir)modules\$(var.PeekProjectName)\$(var.File)" />
|
||||
</Component>
|
||||
<?endforeach?>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- Peek Assets -->
|
||||
<DirectoryRef Id="PeekAssetsInstallFolder" FileSource="$(var.BinDir)modules\$(var.PeekProjectName)\Assets">
|
||||
<?foreach File in $(var.PeekAssetsFiles)?>
|
||||
<Component Id="Peek_Assets_$(var.File)" Win64="yes">
|
||||
<File Id="PeekAssetsFile_$(var.File)" Source="$(var.BinDir)modules\$(var.PeekProjectName)\Assets\$(var.File)" />
|
||||
</Component>
|
||||
<?endforeach?>
|
||||
<Component Id="Peek_Assets_Applist_Scale100" Win64="yes">
|
||||
<File Id="Peek_AppList_Scale100" Source="$(var.BinDir)modules\$(var.PeekProjectName)\Assets\AppList.scale-100.png" />
|
||||
</Component>
|
||||
<Component Id="Peek_Assets_Applist_Scale125" Win64="yes">
|
||||
<File Id="Peek_AppList_Scale125" Source="$(var.BinDir)modules\$(var.PeekProjectName)\Assets\AppList.scale-125.png" />
|
||||
</Component>
|
||||
<Component Id="Peek_Assets_Applist_Scale150" Win64="yes">
|
||||
<File Id="Peek_AppList_Scale150" Source="$(var.BinDir)modules\$(var.PeekProjectName)\Assets\AppList.scale-150.png" />
|
||||
</Component>
|
||||
<Component Id="Peek_Assets_Applist_Scale200" Win64="yes">
|
||||
<File Id="Peek_AppList_Scale200" Source="$(var.BinDir)modules\$(var.PeekProjectName)\Assets\AppList.scale-200.png" />
|
||||
</Component>
|
||||
<Component Id="Peek_Assets_Applist_Scale400" Win64="yes">
|
||||
<File Id="Peek_AppList_Scale400" Source="$(var.BinDir)modules\$(var.PeekProjectName)\Assets\AppList.scale-400.png" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- KBM -->
|
||||
<DirectoryRef Id="KeyboardManagerInstallFolder" FileSource="$(var.BinDir)modules\$(var.KeyboardManagerProjectName)\">
|
||||
<Component Id="Module_KeyboardManager" Win64="yes">
|
||||
@@ -1307,6 +1354,18 @@
|
||||
<ComponentRef Id="FileLocksmith_Assets_Applist_Scale150" />
|
||||
<ComponentRef Id="FileLocksmith_Assets_Applist_Scale200" />
|
||||
<ComponentRef Id="FileLocksmith_Assets_Applist_Scale400" />
|
||||
<ComponentRef Id="Module_Peek" />
|
||||
<?foreach File in $(var.PeekFiles)?>
|
||||
<ComponentRef Id="Peek_$(var.File)" />
|
||||
<?endforeach?>
|
||||
<?foreach File in $(var.PeekAssetsFiles)?>
|
||||
<ComponentRef Id="Peek_Assets_$(var.File)" />
|
||||
<?endforeach?>
|
||||
<ComponentRef Id="Peek_Assets_Applist_Scale100" />
|
||||
<ComponentRef Id="Peek_Assets_Applist_Scale125" />
|
||||
<ComponentRef Id="Peek_Assets_Applist_Scale150" />
|
||||
<ComponentRef Id="Peek_Assets_Applist_Scale200" />
|
||||
<ComponentRef Id="Peek_Assets_Applist_Scale400" />
|
||||
<?foreach File in $(var.PowerAccentFiles)?>
|
||||
<ComponentRef Id="PowerAccent_$(var.File)" />
|
||||
<?endforeach?>
|
||||
@@ -1715,7 +1774,7 @@
|
||||
<!-- Localization languages shipped with WinAppSDK. We should ship these as well. -->
|
||||
<?define WinAppSDKLocLanguageList = af-ZA;ar-SA;az-Latn-AZ;bg-BG;bs-Latn-BA;ca-ES;cs-CZ;cy-GB;da-DK;de-DE;el-GR;en-GB;en-us;es-ES;es-MX;et-EE;eu-ES;fa-IR;fi-FI;fr-CA;fr-FR;gl-ES;he-IL;hi-IN;hr-HR;hu-HU;id-ID;is-IS;it-IT;ja-JP;ka-GE;kk-KZ;ko-KR;lt-LT;lv-LV;ms-MY;nb-NO;nl-NL;nn-NO;pl-PL;pt-BR;pt-PT;ro-RO;ru-RU;sk-SK;sl-SI;sq-AL;sr-Cyrl-RS;sr-Latn-RS;sv-SE;th-TH;tr-TR;uk-UA;vi-VN;zh-CN;zh-TW?>
|
||||
<Fragment>
|
||||
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder;HostsInstallFolder;FileLocksmithInstallFolder?>
|
||||
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder;HostsInstallFolder;FileLocksmithInstallFolder;PeekInstallFolder?>
|
||||
<DirectoryRef Id="$(var.ParentDirectory)">
|
||||
<?foreach Language in $(var.WinAppSDKLocLanguageList)?>
|
||||
<?if $(var.Language) = af-ZA?>
|
||||
@@ -2048,6 +2107,13 @@
|
||||
<File Id="FileLocksmith_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.FileLocksmithProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
|
||||
<File Id="FileLocksmith_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.FileLocksmithProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
|
||||
</Component>
|
||||
<Component
|
||||
Id="Peek_WinAppSDKLoc_$(var.IdSafeLanguage)_Component"
|
||||
Directory="WinAppSDKLoc$(var.IdSafeLanguage)PeekInstallFolder"
|
||||
Guid="$(var.CompGUIDPrefix)06">
|
||||
<File Id="Peek_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.PeekProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
|
||||
<File Id="Peek_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.PeekProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
|
||||
</Component>
|
||||
<?undef IdSafeLanguage?>
|
||||
<?undef CompGUIDPrefix?>
|
||||
<?endforeach?>
|
||||
|
||||
@@ -1230,7 +1230,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
}
|
||||
processes.resize(bytes / sizeof(processes[0]));
|
||||
|
||||
std::array<std::wstring_view, 9> processesToTerminate = {
|
||||
std::array<std::wstring_view, 10> processesToTerminate = {
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.Settings.exe",
|
||||
L"PowerToys.Awake.exe",
|
||||
@@ -1239,6 +1239,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.FileLocksmithUI.exe",
|
||||
L"PowerToys.ColorPickerUI.exe",
|
||||
L"PowerToys.AlwaysOnTop.exe",
|
||||
L"PowerToys.PeekUI.exe",
|
||||
L"PowerToys.exe"
|
||||
};
|
||||
|
||||
|
||||
@@ -203,6 +203,10 @@ public
|
||||
return gcnew String(CommonSharedConstants::AWAKE_EXIT_EVENT);
|
||||
}
|
||||
|
||||
static String^ ShowPeekEvent() {
|
||||
return gcnew String(CommonSharedConstants::SHOW_PEEK_SHARED_EVENT);
|
||||
}
|
||||
|
||||
static String ^ PowerAccentExitEvent() {
|
||||
return gcnew String(CommonSharedConstants::POWERACCENT_EXIT_EVENT);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ namespace CommonSharedConstants
|
||||
// Path to the event used by PowerOCR
|
||||
const wchar_t SHOW_POWEROCR_SHARED_EVENT[] = L"Local\\PowerOCREvent-dc864e06-e1af-4ecc-9078-f98bee745e3a";
|
||||
|
||||
// Path to the event used to show Peek
|
||||
const wchar_t SHOW_PEEK_SHARED_EVENT[] = L"Local\\ShowPeekEvent";
|
||||
|
||||
// Max DWORD for key code to disable keys.
|
||||
const DWORD VK_DISABLED = 0x100;
|
||||
}
|
||||
|
||||
14
src/modules/peek/Peek.Common/Converters/BoolConverter.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Peek.Common.Converters
|
||||
{
|
||||
public static class BoolConverter
|
||||
{
|
||||
public static bool Invert(bool value)
|
||||
{
|
||||
return !value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 Peek.Common.Extensions
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
public static class DispatcherExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Run work on UI thread safely.
|
||||
/// </summary>
|
||||
/// <returns>True if the work was run successfully, False otherwise.</returns>
|
||||
public static Task RunOnUiThread(this DispatcherQueue dispatcher, Func<Task> work)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
dispatcher.TryEnqueue(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await work();
|
||||
|
||||
tcs.SetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
tcs.SetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/modules/peek/Peek.Common/Extensions/TaskExtension.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 Peek.Common.Extensions
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public static class TaskExtension
|
||||
{
|
||||
public static Task<bool> RunSafe(Func<Task> work)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await work();
|
||||
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 Peek.Common.Helpers
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
|
||||
public static class ReadableStringHelper
|
||||
{
|
||||
private const int DecimalPercision = 10;
|
||||
|
||||
public static string BytesToReadableString(ulong bytes)
|
||||
{
|
||||
var resourceLoader = ResourceLoader.GetForViewIndependentUse();
|
||||
List<string> format = new List<string>
|
||||
{
|
||||
resourceLoader.GetString("ReadableString_ByteAbbreviationFormat"), // "B"
|
||||
resourceLoader.GetString("ReadableString_KiloByteAbbreviationFormat"), // "KB"
|
||||
resourceLoader.GetString("ReadableString_MegaByteAbbreviationFormat"), // "MB"
|
||||
resourceLoader.GetString("ReadableString_GigaByteAbbreviationFormat"), // "GB"
|
||||
resourceLoader.GetString("ReadableString_TeraByteAbbreviationFormat"), // "TB"
|
||||
resourceLoader.GetString("ReadableString_PetaByteAbbreviationFormat"), // "PB"
|
||||
resourceLoader.GetString("ReadableString_ExaByteAbbreviationFormat"), // "EB"
|
||||
};
|
||||
|
||||
int index = 0;
|
||||
double number = 0.0;
|
||||
|
||||
if (bytes > 0)
|
||||
{
|
||||
index = (int)Math.Floor(Math.Log(bytes) / Math.Log(1024));
|
||||
number = Math.Round((bytes / Math.Pow(1024, index)) * DecimalPercision) / DecimalPercision;
|
||||
}
|
||||
|
||||
return string.Format(format[index], number);
|
||||
}
|
||||
|
||||
public static string FormatResourceString(string resourceId, object? args)
|
||||
{
|
||||
var formatString = ResourceLoader.GetForViewIndependentUse()?.GetString(resourceId);
|
||||
var formattedString = string.IsNullOrEmpty(formatString) ? string.Empty : string.Format(formatString, args);
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
|
||||
public static string FormatResourceString(string resourceId, object? args0, object? args1)
|
||||
{
|
||||
var formatString = ResourceLoader.GetForViewIndependentUse()?.GetString(resourceId);
|
||||
var formattedString = string.IsNullOrEmpty(formatString) ? string.Empty : string.Format(formatString, args0, args1);
|
||||
|
||||
return formattedString;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/modules/peek/Peek.Common/Models/File.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 Peek.Common.Models
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class File
|
||||
{
|
||||
private StorageFile? storageFile;
|
||||
|
||||
public File(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Path { get; init; }
|
||||
|
||||
public string FileName => System.IO.Path.GetFileName(Path);
|
||||
|
||||
public string Extension => System.IO.Path.GetExtension(Path).ToLower();
|
||||
|
||||
public DateTime DateModified => System.IO.File.GetCreationTime(Path);
|
||||
|
||||
public async Task<StorageFile> GetStorageFileAsync()
|
||||
{
|
||||
if (storageFile == null)
|
||||
{
|
||||
storageFile = await StorageFile.GetFileFromPathAsync(Path);
|
||||
}
|
||||
|
||||
return storageFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/modules/peek/Peek.Common/Models/HResult.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Peek.Common.Models
|
||||
{
|
||||
public enum HResult
|
||||
{
|
||||
Ok = 0x0000,
|
||||
False = 0x0001,
|
||||
InvalidArguments = unchecked((int)0x80070057),
|
||||
OutOfMemory = unchecked((int)0x8007000E),
|
||||
NoInterface = unchecked((int)0x80004002),
|
||||
Fail = unchecked((int)0x80004005),
|
||||
ExtractionFailed = unchecked((int)0x8004B200),
|
||||
ElementNotFound = unchecked((int)0x80070490),
|
||||
TypeElementNotFound = unchecked((int)0x8002802B),
|
||||
NoObject = unchecked((int)0x800401E5),
|
||||
Win32ErrorCanceled = 1223,
|
||||
Canceled = unchecked((int)0x800704C7),
|
||||
ResourceInUse = unchecked((int)0x800700AA),
|
||||
AccessDenied = unchecked((int)0x80030005),
|
||||
}
|
||||
}
|
||||
41
src/modules/peek/Peek.Common/Models/IShellItem.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 Peek.Common.Models
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
||||
public interface IShellItem
|
||||
{
|
||||
void BindToHandler(
|
||||
IntPtr pbc,
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
|
||||
out IntPtr ppv);
|
||||
|
||||
void GetParent(out IShellItem ppsi);
|
||||
|
||||
void GetDisplayName(Sigdn sigdnName, out IntPtr ppszName);
|
||||
|
||||
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
||||
|
||||
void Compare(IShellItem psi, uint hint, out int piOrder);
|
||||
}
|
||||
|
||||
public enum Sigdn : uint
|
||||
{
|
||||
NormalDisplay = 0,
|
||||
ParentRelativeParsing = 0x80018001,
|
||||
ParentRelativeForAddressBar = 0x8001c001,
|
||||
DesktopAbsoluteParsing = 0x80028000,
|
||||
ParentRelativeEditing = 0x80031001,
|
||||
DesktopAbsoluteEditing = 0x8004c000,
|
||||
FileSysPath = 0x80058000,
|
||||
Url = 0x80068000,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 Peek.Common.Models
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[ComImport]
|
||||
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IShellItemImageFactory
|
||||
{
|
||||
[PreserveSig]
|
||||
HResult GetImage(
|
||||
[In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
|
||||
[In] ThumbnailOptions flags,
|
||||
[Out] out IntPtr phbm);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeSize
|
||||
{
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public int Width
|
||||
{
|
||||
set { width = value; }
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
set { height = value; }
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ThumbnailOptions
|
||||
{
|
||||
None = 0x00,
|
||||
BiggerSizeOk = 0x01,
|
||||
InMemoryOnly = 0x02,
|
||||
IconOnly = 0x04,
|
||||
ThumbnailOnly = 0x08,
|
||||
InCacheOnly = 0x10,
|
||||
ScaleUp = 0x100,
|
||||
}
|
||||
}
|
||||
650
src/modules/peek/Peek.Common/Models/PropertyStoreShellApi.cs
Normal file
@@ -0,0 +1,650 @@
|
||||
// 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 Peek.Common.Models
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
public static class PropertyStoreShellApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the path to the given known folder.
|
||||
/// </summary>
|
||||
/// <param name="knownFolderId">Guid for given known folder</param>
|
||||
/// <returns>The path to the known folder.</returns>
|
||||
public static string GetKnownFolderPath(Guid knownFolderId)
|
||||
{
|
||||
string path;
|
||||
int hResult = SHGetKnownFolderPath(knownFolderId, 0, IntPtr.Zero, out path);
|
||||
Marshal.ThrowExceptionForHR(hResult);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a IPropertyStore interface from the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The file/folder path</param>
|
||||
/// <param name="flags">The property store flags</param>
|
||||
/// <returns>an IPropertyStroe interface</returns>
|
||||
public static IPropertyStore GetPropertyStoreFromPath(string path, PropertyStoreFlags flags = PropertyStoreFlags.EXTRINSICPROPERTIES)
|
||||
{
|
||||
PropertyStoreShellApi.IShellItem2? shellItem2 = null;
|
||||
IntPtr ppPropertyStore = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
PropertyStoreShellApi.SHCreateItemFromParsingName(path, IntPtr.Zero, typeof(PropertyStoreShellApi.IShellItem2).GUID, out shellItem2);
|
||||
|
||||
if (shellItem2 == null)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Unable to get an IShellItem2 reference from file {0}.", path));
|
||||
}
|
||||
|
||||
int hr = shellItem2.GetPropertyStore((int)flags, typeof(PropertyStoreShellApi.IPropertyStore).GUID, out ppPropertyStore);
|
||||
|
||||
if (hr != 0)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("GetPropertyStore retunred hresult={0}", hr));
|
||||
}
|
||||
|
||||
return (PropertyStoreShellApi.IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ppPropertyStore != IntPtr.Zero)
|
||||
{
|
||||
Marshal.Release(ppPropertyStore);
|
||||
}
|
||||
|
||||
if (shellItem2 != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(shellItem2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that retrieves a uint value from the given property store.
|
||||
/// Returns 0 if the value is not a VT_UI4 (4-byte unsigned integer in little-endian order).
|
||||
/// </summary>
|
||||
/// <param name="propertyStore">The property store</param>
|
||||
/// <param name="key">The pkey</param>
|
||||
/// <returns>The uint value</returns>
|
||||
public static uint GetUIntFromPropertyStore(IPropertyStore propertyStore, PropertyKey key)
|
||||
{
|
||||
if (propertyStore == null)
|
||||
{
|
||||
throw new ArgumentNullException("propertyStore");
|
||||
}
|
||||
|
||||
PropVariant propVar;
|
||||
|
||||
propertyStore.GetValue(ref key, out propVar);
|
||||
|
||||
// VT_UI4 Indicates a 4-byte unsigned integer formatted in little-endian byte order.
|
||||
if ((VarEnum)propVar.Vt == VarEnum.VT_UI4)
|
||||
{
|
||||
return propVar.UlVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that retrieves a ulong value from the given property store.
|
||||
/// Returns 0 if the value is not a VT_UI8 (8-byte unsigned integer in little-endian order).
|
||||
/// </summary>
|
||||
/// <param name="propertyStore">The property store</param>
|
||||
/// <param name="key">the pkey</param>
|
||||
/// <returns>the ulong value</returns>
|
||||
public static ulong GetULongFromPropertyStore(IPropertyStore propertyStore, PropertyKey key)
|
||||
{
|
||||
if (propertyStore == null)
|
||||
{
|
||||
throw new ArgumentNullException("propertyStore");
|
||||
}
|
||||
|
||||
PropVariant propVar;
|
||||
|
||||
propertyStore.GetValue(ref key, out propVar);
|
||||
|
||||
// VT_UI8 Indicates an 8-byte unsigned integer formatted in little-endian byte order.
|
||||
if ((VarEnum)propVar.Vt == VarEnum.VT_UI8)
|
||||
{
|
||||
return propVar.UhVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that retrieves a string value from the given property store.
|
||||
/// </summary>
|
||||
/// <param name="propertyStore">The property store</param>
|
||||
/// <param name="key">The pkey</param>
|
||||
/// <returns>The string value</returns>
|
||||
public static string GetStringFromPropertyStore(IPropertyStore propertyStore, PropertyKey key)
|
||||
{
|
||||
PropVariant propVar;
|
||||
|
||||
propertyStore.GetValue(ref key, out propVar);
|
||||
|
||||
if ((VarEnum)propVar.Vt == VarEnum.VT_LPWSTR)
|
||||
{
|
||||
return Marshal.PtrToStringUni(propVar.P) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that retrieves an array of string values from the given property store.
|
||||
/// </summary>
|
||||
/// <param name="propertyStore">The property store</param>
|
||||
/// <param name="key">The pkey</param>
|
||||
/// <returns>The array of string values</returns>
|
||||
public static string[] GetStringArrayFromPropertyStore(IPropertyStore propertyStore, PropertyKey key)
|
||||
{
|
||||
PropVariant propVar;
|
||||
propertyStore.GetValue(ref key, out propVar);
|
||||
|
||||
List<string> values = new List<string>();
|
||||
|
||||
if ((VarEnum)propVar.Vt == (VarEnum.VT_LPWSTR | VarEnum.VT_VECTOR))
|
||||
{
|
||||
for (int elementIndex = 0; elementIndex < propVar.Calpwstr.CElems; elementIndex++)
|
||||
{
|
||||
var stringVal = Marshal.PtrToStringUni(Marshal.ReadIntPtr(propVar.Calpwstr.PElems, elementIndex));
|
||||
if (stringVal != null)
|
||||
{
|
||||
values.Add(stringVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values.ToArray();
|
||||
}
|
||||
|
||||
private const string IIDIShellItem = "43826D1E-E718-42EE-BC55-A1E261C37BFE";
|
||||
private const string IIDIShellFolder2 = "93F2F68C-1D1B-11D3-A30E-00C04F79ABD1";
|
||||
private const string IIDIEnumIDList = "000214F2-0000-0000-C000-000000000046";
|
||||
private const string BHIDSFObject = "3981e224-f559-11d3-8e3a-00c04f6837d5";
|
||||
private const int KfFlagDefaultPath = 0x00000400;
|
||||
private const int SigdnNormalDisplay = 0;
|
||||
|
||||
[Flags]
|
||||
public enum Shcontf : int
|
||||
{
|
||||
Folders = 0x0020,
|
||||
NonFolders = 0x0040,
|
||||
IncludeHidden = 0x0080,
|
||||
InitOnFirstNext = 0x0100,
|
||||
NetPrinterSearch = 0x0200,
|
||||
Shareable = 0x0400,
|
||||
Storage = 0x0800,
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Justification = "Targeting Windows (X86/AMD64/ARM) only")]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Strret
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int UType;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public IntPtr POleStr;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public IntPtr PStr;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public int UOffset;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public IntPtr CStr;
|
||||
}
|
||||
|
||||
public enum Shgno
|
||||
{
|
||||
Normal = 0x0000,
|
||||
InFolder = 0x0001,
|
||||
ForEditing = 0x1000,
|
||||
ForAddressBar = 0x4000,
|
||||
ForParsing = 0x8000,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct SHELLDETAILS
|
||||
{
|
||||
public int Fmt;
|
||||
public int CxChar;
|
||||
public Strret Str;
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid(IIDIShellFolder2)]
|
||||
public interface IShellFolder2
|
||||
{
|
||||
[PreserveSig]
|
||||
int ParseDisplayName(IntPtr hwnd, IntPtr pbc, [MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, ref int pchEaten, out IntPtr ppidl, ref int pdwAttributes);
|
||||
|
||||
[PreserveSig]
|
||||
int EnumObjects(IntPtr hwnd, Shcontf grfFlags, out IntPtr enumIDList);
|
||||
|
||||
[PreserveSig]
|
||||
int BindToObject(IntPtr pidl, IntPtr pbc, ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int BindToStorage(IntPtr pidl, IntPtr pbc, ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2);
|
||||
|
||||
[PreserveSig]
|
||||
int CreateViewObject(IntPtr hwndOwner, Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetAttributesOf(int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, ref IntPtr rgfInOut);
|
||||
|
||||
[PreserveSig]
|
||||
int GetUIObjectOf(IntPtr hwndOwner, int cidl, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, ref Guid riid, IntPtr rgfReserved, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDisplayNameOf(IntPtr pidl, Shgno uFlags, out Strret lpName);
|
||||
|
||||
[PreserveSig]
|
||||
int SetNameOf(IntPtr hwnd, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string pszName, int uFlags, out IntPtr ppidlOut);
|
||||
|
||||
[PreserveSig]
|
||||
int EnumSearches(out IntPtr ppenum);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDefaultColumn(int dwReserved, ref IntPtr pSort, out IntPtr pDisplay);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDefaultColumnState(int iColumn, out IntPtr pcsFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDefaultSearchGUID(out IntPtr guid);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDetailsEx(IntPtr pidl, IntPtr pscid, out IntPtr pv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDetailsOf(IntPtr pidl, int iColumn, ref SHELLDETAILS psd);
|
||||
|
||||
[PreserveSig]
|
||||
int MapColumnToSCID(int icolumn, IntPtr pscid);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid(IIDIEnumIDList)]
|
||||
public interface IEnumIDList
|
||||
{
|
||||
[PreserveSig]
|
||||
int Next(int celt, out IntPtr rgelt, out int pceltFetched);
|
||||
|
||||
[PreserveSig]
|
||||
int Skip(int celt);
|
||||
|
||||
[PreserveSig]
|
||||
int Reset();
|
||||
|
||||
[PreserveSig]
|
||||
int Clone(out IEnumIDList ppenum);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid(IIDIShellItem)]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IShellItem
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
int GetDisplayName([In] int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetAttributes([In] int sfgaoMask, out int psfgaoAttribs);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct SHFILEINFO
|
||||
{
|
||||
public IntPtr HIcon;
|
||||
public int IIcon;
|
||||
public uint DwAttributes;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string SzDisplayName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
|
||||
public string SzTypeName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Blob
|
||||
{
|
||||
public int CbSize;
|
||||
public IntPtr PBlobData;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct PropVariant
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public short Vt;
|
||||
[FieldOffset(2)]
|
||||
public short WReserved1;
|
||||
[FieldOffset(4)]
|
||||
public short WReserved2;
|
||||
[FieldOffset(6)]
|
||||
public short WReserved3;
|
||||
[FieldOffset(8)]
|
||||
public sbyte CVal;
|
||||
[FieldOffset(8)]
|
||||
public byte BVal;
|
||||
[FieldOffset(8)]
|
||||
public short IVal;
|
||||
[FieldOffset(8)]
|
||||
public ushort UiVal;
|
||||
[FieldOffset(8)]
|
||||
public int LVal;
|
||||
[FieldOffset(8)]
|
||||
public uint UlVal;
|
||||
[FieldOffset(8)]
|
||||
public int IntVal;
|
||||
[FieldOffset(8)]
|
||||
public uint UintVal;
|
||||
[FieldOffset(8)]
|
||||
public long HVal;
|
||||
[FieldOffset(8)]
|
||||
public ulong UhVal;
|
||||
[FieldOffset(8)]
|
||||
public float FltVal;
|
||||
[FieldOffset(8)]
|
||||
public double DblVal;
|
||||
[FieldOffset(8)]
|
||||
public bool BoolVal;
|
||||
[FieldOffset(8)]
|
||||
public int Scode;
|
||||
[FieldOffset(8)]
|
||||
public DateTime Date;
|
||||
[FieldOffset(8)]
|
||||
public FileTime Filetime;
|
||||
[FieldOffset(8)]
|
||||
public Blob Blob;
|
||||
[FieldOffset(8)]
|
||||
public IntPtr P;
|
||||
[FieldOffset(8)]
|
||||
public CALPWSTR Calpwstr;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct CALPWSTR
|
||||
{
|
||||
public uint CElems;
|
||||
public IntPtr PElems;
|
||||
}
|
||||
|
||||
public enum SIGDN : uint
|
||||
{
|
||||
NORMALDISPLAY = 0,
|
||||
PARENTRELATIVEPARSING = 0x80018001,
|
||||
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
|
||||
DESKTOPABSOLUTEPARSING = 0x80028000,
|
||||
PARENTRELATIVEEDITING = 0x80031001,
|
||||
DESKTOPABSOLUTEEDITING = 0x8004c000,
|
||||
FILESYSPATH = 0x80058000,
|
||||
URL = 0x80068000,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SYSTEMTIME
|
||||
{
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Year;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Month;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short DayOfWeek;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Day;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Hour;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Minute;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Second;
|
||||
[MarshalAs(UnmanagedType.U2)]
|
||||
public short Milliseconds;
|
||||
|
||||
public SYSTEMTIME(DateTime dt)
|
||||
{
|
||||
dt = dt.ToUniversalTime(); // SetSystemTime expects the SYSTEMTIME in UTC
|
||||
Year = (short)dt.Year;
|
||||
Month = (short)dt.Month;
|
||||
DayOfWeek = (short)dt.DayOfWeek;
|
||||
Day = (short)dt.Day;
|
||||
Hour = (short)dt.Hour;
|
||||
Minute = (short)dt.Minute;
|
||||
Second = (short)dt.Second;
|
||||
Milliseconds = (short)dt.Millisecond;
|
||||
}
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("7E9FB0D3-919F-4307-AB2E-9B1860310C93")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IShellItem2 : IShellItem
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
new void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
new void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
new int GetDisplayName([In] int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
new void GetAttributes([In] int sfgaoMask, out int psfgaoAttribs);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
new void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
|
||||
|
||||
[PreserveSig]
|
||||
int GetPropertyStore(int flags, ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetPropertyStoreWithCreateObject(ref PropertyStoreFlags flags, ref IntPtr punkFactory, ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetPropertyStoreForKeys(ref PropertyKey keys, uint cKeys, ref PropertyStoreFlags flags, ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetPropertyDescriptionList(ref PropertyKey key, ref Guid riid, out IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int Update(ref IntPtr pbc);
|
||||
|
||||
[PreserveSig]
|
||||
int GetProperty(ref PropertyKey key, out PropVariant pPropVar);
|
||||
|
||||
[PreserveSig]
|
||||
int GetCLSID(ref PropertyKey key, out Guid clsid);
|
||||
|
||||
[PreserveSig]
|
||||
int GetFileTime(ref PropertyKey key, out FileTime pft);
|
||||
|
||||
[PreserveSig]
|
||||
int GetInt32(ref PropertyKey key, out int pi);
|
||||
|
||||
[PreserveSig]
|
||||
int GetString(ref PropertyKey key, [MarshalAs(UnmanagedType.LPWStr)] string ppsz);
|
||||
|
||||
[PreserveSig]
|
||||
int GetUint32(ref PropertyKey key, out uint pui);
|
||||
|
||||
[PreserveSig]
|
||||
int GetUint64(ref PropertyKey key, out uint pull);
|
||||
|
||||
[PreserveSig]
|
||||
int GetBool(ref PropertyKey key, bool pf);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FileTime
|
||||
{
|
||||
public int DWHighDateTime;
|
||||
public int DWLowDateTime;
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IPropertyStore
|
||||
{
|
||||
void GetCount(out uint propertyCount);
|
||||
|
||||
void GetAt(uint iProp, out PropertyKey pkey);
|
||||
|
||||
void GetValue(ref PropertyKey key, out PropVariant pv);
|
||||
|
||||
void SetValue(ref PropertyKey key, ref PropVariant pv);
|
||||
|
||||
void Commit();
|
||||
}
|
||||
|
||||
public enum PropertyStoreFlags
|
||||
{
|
||||
DEFAULT = 0x00000000,
|
||||
HANDLERPROPERTIESONLY = 0x00000001,
|
||||
READWRITE = 0x00000002,
|
||||
TEMPORARY = 0x00000004,
|
||||
FASTPROPERTIESONLY = 0x00000008,
|
||||
OPENSLOWITEM = 0x00000010,
|
||||
DELAYCREATION = 0x00000020,
|
||||
BESTEFFORT = 0x00000040,
|
||||
NO_OPLOCK = 0x00000080,
|
||||
PREFERQUERYPROPERTIES = 0x00000100,
|
||||
EXTRINSICPROPERTIES = 0x00000200,
|
||||
EXTRINSICPROPERTIESONLY = 0x00000400,
|
||||
MASK_VALID = 0x000007ff,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct PropertyKey
|
||||
{
|
||||
public Guid FormatId;
|
||||
public int PropertyId;
|
||||
|
||||
public PropertyKey(Guid guid, int propertyId)
|
||||
{
|
||||
this.FormatId = guid;
|
||||
this.PropertyId = propertyId;
|
||||
}
|
||||
|
||||
public PropertyKey(uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h, uint i, uint j, uint k, int propertyId)
|
||||
: this(new Guid((uint)a, (ushort)b, (ushort)c, (byte)d, (byte)e, (byte)f, (byte)g, (byte)h, (byte)i, (byte)j, (byte)k), propertyId)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if ((obj == null) || !(obj is PropertyKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PropertyKey pk = (PropertyKey)obj;
|
||||
|
||||
return FormatId.Equals(pk.FormatId) && (PropertyId == pk.PropertyId);
|
||||
}
|
||||
|
||||
public static bool operator ==(PropertyKey a, PropertyKey b)
|
||||
{
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.FormatId == b.FormatId && a.PropertyId == b.PropertyId;
|
||||
}
|
||||
|
||||
public static bool operator !=(PropertyKey a, PropertyKey b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return FormatId.GetHashCode() ^ PropertyId;
|
||||
}
|
||||
|
||||
// File properties: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsp/2dbe759c-c955-4770-a545-e46d7f6332ed
|
||||
public static readonly PropertyKey ImageHorizontalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 3);
|
||||
public static readonly PropertyKey ImageVerticalSize = new PropertyKey(new Guid(0x6444048F, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 4);
|
||||
public static readonly PropertyKey FileSizeBytes = new PropertyKey(new Guid(0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac), 12);
|
||||
public static readonly PropertyKey FileType = new PropertyKey(new Guid(0xd5cdd502, 0x2e9c, 0x101b, 0x93, 0x97, 0x08, 0x00, 0x2b, 0x2c, 0xf9, 0xae), 26);
|
||||
}
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int StrRetToBuf(ref Strret pstr, IntPtr pidl, StringBuilder pszBuf, int cchBuf);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a specified string from a specified resource via an indirect string
|
||||
/// </summary>
|
||||
/// <param name="pszSource">An indirect string representing the desired string from a specified resource file</param>
|
||||
/// <param name="pszOutBuf">An output string, which receives the native function's outputted resource string</param>
|
||||
/// <param name="cchOutBuf">The buffer size to hold the output string, in characters</param>
|
||||
/// <param name="ppvReserved">A reserved pointer (void**)</param>
|
||||
/// <returns>Returns an HRESULT representing the success/failure of the native function</returns>
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern uint SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out string pszPath);
|
||||
|
||||
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int SHGetKnownFolderItem([In] ref Guid rfid, [In] int dwFlags, [In] IntPtr hToken, [In] ref Guid riid, [Out] out IntPtr ppv);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern int SHGetPropertyStoreFromParsingName(
|
||||
string pszPath,
|
||||
IntPtr zeroWorks,
|
||||
PropertyStoreFlags flags,
|
||||
ref Guid iIdPropStore,
|
||||
[Out] out IPropertyStore propertyStore);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
|
||||
private static extern void SHCreateItemFromParsingName(
|
||||
[In][MarshalAs(UnmanagedType.LPWStr)] string pszPath,
|
||||
[In] IntPtr pbc,
|
||||
[In][MarshalAs(UnmanagedType.LPStruct)] Guid riid,
|
||||
[Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem2 ppv);
|
||||
}
|
||||
}
|
||||
16
src/modules/peek/Peek.Common/Peek.Common.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>Peek.Common</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
|
||||
<!-- Licensed under the MIT License. See LICENSE in the project root for license information. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.Controls.BrowserControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Peek.FilePreviewer.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<controls:WebView2 x:Name="PreviewBrowser"
|
||||
Loaded="PreviewWV2_Loaded"
|
||||
NavigationStarting="PreviewBrowser_NavigationStarting"
|
||||
NavigationCompleted="PreviewWV2_NavigationCompleted"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,126 @@
|
||||
// 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 Peek.FilePreviewer.Controls
|
||||
{
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Windows.System;
|
||||
|
||||
public sealed partial class BrowserControl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper private Uri where we cache the last navigated page
|
||||
/// so we can redirect internal PDF or Webpage links to external
|
||||
/// webbrowser, avoiding WebView internal navigation.
|
||||
/// </summary>
|
||||
private Uri? _navigatedUri;
|
||||
|
||||
public delegate void NavigationCompletedHandler(WebView2? sender, CoreWebView2NavigationCompletedEventArgs? args);
|
||||
|
||||
public event NavigationCompletedHandler? NavigationCompleted;
|
||||
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
nameof(Source),
|
||||
typeof(Uri),
|
||||
typeof(BrowserControl),
|
||||
new PropertyMetadata(null, new PropertyChangedCallback((d, e) => ((BrowserControl)d).SourcePropertyChanged())));
|
||||
|
||||
public static readonly DependencyProperty IsNavigationCompletedProperty = DependencyProperty.Register(
|
||||
nameof(IsNavigationCompleted),
|
||||
typeof(bool),
|
||||
typeof(BrowserControl),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
public Uri? Source
|
||||
{
|
||||
get { return (Uri)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsNavigationCompleted
|
||||
{
|
||||
get { return (bool)GetValue(IsNavigationCompletedProperty); }
|
||||
set { SetValue(IsNavigationCompletedProperty, value); }
|
||||
}
|
||||
|
||||
public BrowserControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the to the <see cref="Uri"/> set in <see cref="Source"/>.
|
||||
/// Calling <see cref="Navigate"/> will always trigger a navigation/refresh
|
||||
/// even if web target file is the same.
|
||||
/// </summary>
|
||||
public void Navigate()
|
||||
{
|
||||
IsNavigationCompleted = false;
|
||||
_navigatedUri = null;
|
||||
|
||||
if (Source != null)
|
||||
{
|
||||
/* CoreWebView2.Navigate() will always trigger a navigation even if the content/URI is the same.
|
||||
* Use WebView2.Source to avoid re-navigating to the same content. */
|
||||
PreviewBrowser.CoreWebView2.Navigate(Source.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void SourcePropertyChanged()
|
||||
{
|
||||
Navigate();
|
||||
}
|
||||
|
||||
private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
await PreviewBrowser.EnsureCoreWebView2Async();
|
||||
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDevToolsEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreHostObjectsAllowed = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.IsGeneralAutofillEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.IsPasswordAutosaveEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.IsScriptEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.IsWebMessageEnabled = false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: exception / telemetry log?
|
||||
}
|
||||
}
|
||||
|
||||
private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
|
||||
{
|
||||
if (_navigatedUri == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In case user starts or tries to navigate from within the HTML file we launch default web browser for navigation.
|
||||
if (args.Uri != null && args.Uri != _navigatedUri?.ToString() && args.IsUserInitiated)
|
||||
{
|
||||
args.Cancel = true;
|
||||
await Launcher.LaunchUriAsync(new Uri(args.Uri));
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewWV2_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
|
||||
{
|
||||
if (args.IsSuccess)
|
||||
{
|
||||
IsNavigationCompleted = true;
|
||||
|
||||
_navigatedUri = Source;
|
||||
}
|
||||
|
||||
NavigationCompleted?.Invoke(sender, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
Normal file
@@ -0,0 +1,44 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.FilePreview"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Peek.FilePreviewer.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Peek.FilePreviewer"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:previewers="using:Peek.FilePreviewer.Previewers"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<ProgressRing
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsActive="{x:Bind MatchPreviewState(Previewer.State, previewers:PreviewState.Loading), Mode=OneWay}" />
|
||||
|
||||
<Image
|
||||
x:Name="ImagePreview"
|
||||
Source="{x:Bind BitmapPreviewer.Preview, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind ImageInfoTooltip, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(BitmapPreviewer, Previewer.State), Mode=OneWay}" />
|
||||
|
||||
<controls:BrowserControl
|
||||
x:Name="BrowserPreview"
|
||||
x:Load="True"
|
||||
NavigationCompleted="PreviewBrowser_NavigationCompleted"
|
||||
Source="{x:Bind BrowserPreviewer.Preview, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(BrowserPreviewer, Previewer.State), Mode=OneWay, FallbackValue=Collapsed}" />
|
||||
|
||||
<local:UnsupportedFilePreview
|
||||
x:Name="UnsupportedFilePreview"
|
||||
DateModified="{x:Bind UnsupportedFilePreviewer.DateModified, Mode=OneWay}"
|
||||
FileName="{x:Bind UnsupportedFilePreviewer.FileName, Mode=OneWay}"
|
||||
FileSize="{x:Bind UnsupportedFilePreviewer.FileSize, Mode=OneWay}"
|
||||
FileType="{x:Bind UnsupportedFilePreviewer.FileType, Mode=OneWay}"
|
||||
IconPreview="{x:Bind UnsupportedFilePreviewer.IconPreview, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(UnsupportedFilePreviewer, Previewer.State), Mode=OneWay}" />
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
175
src/modules/peek/Peek.FilePreviewer/FilePreview.xaml.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
// 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 Peek.FilePreviewer
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common.Helpers;
|
||||
using Peek.Common.Models;
|
||||
using Peek.FilePreviewer.Models;
|
||||
using Peek.FilePreviewer.Previewers;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Windows.Foundation;
|
||||
|
||||
[INotifyPropertyChanged]
|
||||
public sealed partial class FilePreview : UserControl
|
||||
{
|
||||
private readonly PreviewerFactory previewerFactory = new ();
|
||||
|
||||
public event EventHandler<PreviewSizeChangedArgs>? PreviewSizeChanged;
|
||||
|
||||
public static readonly DependencyProperty FilesProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(File),
|
||||
typeof(File),
|
||||
typeof(FilePreview),
|
||||
new PropertyMetadata(false, async (d, e) => await ((FilePreview)d).OnFilePropertyChanged()));
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(BitmapPreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(BrowserPreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
|
||||
|
||||
private IPreviewer? previewer;
|
||||
|
||||
[ObservableProperty]
|
||||
private string imageInfoTooltip = ResourceLoader.GetForViewIndependentUse().GetString("PreviewTooltip_Blank");
|
||||
|
||||
public FilePreview()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void Previewer_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
// Fallback on DefaultPreviewer if we fail to load the correct Preview
|
||||
if (e.PropertyName == nameof(IPreviewer.State))
|
||||
{
|
||||
if (Previewer?.State == PreviewState.Error)
|
||||
{
|
||||
Previewer = previewerFactory.CreateDefaultPreviewer(File);
|
||||
await UpdatePreviewAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IBitmapPreviewer? BitmapPreviewer => Previewer as IBitmapPreviewer;
|
||||
|
||||
public IBrowserPreviewer? BrowserPreviewer => Previewer as IBrowserPreviewer;
|
||||
|
||||
public bool IsImageVisible => BitmapPreviewer != null;
|
||||
|
||||
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
|
||||
|
||||
public bool IsUnsupportedPreviewVisible => UnsupportedFilePreviewer != null;
|
||||
|
||||
public File File
|
||||
{
|
||||
get => (File)GetValue(FilesProperty);
|
||||
set => SetValue(FilesProperty, value);
|
||||
}
|
||||
|
||||
public bool MatchPreviewState(PreviewState? value, PreviewState stateToMatch)
|
||||
{
|
||||
return value == stateToMatch;
|
||||
}
|
||||
|
||||
public Visibility IsPreviewVisible(IPreviewer? previewer, PreviewState? state)
|
||||
{
|
||||
var isValidPreview = previewer != null && MatchPreviewState(state, PreviewState.Loaded);
|
||||
return isValidPreview ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async Task OnFilePropertyChanged()
|
||||
{
|
||||
// TODO: track and cancel existing async preview tasks
|
||||
// https://github.com/microsoft/PowerToys/issues/22480
|
||||
if (File == null)
|
||||
{
|
||||
Previewer = null;
|
||||
ImagePreview.Visibility = Visibility.Collapsed;
|
||||
BrowserPreview.Visibility = Visibility.Collapsed;
|
||||
UnsupportedFilePreview.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
Previewer = previewerFactory.Create(File);
|
||||
await UpdatePreviewAsync();
|
||||
}
|
||||
|
||||
private async Task UpdatePreviewAsync()
|
||||
{
|
||||
if (Previewer != null)
|
||||
{
|
||||
var size = await Previewer.GetPreviewSizeAsync();
|
||||
SizeFormat windowSizeFormat = UnsupportedFilePreviewer != null ? SizeFormat.Percentage : SizeFormat.Pixels;
|
||||
PreviewSizeChanged?.Invoke(this, new PreviewSizeChangedArgs(size, windowSizeFormat));
|
||||
await Previewer.LoadPreviewAsync();
|
||||
}
|
||||
|
||||
await UpdateImageTooltipAsync();
|
||||
}
|
||||
|
||||
partial void OnPreviewerChanging(IPreviewer? value)
|
||||
{
|
||||
if (Previewer != null)
|
||||
{
|
||||
Previewer.PropertyChanged -= Previewer_PropertyChanged;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
value.PropertyChanged += Previewer_PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewBrowser_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
|
||||
{
|
||||
// Once browser has completed navigation it is ready to be visible
|
||||
if (BrowserPreviewer != null)
|
||||
{
|
||||
BrowserPreviewer.State = PreviewState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateImageTooltipAsync()
|
||||
{
|
||||
if (File == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch and format available file properties
|
||||
var sb = new StringBuilder();
|
||||
|
||||
string fileNameFormatted = ReadableStringHelper.FormatResourceString("PreviewTooltip_FileName", File.FileName);
|
||||
sb.Append(fileNameFormatted);
|
||||
|
||||
string fileType = await PropertyHelper.GetFileType(File.Path);
|
||||
string fileTypeFormatted = string.IsNullOrEmpty(fileType) ? string.Empty : "\n" + ReadableStringHelper.FormatResourceString("PreviewTooltip_FileType", fileType);
|
||||
sb.Append(fileTypeFormatted);
|
||||
|
||||
string dateModified = File.DateModified.ToString();
|
||||
string dateModifiedFormatted = string.IsNullOrEmpty(dateModified) ? string.Empty : "\n" + ReadableStringHelper.FormatResourceString("PreviewTooltip_DateModified", dateModified);
|
||||
sb.Append(dateModifiedFormatted);
|
||||
|
||||
Size dimensions = await PropertyHelper.GetImageSize(File.Path);
|
||||
string dimensionsFormatted = dimensions.IsEmpty ? string.Empty : "\n" + ReadableStringHelper.FormatResourceString("PreviewTooltip_Dimensions", dimensions.Width, dimensions.Height);
|
||||
sb.Append(dimensionsFormatted);
|
||||
|
||||
ulong bytes = await PropertyHelper.GetFileSizeInBytes(File.Path);
|
||||
string fileSize = ReadableStringHelper.BytesToReadableString(bytes);
|
||||
string fileSizeFormatted = string.IsNullOrEmpty(fileSize) ? string.Empty : "\n" + ReadableStringHelper.FormatResourceString("PreviewTooltip_FileSize", fileSize);
|
||||
sb.Append(fileSizeFormatted);
|
||||
|
||||
ImageInfoTooltip = sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Peek.FilePreviewer.Models
|
||||
{
|
||||
using Windows.Foundation;
|
||||
|
||||
public enum SizeFormat
|
||||
{
|
||||
Pixels,
|
||||
Percentage,
|
||||
}
|
||||
|
||||
public class PreviewSizeChangedArgs
|
||||
{
|
||||
public PreviewSizeChangedArgs(Size windowSizeRequested, SizeFormat sizeFormat = SizeFormat.Pixels)
|
||||
{
|
||||
WindowSizeRequested = windowSizeRequested;
|
||||
WindowSizeFormat = sizeFormat;
|
||||
}
|
||||
|
||||
public Size WindowSizeRequested { get; init; }
|
||||
|
||||
public SizeFormat WindowSizeFormat { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>Peek.FilePreviewer</RootNamespace>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\BrowserControl.xaml" />
|
||||
<None Remove="FilePreview.xaml" />
|
||||
<None Remove="UnsupportedFilePreview.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\Peek.Common\Peek.Common.csproj" />
|
||||
<ProjectReference Include="..\WIC\WIC.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="FilePreview.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="UnsupportedFilePreview.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\BrowserControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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 Peek.FilePreviewer.Previewers.Helpers
|
||||
{
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using Peek.Common;
|
||||
using Peek.Common.Models;
|
||||
|
||||
public static class IconHelper
|
||||
{
|
||||
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
|
||||
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
|
||||
|
||||
public static HResult GetIcon(string fileName, out IntPtr hbitmap)
|
||||
{
|
||||
Guid shellItem2Guid = new Guid(IShellItem2Guid);
|
||||
int retCode = NativeMethods.SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out IShellItem nativeShellItem);
|
||||
|
||||
if (retCode != 0)
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(retCode)!;
|
||||
}
|
||||
|
||||
NativeSize large = new NativeSize { Width = 256, Height = 256 };
|
||||
var options = ThumbnailOptions.BiggerSizeOk | ThumbnailOptions.IconOnly;
|
||||
|
||||
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(large, options, out hbitmap);
|
||||
|
||||
if (hr != HResult.Ok)
|
||||
{
|
||||
// TODO: fallback to a generic icon
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(nativeShellItem);
|
||||
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
|
||||
public interface IBitmapPreviewer : IPreviewer
|
||||
{
|
||||
public BitmapSource? Preview { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
|
||||
public interface IBrowserPreviewer : IPreviewer
|
||||
{
|
||||
public Uri? Preview { get; }
|
||||
}
|
||||
}
|
||||
30
src/modules/peek/Peek.FilePreviewer/Previewers/IPreviewer.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
|
||||
public interface IPreviewer : INotifyPropertyChanged
|
||||
{
|
||||
PreviewState State { get; set; }
|
||||
|
||||
public static bool IsFileTypeSupported(string fileExt) => throw new NotImplementedException();
|
||||
|
||||
public Task<Size> GetPreviewSizeAsync();
|
||||
|
||||
Task LoadPreviewAsync();
|
||||
}
|
||||
|
||||
public enum PreviewState
|
||||
{
|
||||
Uninitialized,
|
||||
Loading,
|
||||
Loaded,
|
||||
Error,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
|
||||
public interface IUnsupportedFilePreviewer : IPreviewer
|
||||
{
|
||||
public BitmapSource? IconPreview { get; }
|
||||
|
||||
public string? FileName { get; }
|
||||
|
||||
public string? FileType { get; }
|
||||
|
||||
public string? FileSize { get; }
|
||||
|
||||
public string? DateModified { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Peek.Common
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Peek.Common.Models;
|
||||
|
||||
public static class NativeMethods
|
||||
{
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern int SHCreateItemFromParsingName(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string path,
|
||||
IntPtr pbc,
|
||||
ref Guid riid,
|
||||
[MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteObject(IntPtr hObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Peek.Common.Models;
|
||||
using Windows.Foundation;
|
||||
|
||||
public static class PropertyHelper
|
||||
{
|
||||
public static Task<Size> GetImageSize(string filePath)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var propertyStore = PropertyStoreShellApi.GetPropertyStoreFromPath(filePath, PropertyStoreShellApi.PropertyStoreFlags.OPENSLOWITEM);
|
||||
if (propertyStore != null)
|
||||
{
|
||||
var width = (int)PropertyStoreShellApi.GetUIntFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.ImageHorizontalSize);
|
||||
var height = (int)PropertyStoreShellApi.GetUIntFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.ImageVerticalSize);
|
||||
|
||||
Marshal.ReleaseComObject(propertyStore);
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
return Size.Empty;
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<ulong> GetFileSizeInBytes(string filePath)
|
||||
{
|
||||
ulong bytes = 0;
|
||||
var propertyStore = PropertyStoreShellApi.GetPropertyStoreFromPath(filePath, PropertyStoreShellApi.PropertyStoreFlags.OPENSLOWITEM);
|
||||
if (propertyStore != null)
|
||||
{
|
||||
bytes = PropertyStoreShellApi.GetULongFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.FileSizeBytes);
|
||||
|
||||
Marshal.ReleaseComObject(propertyStore);
|
||||
}
|
||||
|
||||
return Task.FromResult(bytes);
|
||||
}
|
||||
|
||||
public static Task<string> GetFileType(string filePath)
|
||||
{
|
||||
var type = string.Empty;
|
||||
var propertyStore = PropertyStoreShellApi.GetPropertyStoreFromPath(filePath, PropertyStoreShellApi.PropertyStoreFlags.OPENSLOWITEM);
|
||||
if (propertyStore != null)
|
||||
{
|
||||
// TODO: find a way to get user friendly description
|
||||
type = PropertyStoreShellApi.GetStringFromPropertyStore(propertyStore, PropertyStoreShellApi.PropertyKey.FileType);
|
||||
|
||||
Marshal.ReleaseComObject(propertyStore);
|
||||
}
|
||||
|
||||
return Task.FromResult(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common;
|
||||
using Peek.Common.Models;
|
||||
|
||||
public static class ThumbnailHelper
|
||||
{
|
||||
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
|
||||
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
|
||||
|
||||
public static readonly NativeSize HighQualityThumbnailSize = new NativeSize { Width = 720, Height = 720, };
|
||||
public static readonly NativeSize LowQualityThumbnailSize = new NativeSize { Width = 256, Height = 256, };
|
||||
|
||||
private static readonly NativeSize FallBackThumbnailSize = new NativeSize { Width = 96, Height = 96, };
|
||||
private static readonly NativeSize LastFallBackThumbnailSize = new NativeSize { Width = 32, Height = 32, };
|
||||
|
||||
private static readonly List<NativeSize> ThumbnailFallBackSizes = new List<NativeSize>
|
||||
{
|
||||
HighQualityThumbnailSize,
|
||||
LowQualityThumbnailSize,
|
||||
FallBackThumbnailSize,
|
||||
LastFallBackThumbnailSize,
|
||||
};
|
||||
|
||||
// TODO: Add a re-try system if there is no thumbnail of requested size.
|
||||
public static HResult GetThumbnail(string filename, out IntPtr hbitmap, NativeSize thumbnailSize)
|
||||
{
|
||||
Guid shellItem2Guid = new Guid(IShellItem2Guid);
|
||||
int retCode = NativeMethods.SHCreateItemFromParsingName(filename, IntPtr.Zero, ref shellItem2Guid, out IShellItem nativeShellItem);
|
||||
|
||||
if (retCode != 0)
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(retCode)!;
|
||||
}
|
||||
|
||||
var options = ThumbnailOptions.BiggerSizeOk | ThumbnailOptions.ThumbnailOnly | ThumbnailOptions.ScaleUp;
|
||||
|
||||
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(thumbnailSize, options, out hbitmap);
|
||||
|
||||
// Try to get thumbnail using the fallback sizes order
|
||||
if (hr != HResult.Ok)
|
||||
{
|
||||
var currentThumbnailFallBackIndex = ThumbnailFallBackSizes.IndexOf(thumbnailSize);
|
||||
var nextThumbnailFallBackIndex = currentThumbnailFallBackIndex + 1;
|
||||
if (nextThumbnailFallBackIndex < ThumbnailFallBackSizes.Count - 1)
|
||||
{
|
||||
hr = GetThumbnail(filename, out hbitmap, ThumbnailFallBackSizes[nextThumbnailFallBackIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(nativeShellItem);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
public static async Task<BitmapImage?> GetThumbnailAsync(string path, uint size)
|
||||
{
|
||||
BitmapImage? bitmapImage = null;
|
||||
|
||||
// preview image
|
||||
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(path);
|
||||
if (file == null)
|
||||
{
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
var imageStream = await file.GetThumbnailAsync(
|
||||
Windows.Storage.FileProperties.ThumbnailMode.SingleItem,
|
||||
size,
|
||||
Windows.Storage.FileProperties.ThumbnailOptions.None);
|
||||
|
||||
if (imageStream == null)
|
||||
{
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
bitmapImage = new BitmapImage();
|
||||
bitmapImage.SetSource(imageStream);
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using WIC;
|
||||
|
||||
public static class WICHelper
|
||||
{
|
||||
public static Task<Windows.Foundation.Size> GetImageSize(string filePath)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
// TODO: Find a way to get file metadata without hydrating files. Look into Shell API/Windows Property System, e.g., IPropertyStore
|
||||
IWICImagingFactory factory = (IWICImagingFactory)new WICImagingFactoryClass();
|
||||
var decoder = factory.CreateDecoderFromFilename(filePath, IntPtr.Zero, StreamAccessMode.GENERIC_READ, WICDecodeOptions.WICDecodeMetadataCacheOnLoad);
|
||||
var frame = decoder?.GetFrame(0);
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
// TODO: Respect EXIF data and find correct orientation
|
||||
frame?.GetSize(out width, out height);
|
||||
|
||||
return new Windows.Foundation.Size(width, height);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common;
|
||||
using Peek.Common.Extensions;
|
||||
using Windows.Foundation;
|
||||
using File = Peek.Common.Models.File;
|
||||
|
||||
public partial class ImagePreviewer : ObservableObject, IBitmapPreviewer, IDisposable
|
||||
{
|
||||
[ObservableProperty]
|
||||
private BitmapSource? preview;
|
||||
|
||||
[ObservableProperty]
|
||||
private PreviewState state;
|
||||
|
||||
public ImagePreviewer(File file)
|
||||
{
|
||||
File = file;
|
||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
private File File { get; }
|
||||
|
||||
private DispatcherQueue Dispatcher { get; }
|
||||
|
||||
private Task<bool>? LowQualityThumbnailTask { get; set; }
|
||||
|
||||
private Task<bool>? HighQualityThumbnailTask { get; set; }
|
||||
|
||||
private Task<bool>? FullQualityImageTask { get; set; }
|
||||
|
||||
private bool IsHighQualityThumbnailLoaded => HighQualityThumbnailTask?.Status == TaskStatus.RanToCompletion;
|
||||
|
||||
private bool IsFullImageLoaded => FullQualityImageTask?.Status == TaskStatus.RanToCompletion;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task<Size> GetPreviewSizeAsync()
|
||||
{
|
||||
var propertyImageSize = await PropertyHelper.GetImageSize(File.Path);
|
||||
if (propertyImageSize != Size.Empty)
|
||||
{
|
||||
return propertyImageSize;
|
||||
}
|
||||
|
||||
return await WICHelper.GetImageSize(File.Path);
|
||||
}
|
||||
|
||||
public async Task LoadPreviewAsync()
|
||||
{
|
||||
State = PreviewState.Loading;
|
||||
|
||||
LowQualityThumbnailTask = LoadLowQualityThumbnailAsync();
|
||||
HighQualityThumbnailTask = LoadHighQualityThumbnailAsync();
|
||||
FullQualityImageTask = LoadFullQualityImageAsync();
|
||||
|
||||
await Task.WhenAll(LowQualityThumbnailTask, HighQualityThumbnailTask, FullQualityImageTask);
|
||||
|
||||
if (Preview == null && HasFailedLoadingPreview())
|
||||
{
|
||||
State = PreviewState.Error;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Preview))
|
||||
{
|
||||
if (Preview != null)
|
||||
{
|
||||
State = PreviewState.Loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task<bool> LoadLowQualityThumbnailAsync()
|
||||
{
|
||||
return TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsFullImageLoaded && !IsHighQualityThumbnailLoaded)
|
||||
{
|
||||
var hr = ThumbnailHelper.GetThumbnail(Path.GetFullPath(File.Path), out IntPtr hbitmap, ThumbnailHelper.LowQualityThumbnailSize);
|
||||
if (hr != Common.Models.HResult.Ok)
|
||||
{
|
||||
Debug.WriteLine("Error loading low quality thumbnail - hresult: " + hr);
|
||||
|
||||
throw new ArgumentNullException(nameof(hbitmap));
|
||||
}
|
||||
|
||||
await Dispatcher.RunOnUiThread(async () =>
|
||||
{
|
||||
var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap);
|
||||
Preview = thumbnailBitmap;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Task<bool> LoadHighQualityThumbnailAsync()
|
||||
{
|
||||
return TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsFullImageLoaded)
|
||||
{
|
||||
var hr = ThumbnailHelper.GetThumbnail(Path.GetFullPath(File.Path), out IntPtr hbitmap, ThumbnailHelper.HighQualityThumbnailSize);
|
||||
if (hr != Common.Models.HResult.Ok)
|
||||
{
|
||||
Debug.WriteLine("Error loading high quality thumbnail - hresult: " + hr);
|
||||
|
||||
throw new ArgumentNullException(nameof(hbitmap));
|
||||
}
|
||||
|
||||
await Dispatcher.RunOnUiThread(async () =>
|
||||
{
|
||||
var thumbnailBitmap = await GetBitmapFromHBitmapAsync(hbitmap);
|
||||
Preview = thumbnailBitmap;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Task<bool> LoadFullQualityImageAsync()
|
||||
{
|
||||
return TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Check if this is performant
|
||||
await Dispatcher.RunOnUiThread(async () =>
|
||||
{
|
||||
var bitmap = await GetFullBitmapFromPathAsync(File.Path);
|
||||
Preview = bitmap;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private bool HasFailedLoadingPreview()
|
||||
{
|
||||
var hasFailedLoadingLowQualityThumbnail = !(LowQualityThumbnailTask?.Result ?? true);
|
||||
var hasFailedLoadingHighQualityThumbnail = !(HighQualityThumbnailTask?.Result ?? true);
|
||||
var hasFailedLoadingFullQualityImage = !(FullQualityImageTask?.Result ?? true);
|
||||
|
||||
return hasFailedLoadingLowQualityThumbnail && hasFailedLoadingHighQualityThumbnail && hasFailedLoadingFullQualityImage;
|
||||
}
|
||||
|
||||
private static async Task<BitmapImage> GetFullBitmapFromPathAsync(string path)
|
||||
{
|
||||
var bitmap = new BitmapImage();
|
||||
using (FileStream stream = System.IO.File.OpenRead(path))
|
||||
{
|
||||
await bitmap.SetSourceAsync(stream.AsRandomAccessStream());
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static async Task<BitmapSource> GetBitmapFromHBitmapAsync(IntPtr hbitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bitmap = System.Drawing.Image.FromHbitmap(hbitmap);
|
||||
var bitmapImage = new BitmapImage();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(stream, ImageFormat.Bmp);
|
||||
stream.Position = 0;
|
||||
await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
|
||||
}
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// delete HBitmap to avoid memory leaks
|
||||
NativeMethods.DeleteObject(hbitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsFileTypeSupported(string fileExt)
|
||||
{
|
||||
return _supportedFileTypes.Contains(fileExt);
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> _supportedFileTypes = new HashSet<string>
|
||||
{
|
||||
// Image types
|
||||
".bmp",
|
||||
".gif",
|
||||
".jpg",
|
||||
".jfif",
|
||||
".jfi",
|
||||
".jif",
|
||||
".jpeg",
|
||||
".jpe",
|
||||
|
||||
// ".png", // The current ImagePreviewer logic does not support transparency so PNG has it's own logic in PngPreviewer
|
||||
".tif",
|
||||
".tiff",
|
||||
".dib",
|
||||
|
||||
".heic", // Error in System.Drawing.Image.FromHbitmap(hbitmap);
|
||||
".heif",
|
||||
".hif",
|
||||
".avif",
|
||||
".jxr",
|
||||
".wdp",
|
||||
".ico",
|
||||
".thumb",
|
||||
|
||||
// Raw types
|
||||
".arw",
|
||||
".cr2",
|
||||
|
||||
// ".crw", // Error in WICImageFactory.CreateDecoderFromFilename
|
||||
// ".erf", // Error in WICImageFactory.CreateDecoderFromFilename
|
||||
".kdc",
|
||||
".mrw",
|
||||
".nef",
|
||||
".nrw",
|
||||
".orf",
|
||||
".pef",
|
||||
".raf",
|
||||
".raw",
|
||||
".rw2",
|
||||
".rwl",
|
||||
".sr2",
|
||||
".srw",
|
||||
".srf",
|
||||
".dcs",
|
||||
".dcr",
|
||||
".drf",
|
||||
".k25",
|
||||
".3fr",
|
||||
".ari",
|
||||
".bay",
|
||||
".cap",
|
||||
".iiq",
|
||||
".eip",
|
||||
".fff",
|
||||
".mef",
|
||||
".mdc",
|
||||
".mos",
|
||||
".R3D",
|
||||
".rwz",
|
||||
".x3f",
|
||||
".ori",
|
||||
".cr3",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using File = Peek.Common.Models.File;
|
||||
|
||||
public partial class PngPreviewer : ObservableObject, IBitmapPreviewer
|
||||
{
|
||||
private readonly uint _png_image_size = 1280;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsPreviewLoaded))]
|
||||
private BitmapSource? preview;
|
||||
|
||||
[ObservableProperty]
|
||||
private PreviewState state;
|
||||
|
||||
public PngPreviewer(File file)
|
||||
{
|
||||
File = file;
|
||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
public bool IsPreviewLoaded => preview != null;
|
||||
|
||||
private File File { get; }
|
||||
|
||||
private DispatcherQueue Dispatcher { get; }
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task<Size> GetPreviewSizeAsync()
|
||||
{
|
||||
var propertyImageSize = await PropertyHelper.GetImageSize(File.Path);
|
||||
if (propertyImageSize != Size.Empty)
|
||||
{
|
||||
return propertyImageSize;
|
||||
}
|
||||
|
||||
return await WICHelper.GetImageSize(File.Path);
|
||||
}
|
||||
|
||||
public async Task LoadPreviewAsync()
|
||||
{
|
||||
State = PreviewState.Loading;
|
||||
|
||||
var previewTask = LoadPreviewImageAsync();
|
||||
|
||||
await Task.WhenAll(previewTask);
|
||||
|
||||
if (Preview == null)
|
||||
{
|
||||
State = PreviewState.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsFileTypeSupported(string fileExt)
|
||||
{
|
||||
return fileExt == ".png" ? true : false;
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Preview))
|
||||
{
|
||||
if (Preview != null)
|
||||
{
|
||||
State = PreviewState.Loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task LoadPreviewImageAsync()
|
||||
{
|
||||
var thumbnailTCS = new TaskCompletionSource();
|
||||
Dispatcher.TryEnqueue(async () =>
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
Preview = await ThumbnailHelper.GetThumbnailAsync(File.Path, _png_image_size);
|
||||
|
||||
thumbnailTCS.SetResult();
|
||||
});
|
||||
|
||||
return thumbnailTCS.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using Peek.Common.Models;
|
||||
|
||||
public class PreviewerFactory
|
||||
{
|
||||
public IPreviewer Create(File file)
|
||||
{
|
||||
if (PngPreviewer.IsFileTypeSupported(file.Extension))
|
||||
{
|
||||
return new PngPreviewer(file);
|
||||
}
|
||||
else if (ImagePreviewer.IsFileTypeSupported(file.Extension))
|
||||
{
|
||||
return new ImagePreviewer(file);
|
||||
}
|
||||
else if (WebBrowserPreviewer.IsFileTypeSupported(file.Extension))
|
||||
{
|
||||
return new WebBrowserPreviewer(file);
|
||||
}
|
||||
|
||||
// Other previewer types check their supported file types here
|
||||
return CreateDefaultPreviewer(file);
|
||||
}
|
||||
|
||||
public IPreviewer CreateDefaultPreviewer(File file)
|
||||
{
|
||||
return new UnsupportedFilePreviewer(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// 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 Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common;
|
||||
using Peek.Common.Extensions;
|
||||
using Peek.Common.Helpers;
|
||||
using Peek.FilePreviewer.Previewers.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
using File = Peek.Common.Models.File;
|
||||
|
||||
public partial class UnsupportedFilePreviewer : ObservableObject, IUnsupportedFilePreviewer, IDisposable
|
||||
{
|
||||
[ObservableProperty]
|
||||
private BitmapSource? iconPreview;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileName;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileType;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileSize;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? dateModified;
|
||||
|
||||
[ObservableProperty]
|
||||
private PreviewState state;
|
||||
|
||||
public UnsupportedFilePreviewer(File file)
|
||||
{
|
||||
File = file;
|
||||
FileName = file.FileName;
|
||||
DateModified = file.DateModified.ToString();
|
||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
|
||||
var settingsUtils = new SettingsUtils();
|
||||
var settings = settingsUtils.GetSettingsOrDefault<PeekSettings>(PeekSettings.ModuleName);
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
UnsupportedFileWidthPercent = settings.Properties.UnsupportedFileWidthPercent / 100.0;
|
||||
UnsupportedFileHeightPercent = settings.Properties.UnsupportedFileHeightPercent / 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
private double UnsupportedFileWidthPercent { get; set; }
|
||||
|
||||
private double UnsupportedFileHeightPercent { get; set; }
|
||||
|
||||
public bool IsPreviewLoaded => iconPreview != null;
|
||||
|
||||
private File File { get; }
|
||||
|
||||
private DispatcherQueue Dispatcher { get; }
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
|
||||
private Task<bool>? IconPreviewTask { get; set; }
|
||||
|
||||
private Task<bool>? DisplayInfoTask { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public Task<Size> GetPreviewSizeAsync()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
return new Size(UnsupportedFileWidthPercent, UnsupportedFileHeightPercent);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task LoadPreviewAsync()
|
||||
{
|
||||
State = PreviewState.Loading;
|
||||
|
||||
IconPreviewTask = LoadIconPreviewAsync();
|
||||
DisplayInfoTask = LoadDisplayInfoAsync();
|
||||
|
||||
await Task.WhenAll(IconPreviewTask, DisplayInfoTask);
|
||||
|
||||
if (HasFailedLoadingPreview())
|
||||
{
|
||||
State = PreviewState.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> LoadIconPreviewAsync()
|
||||
{
|
||||
return TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Get icon with transparency
|
||||
IconHelper.GetIcon(Path.GetFullPath(File.Path), out IntPtr hbitmap);
|
||||
await Dispatcher.RunOnUiThread(async () =>
|
||||
{
|
||||
var iconBitmap = await GetBitmapFromHBitmapAsync(hbitmap);
|
||||
IconPreview = iconBitmap;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> LoadDisplayInfoAsync()
|
||||
{
|
||||
return TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
// File Properties
|
||||
var bytes = await PropertyHelper.GetFileSizeInBytes(File.Path);
|
||||
var type = await PropertyHelper.GetFileType(File.Path);
|
||||
|
||||
await Dispatcher.RunOnUiThread(() =>
|
||||
{
|
||||
FileSize = ReadableStringHelper.BytesToReadableString(bytes);
|
||||
FileType = type;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(IconPreview))
|
||||
{
|
||||
if (IconPreview != null)
|
||||
{
|
||||
State = PreviewState.Loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasFailedLoadingPreview()
|
||||
{
|
||||
var hasFailedLoadingIconPreview = !(IconPreviewTask?.Result ?? true);
|
||||
var hasFailedLoadingDisplayInfo = !(DisplayInfoTask?.Result ?? true);
|
||||
|
||||
return hasFailedLoadingIconPreview && hasFailedLoadingDisplayInfo;
|
||||
}
|
||||
|
||||
// TODO: Move this to a helper file (ImagePrevier uses the same code)
|
||||
private static async Task<BitmapSource> GetBitmapFromHBitmapAsync(IntPtr hbitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bitmap = System.Drawing.Image.FromHbitmap(hbitmap);
|
||||
var bitmapImage = new BitmapImage();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(stream, ImageFormat.Bmp);
|
||||
stream.Position = 0;
|
||||
await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream());
|
||||
}
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// delete HBitmap to avoid memory leaks
|
||||
NativeMethods.DeleteObject(hbitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Peek.FilePreviewer.Controls;
|
||||
using Windows.Foundation;
|
||||
using File = Peek.Common.Models.File;
|
||||
|
||||
public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer
|
||||
{
|
||||
private static readonly HashSet<string> _supportedFileTypes = new HashSet<string>
|
||||
{
|
||||
// Web
|
||||
".html",
|
||||
".htm",
|
||||
|
||||
// Document
|
||||
".pdf",
|
||||
};
|
||||
|
||||
[ObservableProperty]
|
||||
private Uri? preview;
|
||||
|
||||
[ObservableProperty]
|
||||
private PreviewState state;
|
||||
|
||||
public WebBrowserPreviewer(File file)
|
||||
{
|
||||
File = file;
|
||||
}
|
||||
|
||||
private File File { get; }
|
||||
|
||||
public Task<Size> GetPreviewSizeAsync()
|
||||
{
|
||||
// TODO: define how to proper window size on HTML content.
|
||||
var size = new Size(1280, 720);
|
||||
return Task.FromResult(size);
|
||||
}
|
||||
|
||||
public Task LoadPreviewAsync()
|
||||
{
|
||||
State = PreviewState.Loading;
|
||||
|
||||
Preview = new Uri(File.Path);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static bool IsFileTypeSupported(string fileExt)
|
||||
{
|
||||
return _supportedFileTypes.Contains(fileExt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.UnsupportedFilePreview"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
Margin="48"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="Icon" Width="Auto" />
|
||||
<ColumnDefinition x:Name="FileInfo" Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
x:Name="PreviewImage"
|
||||
Grid.Column="0"
|
||||
Width="180"
|
||||
Height="180"
|
||||
Margin="24"
|
||||
Source="{x:Bind IconPreview, Mode=OneWay}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="5">
|
||||
|
||||
<TextBlock
|
||||
FontSize="26"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind FileName, Mode=OneWay}" />
|
||||
<TextBlock Text="{x:Bind FormattedFileType, Mode=OneWay}" />
|
||||
<TextBlock Text="{x:Bind FormattedFileSize, Mode=OneWay}" />
|
||||
<TextBlock Text="{x:Bind FormattedDateModified, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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 Peek.FilePreviewer
|
||||
{
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common.Helpers;
|
||||
|
||||
[INotifyPropertyChanged]
|
||||
public sealed partial class UnsupportedFilePreview : UserControl
|
||||
{
|
||||
[ObservableProperty]
|
||||
private BitmapSource? iconPreview;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileName;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(FormattedFileType))]
|
||||
private string? fileType;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(FormattedFileSize))]
|
||||
private string? fileSize;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(FormattedDateModified))]
|
||||
private string? dateModified;
|
||||
|
||||
public string FormattedFileType => ReadableStringHelper.FormatResourceString("UnsupportedFile_FileType", FileType);
|
||||
|
||||
public string FormattedFileSize => ReadableStringHelper.FormatResourceString("UnsupportedFile_FileSize", FileSize);
|
||||
|
||||
public string FormattedDateModified => ReadableStringHelper.FormatResourceString("UnsupportedFile_DateModified", DateModified);
|
||||
|
||||
public UnsupportedFilePreview()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/modules/peek/Peek.UI.WPF/App.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Application
|
||||
x:Class="Peek.UI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
StartupUri="Views/MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources />
|
||||
<ui:XamlControlsResources />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
94
src/modules/peek/Peek.UI.WPF/App.xaml.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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.Threading;
|
||||
using System.Windows;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace Peek.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
private ThemeManager? _themeManager;
|
||||
|
||||
private Mutex? _instanceMutex;
|
||||
private static string[] _args = Array.Empty<string>();
|
||||
private int _powerToysRunnerPid;
|
||||
private bool disposedValue;
|
||||
|
||||
// TODO: Make sure no window appears or blinks at startup
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
_args = e?.Args ?? Array.Empty<string>();
|
||||
|
||||
// allow only one instance of peek
|
||||
_instanceMutex = new Mutex(true, @"Local\PowerToys_Peek_InstanceMutex", out bool createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
_instanceMutex = null;
|
||||
Environment.Exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_args?.Length > 0)
|
||||
{
|
||||
_ = int.TryParse(_args[0], out _powerToysRunnerPid);
|
||||
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
{
|
||||
Environment.Exit(0);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_powerToysRunnerPid = -1;
|
||||
}
|
||||
|
||||
_themeManager = new ThemeManager(this);
|
||||
base.OnStartup(e);
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
if (_instanceMutex != null)
|
||||
{
|
||||
_instanceMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_instanceMutex?.Dispose();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public bool IsRunningDetachedFromPowerToys()
|
||||
{
|
||||
return _powerToysRunnerPid == -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/modules/peek/Peek.UI.WPF/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is locate (used if a resource is not found in the page, app, or any theme specific resource dictionaries)
|
||||
]
|
||||
BIN
src/modules/peek/Peek.UI.WPF/Assets/error.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Peek.UI.Extensions
|
||||
{
|
||||
public static class LinkedListNodeExtensions
|
||||
{
|
||||
public static LinkedListNode<T>? GetNextOrFirst<T>(this LinkedListNode<T> current)
|
||||
{
|
||||
return current.Next ?? current.List?.First;
|
||||
}
|
||||
|
||||
public static LinkedListNode<T>? GetPreviousOrLast<T>(this LinkedListNode<T> current)
|
||||
{
|
||||
return current.Previous ?? current.List?.Last;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/modules/peek/Peek.UI.WPF/Extensions/SizeExtensions.cs
Normal file
@@ -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.Windows;
|
||||
|
||||
namespace Peek.UI.Extensions
|
||||
{
|
||||
public static class SizeExtensions
|
||||
{
|
||||
public static Rect Fit(this Size sizeToFit, Rect bounds, Size maxSize, Size minSize, Size allowedGap, double reservedHeight)
|
||||
{
|
||||
double resultingWidth = sizeToFit.Width;
|
||||
double resultingHeight = sizeToFit.Height;
|
||||
|
||||
var ratioWidth = sizeToFit.Width / maxSize.Width;
|
||||
var ratioHeight = sizeToFit.Height / maxSize.Height;
|
||||
|
||||
if (ratioWidth > ratioHeight)
|
||||
{
|
||||
if (ratioWidth > 1)
|
||||
{
|
||||
resultingWidth = maxSize.Width;
|
||||
resultingHeight = sizeToFit.Height / ratioWidth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ratioHeight > 1)
|
||||
{
|
||||
resultingWidth = sizeToFit.Width / ratioHeight;
|
||||
resultingHeight = maxSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
if (resultingWidth < minSize.Width - allowedGap.Width)
|
||||
{
|
||||
resultingWidth = minSize.Width;
|
||||
}
|
||||
|
||||
if (resultingHeight < minSize.Height - allowedGap.Height)
|
||||
{
|
||||
resultingHeight = minSize.Height;
|
||||
}
|
||||
|
||||
resultingHeight += reservedHeight;
|
||||
|
||||
// Calculate offsets to center content
|
||||
double offsetX = (maxSize.Width - resultingWidth) / 2;
|
||||
double offsetY = (maxSize.Height - resultingHeight) / 2;
|
||||
|
||||
var maxWindowLeft = bounds.Left + ((bounds.Right - bounds.Left - maxSize.Width) / 2);
|
||||
var maxWindowTop = bounds.Top + ((bounds.Bottom - bounds.Top - maxSize.Height) / 2);
|
||||
|
||||
var resultingLeft = maxWindowLeft + offsetX;
|
||||
var resultingTop = maxWindowTop + offsetY;
|
||||
|
||||
return new Rect(resultingLeft, resultingTop, resultingWidth, resultingHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/modules/peek/Peek.UI.WPF/Extensions/WindowExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Interop;
|
||||
using Peek.UI.Native;
|
||||
using static Peek.UI.Native.NativeModels;
|
||||
|
||||
namespace Peek.UI.Extensions
|
||||
{
|
||||
public static class WindowExtensions
|
||||
{
|
||||
public static void SetToolStyle(this Window window)
|
||||
{
|
||||
var handle = new WindowInteropHelper(window).Handle;
|
||||
_ = NativeMethods.SetWindowLong(handle, GwlExStyle, NativeMethods.GetWindowLong(handle, GwlExStyle) | WsExToolWindow);
|
||||
}
|
||||
|
||||
public static void BringToForeground(this Window window)
|
||||
{
|
||||
// Use SendInput hack to allow Activate to work - required to resolve focus issue https://github.com/microsoft/PowerToys/issues/4270
|
||||
Input input = new Input { Type = InputType.InputMouse, Data = { } };
|
||||
Input[] inputs = new Input[] { input };
|
||||
|
||||
// Send empty mouse event. This makes this thread the last to send input, and hence allows it to pass foreground permission checks
|
||||
_ = NativeMethods.SendInput(1, inputs, Input.Size);
|
||||
|
||||
window.Activate();
|
||||
}
|
||||
|
||||
public static void RoundCorners(this Window window)
|
||||
{
|
||||
IntPtr hWnd = new System.Windows.Interop.WindowInteropHelper(Window.GetWindow(window)).EnsureHandle();
|
||||
var attribute = DwmWindowAttributed.DwmaWindowCornerPreference;
|
||||
var preference = DwmWindowCornerPreference.DwmCpRound;
|
||||
NativeMethods.DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/modules/peek/Peek.UI.WPF/Helpers/FileExplorerHelper.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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.Collections.Generic;
|
||||
|
||||
namespace Peek.UI.Helpers
|
||||
{
|
||||
public static class FileExplorerHelper
|
||||
{
|
||||
public static IEnumerable<string> GetSelectedItems(IntPtr handle)
|
||||
{
|
||||
var selectedItems = new List<string>();
|
||||
var shell = new Shell32.Shell();
|
||||
foreach (SHDocVw.InternetExplorer window in shell.Windows())
|
||||
{
|
||||
if (window.HWND == (int)handle)
|
||||
{
|
||||
Shell32.FolderItems items = ((Shell32.IShellFolderViewDual2)window.Document).SelectedItems();
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
foreach (Shell32.FolderItem item in items)
|
||||
{
|
||||
selectedItems.Add(item.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
src/modules/peek/Peek.UI.WPF/Helpers/FileLoadHelper.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Peek.UI.Models;
|
||||
|
||||
namespace Peek.UI.Helpers
|
||||
{
|
||||
public static class FileLoadHelper
|
||||
{
|
||||
public static Task<DimensionData> LoadDimensionsAsync(string filename)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
Size size = new Size(0, 0);
|
||||
try
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(filename))
|
||||
{
|
||||
string extension = Path.GetExtension(stream.Name);
|
||||
if (FileTypeHelper.IsSupportedImage(extension))
|
||||
{
|
||||
using (System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(stream, false, false))
|
||||
{
|
||||
var rotation = EvaluateRotationToApply(sourceImage);
|
||||
if (rotation == Rotation.Rotate90 || rotation == Rotation.Rotate270)
|
||||
{
|
||||
size = new Size(sourceImage.Height, sourceImage.Width);
|
||||
}
|
||||
else
|
||||
{
|
||||
size = new Size(sourceImage.Width, sourceImage.Height);
|
||||
}
|
||||
|
||||
return Task.FromResult(new DimensionData { Size = size, Rotation = rotation });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(new DimensionData { Size = size, Rotation = Rotation.Rotate0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return Task.FromResult(new DimensionData { Size = size, Rotation = Rotation.Rotate0 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<BitmapSource> LoadThumbnailAsync(string filename, bool iconFallback)
|
||||
{
|
||||
var thumbnail = await Task.Run(() =>
|
||||
{
|
||||
var bitmapSource = ThumbnailHelper.GetThumbnail(filename, iconFallback);
|
||||
bitmapSource.Freeze();
|
||||
return bitmapSource;
|
||||
});
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public static Task<BitmapSource> LoadIconAsync(string filename)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var bitmapSource = ThumbnailHelper.GetIcon(filename);
|
||||
bitmapSource.Freeze();
|
||||
return bitmapSource;
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<BitmapImage> LoadFullImageAsync(string filename, Rotation rotation)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var bitmap = new BitmapImage();
|
||||
bitmap.BeginInit();
|
||||
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmap.UriSource = new Uri(filename);
|
||||
bitmap.Rotation = rotation;
|
||||
bitmap.EndInit();
|
||||
bitmap.Freeze();
|
||||
return bitmap;
|
||||
});
|
||||
}
|
||||
|
||||
private static Rotation EvaluateRotationToApply(System.Drawing.Image image)
|
||||
{
|
||||
PropertyItem? property = image.PropertyItems?.FirstOrDefault(p => p.Id == 274);
|
||||
|
||||
if (property != null && property.Value != null && property.Value.Length > 0)
|
||||
{
|
||||
int orientation = property.Value[0];
|
||||
|
||||
if (orientation == 6)
|
||||
{
|
||||
return Rotation.Rotate90;
|
||||
}
|
||||
|
||||
if (orientation == 3)
|
||||
{
|
||||
return Rotation.Rotate180;
|
||||
}
|
||||
|
||||
if (orientation == 8)
|
||||
{
|
||||
return Rotation.Rotate270;
|
||||
}
|
||||
}
|
||||
|
||||
return Rotation.Rotate0;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/modules/peek/Peek.UI.WPF/Helpers/FileTypeHelper.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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 Peek.UI.Native;
|
||||
using static Peek.UI.Native.NativeModels;
|
||||
|
||||
namespace Peek.UI.Helpers
|
||||
{
|
||||
public static class FileTypeHelper
|
||||
{
|
||||
public static bool IsSupportedImage(string extension) => extension switch
|
||||
{
|
||||
".bmp" => true,
|
||||
".gif" => true,
|
||||
".jpg" => true,
|
||||
".jfif" => true,
|
||||
".jfi" => true,
|
||||
".jif" => true,
|
||||
".jpeg" => true,
|
||||
".jpe" => true,
|
||||
".png" => true,
|
||||
".tif" => true,
|
||||
".tiff" => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsMedia(string extension)
|
||||
{
|
||||
return IsImage(extension) || IsVideo(extension);
|
||||
}
|
||||
|
||||
public static bool IsImage(string extension)
|
||||
{
|
||||
return IsPerceivedType(extension, PerceivedType.Image);
|
||||
}
|
||||
|
||||
public static bool IsVideo(string extension)
|
||||
{
|
||||
return IsPerceivedType(extension, PerceivedType.Video);
|
||||
}
|
||||
|
||||
public static bool IsDocument(string extension)
|
||||
{
|
||||
return IsPerceivedType(extension, PerceivedType.Document);
|
||||
}
|
||||
|
||||
internal static bool IsPerceivedType(string extension, PerceivedType perceivedType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PerceivedType perceived;
|
||||
Perceived flag;
|
||||
bool isPerceivedType = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (NativeMethods.AssocGetPerceivedType(extension, out perceived, out flag, IntPtr.Zero) == HResult.Ok)
|
||||
{
|
||||
isPerceivedType = perceived == perceivedType;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// TODO: AssocGetPerceivedType throws on some file types (json, ps1, exe, etc.)
|
||||
// Properly handle these
|
||||
return false;
|
||||
}
|
||||
|
||||
return isPerceivedType;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/modules/peek/Peek.UI.WPF/Helpers/ThumbnailHelper.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Peek.UI.Models;
|
||||
using Peek.UI.Native;
|
||||
using static Peek.UI.Native.NativeModels;
|
||||
|
||||
namespace Peek.UI.Helpers
|
||||
{
|
||||
public static class ThumbnailHelper
|
||||
{
|
||||
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
|
||||
public static readonly string ProgramDirectory = Directory.GetParent(Assembly.Location)!.ToString();
|
||||
public static readonly string ErrorIcon = Path.Combine(ProgramDirectory, "Assets", "error.png");
|
||||
|
||||
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
|
||||
private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
|
||||
|
||||
public static BitmapSource GetIcon(string fileName)
|
||||
{
|
||||
IntPtr hbitmap;
|
||||
HResult hr = GetIconImpl(Path.GetFullPath(fileName), out hbitmap);
|
||||
|
||||
if (hr != HResult.Ok)
|
||||
{
|
||||
return new BitmapImage(new Uri(ErrorIcon));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// delete HBitmap to avoid memory leaks
|
||||
NativeMethods.DeleteObject(hbitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public static BitmapSource GetThumbnail(string fileName, bool iconFallback)
|
||||
{
|
||||
IntPtr hbitmap;
|
||||
HResult hr = GetThumbnailImpl(Path.GetFullPath(fileName), out hbitmap);
|
||||
|
||||
if (hr != HResult.Ok && iconFallback)
|
||||
{
|
||||
return GetIcon(fileName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// delete HBitmap to avoid memory leaks
|
||||
NativeMethods.DeleteObject(hbitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private static HResult GetIconImpl(string filename, out IntPtr hbitmap)
|
||||
{
|
||||
Guid shellItem2Guid = new Guid(IShellItem2Guid);
|
||||
int retCode = NativeMethods.SHCreateItemFromParsingName(filename, IntPtr.Zero, ref shellItem2Guid, out IShellItem nativeShellItem);
|
||||
|
||||
if (retCode != 0)
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(retCode)!;
|
||||
}
|
||||
|
||||
NativeSize large = new NativeSize { Width = 256, Height = 256 };
|
||||
var options = ThumbnailOptions.BiggerSizeOk | ThumbnailOptions.IconOnly;
|
||||
|
||||
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(large, options, out hbitmap);
|
||||
|
||||
Marshal.ReleaseComObject(nativeShellItem);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
private static HResult GetThumbnailImpl(string filename, out IntPtr hbitmap)
|
||||
{
|
||||
Guid shellItem2Guid = new Guid(IShellItem2Guid);
|
||||
int retCode = NativeMethods.SHCreateItemFromParsingName(filename, IntPtr.Zero, ref shellItem2Guid, out IShellItem nativeShellItem);
|
||||
|
||||
if (retCode != 0)
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(retCode)!;
|
||||
}
|
||||
|
||||
var extraLarge = new NativeSize { Width = 1024, Height = 1024, };
|
||||
var large = new NativeSize { Width = 256, Height = 256 };
|
||||
var medium = new NativeSize { Width = 96, Height = 96 };
|
||||
var small = new NativeSize { Width = 32, Height = 32 };
|
||||
|
||||
var options = ThumbnailOptions.BiggerSizeOk | ThumbnailOptions.ThumbnailOnly | ThumbnailOptions.ScaleUp;
|
||||
|
||||
HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(extraLarge, options, out hbitmap);
|
||||
|
||||
if (hr != HResult.Ok)
|
||||
{
|
||||
hr = ((IShellItemImageFactory)nativeShellItem).GetImage(large, options, out hbitmap);
|
||||
}
|
||||
|
||||
if (hr != HResult.Ok)
|
||||
{
|
||||
hr = ((IShellItemImageFactory)nativeShellItem).GetImage(medium, options, out hbitmap);
|
||||
}
|
||||
|
||||
if (hr != HResult.Ok)
|
||||
{
|
||||
hr = ((IShellItemImageFactory)nativeShellItem).GetImage(small, options, out hbitmap);
|
||||
}
|
||||
|
||||
Marshal.ReleaseComObject(nativeShellItem);
|
||||
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/modules/peek/Peek.UI.WPF/Models/DimensionData.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Peek.UI.Models
|
||||
{
|
||||
public class DimensionData
|
||||
{
|
||||
public Size Size { get; set; }
|
||||
|
||||
public Rotation Rotation { get; set; }
|
||||
}
|
||||
}
|
||||
30
src/modules/peek/Peek.UI.WPF/Models/IShellItem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 static Peek.UI.Native.NativeModels;
|
||||
|
||||
namespace Peek.UI.Models
|
||||
{
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
||||
internal interface IShellItem
|
||||
{
|
||||
void BindToHandler(
|
||||
IntPtr pbc,
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
|
||||
out IntPtr ppv);
|
||||
|
||||
void GetParent(out IShellItem ppsi);
|
||||
|
||||
void GetDisplayName(Sigdn sigdnName, out IntPtr ppszName);
|
||||
|
||||
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
||||
|
||||
void Compare(IShellItem psi, uint hint, out int piOrder);
|
||||
}
|
||||
}
|
||||
19
src/modules/peek/Peek.UI.WPF/Models/ObservableObject.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Peek.UI.Models
|
||||
{
|
||||
public abstract class ObservableObject : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/modules/peek/Peek.UI.WPF/Models/ObservableRectangle.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
// 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 Peek.UI.Models
|
||||
{
|
||||
public class ObservableRectangle : ObservableObject
|
||||
{
|
||||
private double _left;
|
||||
|
||||
public double Left
|
||||
{
|
||||
get
|
||||
{
|
||||
return _left;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_left != value)
|
||||
{
|
||||
_left = value;
|
||||
OnPropertyChanged(nameof(Left));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _top;
|
||||
|
||||
public double Top
|
||||
{
|
||||
get
|
||||
{
|
||||
return _top;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_top != value)
|
||||
{
|
||||
_top = value;
|
||||
OnPropertyChanged(nameof(Top));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _height;
|
||||
|
||||
public double Height
|
||||
{
|
||||
get
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_height != value)
|
||||
{
|
||||
_height = value;
|
||||
OnPropertyChanged(nameof(Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _width;
|
||||
|
||||
public double Width
|
||||
{
|
||||
get
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_width != value)
|
||||
{
|
||||
_width = value;
|
||||
OnPropertyChanged(nameof(Width));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/modules/peek/Peek.UI.WPF/Models/ObservableWindowData.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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.Windows;
|
||||
|
||||
namespace Peek.UI.Models
|
||||
{
|
||||
public class ObservableWindowData : ObservableObject
|
||||
{
|
||||
private double _titleBarHeight;
|
||||
|
||||
public double TitleBarHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return _titleBarHeight;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_titleBarHeight != value)
|
||||
{
|
||||
_titleBarHeight = value;
|
||||
OnPropertyChanged(nameof(TitleBarHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableRectangle _rectangle = new ObservableRectangle();
|
||||
|
||||
public ObservableRectangle Rectangle
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rectangle;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_rectangle != value)
|
||||
{
|
||||
_rectangle = value;
|
||||
OnPropertyChanged(nameof(Rectangle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _title = string.Empty;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_title != value)
|
||||
{
|
||||
_title = value;
|
||||
OnPropertyChanged(nameof(Title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Visibility _visibility;
|
||||
|
||||
public Visibility Visibility
|
||||
{
|
||||
get
|
||||
{
|
||||
return _visibility;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_visibility != value)
|
||||
{
|
||||
_visibility = value;
|
||||
|
||||
OnPropertyChanged(nameof(Visibility));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/modules/peek/Peek.UI.WPF/Native/NativeEventWaiter.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace Peek.UI.Native
|
||||
{
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
new Thread(() =>
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
while (true)
|
||||
{
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(callback);
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/modules/peek/Peek.UI.WPF/Native/NativeMethods.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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 Peek.UI.Models;
|
||||
using static Peek.UI.Native.NativeModels;
|
||||
|
||||
namespace Peek.UI.Native
|
||||
{
|
||||
public static class NativeMethods
|
||||
{
|
||||
[DllImport("dwmapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern long DwmSetWindowAttribute(
|
||||
IntPtr hwnd,
|
||||
DwmWindowAttributed attribute,
|
||||
ref DwmWindowCornerPreference pvAttribute,
|
||||
uint cbAttribute);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteObject(IntPtr hObject);
|
||||
|
||||
[DllImport("Shlwapi.dll", ExactSpelling = true, PreserveSig = false)]
|
||||
internal static extern HResult AssocGetPerceivedType(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string extension,
|
||||
out PerceivedType perceivedType,
|
||||
out Perceived perceivedFlags,
|
||||
IntPtr ptrType);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern int SHCreateItemFromParsingName(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string path,
|
||||
IntPtr pbc,
|
||||
ref Guid riid,
|
||||
[MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
|
||||
internal static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize);
|
||||
}
|
||||
}
|
||||
182
src/modules/peek/Peek.UI.WPF/Native/NativeModels.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
// 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;
|
||||
|
||||
namespace Peek.UI.Native
|
||||
{
|
||||
public class NativeModels
|
||||
{
|
||||
public const int GwlExStyle = -20;
|
||||
public const int WsExToolWindow = 0x00000080;
|
||||
|
||||
public enum PerceivedType
|
||||
{
|
||||
Folder = -1,
|
||||
Unknown = 0,
|
||||
Image = 2,
|
||||
Video = 4,
|
||||
Document = 6,
|
||||
}
|
||||
|
||||
public enum Perceived
|
||||
{
|
||||
Undefined = 0x0000,
|
||||
Softcoded = 0x0001,
|
||||
Hardcoded = 0x0002,
|
||||
NativeSupport = 0x0004,
|
||||
GdiPlus = 0x0010,
|
||||
WMSDK = 0x0020,
|
||||
ZipFolder = 0x0040,
|
||||
}
|
||||
|
||||
public enum HResult
|
||||
{
|
||||
Ok = 0x0000,
|
||||
False = 0x0001,
|
||||
InvalidArguments = unchecked((int)0x80070057),
|
||||
OutOfMemory = unchecked((int)0x8007000E),
|
||||
NoInterface = unchecked((int)0x80004002),
|
||||
Fail = unchecked((int)0x80004005),
|
||||
ExtractionFailed = unchecked((int)0x8004B200),
|
||||
ElementNotFound = unchecked((int)0x80070490),
|
||||
TypeElementNotFound = unchecked((int)0x8002802B),
|
||||
NoObject = unchecked((int)0x800401E5),
|
||||
Win32ErrorCanceled = 1223,
|
||||
Canceled = unchecked((int)0x800704C7),
|
||||
ResourceInUse = unchecked((int)0x800700AA),
|
||||
AccessDenied = unchecked((int)0x80030005),
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Input
|
||||
{
|
||||
public InputType Type;
|
||||
public InputUnion Data;
|
||||
|
||||
public static int Size
|
||||
{
|
||||
get { return Marshal.SizeOf(typeof(Input)); }
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct InputUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public MouseInput Mi;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public KeybdInput Ki;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public HardwareInput Hi;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MouseInput
|
||||
{
|
||||
public int Dx;
|
||||
public int Dy;
|
||||
public int MouseData;
|
||||
public uint DwFlags;
|
||||
public uint Time;
|
||||
public UIntPtr DwExtraInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct KeybdInput
|
||||
{
|
||||
public short WVk;
|
||||
public short WScan;
|
||||
public uint DwFlags;
|
||||
public int Time;
|
||||
public UIntPtr DwExtraInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct HardwareInput
|
||||
{
|
||||
public int UMsg;
|
||||
public short WParamL;
|
||||
public short WParamH;
|
||||
}
|
||||
|
||||
public enum InputType : uint
|
||||
{
|
||||
InputMouse = 0,
|
||||
InputKeyboard = 1,
|
||||
InputHardware = 2,
|
||||
}
|
||||
|
||||
public enum Sigdn : uint
|
||||
{
|
||||
NormalDisplay = 0,
|
||||
ParentRelativeParsing = 0x80018001,
|
||||
ParentRelativeForAddressBar = 0x8001c001,
|
||||
DesktopAbsoluteParsing = 0x80028000,
|
||||
ParentRelativeEditing = 0x80031001,
|
||||
DesktopAbsoluteEditing = 0x8004c000,
|
||||
FileSysPath = 0x80058000,
|
||||
Url = 0x80068000,
|
||||
}
|
||||
|
||||
public enum DwmWindowAttributed
|
||||
{
|
||||
DwmaWindowCornerPreference = 33,
|
||||
}
|
||||
|
||||
// The DWM_WINDOW_CORNER_PREFERENCE enum for DwmSetWindowAttribute's third parameter, which tells the function
|
||||
// what value of the enum to set.
|
||||
public enum DwmWindowCornerPreference
|
||||
{
|
||||
DwmCpDefault = 0,
|
||||
DwmCpDoNotRound = 1,
|
||||
DwmCpRound = 2,
|
||||
DwmCpRoundSmall = 3,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ThumbnailOptions
|
||||
{
|
||||
None = 0x00,
|
||||
BiggerSizeOk = 0x01,
|
||||
InMemoryOnly = 0x02,
|
||||
IconOnly = 0x04,
|
||||
ThumbnailOnly = 0x08,
|
||||
InCacheOnly = 0x10,
|
||||
ScaleUp = 0x100,
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IShellItemImageFactory
|
||||
{
|
||||
[PreserveSig]
|
||||
HResult GetImage(
|
||||
[In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
|
||||
[In] ThumbnailOptions flags,
|
||||
[Out] out IntPtr phbm);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct NativeSize
|
||||
{
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
public int Width
|
||||
{
|
||||
set { width = value; }
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
set { height = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/modules/peek/Peek.UI.WPF/Peek.UI.WPF.csproj
Normal file
@@ -0,0 +1,90 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<AssemblyName>PowerToys.Peek.UI.WPF</AssemblyName>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\modules\Peek\</OutputPath>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<ApplicationIcon>Resources\FluentIconsPeek.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<COMReference Include="Shell32">
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="SHDocVw">
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<VersionMinor>1</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>eab22ac0-30c1-11cf-a7eb-0000c05bae0b</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\Peek.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Peek.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Resources\Peek.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Assets\error.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
|
||||
<PackageReference Include="WpfScreenHelper" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Themes\Dark.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
<Page Update="Themes\HighContrast1.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
<Page Update="Themes\HighContrast2.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
<Page Update="Themes\HighContrastBlack.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
<Page Update="Themes\HighContrastWhite.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
<Page Update="Themes\Light.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
BIN
src/modules/peek/Peek.UI.WPF/Resources/Peek.ico
Normal file
|
After Width: | Height: | Size: 149 KiB |
19
src/modules/peek/Peek.UI.WPF/Themes/Dark.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">Dark.Accent1</system:String>
|
||||
<system:String x:Key="Theme.Origin">PowerToysPeek</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent1 (Dark)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">Dark</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent1</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">Black</Color>
|
||||
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF3a3a3a" />
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF333333" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF999999" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="Transparent" />
|
||||
</ResourceDictionary>
|
||||
19
src/modules/peek/Peek.UI.WPF/Themes/HighContrast1.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent2</system:String>
|
||||
<system:String x:Key="Theme.Origin">PowerToysPeek</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent2 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent2</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF3a3a3a" />
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF333333" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffff00" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF00ff00" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FFffff00" />
|
||||
</ResourceDictionary>
|
||||
19
src/modules/peek/Peek.UI.WPF/Themes/HighContrast2.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent3</system:String>
|
||||
<system:String x:Key="Theme.Origin">PowerToysPeek</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent3 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent3</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF3a3a3a" />
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF333333" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF00ff00" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FFc0c0c0" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="#FF00ff00" />
|
||||
</ResourceDictionary>
|
||||
19
src/modules/peek/Peek.UI.WPF/Themes/HighContrastBlack.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent4</system:String>
|
||||
<system:String x:Key="Theme.Origin">PowerToysPeek</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent4 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent4</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FF3a3a3a" />
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FF333333" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FFffffff" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF1aebff" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="White" />
|
||||
</ResourceDictionary>
|
||||
19
src/modules/peek/Peek.UI.WPF/Themes/HighContrastWhite.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">HighContrast.Accent5</system:String>
|
||||
<system:String x:Key="Theme.Origin">PowerToysPeek</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent5 (HighContrast)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">HighContrast</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent5</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFf3f3f3" />
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FFffffff" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF000000" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF676666" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="Black" />
|
||||
</ResourceDictionary>
|
||||
19
src/modules/peek/Peek.UI.WPF/Themes/Light.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
|
||||
<!-- Metadata -->
|
||||
<system:String x:Key="Theme.Name">Light.Accent1</system:String>
|
||||
<system:String x:Key="Theme.Origin">PowerToysPeek</system:String>
|
||||
<system:String x:Key="Theme.DisplayName">Accent1 (Light)</system:String>
|
||||
<system:String x:Key="Theme.BaseColorScheme">Light</system:String>
|
||||
<system:String x:Key="Theme.ColorScheme">Accent1</system:String>
|
||||
<Color x:Key="Theme.PrimaryAccentColor">White</Color>
|
||||
|
||||
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="#FFf3f3f3" />
|
||||
<SolidColorBrush x:Key="PrimaryBackgroundBrush" Color="#FFffffff" />
|
||||
<SolidColorBrush x:Key="PrimaryForegroundBrush" Color="#FF000000" />
|
||||
<SolidColorBrush x:Key="SecondaryForegroundBrush" Color="#FF676666" />
|
||||
<SolidColorBrush x:Key="PrimaryBorderBrush" Color="Transparent" />
|
||||
</ResourceDictionary>
|
||||
301
src/modules/peek/Peek.UI.WPF/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Peek.UI.Extensions;
|
||||
using Peek.UI.Helpers;
|
||||
using Peek.UI.Models;
|
||||
using Peek.UI.Native;
|
||||
using WpfScreenHelper;
|
||||
using Size = System.Windows.Size;
|
||||
|
||||
namespace Peek.UI.ViewModels
|
||||
{
|
||||
public class MainViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private const double ImageScale = 0.75;
|
||||
private static readonly Size MinWindowSize = new Size(720, 720);
|
||||
private static readonly Size AllowedContentGap = new Size(220, 220);
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
private CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||
|
||||
public IntPtr ForegroundWindowHandle { get; internal set; }
|
||||
|
||||
public Image ImageControl { get; set; }
|
||||
|
||||
public LinkedList<string> SelectedFilePaths { get; set; } = new LinkedList<string>();
|
||||
|
||||
private BitmapSource? _bitmap;
|
||||
|
||||
public BitmapSource? Bitmap
|
||||
{
|
||||
get
|
||||
{
|
||||
return _bitmap;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_bitmap != value)
|
||||
{
|
||||
_bitmap = value;
|
||||
OnPropertyChanged(nameof(Bitmap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedListNode<string>? _currentSelectedFilePath;
|
||||
|
||||
public LinkedListNode<string>? CurrentSelectedFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentSelectedFilePath;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_currentSelectedFilePath != value)
|
||||
{
|
||||
_currentSelectedFilePath = value;
|
||||
var title = Path.GetFileName(_currentSelectedFilePath?.Value ?? string.Empty);
|
||||
MainWindowData.Title = title;
|
||||
OnPropertyChanged(nameof(CurrentSelectedFilePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility IsImageReady => IsLoading ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
private bool _isLoading = true;
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isLoading;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_isLoading != value)
|
||||
{
|
||||
_isLoading = value;
|
||||
OnPropertyChanged(nameof(IsLoading));
|
||||
OnPropertyChanged(nameof(IsImageReady));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableWindowData _mainWindowData = new ObservableWindowData();
|
||||
|
||||
public ObservableWindowData MainWindowData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mainWindowData;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_mainWindowData != value)
|
||||
{
|
||||
_mainWindowData = value;
|
||||
OnPropertyChanged(nameof(MainWindowData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MainViewModel(Image imageControl)
|
||||
{
|
||||
ImageControl = imageControl;
|
||||
}
|
||||
|
||||
// TODO: Implement proper disposal pattern
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
CurrentSelectedFilePath = null;
|
||||
MainWindowData.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public bool TryUpdateSelectedFilePaths()
|
||||
{
|
||||
ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
|
||||
// TODO: Get all neighborings files in correct sorted order
|
||||
var selectedItems = FileExplorerHelper.GetSelectedItems(ForegroundWindowHandle);
|
||||
|
||||
var isDifferentSelectedItems = !SelectedFilePaths.SequenceEqual(selectedItems);
|
||||
|
||||
if (isDifferentSelectedItems)
|
||||
{
|
||||
SelectedFilePaths = new LinkedList<string>(selectedItems);
|
||||
}
|
||||
|
||||
CurrentSelectedFilePath = SelectedFilePaths.First;
|
||||
|
||||
return isDifferentSelectedItems;
|
||||
}
|
||||
|
||||
// TODO: Implement proper cancellation pattern to support quick navigation
|
||||
public async Task RenderImageToWindowAsync(string filename)
|
||||
{
|
||||
IsLoading = true;
|
||||
|
||||
var screen = Screen.FromHandle(ForegroundWindowHandle);
|
||||
Size maxWindowSize = new Size(screen.WpfBounds.Width * ImageScale, screen.WpfBounds.Height * ImageScale);
|
||||
|
||||
// TODO: Support preview or thumbnail for document files
|
||||
if (FileTypeHelper.IsSupportedImage(Path.GetExtension(filename)))
|
||||
{
|
||||
await RenderSupportedImageToWindowAsync(filename, screen.Bounds, maxWindowSize);
|
||||
}
|
||||
else if (FileTypeHelper.IsMedia(Path.GetExtension(filename)) || FileTypeHelper.IsDocument(Path.GetExtension(filename)))
|
||||
{
|
||||
await RenderMediaOrDocumentToWindowAsync(filename, screen.Bounds, maxWindowSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
await RenderUnsupportedFileToWindowAsync(filename, screen.Bounds, maxWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RenderSupportedImageToWindowAsync(string filename, Rect windowBounds, Size maxWindowSize)
|
||||
{
|
||||
DimensionData dimensionData = await FileLoadHelper.LoadDimensionsAsync(filename);
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
var windowRect = dimensionData.Size.Fit(windowBounds, maxWindowSize, MinWindowSize, AllowedContentGap, MainWindowData.TitleBarHeight);
|
||||
|
||||
MainWindowData.Rectangle.Width = windowRect.Width;
|
||||
MainWindowData.Rectangle.Height = windowRect.Height;
|
||||
MainWindowData.Rectangle.Left = windowRect.Left;
|
||||
MainWindowData.Rectangle.Top = windowRect.Top;
|
||||
|
||||
if (dimensionData.Size.Width > MainWindowData.Rectangle.Width || dimensionData.Size.Height > MainWindowData.Rectangle.Height)
|
||||
{
|
||||
ImageControl.StretchDirection = StretchDirection.Both;
|
||||
}
|
||||
else
|
||||
{
|
||||
ImageControl.StretchDirection = StretchDirection.DownOnly;
|
||||
}
|
||||
|
||||
await LoadImageAsync(filename, ImageControl, dimensionData.Rotation, CancellationToken);
|
||||
}
|
||||
|
||||
private async Task RenderMediaOrDocumentToWindowAsync(string filename, Rect windowBounds, Size maxWindowSize)
|
||||
{
|
||||
var bitmap = await FileLoadHelper.LoadThumbnailAsync(filename, true);
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap = bitmap;
|
||||
|
||||
var imageSize = new Size(bitmap.PixelWidth, bitmap.PixelHeight);
|
||||
var windowRect = imageSize.Fit(windowBounds, maxWindowSize, MinWindowSize, AllowedContentGap, MainWindowData.TitleBarHeight);
|
||||
|
||||
MainWindowData.Rectangle.Width = windowRect.Width;
|
||||
MainWindowData.Rectangle.Height = windowRect.Height;
|
||||
MainWindowData.Rectangle.Left = windowRect.Left;
|
||||
MainWindowData.Rectangle.Top = windowRect.Top;
|
||||
|
||||
MainWindowData.Visibility = Visibility.Visible;
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
private async Task RenderUnsupportedFileToWindowAsync(string filename, Rect windowBounds, Size maxWindowSize)
|
||||
{
|
||||
var contentSize = new Size(0, 0);
|
||||
var windowRect = contentSize.Fit(windowBounds, maxWindowSize, MinWindowSize, AllowedContentGap, MainWindowData.TitleBarHeight);
|
||||
|
||||
MainWindowData.Rectangle.Width = windowRect.Width;
|
||||
MainWindowData.Rectangle.Height = windowRect.Height;
|
||||
MainWindowData.Rectangle.Left = windowRect.Left;
|
||||
MainWindowData.Rectangle.Top = windowRect.Top;
|
||||
|
||||
var bitmap = await FileLoadHelper.LoadIconAsync(filename);
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap = bitmap;
|
||||
MainWindowData.Visibility = Visibility.Visible;
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
private Task LoadImageAsync(string filename, System.Windows.Controls.Image imageControl, Rotation rotation, CancellationToken cancellationToken)
|
||||
{
|
||||
bool isFullImageLoaded = false;
|
||||
bool isThumbnailLoaded = false;
|
||||
var thumbnailLoadTask = imageControl.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var bitmap = await FileLoadHelper.LoadThumbnailAsync(filename, false);
|
||||
isThumbnailLoaded = true;
|
||||
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFullImageLoaded)
|
||||
{
|
||||
Bitmap = bitmap;
|
||||
MainWindowData.Visibility = Visibility.Visible;
|
||||
IsLoading = false;
|
||||
}
|
||||
});
|
||||
|
||||
var fullImageLoadTask = imageControl.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var bitmap = await FileLoadHelper.LoadFullImageAsync(filename, rotation);
|
||||
isFullImageLoaded = true;
|
||||
|
||||
if (CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap = bitmap;
|
||||
if (!isThumbnailLoaded)
|
||||
{
|
||||
MainWindowData.Visibility = Visibility.Visible;
|
||||
IsLoading = false;
|
||||
}
|
||||
});
|
||||
|
||||
return Task.WhenAll(thumbnailLoadTask, fullImageLoadTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/modules/peek/Peek.UI.WPF/Views/MainWindow.xaml
Normal file
@@ -0,0 +1,37 @@
|
||||
<Window
|
||||
x:Class="Peek.UI.Views.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:vm="clr-namespace:Peek.UI.ViewModels"
|
||||
Title="{Binding MainWindowData.Title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="{Binding MainWindowData.Rectangle.Width, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Height="{Binding MainWindowData.Rectangle.Height, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
d:DataContext="{d:DesignInstance vm:MainViewModel}"
|
||||
ui:TitleBar.Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
ui:TitleBar.IsIconVisible="True"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
Background="{DynamicResource PrimaryBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
Icon="/PowerToys.Peek.UI;component/Resources/Peek.ico"
|
||||
Left="{Binding MainWindowData.Rectangle.Left, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ResizeMode="NoResize"
|
||||
Top="{Binding MainWindowData.Rectangle.Top, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Visibility="{Binding MainWindowData.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
WindowStartupLocation="Manual"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<ui:ProgressRing IsActive="{Binding IsLoading, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<!-- TODO: Create UI control that displays unsupported file gracefully -->
|
||||
<Image
|
||||
x:Name="ImageControl"
|
||||
Source="{Binding Bitmap, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Stretch="Uniform"
|
||||
StretchDirection="DownOnly"
|
||||
Visibility="{Binding IsImageReady, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
</Window>
|
||||
115
src/modules/peek/Peek.UI.WPF/Views/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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.Windows;
|
||||
using System.Windows.Input;
|
||||
using interop;
|
||||
using ModernWpf.Controls;
|
||||
using Peek.UI.Extensions;
|
||||
using Peek.UI.Native;
|
||||
using Peek.UI.ViewModels;
|
||||
|
||||
namespace Peek.UI.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window, IDisposable
|
||||
{
|
||||
private readonly MainViewModel _viewModel;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.RoundCorners();
|
||||
|
||||
_viewModel = new MainViewModel(ImageControl);
|
||||
_viewModel.PropertyChanged += MainViewModel_PropertyChanged;
|
||||
|
||||
DataContext = _viewModel;
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey);
|
||||
|
||||
Loaded += MainWindow_Loaded;
|
||||
Closing += MainWindow_Closing;
|
||||
KeyDown += MainWindow_KeyDown;
|
||||
}
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.MainWindowData.Visibility = Visibility.Collapsed;
|
||||
_viewModel.MainWindowData.TitleBarHeight = TitleBar.GetHeight(this);
|
||||
_viewModel.ImageControl = ImageControl;
|
||||
}
|
||||
|
||||
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
_viewModel.MainWindowData.Visibility = Visibility.Collapsed;
|
||||
e.Cancel = true;
|
||||
}
|
||||
|
||||
private void MainWindow_KeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (!e.IsRepeat && _viewModel.CurrentSelectedFilePath != null)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left:
|
||||
_viewModel.CurrentSelectedFilePath = _viewModel.CurrentSelectedFilePath.GetPreviousOrLast();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Right:
|
||||
_viewModel.CurrentSelectedFilePath = _viewModel.CurrentSelectedFilePath.GetNextOrFirst();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_viewModel.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
this.SetToolStyle();
|
||||
}
|
||||
|
||||
private async void MainViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(MainViewModel.CurrentSelectedFilePath):
|
||||
if (_viewModel.CurrentSelectedFilePath != null)
|
||||
{
|
||||
await _viewModel.RenderImageToWindowAsync(_viewModel.CurrentSelectedFilePath.Value);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPeekHotkey()
|
||||
{
|
||||
if (IsActive && _viewModel.MainWindowData.Visibility == Visibility.Visible)
|
||||
{
|
||||
_viewModel.ClearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
_viewModel.TryUpdateSelectedFilePaths();
|
||||
}
|
||||
|
||||
this.BringToForeground();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/peek/Peek.UI/App.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<Application
|
||||
x:Class="Peek.UI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Peek.UI">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
58
src/modules/peek/Peek.UI/App.xaml.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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 Peek.UI
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinUIEx;
|
||||
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static int PowerToysPID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
if (cmdArgs?.Length > 1)
|
||||
{
|
||||
if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid))
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
|
||||
{
|
||||
Environment.Exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window = new MainWindow();
|
||||
|
||||
window.Activate();
|
||||
window.Hide();
|
||||
}
|
||||
|
||||
private Window? window;
|
||||
}
|
||||
}
|
||||
BIN
src/modules/peek/Peek.UI/Assets/AppList.scale-100.png
Normal file
|
After Width: | Height: | Size: 513 B |
BIN
src/modules/peek/Peek.UI/Assets/AppList.scale-125.png
Normal file
|
After Width: | Height: | Size: 638 B |
BIN
src/modules/peek/Peek.UI/Assets/AppList.scale-150.png
Normal file
|
After Width: | Height: | Size: 745 B |
BIN
src/modules/peek/Peek.UI/Assets/AppList.scale-200.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/modules/peek/Peek.UI/Assets/AppList.scale-400.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/modules/peek/Peek.UI/Assets/Icon.ico
Normal file
|
After Width: | Height: | Size: 66 KiB |
49
src/modules/peek/Peek.UI/Extensions/SizeExtensions.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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 Peek.UI.Extensions
|
||||
{
|
||||
using Windows.Foundation;
|
||||
|
||||
public static class SizeExtensions
|
||||
{
|
||||
public static Size Fit(this Size sizeToFit, Size maxSize, Size minSize)
|
||||
{
|
||||
double fittedWidth = sizeToFit.Width;
|
||||
double fittedHeight = sizeToFit.Height;
|
||||
|
||||
double ratioWidth = sizeToFit.Width / maxSize.Width;
|
||||
double ratioHeight = sizeToFit.Height / maxSize.Height;
|
||||
|
||||
if (ratioWidth > ratioHeight)
|
||||
{
|
||||
if (ratioWidth > 1)
|
||||
{
|
||||
fittedWidth = maxSize.Width;
|
||||
fittedHeight = sizeToFit.Height / ratioWidth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ratioHeight > 1)
|
||||
{
|
||||
fittedWidth = sizeToFit.Width / ratioHeight;
|
||||
fittedHeight = maxSize.Height;
|
||||
}
|
||||
}
|
||||
|
||||
if (fittedWidth < minSize.Width)
|
||||
{
|
||||
fittedWidth = minSize.Width;
|
||||
}
|
||||
|
||||
if (fittedHeight < minSize.Height)
|
||||
{
|
||||
fittedHeight = minSize.Height;
|
||||
}
|
||||
|
||||
return new Size(fittedWidth, fittedHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
// 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 Peek.UI.Extensions
|
||||
{
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Peek.UI.Native;
|
||||
using Windows.Foundation;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.Gdi;
|
||||
using WinUIEx;
|
||||
|
||||
public static class WindowExtensions
|
||||
{
|
||||
public static Size GetMonitorSize(this Window window)
|
||||
{
|
||||
var hwnd = new HWND(window.GetWindowHandle());
|
||||
var hwndDesktop = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO info = new ();
|
||||
info.cbSize = 40;
|
||||
PInvoke.GetMonitorInfo(hwndDesktop, ref info);
|
||||
double monitorWidth = info.rcMonitor.left + info.rcMonitor.right;
|
||||
double monitorHeight = info.rcMonitor.bottom + info.rcMonitor.top;
|
||||
|
||||
return new Size(monitorWidth, monitorHeight);
|
||||
}
|
||||
|
||||
public static double GetMonitorScale(this Window window)
|
||||
{
|
||||
var hwnd = new HWND(window.GetWindowHandle());
|
||||
var dpi = PInvoke.GetDpiForWindow(new HWND(hwnd));
|
||||
double scalingFactor = dpi / 96d;
|
||||
|
||||
return scalingFactor;
|
||||
}
|
||||
|
||||
public static void BringToForeground(this Window window)
|
||||
{
|
||||
var windowHandle = window.GetWindowHandle();
|
||||
|
||||
// Restore the window.
|
||||
_ = NativeMethods.SendMessage(windowHandle, NativeMethods.WM_SYSCOMMAND, NativeMethods.SC_RESTORE, -2);
|
||||
|
||||
// Bring the window to the front.
|
||||
if (!NativeMethods.SetWindowPos(
|
||||
windowHandle,
|
||||
NativeMethods.HWND_TOP,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
NativeMethods.SWP_NOMOVE | NativeMethods.SWP_DRAWFRAME | NativeMethods.SWP_NOSIZE | NativeMethods.SWP_SHOWWINDOW))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to set window position.");
|
||||
}
|
||||
|
||||
// Grab the SetForegroundWindow privilege from the shell process.
|
||||
AcquireForegroundPrivilege();
|
||||
|
||||
// Make our window the foreground window.
|
||||
_ = NativeMethods.SetForegroundWindow(windowHandle);
|
||||
}
|
||||
|
||||
private static void AcquireForegroundPrivilege()
|
||||
{
|
||||
IntPtr remoteProcessHandle = 0;
|
||||
IntPtr user32Handle = 0;
|
||||
IntPtr remoteThreadHandle = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the handle of the shell window.
|
||||
IntPtr topHandle = NativeMethods.GetShellWindow();
|
||||
if (topHandle == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get the shell desktop window.");
|
||||
}
|
||||
|
||||
// Open the process that owns it.
|
||||
IntPtr remoteProcessId = 0;
|
||||
NativeMethods.GetWindowThreadProcessId(topHandle, ref remoteProcessId);
|
||||
if (remoteProcessId == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get the shell process ID.");
|
||||
}
|
||||
|
||||
remoteProcessHandle = NativeMethods.OpenProcess(NativeMethods.PROCESS_ALL_ACCESS, false, remoteProcessId);
|
||||
if (remoteProcessHandle == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to open the shell process.");
|
||||
}
|
||||
|
||||
// Get the address of the AllowSetForegroundWindow API.
|
||||
user32Handle = NativeMethods.LoadLibrary("user32.dll");
|
||||
IntPtr entryPoint = NativeMethods.GetProcAddress(user32Handle, "AllowSetForegroundWindow");
|
||||
|
||||
// Create a remote thread in the other process and make it call the API.
|
||||
remoteThreadHandle = NativeMethods.CreateRemoteThread(
|
||||
remoteProcessHandle,
|
||||
0,
|
||||
100000,
|
||||
entryPoint,
|
||||
NativeMethods.GetCurrentProcessId(),
|
||||
0,
|
||||
0);
|
||||
if (remoteThreadHandle == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create the remote thread.");
|
||||
}
|
||||
|
||||
// Wait for the remote thread to terminate.
|
||||
_ = NativeMethods.WaitForSingleObject(remoteThreadHandle, 5000);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (remoteProcessHandle != 0)
|
||||
{
|
||||
_ = NativeMethods.CloseHandle(remoteProcessHandle);
|
||||
}
|
||||
|
||||
if (remoteThreadHandle != 0)
|
||||
{
|
||||
_ = NativeMethods.CloseHandle(remoteThreadHandle);
|
||||
}
|
||||
|
||||
if (user32Handle != 0)
|
||||
{
|
||||
_ = NativeMethods.FreeLibrary(user32Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
184
src/modules/peek/Peek.UI/FileSystem/FolderItemsQuery.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
// 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 Peek.UI.FileSystem
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Peek.Common.Models;
|
||||
using Peek.UI.Helpers;
|
||||
|
||||
public partial class FolderItemsQuery : ObservableObject
|
||||
{
|
||||
private const int UninitializedItemIndex = -1;
|
||||
private readonly object _mutateQueryDataLock = new ();
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
[ObservableProperty]
|
||||
private File? currentFile;
|
||||
|
||||
[ObservableProperty]
|
||||
private List<File> files = new ();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isMultiSelection;
|
||||
|
||||
[ObservableProperty]
|
||||
private int currentItemIndex = UninitializedItemIndex;
|
||||
|
||||
private CancellationTokenSource CancellationTokenSource { get; set; } = new CancellationTokenSource();
|
||||
|
||||
private Task? InitializeFilesTask { get; set; } = null;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
CurrentFile = null;
|
||||
IsMultiSelection = false;
|
||||
|
||||
if (InitializeFilesTask != null && InitializeFilesTask.Status == TaskStatus.Running)
|
||||
{
|
||||
Debug.WriteLine("Detected existing initializeFilesTask running. Cancelling it..");
|
||||
CancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
InitializeFilesTask = null;
|
||||
|
||||
lock (_mutateQueryDataLock)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Files = new List<File>();
|
||||
CurrentItemIndex = UninitializedItemIndex;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCurrentItemIndex(int desiredIndex)
|
||||
{
|
||||
if (Files.Count <= 1 || CurrentItemIndex == UninitializedItemIndex ||
|
||||
(InitializeFilesTask != null && InitializeFilesTask.Status == TaskStatus.Running))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Current index wraps around when reaching min/max folder item indices
|
||||
desiredIndex %= Files.Count;
|
||||
CurrentItemIndex = desiredIndex < 0 ? Files.Count + desiredIndex : desiredIndex;
|
||||
|
||||
if (CurrentItemIndex < 0 || CurrentItemIndex >= Files.Count)
|
||||
{
|
||||
Debug.Assert(false, "Out of bounds folder item index detected.");
|
||||
CurrentItemIndex = 0;
|
||||
}
|
||||
|
||||
CurrentFile = Files[CurrentItemIndex];
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
var folderView = FileExplorerHelper.GetCurrentFolderView();
|
||||
if (folderView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Shell32.FolderItems selectedItems = folderView.SelectedItems();
|
||||
if (selectedItems == null || selectedItems.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsMultiSelection = selectedItems.Count > 1;
|
||||
|
||||
// Prioritize setting CurrentFile, which notifies UI
|
||||
var firstSelectedItem = selectedItems.Item(0);
|
||||
CurrentFile = new File(firstSelectedItem.Path);
|
||||
|
||||
var items = selectedItems.Count > 1 ? selectedItems : folderView.Folder?.Items();
|
||||
if (items == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (InitializeFilesTask != null && InitializeFilesTask.Status == TaskStatus.Running)
|
||||
{
|
||||
Debug.WriteLine("Detected unexpected existing initializeFilesTask running. Cancelling it..");
|
||||
CancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
CancellationTokenSource = new CancellationTokenSource();
|
||||
InitializeFilesTask = new Task(() => InitializeFiles(items, firstSelectedItem, CancellationTokenSource.Token));
|
||||
|
||||
// Execute file initialization/querying on background thread
|
||||
InitializeFilesTask.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("Exception trying to run initializeFilesTask:\n" + e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Finds index of firstSelectedItem either amongst folder items, initializing our internal File list
|
||||
// since storing Shell32.FolderItems as a field isn't reliable.
|
||||
// Can take a few seconds for folders with 1000s of items; ensure it runs on a background thread.
|
||||
//
|
||||
// TODO optimization:
|
||||
// Handle case where selected items count > 1 separately. Although it'll still be slow for 1000s of items selected,
|
||||
// we can leverage faster APIs like Windows.Storage when 1 item is selected, and navigation is scoped to
|
||||
// the entire folder. We can then avoid iterating through all items here, and maintain a dynamic window of
|
||||
// loaded items around the current item index.
|
||||
private void InitializeFiles(
|
||||
Shell32.FolderItems items,
|
||||
Shell32.FolderItem firstSelectedItem,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tempFiles = new List<File>(items.Count);
|
||||
var tempCurIndex = UninitializedItemIndex;
|
||||
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var item = items.Item(i);
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Name == firstSelectedItem.Name)
|
||||
{
|
||||
tempCurIndex = i;
|
||||
}
|
||||
|
||||
tempFiles.Add(new File(item.Path));
|
||||
}
|
||||
|
||||
if (tempCurIndex == UninitializedItemIndex)
|
||||
{
|
||||
Debug.WriteLine("File query initialization: selectedItem index not found. Navigation remains disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
lock (_mutateQueryDataLock)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Files = tempFiles;
|
||||
CurrentItemIndex = tempCurIndex;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/modules/peek/Peek.UI/Helpers/DefaultAppHelper.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Peek.Common.Models;
|
||||
using Peek.UI.Native;
|
||||
|
||||
namespace Peek.UI.Helpers
|
||||
{
|
||||
public static class DefaultAppHelper
|
||||
{
|
||||
public static string TryGetDefaultAppName(string extension)
|
||||
{
|
||||
string appName = string.Empty;
|
||||
|
||||
// Get the length of the app name
|
||||
uint length = 0;
|
||||
HResult ret = NativeMethods.AssocQueryString(NativeMethods.AssocF.Verify, NativeMethods.AssocStr.FriendlyAppName, extension, null, null, ref length);
|
||||
if (ret != HResult.False)
|
||||
{
|
||||
Debug.WriteLine($"Error when getting accessString for {extension} file: {Marshal.GetExceptionForHR((int)ret)!.Message}");
|
||||
return appName;
|
||||
}
|
||||
|
||||
// Get the the app name
|
||||
StringBuilder sb = new ((int)length);
|
||||
ret = NativeMethods.AssocQueryString(NativeMethods.AssocF.Verify, NativeMethods.AssocStr.FriendlyAppName, extension, null, sb, ref length);
|
||||
if (ret != HResult.Ok)
|
||||
{
|
||||
Debug.WriteLine($"Error when getting accessString for {extension} file: {Marshal.GetExceptionForHR((int)ret)!.Message}" );
|
||||
return appName;
|
||||
}
|
||||
|
||||
appName = sb.ToString();
|
||||
return appName;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 System.Text;
|
||||
using Peek.UI.Native;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
|
||||
namespace Peek.UI.Helpers
|
||||
{
|
||||
public static class FileExplorerHelper
|
||||
{
|
||||
public static Shell32.IShellFolderViewDual2? GetCurrentFolderView()
|
||||
{
|
||||
var foregroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
|
||||
int capacity = PInvoke.GetWindowTextLength(new HWND(foregroundWindowHandle)) * 2;
|
||||
StringBuilder foregroundWindowTitleBuffer = new StringBuilder(capacity);
|
||||
NativeMethods.GetWindowText(new HWND(foregroundWindowHandle), foregroundWindowTitleBuffer, foregroundWindowTitleBuffer.Capacity);
|
||||
|
||||
string foregroundWindowTitle = foregroundWindowTitleBuffer.ToString();
|
||||
|
||||
var shell = new Shell32.Shell();
|
||||
foreach (SHDocVw.InternetExplorer window in shell.Windows())
|
||||
{
|
||||
var shellFolderView = (Shell32.IShellFolderViewDual2)window.Document;
|
||||
var folderTitle = shellFolderView.Folder.Title;
|
||||
|
||||
if (window.HWND == (int)foregroundWindowHandle && folderTitle == foregroundWindowTitle)
|
||||
{
|
||||
return shellFolderView;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/modules/peek/Peek.UI/MainWindow.xaml
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<winuiex:WindowEx
|
||||
x:Class="Peek.UI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fp="using:Peek.FilePreviewer"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:Peek.UI.Views"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
mc:Ignorable="d">
|
||||
<winuiex:WindowEx.Backdrop>
|
||||
<winuiex:MicaSystemBackdrop />
|
||||
</winuiex:WindowEx.Backdrop>
|
||||
|
||||
<Grid KeyboardAcceleratorPlacementMode="Hidden">
|
||||
<Grid.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Left" Invoked="LeftNavigationInvoked" />
|
||||
<KeyboardAccelerator Key="Right" Invoked="RightNavigationInvoked" />
|
||||
</Grid.KeyboardAccelerators>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<views:TitleBar
|
||||
x:Name="TitleBarControl"
|
||||
Grid.Row="0"
|
||||
File="{x:Bind ViewModel.FolderItemsQuery.CurrentFile, Mode=OneWay}"
|
||||
FileIndex="{x:Bind ViewModel.FolderItemsQuery.CurrentItemIndex, Mode=OneWay}"
|
||||
IsMultiSelection="{x:Bind ViewModel.FolderItemsQuery.IsMultiSelection, Mode=OneWay}"
|
||||
NumberOfFiles="{x:Bind ViewModel.FolderItemsQuery.Files.Count, Mode=OneWay}" />
|
||||
|
||||
<fp:FilePreview
|
||||
Grid.Row="1"
|
||||
File="{x:Bind ViewModel.FolderItemsQuery.CurrentFile, Mode=OneWay}"
|
||||
PreviewSizeChanged="FilePreviewer_PreviewSizeChanged" />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
143
src/modules/peek/Peek.UI/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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 Peek.UI
|
||||
{
|
||||
using System.Diagnostics;
|
||||
using interop;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Peek.FilePreviewer.Models;
|
||||
using Peek.UI.Extensions;
|
||||
using Peek.UI.Native;
|
||||
using Windows.Foundation;
|
||||
using Windows.Win32;
|
||||
using WinUIEx;
|
||||
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
{
|
||||
private const double MaxWindowToMonitorRatio = 0.80;
|
||||
private const double MinWindowHeight = 500;
|
||||
private const double MinWindowWidth = 680;
|
||||
private const double WindowWidthContentPadding = 7;
|
||||
private const double WindowHeightContentPadding = 16;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new MainWindowViewModel();
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey);
|
||||
|
||||
TitleBarControl.SetTitleBarToWindow(this);
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
}
|
||||
|
||||
public MainWindowViewModel ViewModel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Handle Peek hotkey, by toggling the window visibility and querying files when necessary.
|
||||
/// </summary>
|
||||
private void OnPeekHotkey()
|
||||
{
|
||||
if (AppWindow.IsVisible)
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void LeftNavigationInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
|
||||
{
|
||||
ViewModel.AttemptLeftNavigation();
|
||||
}
|
||||
|
||||
private void RightNavigationInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
|
||||
{
|
||||
ViewModel.AttemptRightNavigation();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
ViewModel.FolderItemsQuery.Start();
|
||||
}
|
||||
|
||||
private void Uninitialize()
|
||||
{
|
||||
this.Hide();
|
||||
|
||||
// TODO: move into general ViewModel method when needed
|
||||
ViewModel.FolderItemsQuery.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle FilePreviewerSizeChanged event to adjust window size and position accordingly.
|
||||
/// </summary>
|
||||
/// <param name="sender">object</param>
|
||||
/// <param name="e">PreviewSizeChangedArgs</param>
|
||||
private void FilePreviewer_PreviewSizeChanged(object sender, PreviewSizeChangedArgs e)
|
||||
{
|
||||
// TODO: Use design-defined rules for adjusted window size
|
||||
var requestedSize = e.WindowSizeRequested;
|
||||
var monitorSize = this.GetMonitorSize();
|
||||
|
||||
var titleBarHeight = TitleBarControl.ActualHeight;
|
||||
|
||||
var maxContentSize = new Size(0, 0);
|
||||
var minContentSize = new Size(MinWindowWidth, MinWindowHeight - titleBarHeight);
|
||||
|
||||
var adjustedContentSize = new Size(0, 0);
|
||||
|
||||
if (e.WindowSizeFormat == SizeFormat.Percentage)
|
||||
{
|
||||
maxContentSize = new Size(monitorSize.Width * requestedSize.Width, (monitorSize.Height - titleBarHeight) * requestedSize.Height);
|
||||
minContentSize = new Size(MinWindowWidth, MinWindowHeight - titleBarHeight);
|
||||
|
||||
adjustedContentSize = maxContentSize.Fit(maxContentSize, minContentSize);
|
||||
}
|
||||
else if (e.WindowSizeFormat == SizeFormat.Pixels)
|
||||
{
|
||||
maxContentSize = new Size(monitorSize.Width * MaxWindowToMonitorRatio, (monitorSize.Height - titleBarHeight) * MaxWindowToMonitorRatio);
|
||||
minContentSize = new Size(MinWindowWidth, MinWindowHeight - titleBarHeight);
|
||||
|
||||
adjustedContentSize = requestedSize.Fit(maxContentSize, minContentSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, "Unknown SizeFormat set for resizing window.");
|
||||
adjustedContentSize = minContentSize;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Only re-center if window has not been resized by user (or use design-defined logic).
|
||||
// TODO: Investigate why portrait images do not perfectly fit edge-to-edge
|
||||
var monitorScale = this.GetMonitorScale();
|
||||
var scaledWindowWidth = adjustedContentSize.Width / monitorScale;
|
||||
var scaledWindowHeight = adjustedContentSize.Height / monitorScale;
|
||||
|
||||
this.CenterOnScreen(scaledWindowWidth + WindowHeightContentPadding, scaledWindowHeight + titleBarHeight + WindowWidthContentPadding);
|
||||
this.BringToForeground();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle AppWindow closing to prevent app termination on close.
|
||||
/// </summary>
|
||||
/// <param name="sender">AppWindow</param>
|
||||
/// <param name="args">AppWindowClosingEventArgs</param>
|
||||
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
Uninitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/modules/peek/Peek.UI/MainWindowViewModel.cs
Normal file
@@ -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.
|
||||
|
||||
namespace Peek.UI
|
||||
{
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Peek.UI.FileSystem;
|
||||
|
||||
public partial class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
private const int NavigationThrottleDelayMs = 100;
|
||||
|
||||
[ObservableProperty]
|
||||
private FolderItemsQuery _folderItemsQuery = new ();
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
NavigationThrottleTimer.Tick += NavigationThrottleTimer_Tick;
|
||||
NavigationThrottleTimer.Interval = TimeSpan.FromMilliseconds(NavigationThrottleDelayMs);
|
||||
}
|
||||
|
||||
private DispatcherTimer NavigationThrottleTimer { get; set; } = new ();
|
||||
|
||||
public void AttemptLeftNavigation()
|
||||
{
|
||||
if (NavigationThrottleTimer.IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationThrottleTimer.Start();
|
||||
|
||||
// TODO: return a bool so UI can give feedback in case navigation is unavailable
|
||||
FolderItemsQuery.UpdateCurrentItemIndex(FolderItemsQuery.CurrentItemIndex - 1);
|
||||
}
|
||||
|
||||
public void AttemptRightNavigation()
|
||||
{
|
||||
if (NavigationThrottleTimer.IsEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationThrottleTimer.Start();
|
||||
|
||||
// TODO: return a bool so UI can give feedback in case navigation is unavailable
|
||||
FolderItemsQuery.UpdateCurrentItemIndex(FolderItemsQuery.CurrentItemIndex + 1);
|
||||
}
|
||||
|
||||
private void NavigationThrottleTimer_Tick(object? sender, object e)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
((DispatcherTimer)sender).Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
220
src/modules/peek/Peek.UI/Native/NativeMethods.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
// 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 Peek.UI.Native
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Peek.Common.Models;
|
||||
|
||||
public static class NativeMethods
|
||||
{
|
||||
internal const uint PROCESS_ALL_ACCESS = 0x1f0fff;
|
||||
internal const IntPtr HWND_TOP = 0;
|
||||
internal const uint SWP_DRAWFRAME = 0x0020;
|
||||
internal const uint SWP_NOMOVE = 0x0002;
|
||||
internal const uint SWP_NOSIZE = 0x0001;
|
||||
internal const uint SWP_SHOWWINDOW = 0x0040;
|
||||
internal const int WM_SYSCOMMAND = 0x0112;
|
||||
internal const int SC_RESTORE = 0xF120;
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
|
||||
internal static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern HResult AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string? pszExtra, [Out] StringBuilder? pszOut, [In][Out] ref uint pcchOut);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, ref IntPtr ProcessId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr OpenProcess(uint fdwAccess, bool fInherit, IntPtr IDProcess);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern int CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr LoadLibrary(string lpLibName);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern bool FreeLibrary(IntPtr lib);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr bogusAttributes, int dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, int dwCreationFlags, IntPtr lpThreadId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern uint WaitForSingleObject(IntPtr hObject, int dwMilliseconds);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern IntPtr GetShellWindow();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
internal static extern int GetCurrentProcessId();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern int SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern int GetWindowText(Windows.Win32.Foundation.HWND hWnd, StringBuilder lpString, int nMaxCount);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AssocF
|
||||
{
|
||||
None = 0,
|
||||
Init_NoRemapCLSID = 0x1,
|
||||
Init_ByExeName = 0x2,
|
||||
Open_ByExeName = 0x2,
|
||||
Init_DefaultToStar = 0x4,
|
||||
Init_DefaultToFolder = 0x8,
|
||||
NoUserSettings = 0x10,
|
||||
NoTruncate = 0x20,
|
||||
Verify = 0x40,
|
||||
RemapRunDll = 0x80,
|
||||
NoFixUps = 0x100,
|
||||
IgnoreBaseClass = 0x200,
|
||||
}
|
||||
|
||||
public enum AssocStr
|
||||
{
|
||||
Command = 1,
|
||||
Executable,
|
||||
FriendlyDocName,
|
||||
FriendlyAppName,
|
||||
NoOpen,
|
||||
ShellNewValue,
|
||||
DDECommand,
|
||||
DDEIfExec,
|
||||
DDEApplication,
|
||||
DDETopic,
|
||||
}
|
||||
|
||||
public enum AccessibleObjectID : uint
|
||||
{
|
||||
OBJID_WINDOW = 0x00000000,
|
||||
OBJID_SYSMENU = 0xFFFFFFFF,
|
||||
OBJID_TITLEBAR = 0xFFFFFFFE,
|
||||
OBJID_MENU = 0xFFFFFFFD,
|
||||
OBJID_CLIENT = 0xFFFFFFFC,
|
||||
OBJID_VSCROLL = 0xFFFFFFFB,
|
||||
OBJID_HSCROLL = 0xFFFFFFFA,
|
||||
OBJID_SIZEGRIP = 0xFFFFFFF9,
|
||||
OBJID_CARET = 0xFFFFFFF8,
|
||||
OBJID_CURSOR = 0xFFFFFFF7,
|
||||
OBJID_ALERT = 0xFFFFFFF6,
|
||||
OBJID_SOUND = 0xFFFFFFF5,
|
||||
}
|
||||
|
||||
public enum WindowEvent : uint
|
||||
{
|
||||
EVENT_MIN = 0x00000001,
|
||||
EVENT_SYSTEM_START = 0x0001,
|
||||
EVENT_SYSTEM_SOUND = 0x0001,
|
||||
EVENT_SYSTEM_ALERT = 0x0002,
|
||||
EVENT_SYSTEM_FOREGROUND = 0x0003,
|
||||
EVENT_SYSTEM_MENUSTART = 0x0004,
|
||||
EVENT_SYSTEM_MENUEND = 0x0005,
|
||||
EVENT_SYSTEM_MENUPOPUPSTART = 0x0006,
|
||||
EVENT_SYSTEM_MENUPOPUPEND = 0x0007,
|
||||
EVENT_SYSTEM_CAPTURESTART = 0x0008,
|
||||
EVENT_SYSTEM_CAPTUREEND = 0x0009,
|
||||
EVENT_SYSTEM_MOVESIZESTART = 0x000A,
|
||||
EVENT_SYSTEM_MOVESIZEEND = 0x000B,
|
||||
EVENT_SYSTEM_CONTEXTHELPSTART = 0x000C,
|
||||
EVENT_SYSTEM_CONTEXTHELPEND = 0x000D,
|
||||
EVENT_SYSTEM_DRAGDROPSTART = 0x000E,
|
||||
EVENT_SYSTEM_DRAGDROPEND = 0x000F,
|
||||
EVENT_SYSTEM_DIALOGSTART = 0x0010,
|
||||
EVENT_SYSTEM_DIALOGEND = 0x0011,
|
||||
EVENT_SYSTEM_SCROLLINGSTART = 0x0012,
|
||||
EVENT_SYSTEM_SCROLLINGEND = 0x0013,
|
||||
EVENT_SYSTEM_SWITCHSTART = 0x0014,
|
||||
EVENT_SYSTEM_SWITCHEND = 0x0015,
|
||||
EVENT_SYSTEM_MINIMIZESTART = 0x0016,
|
||||
EVENT_SYSTEM_MINIMIZEEND = 0x0017,
|
||||
EVENT_SYSTEM_DESKTOPSWITCH = 0x0020,
|
||||
EVENT_SYSTEM_END = 0x00FF,
|
||||
EVENT_OEM_DEFINED_START = 0x0101,
|
||||
EVENT_OEM_DEFINED_END = 0x01FF,
|
||||
EVENT_CONSOLE_START = 0x4001,
|
||||
EVENT_CONSOLE_CARET = 0x4001,
|
||||
EVENT_CONSOLE_UPDATE_REGION = 0x4002,
|
||||
EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003,
|
||||
EVENT_CONSOLE_UPDATE_SCROLL = 0x4004,
|
||||
EVENT_CONSOLE_LAYOUT = 0x4005,
|
||||
EVENT_CONSOLE_START_APPLICATION = 0x4006,
|
||||
EVENT_CONSOLE_END_APPLICATION = 0x4007,
|
||||
EVENT_CONSOLE_END = 0x40FF,
|
||||
EVENT_UIA_EVENTID_START = 0x4E00,
|
||||
EVENT_UIA_EVENTID_END = 0x4EFF,
|
||||
EVENT_UIA_PROPID_START = 0x7500,
|
||||
EVENT_UIA_PROPID_END = 0x75FF,
|
||||
EVENT_OBJECT_START = 0x8000,
|
||||
EVENT_OBJECT_CREATE = 0x8000,
|
||||
EVENT_OBJECT_DESTROY = 0x8001,
|
||||
EVENT_OBJECT_SHOW = 0x8002,
|
||||
EVENT_OBJECT_HIDE = 0x8003,
|
||||
EVENT_OBJECT_REORDER = 0x8004,
|
||||
EVENT_OBJECT_FOCUS = 0x8005,
|
||||
EVENT_OBJECT_SELECTION = 0x8006,
|
||||
EVENT_OBJECT_SELECTIONADD = 0x8007,
|
||||
EVENT_OBJECT_SELECTIONREMOVE = 0x8008,
|
||||
EVENT_OBJECT_SELECTIONWITHIN = 0x8009,
|
||||
EVENT_OBJECT_STATECHANGE = 0x800A,
|
||||
EVENT_OBJECT_LOCATIONCHANGE = 0x800B,
|
||||
EVENT_OBJECT_NAMECHANGE = 0x800C,
|
||||
EVENT_OBJECT_DESCRIPTIONCHANGE = 0x800D,
|
||||
EVENT_OBJECT_VALUECHANGE = 0x800E,
|
||||
EVENT_OBJECT_PARENTCHANGE = 0x800F,
|
||||
EVENT_OBJECT_HELPCHANGE = 0x8010,
|
||||
EVENT_OBJECT_DEFACTIONCHANGE = 0x8011,
|
||||
EVENT_OBJECT_ACCELERATORCHANGE = 0x8012,
|
||||
EVENT_OBJECT_INVOKED = 0x8013,
|
||||
EVENT_OBJECT_TEXTSELECTIONCHANGED = 0x8014,
|
||||
EVENT_OBJECT_CONTENTSCROLLED = 0x8015,
|
||||
EVENT_SYSTEM_ARRANGMENTPREVIEW = 0x8016,
|
||||
EVENT_OBJECT_CLOAKED = 0x8017,
|
||||
EVENT_OBJECT_UNCLOAKED = 0x8018,
|
||||
EVENT_OBJECT_LIVEREGIONCHANGED = 0x8019,
|
||||
EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED = 0x8020,
|
||||
EVENT_OBJECT_DRAGSTART = 0x8021,
|
||||
EVENT_OBJECT_DRAGCANCEL = 0x8022,
|
||||
EVENT_OBJECT_DRAGCOMPLETE = 0x8023,
|
||||
EVENT_OBJECT_DRAGENTER = 0x8024,
|
||||
EVENT_OBJECT_DRAGLEAVE = 0x8025,
|
||||
EVENT_OBJECT_DRAGDROPPED = 0x8026,
|
||||
EVENT_OBJECT_IME_SHOW = 0x8027,
|
||||
EVENT_OBJECT_IME_HIDE = 0x8028,
|
||||
EVENT_OBJECT_IME_CHANGE = 0x8029,
|
||||
EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED = 0x8030,
|
||||
EVENT_OBJECT_END = 0x80FF,
|
||||
EVENT_ATOM_START = 0xC000,
|
||||
EVENT_AIA_START = 0xA000,
|
||||
EVENT_AIA_END = 0xAFFF,
|
||||
EVENT_ATOM_END = 0xFFFF,
|
||||
EVENT_MAX = 0x7FFFFFFF,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum WinEventHookFlags : uint
|
||||
{
|
||||
WINEVENT_OUTOFCONTEXT = 0x0000,
|
||||
WINEVENT_SKIPOWNTHREAD = 0x0001,
|
||||
WINEVENT_SKIPOWNPROCESS = 0x0002,
|
||||
WINEVENT_INCONTEXT = 0x0004,
|
||||
}
|
||||
}
|
||||
29
src/modules/peek/Peek.UI/NativeEventWaiter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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.Threading;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace Peek.UI.Native
|
||||
{
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
new Thread(() =>
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
while (true)
|
||||
{
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
dispatcherQueue.TryEnqueue(() => callback());
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/modules/peek/Peek.UI/NativeMethods.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
MonitorFromWindow
|
||||
GetMonitorInfo
|
||||
GetDpiForWindow
|
||||
GetWindowTextLength
|
||||
SetWinEventHook
|
||||
UnhookWinEvent
|
||||
48
src/modules/peek/Peek.UI/Package.appxmanifest
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="62c2f436-8802-4c26-a73d-b3b8613016fd"
|
||||
Publisher="CN=sachaple"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Peek.UI</DisplayName>
|
||||
<PublisherDisplayName>sachaple</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Peek.UI"
|
||||
Description="Peek.UI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
99
src/modules/peek/Peek.UI/Peek.UI.csproj
Normal file
@@ -0,0 +1,99 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
<PropertyGroup>
|
||||
<AssemblyName>PowerToys.Peek.UI</AssemblyName>
|
||||
<AssemblyTitle>PowerToys.Peek.UI</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys Peek UI</AssemblyDescription>
|
||||
<RootNamespace>Peek.UI</RootNamespace>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\modules\Peek\</OutputPath>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<Nullable>Enable</Nullable>
|
||||
<ApplicationIcon>Assets\Icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\Icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\AppList.scale-100.png" />
|
||||
<None Remove="Assets\AppList.scale-125.png" />
|
||||
<None Remove="Assets\AppList.scale-150.png" />
|
||||
<None Remove="Assets\AppList.scale-200.png" />
|
||||
<None Remove="Assets\AppList.scale-400.png" />
|
||||
<None Remove="Views\TitleBar.xaml" />
|
||||
<None Remove="Views\UnsupportedFile.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="Shell32">
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="SHDocVw">
|
||||
<VersionMinor>1</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>eab22ac0-30c1-11cf-a7eb-0000c05bae0b</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
|
||||
<PackageReference Include="WinUIEx" Version="1.8.0" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\Peek.Common\Peek.Common.csproj" />
|
||||
<ProjectReference Include="..\Peek.FilePreviewer\Peek.FilePreviewer.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\Icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\UnsupportedFile.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\TitleBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
208
src/modules/peek/Peek.UI/Strings/en-us/Resources.resw
Normal file
@@ -0,0 +1,208 @@
|
||||
<?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="AppTitle.Title" xml:space="preserve">
|
||||
<value>Peek</value>
|
||||
<comment>Name of application.</comment>
|
||||
</data>
|
||||
<data name="AppTitle_FileCounts_Text" xml:space="preserve">
|
||||
<value>({0}/{1})</value>
|
||||
<comment>Text for the file count in the titlebar. 0: the index of the current file. 1: the total number of files selected.</comment>
|
||||
</data>
|
||||
<data name="LaunchAppButton_OpenWith_Text" xml:space="preserve">
|
||||
<value>Open with</value>
|
||||
<comment>Text for button to launch the application picker.</comment>
|
||||
</data>
|
||||
<data name="LaunchAppButton_OpenWith_ToolTip" xml:space="preserve">
|
||||
<value>Open with (Enter)</value>
|
||||
<comment>Tooltip for button to launch the application picker.</comment>
|
||||
</data>
|
||||
<data name="LaunchAppButton_OpenWithApp_Text" xml:space="preserve">
|
||||
<value>Open with {0}</value>
|
||||
<comment>Text for button to launch default application. 0: name of the default application.</comment>
|
||||
</data>
|
||||
<data name="LaunchAppButton_OpenWithApp_ToolTip" xml:space="preserve">
|
||||
<value>Open with {0} (Enter)</value>
|
||||
<comment>Tooltip for button to launch default application. 0: name of the default application.</comment>
|
||||
</data>
|
||||
<data name="UnsupportedFile_FileType" xml:space="preserve">
|
||||
<value>File Type: {0}</value>
|
||||
<comment>File Type label for the unsupported files view. {0} is the type.</comment>
|
||||
</data>
|
||||
<data name="UnsupportedFile_FileSize" xml:space="preserve">
|
||||
<value>Size: {0}</value>
|
||||
<comment>File Size label for the unsupported files view. {0} is the size.</comment>
|
||||
</data>
|
||||
<data name="UnsupportedFile_DateModified" xml:space="preserve">
|
||||
<value>Date Modified: {0}</value>
|
||||
<comment>Date Modified label for the unsupported files view. {0} is the date.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_ByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} B</value>
|
||||
<comment>Abbrivation for the size unit byte.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_KiloByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} KB</value>
|
||||
<comment>Abbrivation for the size unit kilobyte.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_MegaByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} MB</value>
|
||||
<comment>Abbrivation for the size unit megabyte.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_GigaByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} GB</value>
|
||||
<comment>Abbrivation for the size unit gigabyte.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_TeraByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} TB</value>
|
||||
<comment>Abbrivation for the size unit terabyte.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_PetaByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} PB</value>
|
||||
<comment>Abbrivation for the size unit petabyte.</comment>
|
||||
</data>
|
||||
<data name="ReadableString_ExaByteAbbreviationFormat" xml:space="preserve">
|
||||
<value>{0} EB</value>
|
||||
<comment>Abbrivation for the size unit exabyte.</comment>
|
||||
</data>
|
||||
<data name="PreviewTooltip_FileName" xml:space="preserve">
|
||||
<value>Filename: {0}</value>
|
||||
<comment>Filename for the tooltip of preview. {0} is the name.</comment>
|
||||
</data>
|
||||
<data name="PreviewTooltip_FileType" xml:space="preserve">
|
||||
<value>Item Type: {0}</value>
|
||||
<comment>Item Type for the tooltip of preview. {0} is the type.</comment>
|
||||
</data>
|
||||
<data name="PreviewTooltip_DateModified" xml:space="preserve">
|
||||
<value>Date Modified: {0}</value>
|
||||
<comment>Date Modified label for the tooltip of preview. {0} is the date.</comment>
|
||||
</data>
|
||||
<data name="PreviewTooltip_Dimensions" xml:space="preserve">
|
||||
<value>Dimensions: {0} x {1}</value>
|
||||
<comment>Dimensions label for the tooltip of preview. {0} is the width, {1} is the height.</comment>
|
||||
</data>
|
||||
<data name="PreviewTooltip_FileSize" xml:space="preserve">
|
||||
<value>Size: {0}</value>
|
||||
<comment>File Size label for the tooltip of preview. {0} is the size.</comment>
|
||||
</data>
|
||||
<data name="PreviewTooltip_Blank" xml:space="preserve">
|
||||
<value>File preview</value>
|
||||
<comment>Tooltip of preview when there's no file info available.</comment>
|
||||
</data>
|
||||
</root>
|
||||
133
src/modules/peek/Peek.UI/Views/TitleBar.xaml
Normal file
@@ -0,0 +1,133 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.UI.Views.TitleBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Peek.UI.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="TitleBarRootContainer" Height="48">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="SystemLeftPaddingColumn" Width="0" />
|
||||
<ColumnDefinition x:Name="DraggableColumn" Width="*" />
|
||||
<ColumnDefinition x:Name="LaunchAppButtonColumn" Width="Auto" />
|
||||
<ColumnDefinition x:Name="AppRightPaddingColumn" Width="65" />
|
||||
<ColumnDefinition x:Name="SystemRightPaddingColumn" Width="0" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid
|
||||
x:Name="AppIconAndFileTitleContainer"
|
||||
Grid.Column="1"
|
||||
Margin="8,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="AppIconColumn" Width="32" />
|
||||
<ColumnDefinition x:Name="FileTitleColumn" Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
x:Name="PeekLogo"
|
||||
x:Uid="PeekLogo"
|
||||
Grid.Column="0"
|
||||
Width="24"
|
||||
Height="24"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Source="../Assets/AppList.scale-400.png"
|
||||
Stretch="UniformToFill" />
|
||||
|
||||
<Grid
|
||||
x:Name="FileCountAndNameContainer"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
ColumnSpacing="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="FileCountColumn" Width="auto" />
|
||||
<ColumnDefinition x:Name="FileNameColumn" Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
x:Name="AppTitle_FileCount"
|
||||
x:Uid="AppTitle_FileCount"
|
||||
Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind FileCountText, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsMultiSelection, Mode=OneWay}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="AppTitle_FileName"
|
||||
x:Uid="AppTitle_FileName"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind File.FileName, Mode=OneWay}"
|
||||
TextWrapping="NoWrap" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Button
|
||||
x:Name="LaunchAppButton"
|
||||
x:Uid="LaunchAppButton"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Command="{x:Bind LaunchDefaultAppButtonAsyncCommand, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind OpenWithAppToolTip, Mode=OneWay}">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<FontIcon
|
||||
x:Name="LaunchAppButton_Icon"
|
||||
x:Uid="LaunchAppButton_Icon"
|
||||
FontSize="{StaticResource CaptionTextBlockFontSize}"
|
||||
Glyph="" />
|
||||
<TextBlock
|
||||
x:Name="LaunchAppButton_Text"
|
||||
x:Uid="LaunchAppButton_Text"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind OpenWithAppText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
<Button.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Enter" />
|
||||
</Button.KeyboardAccelerators>
|
||||
</Button>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="AdaptiveWidth">
|
||||
<VisualState x:Name="MaximumLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="560" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LaunchAppButton_Text.Visibility" Value="Visible" />
|
||||
<Setter Target="LaunchAppButton.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="MediumLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="340" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LaunchAppButton_Text.Visibility" Value="Collapsed" />
|
||||
<Setter Target="LaunchAppButton.Visibility" Value="Visible" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="MinimumLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LaunchAppButton_Text.Visibility" Value="Collapsed" />
|
||||
<Setter Target="LaunchAppButton.Visibility" Value="Collapsed" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
237
src/modules/peek/Peek.UI/Views/TitleBar.xaml.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
// 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 Peek.UI.Views
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Peek.Common.Models;
|
||||
using Peek.UI.Extensions;
|
||||
using Peek.UI.Helpers;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
using Windows.Graphics;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
using WinUIEx;
|
||||
|
||||
[INotifyPropertyChanged]
|
||||
public sealed partial class TitleBar : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty FileProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(File),
|
||||
typeof(File),
|
||||
typeof(TitleBar),
|
||||
new PropertyMetadata(null, (d, e) => ((TitleBar)d).OnFilePropertyChanged()));
|
||||
|
||||
public static readonly DependencyProperty FileIndexProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(FileIndex),
|
||||
typeof(int),
|
||||
typeof(TitleBar),
|
||||
new PropertyMetadata(-1, (d, e) => ((TitleBar)d).OnFileIndexPropertyChanged()));
|
||||
|
||||
public static readonly DependencyProperty IsMultiSelectionProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsMultiSelection),
|
||||
typeof(bool),
|
||||
typeof(TitleBar),
|
||||
new PropertyMetadata(false));
|
||||
|
||||
public static readonly DependencyProperty NumberOfFilesProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(NumberOfFiles),
|
||||
typeof(int),
|
||||
typeof(TitleBar),
|
||||
new PropertyMetadata(null, null));
|
||||
|
||||
[ObservableProperty]
|
||||
private string openWithAppText = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWith_Text");
|
||||
|
||||
[ObservableProperty]
|
||||
private string openWithAppToolTip = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWith_ToolTip");
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileCountText;
|
||||
|
||||
public TitleBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
TitleBarRootContainer.SizeChanged += TitleBarRootContainer_SizeChanged;
|
||||
}
|
||||
|
||||
public File File
|
||||
{
|
||||
get => (File)GetValue(FileProperty);
|
||||
set => SetValue(FileProperty, value);
|
||||
}
|
||||
|
||||
public int FileIndex
|
||||
{
|
||||
get => (int)GetValue(FileIndexProperty);
|
||||
set => SetValue(FileIndexProperty, value);
|
||||
}
|
||||
|
||||
public bool IsMultiSelection
|
||||
{
|
||||
get => (bool)GetValue(IsMultiSelectionProperty);
|
||||
set => SetValue(IsMultiSelectionProperty, value);
|
||||
}
|
||||
|
||||
public int NumberOfFiles
|
||||
{
|
||||
get => (int)GetValue(NumberOfFilesProperty);
|
||||
set => SetValue(NumberOfFilesProperty, value);
|
||||
}
|
||||
|
||||
private string? DefaultAppName { get; set; }
|
||||
|
||||
private Window? MainWindow { get; set; }
|
||||
|
||||
public void SetTitleBarToWindow(MainWindow mainWindow)
|
||||
{
|
||||
MainWindow = mainWindow;
|
||||
|
||||
if (AppWindowTitleBar.IsCustomizationSupported())
|
||||
{
|
||||
UpdateTitleBarCustomization(mainWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
ThemeHelpers.SetImmersiveDarkMode(hWnd, ThemeHelpers.GetAppTheme() == AppTheme.Dark);
|
||||
Visibility = Visibility.Collapsed;
|
||||
|
||||
// Set window icon
|
||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
|
||||
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
appWindow.SetIcon("Assets/Icon.ico");
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async void LaunchDefaultAppButtonAsync()
|
||||
{
|
||||
StorageFile storageFile = await File.GetStorageFileAsync();
|
||||
LauncherOptions options = new ();
|
||||
|
||||
if (string.IsNullOrEmpty(DefaultAppName))
|
||||
{
|
||||
// If there's no default app found, open the App picker
|
||||
options.DisplayApplicationPicker = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to launch the default app for current file format
|
||||
bool result = await Launcher.LaunchFileAsync(storageFile, options);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
// If we couldn't successfully open the default app, open the App picker as a fallback
|
||||
options.DisplayApplicationPicker = true;
|
||||
await Launcher.LaunchFileAsync(storageFile, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TitleBarRootContainer_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateDragRegion();
|
||||
}
|
||||
|
||||
private void UpdateDragRegion()
|
||||
{
|
||||
if (MainWindow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var appWindow = MainWindow.GetAppWindow();
|
||||
if (AppWindowTitleBar.IsCustomizationSupported() && appWindow != null && appWindow.TitleBar.ExtendsContentIntoTitleBar)
|
||||
{
|
||||
var scale = MainWindow.GetMonitorScale();
|
||||
|
||||
SystemRightPaddingColumn.Width = new GridLength(appWindow.TitleBar.RightInset / scale);
|
||||
SystemLeftPaddingColumn.Width = new GridLength(appWindow.TitleBar.LeftInset / scale);
|
||||
|
||||
var dragRectsList = new List<RectInt32>();
|
||||
|
||||
RectInt32 dragRectangleLeft;
|
||||
dragRectangleLeft.X = (int)(SystemLeftPaddingColumn.ActualWidth * scale);
|
||||
dragRectangleLeft.Y = 0;
|
||||
dragRectangleLeft.Height = (int)(TitleBarRootContainer.ActualHeight * scale);
|
||||
dragRectangleLeft.Width = (int)(DraggableColumn.ActualWidth * scale);
|
||||
|
||||
RectInt32 dragRectangleRight;
|
||||
dragRectangleRight.X = (int)((SystemLeftPaddingColumn.ActualWidth + DraggableColumn.ActualWidth + LaunchAppButtonColumn.ActualWidth) * scale);
|
||||
dragRectangleRight.Y = 0;
|
||||
dragRectangleRight.Height = (int)(TitleBarRootContainer.ActualHeight * scale);
|
||||
dragRectangleRight.Width = (int)(AppRightPaddingColumn.ActualWidth * scale);
|
||||
|
||||
dragRectsList.Add(dragRectangleLeft);
|
||||
dragRectsList.Add(dragRectangleRight);
|
||||
|
||||
appWindow.TitleBar.SetDragRectangles(dragRectsList.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTitleBarCustomization(MainWindow mainWindow)
|
||||
{
|
||||
if (AppWindowTitleBar.IsCustomizationSupported())
|
||||
{
|
||||
AppWindow appWindow = mainWindow.GetAppWindow();
|
||||
appWindow.TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||
|
||||
mainWindow.SetTitleBar(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFilePropertyChanged()
|
||||
{
|
||||
if (File == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateFileCountText();
|
||||
UpdateDefaultAppToLaunch();
|
||||
}
|
||||
|
||||
private void OnFileIndexPropertyChanged()
|
||||
{
|
||||
UpdateFileCountText();
|
||||
}
|
||||
|
||||
private void UpdateFileCountText()
|
||||
{
|
||||
// Update file count
|
||||
if (NumberOfFiles > 1)
|
||||
{
|
||||
string fileCountTextFormat = ResourceLoader.GetForViewIndependentUse().GetString("AppTitle_FileCounts_Text");
|
||||
FileCountText = string.Format(fileCountTextFormat, FileIndex + 1, NumberOfFiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDefaultAppToLaunch()
|
||||
{
|
||||
// Update the name of default app to launch
|
||||
DefaultAppName = DefaultAppHelper.TryGetDefaultAppName(File.Extension);
|
||||
|
||||
string openWithAppTextFormat = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWithApp_Text");
|
||||
OpenWithAppText = string.Format(openWithAppTextFormat, DefaultAppName);
|
||||
|
||||
string openWithAppToolTipFormat = ResourceLoader.GetForViewIndependentUse().GetString("LaunchAppButton_OpenWithApp_ToolTip");
|
||||
OpenWithAppToolTip = string.Format(openWithAppToolTipFormat, DefaultAppName);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/modules/peek/Peek.UI/Window/WindowEventHook.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 Peek.UI.WindowEventHook
|
||||
{
|
||||
using System;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Peek.UI.Native;
|
||||
using Windows.Win32;
|
||||
|
||||
public class WindowEventHook : IDisposable
|
||||
{
|
||||
public event EventHandler<WindowEventHookEventArgs>? WindowEventReceived;
|
||||
|
||||
public WindowEventHook()
|
||||
{
|
||||
var moveOrResizeEvent = WindowEvent.EVENT_SYSTEM_MOVESIZEEND;
|
||||
|
||||
var windowHookEventHandler = new WindowEventProc(OnWindowEventProc);
|
||||
|
||||
var hook = PInvoke.SetWinEventHook(
|
||||
(uint)moveOrResizeEvent,
|
||||
(uint)moveOrResizeEvent,
|
||||
new SafeHandle(),
|
||||
windowHookEventHandler,
|
||||
0,
|
||||
0,
|
||||
WinEventHookFlags.WINEVENT_OUTOFCONTEXT | WinEventHookFlags.WINEVENT_SKIPOWNPROCESS);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void OnWindowEventProc(nint hWinEventHook, WindowEvent eventType, nint hwnd, AccessibleObjectID idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public record WindowEventHookEventArgs(WindowEvent eventType, IntPtr windowHandle);
|
||||
|
||||
public delegate void WindowEventProc(
|
||||
IntPtr hWinEventHook,
|
||||
WindowEvent eventType,
|
||||
IntPtr hwnd,
|
||||
AccessibleObjectID idObject,
|
||||
int idChild,
|
||||
uint dwEventThread,
|
||||
uint dwmsEventTime);
|
||||
}
|
||||
32
src/modules/peek/Peek.UI/Window/WindowEventSafeHandle.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 Peek.UI.WindowEventHook
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Peek.UI.Native;
|
||||
|
||||
public class WindowEventSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private WindowEventSafeHandle(IntPtr handle)
|
||||
: base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
public WindowEventSafeHandle()
|
||||
: base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
NativeMethods.DeleteObject(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/modules/peek/Peek.UI/app.manifest
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="Peek.UI.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
|
||||
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
|
||||
It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
7
src/modules/peek/WIC/Classes/CLSID.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace WIC
|
||||
{
|
||||
internal static class CLSID
|
||||
{
|
||||
public const string WICImagingFactory = "cacaf262-9370-4615-a13b-9f5539da4c0a";
|
||||
}
|
||||
}
|
||||
14
src/modules/peek/WIC/Classes/WICImagingFactory.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WIC
|
||||
{
|
||||
[ComImport]
|
||||
[Guid(IID.IWICImagingFactory)]
|
||||
[CoClass(typeof(WICImagingFactoryClass))]
|
||||
public interface WICImagingFactory : IWICImagingFactory { }
|
||||
|
||||
[ComImport]
|
||||
[Guid(CLSID.WICImagingFactory)]
|
||||
[ComDefaultInterface(typeof(IWICImagingFactory))]
|
||||
public class WICImagingFactoryClass { }
|
||||
}
|
||||
43
src/modules/peek/WIC/CoTaskMemPtr.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WIC
|
||||
{
|
||||
internal struct CoTaskMemPtr : IDisposable
|
||||
{
|
||||
public static CoTaskMemPtr From<T>(T? nullableStructure) where T : struct
|
||||
{
|
||||
IntPtr value;
|
||||
if (nullableStructure.HasValue)
|
||||
{
|
||||
value = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(T)));
|
||||
Marshal.StructureToPtr(nullableStructure, value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = IntPtr.Zero;
|
||||
}
|
||||
return new CoTaskMemPtr(value);
|
||||
}
|
||||
|
||||
public CoTaskMemPtr(IntPtr value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private IntPtr value;
|
||||
|
||||
public static implicit operator IntPtr(CoTaskMemPtr safeIntPtr)
|
||||
{
|
||||
return safeIntPtr.value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (value != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeCoTaskMem(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/modules/peek/WIC/Constants/ContainerFormat.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace WIC
|
||||
{
|
||||
public static class ContainerFormat
|
||||
{
|
||||
public static readonly Guid Bmp = new Guid(0x0af1d87e, 0xfcfe, 0x4188, 0xbd, 0xeb, 0xa7, 0x90, 0x64, 0x71, 0xcb, 0xe3);
|
||||
public static readonly Guid Png = new Guid(0x1b7cfaf4, 0x713f, 0x473c, 0xbb, 0xcd, 0x61, 0x37, 0x42, 0x5f, 0xae, 0xaf);
|
||||
public static readonly Guid Ico = new Guid(0xa3a860c4, 0x338f, 0x4c17, 0x91, 0x9a, 0xfb, 0xa4, 0xb5, 0x62, 0x8f, 0x21);
|
||||
public static readonly Guid Jpeg = new Guid(0x19e4a5aa, 0x5662, 0x4fc5, 0xa0, 0xc0, 0x17, 0x58, 0x02, 0x8e, 0x10, 0x57);
|
||||
public static readonly Guid Tiff = new Guid(0x163bcc30, 0xe2e9, 0x4f0b, 0x96, 0x1d, 0xa3, 0xe9, 0xfd, 0xb7, 0x88, 0xa3);
|
||||
public static readonly Guid Gif = new Guid(0x1f8a5601, 0x7d4d, 0x4cbd, 0x9c, 0x82, 0x1b, 0xc8, 0xd4, 0xee, 0xb9, 0xa5);
|
||||
public static readonly Guid Wmp = new Guid(0x57a37caa, 0x367a, 0x4540, 0x91, 0x6b, 0xf1, 0x83, 0xc5, 0x09, 0x3a, 0x4b);
|
||||
public static readonly Guid Dds = new Guid(0x9967cb95, 0x2e85, 0x4ac8, 0x8c, 0xa2, 0x83, 0xd7, 0xcc, 0xd4, 0x25, 0xc9);
|
||||
}
|
||||
}
|
||||
7
src/modules/peek/WIC/Constants/HResult.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace WIC
|
||||
{
|
||||
public struct HResult
|
||||
{
|
||||
public const int WINCODEC_ERR_PROPERTYNOTFOUND = unchecked((int)0x88982F40);
|
||||
}
|
||||
}
|
||||