Compare commits

...

27 Commits

Author SHA1 Message Date
Samuel Chapleau
bf72adc942 wip 2022-12-08 22:12:32 -08:00
Jojo Zhou
60bf86825b [Peek] Add tooltip to File (#22640)
* Add tooltip to File

* Add placeholder text for no tooltip

* Address comments

* Use StringBuilder

Co-authored-by: Jojo Zhou <yizzho@microsoft.com>
2022-12-08 15:55:28 -08:00
sujessie
4ef3f23897 [Peek] Unsupported File Previewer - Setting Window Size (#22645)
* Adding setting for unsupported file window

* Fix
2022-12-08 15:44:42 -08:00
Robson
5bd9dd5935 [Peek] add extra logic to properly render PNG files with transparency (#22613)
* [Peek] added extra logic to render PNG files with proper transparency

* Moved logic to ThumbnailHelper
Cleanup

* Created a separated previewer for PNG to only load the preview image with thumbnail logic

* removed unused code

* Updated state loading change
2022-12-08 15:42:38 -08:00
Michael Salmon
5590eb9484 [Peek] New File Explorer tabs break Shell API to get selected files (#22641)
* fix FE tab bug

* remove unnecessary unsafe keyword
2022-12-08 15:39:14 -08:00
Jojo Zhou
6f06f76784 [Peek] Refine titlebar adaptive width (#22642)
* Adjust adaptive width of titlebar

* Remove visualstate setters for AppTitle_FileCount

Co-authored-by: Jojo Zhou <yizzho@microsoft.com>
2022-12-08 15:06:16 -08:00
Robson
5981d0e81e [Peek] add PDF viewing support (#22636)
* [Peek] add PDF viewing support

* Fixed issue which would redirect some HTML and PDF files to external browser

* Fixed refactored interface name
2022-12-08 13:41:02 -08:00
Michael Salmon
539a4e5678 [Peek] ImagePreviewer - Handle error states (#22637)
* add better preview state handling

* add error handling in imagepreviewer and better state handling

* fix error handling so exception is not bubbled up

* improve performance and hook up unsupported previewer on error

* remove commented code

* address pr comments
2022-12-08 11:25:29 -08:00
Daniel Chau
aea217ddca [Peek] Fix foregrounding (#22633)
* Fixing foregrounding

* Get window handle inside BringToForeground extension method

Co-authored-by: Daniel Chau <dancha@microsoft.com>
Co-authored-by: Samuel Chapleau <sachaple@microsoft.com>
2022-12-08 10:11:24 -08:00
sujessie
d001a4c0e0 [Peek] Unsupported File Previewer - Formatting string from resources (#22609)
* Moving to string resource usage

* Moving ReadableStringHelper to common project

* Fix comments
2022-12-08 09:49:00 -08:00
Samuel Chapleau
5712123598 Fix titlebar draggable region and interactive region (bump WinAppSdk to latest) 2022-12-07 22:57:45 -08:00
Samuel Chapleau
e504653323 Update titlebar filecount text 2022-12-07 20:47:35 -08:00
Esteban Margaron
b46b8d176f Make CurrentItemIndex setter private 2022-12-07 18:51:02 -08:00
Esteban Margaron
b98f233b75 Fix wrong thread exception 2022-12-07 18:45:11 -08:00
Samuel Chapleau
083ec27a45 Fix crash 2022-12-07 18:02:26 -08:00
Yawen Hou
bca780f886 [Peek] Add customized title bar (#22600)
* Add basic button UI

* Add function to get default app name and to open file in default app

* Correct error output

* Add filename to titlebar

* Remove titlebar text from Resw

* Add basic button UI

* Add function to get default app name and to open file in default app

* Add filename to titlebar

* Correct error output

* Remove titlebar text from Resw

* Add SetDragRectangles

* Correct logic, update function name

* Add localization

* Cleanup and adaptive width

* Add fileIndex/NumberOfFiles for multiple files activation

* Refine titlebar styles

* Update error message; Return HResult from native methods; Update variable initialisation and string null testing

* Titlebar height and adaptive width refinement

* Add fallback to launch app picker if fail to open default app

* Temp change to hide AppTitle_FileCount

* Update launch button to command; Add keyboard accelerator

* Update titlebar inactive background color

* Update tooltip to add keyboard accelerator

* Add comments to resw file

* Fix accidental deletion from previous merge

Co-authored-by: Jojo Zhou <yizzho@microsoft.com>
Co-authored-by: Yawen Hou <yawenhou@microsoft.com>
2022-12-07 16:56:01 -08:00
estebanm123
d4e618cdc9 [Peek] Add basic file querying and navigation (#22589)
* Refactor to facilitate file data initialization

* Extract file-related code to new FileManager class

* Add temp basic version

* Clean + add todo for cancellations

* Fix various nav-related issues

* Temp - start moving iteration-related code to bg thread

* Minor tweaks

* Add FEHelper todo

* Rename FileManager + various tweaks

* Add basic throttling

* Improve bg thread synchronization

* Clean

* Clean

* Rename based on feedback

* Rename FileQuery

* Rename properties

* Rename remaining fields

* Add todos for nav success/failures

Co-authored-by: Esteban Margaron <emargaron@microsoft.com>
2022-12-07 15:03:50 -08:00
Robson
e1cb01d188 [Peek] Add WebView2 integration (#22506)
* First commit with WIP logic to support WV2 in Peek module

* Minor code cleanup and try/catch block

* Added control to wrap WebView2 logic

* Cleanup

* Added logic to handle HTML previewing
Properly update FilePreview according to file type

* Code cleanup
Updated comments

* Updated comment

* Removed comment

* Code cleanup

* Improved opening of web browser preview to avoid "blank" or "seeing previous page" issue
Removed unused method
Added xaml fallback to guarantee default/starting state

* Removed folder

* Updated factory logic to match master

* address code review

* addressed PR review

* address PR review

* Address PR review

* address PR review

* Address PR review
2022-12-07 14:24:18 -08:00
sujessie
bddfe42d39 [Peek] Adding unsupported file previewer (#22598)
* Unsupported file previewer

* Fix file display info

* Fix property store calls

* Update TODO
2022-12-07 13:07:21 -08:00
Daniel Chau
30d346c93f [Peek] Enable PropertyStore for offline files (#22567)
* Enabling PropertyStore for offline files

Co-authored-by: Daniel Chau <dancha@microsoft.com>
2022-12-07 11:20:39 -05:00
Samuel Chapleau
899b5016b9 [Peek] Fix installer builds, project configs and update assets (#22540)
* Update installer

* Fix installer errors

* Fix peek vcxproj

* Add package signing

* Add peek to arm64

* Add back ARM64 toMeasureToolUI

* Add versions to project

* Update assets and icons

* Add correct icon
2022-12-06 18:14:57 -08:00
Samuel Chapleau
0d05089316 Add scale awareness to window centering (#22541) 2022-12-06 17:52:38 -08:00
jth-ms
6295ed86ca Juliata/filetypes (#22538)
* Using the same list of file extensions as Lightbox's AppxManifest, and ensuring we convert file extension to lowercase

* Add IsFileTypeSupported to IPreviewer

* respond to PR comments
2022-12-06 17:18:20 -08:00
Daniel Chau
73925885dd [Peek] Fetching image size through PropertyStore (#22530)
* Fetching metadata from PropertySTore

* Releasing objects to fix crash

* Creating new PropertyHelper

Co-authored-by: Daniel Chau <dancha@microsoft.com>
2022-12-06 15:28:48 -08:00
Michael Salmon
437d2dd5f7 [Peek] Plugin pattern to enable any file type previewing (#22475) 2022-12-06 11:24:21 -08:00
Samuel Chapleau
a4bd09a2c8 Bump Microsoft.Windows.SDK.BuildTools version 2022-12-05 16:00:31 -08:00
Samuel Chapleau
c2aae52bba Peek (#22498)
* Add peek dll project

* add spacebar preview and launch on hotkey press

* add todo

* add process handle to handle continuous press of hotkey

* add tool to stop all powertoys processes

* Add a blank Peek page and update nav menu

* Add some initial content to Peek page including a toggle

* refactor settings parsing

* rename spacebar peek to peek viewer

* rename script to stop powertoys processes

* remove tool

* Adding FileUtils for retrieving selected file in File Explorer

* Remove unnecessary SndPeekSettings

* Add shortcut setting

* Set the shortcut to ctrl+space

* Launching viewer with selected FE file

* Add PeekUI WinUI3 project with interop events

* Moving FileTypeUtils into PeekFileUtils project

* execute winui3 app on hotkey

* Fix paths with spaces

* remove winui3 project

* Resolve comment

* add wpf app with toggle visibility on hotkey

* fix visibility on startup

* remove window properties and add todos

* Fixed hidden extension and system file handling

* wip

* Add working WPF app with FileExplorer querying

* remove c++ projects

* Move native awaiter

* Working Image control with image files

* Resize and move window based on explorer monitor

* Image render, window positioning and sizing clean up

* add window management logic and selection logic

* add extension methods to add circular iterating capability to linkedlistnode

* Add OnArrowKeyPresshandler

* Added titlebar with file name and scaling with titlebar height

* fix flashing window on startup and process kept alive when powertoys exits

* remove wait for debugger loop in ui

* Add KeyIsDown method

* Fix KeyDown issue with Key handled and check for repeat

* Add thumbnail logic

* Add all folder items if only one item is selected

* File type helper

* Using hresult

* Add cancellation and rotation handling

* Use extension instead of path

* fIX CONFLICTS

* Fixing some file type checks

* Add new icon for Peek

* Update page with the new Peek icon

* Initialize IsEnabled and hook ActivationShortcut to dllmain

* add icon to taskbar and titlebar

* Add theme sensitive backgrounds

* rename event handlers

* add settings image

* Move window data into obserable object

* Refactor viewmodel, interop and helpers

* Clean up

* Add loading spinner

* Add todos

* Fix conflicts

* Move native code into its own folder

* Add peek to installer

* Fix building peek and peekui projects

* Replace UWP namespaces to WinAppSDK

* Working WASDK placeholder project

* Add exit when powertoys runner exit

* Working winui3 with image display

* Add WIC project with <TreatWarningAsErros> false for now

* Fit content to window

* Use Size from Windows.Foundation

* Change order

* Add some todos

* Refactored native/interop code and added helpers to imagepreviewer

* Rename projects

* Move some code

* Remove using

Co-authored-by: Michael Salmon <miksalmon@users.noreply.github.com>
Co-authored-by: Michael Salmon 🐟 <michaelpsalmon@outlook.com>
Co-authored-by: Alireza Ebadi Ghajari <alirezae@microsoft.com>
Co-authored-by: Jessie Su <Jessie.Su@microsoft.com>
Co-authored-by: sujessie <102062556+sujessie@users.noreply.github.com>
2022-12-05 09:16:06 -08:00
215 changed files with 10821 additions and 6 deletions

View File

@@ -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",

View File

@@ -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}

View File

@@ -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?>

View File

@@ -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"
};

View File

@@ -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);
}

View File

@@ -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;
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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),
}
}

View 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,
}
}

View File

@@ -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,
}
}

View 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);
}
}

View 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>

View File

@@ -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>

View File

@@ -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);
}
}
}

View 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>

View 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();
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View 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,
}
}

View 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.
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; }
}
}

View 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
{
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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
});
}
}
}

View File

@@ -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",
};
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View 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);
}
}
}
}

View 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.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);
}
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View 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>

View 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;
}
}
}

View 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)
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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.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;
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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; }
}
}

View 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);
}
}

View 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));
}
}
}

View 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));
}
}
}
}
}

View 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));
}
}
}
}
}

View 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();
}
}
}

View 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);
}
}

View 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; }
}
}
}
}

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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);
}
}
}

View 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>

View 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();
}
}
}

View 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>

View 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View 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);
}
}
}

View 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);
}
}
}
}
}

View 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;
});
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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>

View 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();
}
}
}

View 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();
}
}
}

View 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,
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,6 @@
MonitorFromWindow
GetMonitorInfo
GetDpiForWindow
GetWindowTextLength
SetWinEventHook
UnhookWinEvent

View 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>

View 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>

View 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>

View 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="&#xE8E5;" />
<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>

View 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);
}
}
}

View 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);
}

View 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;
}
}
}

View 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>

View File

@@ -0,0 +1,7 @@
namespace WIC
{
internal static class CLSID
{
public const string WICImagingFactory = "cacaf262-9370-4615-a13b-9f5539da4c0a";
}
}

View 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 { }
}

View 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);
}
}
}
}

View 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);
}
}

View File

@@ -0,0 +1,7 @@
namespace WIC
{
public struct HResult
{
public const int WINCODEC_ERR_PROPERTYNOTFOUND = unchecked((int)0x88982F40);
}
}

Some files were not shown because too many files have changed in this diff Show More