[Peek] Powertoys Peek MVP (#25922)

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

* Bump Microsoft.Windows.SDK.BuildTools version

* [Peek] Plugin pattern to enable any file type previewing (#22475)

* [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>

* 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

* Add scale awareness to window centering (#22541)

* [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

* [Peek] Enable PropertyStore for offline files (#22567)

* Enabling PropertyStore for offline files

Co-authored-by: Daniel Chau <dancha@microsoft.com>

* [Peek] Adding unsupported file previewer (#22598)

* Unsupported file previewer

* Fix file display info

* Fix property store calls

* Update TODO

* [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

* [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>

* [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>

* Fix crash

* Fix wrong thread exception

* Make CurrentItemIndex setter private

* Update titlebar filecount text

* Fix titlebar draggable region and interactive region (bump WinAppSdk to latest)

* [Peek] Unsupported File Previewer - Formatting string from resources (#22609)

* Moving to string resource usage

* Moving ReadableStringHelper to common project

* Fix comments

* [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>

* [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

* [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

* [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>

* [Peek] New File Explorer tabs break Shell API to get selected files (#22641)

* fix FE tab bug

* remove unnecessary unsafe keyword

* [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

* [Peek] Unsupported File Previewer - Setting Window Size (#22645)

* Adding setting for unsupported file window

* Fix

* [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>

* Add full image quality support (#22654)

* [Peek] Window foregrounding simplification and fixes + keep window visible if FE single selection changed (#22657)

* Use different apis to bring to foreground removing remote thread wait and work as well as library loading

* Keep window open if single selected file in FE is different

* Removed unused methods

* [Peek] Add cancellation token OnFilePropertyChanged (#22643)

* Cancel file loading before opening another file

* Add omitted cancellation checks

* Catch task cancelled exception; Add more cancellation checkpoints

* Add cancellation checkpoint beofre GetBitmapFromHBitmapAsync

* Correct typo

* Update to pass cancellation token individually to each async methods

* Add lost cancellationToken source

* Add cancellation token to PngPreviewer

Co-authored-by: Yawen Hou <yawenhou@microsoft.com>

* [Peek] Unsupported File Previewer - Preserve Transparency For File Icons (#22650)

* Preserving transparency or icons

* Remove TODO

Co-authored-by: Samuel Chapleau <sachaple@microsoft.com>

* [Peek] Update some installer build steps + assets update (#22683)

* Fix settings & peek.ui.wpf

* Add back missing icon

* Add missing files and actions to installer

* Keep window open if the selected file in explorer is different (only works for single file selection)

* Undo last

* [Peek] Add copy keyboard accelerator (#22647)

* add copy keyboard accelerator

* Fix comments

Co-authored-by: Samuel Chapleau <sachaple@microsoft.com>

* [Peek] add WV2 improvements (behavior and UX) (#22685)

* [Peek] added logic to get max monitor size for opening WebView2

* Removed ununsed dependency property

* Added workaround for cases where the web page would not finish navigating in a quick timing, for example google.com.

* Remove window extensions from common and use nullable size argument instead

Co-authored-by: Samuel Chapleau <sachaple@microsoft.com>

* [Peek] Merge main, self-contained .NET and fix WebView2 user data dir issue (#22899)

* Merge remote-tracking branch 'origin/main' into peek

* Test sc

* Set WebView2 user data dir

* spellcheck

* Fix comment

* Move check if higher quality image is already loaded to the exact line where we change the Preview bitmap (#23083)

* Fix opening Peek when FE window is set to full name path (#23082)

* Move check for png thubmnail loading priority

* Remove Peek.UI.WPF project

* Remove duplicated method in powertoys setup

* [Peek] Fix selecting files from the correct focused opened File Explorer tab & from Desktop (#23489)

* Get file based on active tab handle instead of window title

* Refactor code to get active tab

* Getting all items from the shell API working again, except for desktop

* Refactor and cleanup com & native code

* Add back removed peek xaml assets in Product.wxs

* Remove some dependencies that do not seem necessary in Product.wxs

* [Peek] Small images (#23554)

* change stretch value

* compare with actual window size

* consider scaling factor

* set max size

* clean up

* clean up

* clean up previewers

* scaling factor in bitmap previewer

* max image size property

* [Peek]Handle errors for HEIC/HEIF and fall back to default previewer if there is no thumbnail (#22684)

* Handle errors when getting filesize by falling back to default previewer

* Bringing back other file types that are fixed with these code changes

---------

Co-authored-by: Samuel Chapleau <sachaple@microsoft.com>

* [Peek] Add unsupported file icon fallback (#23735)

* Refactor icon retrieval, refactor hbitmap to bitmap conversion, add icon fallback

* Add svg to assets in installer

* [Peek] Refactoring of file system models, removal of PngPreviewer, retrieving of folder size via Scripting com reference and other fixes (#23955)

* Refactor icon retrieval, refactor hbitmap to bitmap conversion, add icon fallback

* Add svg to assets in installer

* - Refactor File class into IFileSystemItem, FileItem & FolderItem
- Display size for folders using Scripting namespace
- Remove default app buttons for files or folders not supporting it

* Add better content type via storage apis

* Add check for storagefile in PngPreviewer

* Fix png stretching

* Remove png previewer

* Rename ThumbnailOptions.None to ThumbnailOptions.ResizeToFit

* [Peek] Removed monitor percentage evaluation for the UnsupportedFilePreview control (#24002)

* Remove settings for percentage of windows and keep default min size.

* Fix margin on unsupported control

* Use nullable Size for image size & open file on background thread (#24004)

* [Peek] SVG support (#24237)

* svg previewer

* svg size

* set scaling factor

* set image size

* changed image source type

* non nullable image size

* notify svg previewer changed

* uncomment

* rename BitmapPreviewer

* move svg support

* remove svg previewer

* [Peek] Implementation of a performant and reliable Neighboring Files Query (#24943)

* Use IShellItemArray as the backing array of item

* Finalize and cleanup NFQ implementation

* Cleanup remainder of the code

* Remove unused using

* [Peek] Pin the window position  (#24927)

* [Peek] Telemetry and logging (#25231)

* text preview

* scrolling

* changed size

* webview2 preview

* common preview project

* previewpane: use common project

* peek: use common

* previewpane: moved md

* peek: md

* previewpane: clean up

* clean up

* moved monaco files

* moved formatters

* rename

* moved common monaco helper

* dev files support

* installer

* removed versions

* warnings: culture info

* warnings: names

* clean up

* warnings: dispose

* warnings: default values

* warnings

* warnings: charset

* warnings: exceptions

* suppress warning

* installer: added peek

* changed peek guid

* monaco folders

* peek deps

* peek files

* peek resources

* removed additional monaco folder

* set host name

* Update installer

* hardcode monaco path

* leave single webview control

* moved path to common

* project

* more meaningful todos

* moved temp folder cleanup

* todo

* extension check

* spell: monaco

* spellcheck

* spellcheck

* fix id

* fix spelling

* key to spelling

* id fix

* Fix monaco resolution at install time

* Fix user install. Add needed files

* installer: remove peek localization files. It's a WinUI app

* installer:fix signing

* removed unused

* settings: flyout enable/disable for Peek

* simplify string

* property changed handle

* [Peek][Settings] Peek OOBE page (#25895)

* [Peek] GPO (#25918)

* Add Native methods file to exception

* Fix merge issue on solution file

* Adjust spellcheck

* Remove boilerplate code

* Add module interface telemetry

* Remove change to README.md

* Add entry to README

* Clean up some non-changes

* Fix order of Peek in Settings menu

* [Settings] Make peek descriptions more descriptive

---------

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>
Co-authored-by: Daniel Chau <d.chau@alumni.ubc.ca>
Co-authored-by: Daniel Chau <dancha@microsoft.com>
Co-authored-by: jth-ms <73617023+jth-ms@users.noreply.github.com>
Co-authored-by: Robson <rp.pontin@gmail.com>
Co-authored-by: estebanm123 <49930791+estebanm123@users.noreply.github.com>
Co-authored-by: Esteban Margaron <emargaron@microsoft.com>
Co-authored-by: Yawen Hou <Sytta@users.noreply.github.com>
Co-authored-by: Jojo Zhou <yizzho@microsoft.com>
Co-authored-by: Yawen Hou <yawenhou@microsoft.com>
Co-authored-by: Jojo Zhou <39350350+Joanna-Zhou@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
Co-authored-by: Seraphima Zykova <zykovas91@gmail.com>
Co-authored-by: Stefan Markovic <stefan@janeasystems.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
Samuel Chapleau
2023-05-10 10:43:03 -07:00
committed by GitHub
parent 7ae93a9c7f
commit 648f30d1ab
361 changed files with 10709 additions and 182 deletions

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.
using System;
namespace Peek.Common.Constants
{
public static class TempFolderPath
{
public static string Path => $"{Environment.GetEnvironmentVariable("USERPROFILE")}\\AppData\\LocalLow\\Microsoft\\PowerToys\\Peek-Temp";
}
}

View File

@@ -0,0 +1,15 @@
// 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.Constants
{
public static class WindowConstants
{
public const double MaxWindowToMonitorRatio = 0.80;
public const double MinWindowHeight = 500;
public const double MinWindowWidth = 680;
public const double WindowWidthContentPadding = 7;
public const double WindowHeightContentPadding = 16;
}
}

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.
using System;
using System.Threading.Tasks;
using Microsoft.UI.Dispatching;
namespace Peek.Common.Extensions
{
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,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.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using Peek.Common.Models;
using Scripting;
using Windows.Foundation;
using Windows.Storage;
namespace Peek.Common.Extensions
{
public static class IFileSystemItemExtensions
{
public static Size? GetImageSize(this IFileSystemItem item)
{
Size? size = null;
var propertyStore = item.PropertyStore;
var width = propertyStore.TryGetUInt(PropertyKey.ImageHorizontalSize);
var height = propertyStore.TryGetUInt(PropertyKey.ImageVerticalSize);
if (width != null && height != null)
{
size = new Size((int)width, (int)height);
}
return size;
}
public static Size? GetSvgSize(this IFileSystemItem item)
{
Size? size = null;
using (FileStream stream = System.IO.File.OpenRead(item.Path))
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.Async = true;
settings.IgnoreComments = true;
settings.IgnoreProcessingInstructions = true;
settings.IgnoreWhitespace = true;
using (XmlReader reader = XmlReader.Create(stream, settings))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "svg")
{
string? width = reader.GetAttribute("width");
string? height = reader.GetAttribute("height");
if (width != null && height != null)
{
int widthValue = int.Parse(Regex.Match(width, @"\d+").Value, NumberFormatInfo.InvariantInfo);
int heightValue = int.Parse(Regex.Match(height, @"\d+").Value, NumberFormatInfo.InvariantInfo);
size = new Size(widthValue, heightValue);
}
else
{
string? viewBox = reader.GetAttribute("viewBox");
if (viewBox != null)
{
var viewBoxValues = viewBox.Split(' ');
if (viewBoxValues.Length == 4)
{
int viewBoxWidth = int.Parse(viewBoxValues[2], NumberStyles.Integer, CultureInfo.InvariantCulture);
int viewBoxHeight = int.Parse(viewBoxValues[3], NumberStyles.Integer, CultureInfo.InvariantCulture);
size = new Size(viewBoxWidth, viewBoxHeight);
}
}
}
reader.Close();
}
}
}
}
return size;
}
public static ulong GetSizeInBytes(this IFileSystemItem item)
{
ulong sizeInBytes = 0;
switch (item)
{
case FolderItem _:
FileSystemObject fileSystemObject = new FileSystemObject();
Folder folder = fileSystemObject.GetFolder(item.Path);
sizeInBytes = (ulong)folder.Size;
break;
case FileItem _:
var propertyStore = item.PropertyStore;
sizeInBytes = propertyStore.TryGetULong(PropertyKey.FileSizeBytes) ?? 0;
break;
}
return sizeInBytes;
}
public static async Task<string> GetContentTypeAsync(this IFileSystemItem item)
{
string contentType = string.Empty;
var storageItem = await item.GetStorageItemAsync();
switch (storageItem)
{
case StorageFile storageFile:
contentType = storageFile.DisplayType;
break;
case StorageFolder storageFolder:
contentType = storageFolder.DisplayType;
break;
default:
var propertyStore = item.PropertyStore;
contentType = propertyStore.TryGetString(PropertyKey.FileType) ?? string.Empty;
break;
}
return contentType;
}
}
}

View File

@@ -0,0 +1,161 @@
// 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.Runtime.InteropServices;
using Peek.Common.Models;
namespace Peek.Common.Extensions
{
public static class IPropertyStoreExtensions
{
/// <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? TryGetUInt(this IPropertyStore propertyStore, PropertyKey key)
{
if (propertyStore == null)
{
return null;
}
try
{
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 null;
}
}
catch (Exception)
{
return null;
}
}
/// <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? TryGetULong(this IPropertyStore propertyStore, PropertyKey key)
{
if (propertyStore == null)
{
return null;
}
try
{
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 null;
}
}
catch (Exception)
{
return null;
}
}
/// <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? TryGetString(this IPropertyStore propertyStore, PropertyKey key)
{
if (propertyStore == null)
{
return null;
}
try
{
PropVariant propVar;
propertyStore.GetValue(ref key, out propVar);
if ((VarEnum)propVar.Vt == VarEnum.VT_LPWSTR)
{
return Marshal.PtrToStringUni(propVar.P) ?? string.Empty;
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
/// <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[]? TryGetStringArray(this IPropertyStore propertyStore, PropertyKey key)
{
if (propertyStore == null)
{
return null;
}
try
{
PropVariant propVar;
propertyStore.GetValue(ref key, out propVar);
List<string>? values = null;
if ((VarEnum)propVar.Vt == (VarEnum.VT_LPWSTR | VarEnum.VT_VECTOR))
{
values = new List<string>();
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();
}
catch (Exception)
{
return null;
}
}
}
}

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.
using System;
using System.Threading.Tasks;
namespace Peek.Common.Extensions
{
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,33 @@
// 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 Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Streams;
namespace Peek.Common.Helpers
{
public static class ClipboardHelper
{
public static void SaveToClipboard(IStorageItem? storageItem)
{
if (storageItem == null)
{
return;
}
var dataPackage = new DataPackage();
dataPackage.SetStorageItems(new IStorageItem[1] { storageItem }, false);
if (storageItem is StorageFile storageFile)
{
RandomAccessStreamReference imageStreamRef = RandomAccessStreamReference.CreateFromFile(storageFile);
dataPackage.Properties.Thumbnail = imageStreamRef;
dataPackage.SetBitmap(imageStreamRef);
}
Clipboard.SetContent(dataPackage);
}
}
}

View File

@@ -0,0 +1,78 @@
// 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.Diagnostics;
using System.Globalization;
using System.IO;
using Scripting;
namespace Peek.Common.Helpers
{
public static class Logger
{
private static readonly IFileSystem _fileSystem = new FileSystemObject();
private static readonly string ApplicationLogPath = Path.Combine(interop.Constants.AppDataPath(), "Peek\\Logs");
static Logger()
{
if (!_fileSystem.FolderExists(ApplicationLogPath))
{
_fileSystem.CreateFolder(ApplicationLogPath);
}
// Using InvariantCulture since this is used for a log file name
var logFilePath = _fileSystem.BuildPath(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
}
public static void LogError(string message)
{
Log(message, "ERROR");
}
public static void LogError(string message, Exception ex)
{
Log(
message + Environment.NewLine +
ex?.Message + Environment.NewLine +
"Inner exception: " + Environment.NewLine +
ex?.InnerException?.Message + Environment.NewLine +
"Stack trace: " + Environment.NewLine +
ex?.StackTrace,
"ERROR");
}
public static void LogWarning(string message)
{
Log(message, "WARNING");
}
public static void LogInfo(string message)
{
Log(message, "INFO");
}
private static void Log(string message, string type)
{
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
Trace.Indent();
Trace.WriteLine(GetCallerInfo());
Trace.WriteLine(message);
Trace.Unindent();
}
private static string GetCallerInfo()
{
StackTrace stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(3)?.GetMethod();
var className = methodName?.DeclaringType?.Name ?? string.Empty;
return "[Method]: " + methodName?.Name + " [Class]: " + className;
}
}
}

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.Helpers
{
public static class MathHelper
{
public static int Modulo(int a, int b)
{
return a < 0 ? ((a % b) + b) % b : a % b;
}
}
}

View File

@@ -0,0 +1,66 @@
// 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.Globalization;
using System.Runtime.InteropServices;
using Peek.Common.Models;
using Windows.Win32.UI.Shell.PropertiesSystem;
namespace Peek.Common.Helpers
{
public static partial class PropertyStoreHelper
{
/// <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, GETPROPERTYSTOREFLAGS flags = GETPROPERTYSTOREFLAGS.GPS_EXTRINSICPROPERTIES)
{
IShellItem2? shellItem2 = null;
IntPtr ppPropertyStore = IntPtr.Zero;
try
{
SHCreateItemFromParsingName(path, IntPtr.Zero, typeof(IShellItem2).GUID, out shellItem2);
if (shellItem2 == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unable to get an IShellItem2 reference from file {0}.", path));
}
int hr = shellItem2.GetPropertyStore((int)flags, typeof(IPropertyStore).GUID, out ppPropertyStore);
if (hr != 0)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "GetPropertyStore returned hresult={0}", hr));
}
return (IPropertyStore)Marshal.GetObjectForIUnknown(ppPropertyStore);
}
finally
{
if (ppPropertyStore != IntPtr.Zero)
{
Marshal.Release(ppPropertyStore);
}
if (shellItem2 != null)
{
Marshal.ReleaseComObject(shellItem2);
}
}
}
[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,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.
using System;
using System.Collections.Generic;
using System.Globalization;
using Windows.ApplicationModel.Resources;
namespace Peek.Common.Helpers
{
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(CultureInfo.InvariantCulture, 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(CultureInfo.InvariantCulture, 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(CultureInfo.InvariantCulture, formatString, args0, args1);
return formattedString;
}
}
}

View File

@@ -0,0 +1,53 @@
// 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.Tasks;
using Peek.Common.Helpers;
using Windows.Storage;
#nullable enable
namespace Peek.Common.Models
{
public class FileItem : IFileSystemItem
{
private StorageFile? storageFile;
private Lazy<IPropertyStore> _propertyStore;
public FileItem(string path)
{
Path = path;
_propertyStore = new(() => PropertyStoreHelper.GetPropertyStoreFromPath(Path));
}
public string Path { get; init; }
public IPropertyStore PropertyStore => _propertyStore.Value;
public async Task<IStorageItem?> GetStorageItemAsync()
{
return await GetStorageFileAsync();
}
public async Task<StorageFile?> GetStorageFileAsync()
{
if (storageFile == null)
{
try
{
storageFile = await StorageFile.GetFileFromPathAsync(Path);
}
catch (Exception ex)
{
Logger.LogError("Error getting file from path. " + ex.Message);
storageFile = null;
}
}
return storageFile;
}
}
}

View File

@@ -0,0 +1,53 @@
// 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.Tasks;
using Peek.Common.Helpers;
using Windows.Storage;
#nullable enable
namespace Peek.Common.Models
{
public class FolderItem : IFileSystemItem
{
private StorageFolder? storageFolder;
private Lazy<IPropertyStore> _propertyStore;
public FolderItem(string path)
{
Path = path;
_propertyStore = new(() => PropertyStoreHelper.GetPropertyStoreFromPath(Path));
}
public string Path { get; init; }
public IPropertyStore PropertyStore => _propertyStore.Value;
public async Task<IStorageItem?> GetStorageItemAsync()
{
return await GetStorageFolderAsync();
}
public async Task<StorageFolder?> GetStorageFolderAsync()
{
if (storageFolder == null)
{
try
{
storageFolder = await StorageFolder.GetFolderFromPathAsync(Path);
}
catch (Exception ex)
{
Logger.LogError("Error getting folder from path. " + ex.Message);
storageFolder = null;
}
}
return storageFolder;
}
}
}

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.Globalization;
using System.Threading.Tasks;
using Peek.Common.Helpers;
using Windows.Storage;
#nullable enable
namespace Peek.Common.Models
{
public interface IFileSystemItem
{
public DateTime DateModified => System.IO.File.GetCreationTime(Path);
public string Extension => System.IO.Path.GetExtension(Path).ToLower(CultureInfo.InvariantCulture);
public string Name => System.IO.Path.GetFileName(Path);
public string Path { get; init; }
public IPropertyStore PropertyStore { get; }
public Task<IStorageItem?> GetStorageItemAsync();
}
}

View File

@@ -0,0 +1,16 @@
// 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.Common.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct Blob
{
public int CbSize;
public IntPtr PBlobData;
}
}

View File

@@ -0,0 +1,16 @@
// 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.Common.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct CALPWSTR
{
public uint CElems;
public IntPtr PElems;
}
}

View File

@@ -0,0 +1,15 @@
// 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.Runtime.InteropServices;
namespace Peek.Common.Models
{
[StructLayout(LayoutKind.Sequential)]
public struct FileTime
{
public int DWHighDateTime;
public int DWLowDateTime;
}
}

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,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.Runtime.InteropServices;
namespace Peek.Common.Models
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F2-0000-0000-C000-000000000046")]
public interface IEnumIDList
{
[PreserveSig]
#pragma warning disable CA1716
int Next(int celt, out IntPtr rgelt, out int pceltFetched);
#pragma warning restore CA1716
[PreserveSig]
int Skip(int celt);
[PreserveSig]
int Reset();
[PreserveSig]
int Clone(out IEnumIDList ppenum);
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security;
namespace Peek.Common.Models
{
[ComImport]
[Guid("cde725b0-ccc9-4519-917e-325d72fab4ce")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[SuppressUnmanagedCodeSecurity]
public interface IFolderView
{
void GetCurrentViewMode([Out] out uint pViewMode);
void SetCurrentViewMode([In] uint viewMode);
void GetFolder([In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object ppv);
void Item([In] int iItemIndex, [Out] out IntPtr ppidl);
void ItemCount([In] uint uFlags, [Out] out int pcItems);
void Items([In] uint uFlags, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object ppv);
void GetSelectionMarkedItem([Out] out int piItem);
void GetFocusedItem([Out] out int piItem);
void GetItemPosition([In] IntPtr pidl, [Out] out Point ppt);
void GetSpacing([In, Out] ref Point ppt);
void GetDefaultSpacing([Out] out Point ppt);
void GetAutoArrange();
void SelectItem([In] int iItem, [In] uint dwFlags);
void SelectAndPositionItems([In] uint cidl, [In] IntPtr apidl, [In] IntPtr apt, [In] uint dwFlags);
}
}

View File

@@ -0,0 +1,26 @@
// 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.Common.Helpers.PropertyStoreHelper;
namespace Peek.Common.Models
{
[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();
}
}

View File

@@ -0,0 +1,17 @@
// 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.Common.Models
{
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService([MarshalAs(UnmanagedType.LPStruct)] Guid service, [MarshalAs(UnmanagedType.LPStruct)] Guid riid);
}
}

View File

@@ -0,0 +1,46 @@
// 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.Common.Models
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E2-0000-0000-C000-000000000046")]
public interface IShellBrowser
{
void GetWindow(out IntPtr phwnd);
void ContextSensitiveHelp(bool fEnterMode);
void InsertMenusSB(IntPtr hmenuShared, IntPtr lpMenuWidths);
void SetMenuSB(IntPtr hmenuShared, IntPtr holeMenuRes, IntPtr hwndActiveObject);
void RemoveMenusSB(IntPtr hmenuShared);
void SetStatusTextSB(IntPtr pszStatusText);
void EnableModelessSB(bool fEnable);
void TranslateAcceleratorSB(IntPtr pmsg, ushort wID);
void BrowseObject(IntPtr pidl, uint wFlags);
void GetViewStateStream(uint grfMode, IntPtr ppStrm);
void GetControlWindow(uint id, out IntPtr lpIntPtr);
void SendControlMsg(uint id, uint uMsg, uint wParam, uint lParam, IntPtr pret);
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryActiveShellView();
void OnViewWindowActive(IShellView ppshv);
void SetToolbarItems(IntPtr lpButtons, uint nButtons, uint uFlags);
}
}

View File

@@ -0,0 +1,67 @@
// 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 Windows.Win32.UI.Shell;
namespace Peek.Common.Models
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("93F2F68C-1D1B-11D3-A30E-00C04F79ABD1")]
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, SHGDNF 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 pguid);
[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);
}
}

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.
using System;
using System.Runtime.InteropServices;
using Windows.Win32.UI.Shell;
namespace Peek.Common.Models
{
[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);
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetDisplayName(SIGDN sigdnName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
}
}

View File

@@ -0,0 +1,71 @@
// 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.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32.UI.Shell.PropertiesSystem;
namespace Peek.Common.Models
{
[ComImport]
[Guid("7E9FB0D3-919F-4307-AB2E-9B1860310C93")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItem2 : IShellItem
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public 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)]
public int GetDisplayName([In] int sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public 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]
public int GetPropertyStore(int flags, ref Guid riid, out IntPtr ppv);
[PreserveSig]
internal int GetPropertyStoreWithCreateObject(ref GETPROPERTYSTOREFLAGS flags, ref IntPtr punkFactory, ref Guid riid, out IntPtr ppv);
[PreserveSig]
internal int GetPropertyStoreForKeys(ref PropertyKey keys, uint cKeys, ref GETPROPERTYSTOREFLAGS flags, ref Guid riid, out IntPtr ppv);
[PreserveSig]
public int GetPropertyDescriptionList(ref PropertyKey key, ref Guid riid, out IntPtr ppv);
[PreserveSig]
public int Update(ref IntPtr pbc);
[PreserveSig]
public int GetProperty(ref PropertyKey key, out PropVariant pPropVar);
[PreserveSig]
public int GetCLSID(ref PropertyKey key, out Guid clsid);
[PreserveSig]
public int GetFileTime(ref PropertyKey key, out FileTime pft);
[PreserveSig]
public int GetInt32(ref PropertyKey key, out int pi);
[PreserveSig]
public int GetString(ref PropertyKey key, [MarshalAs(UnmanagedType.LPWStr)] string ppsz);
[PreserveSig]
public int GetUint32(ref PropertyKey key, out uint pui);
[PreserveSig]
public int GetUint64(ref PropertyKey key, out uint pull);
[PreserveSig]
public int GetBool(ref PropertyKey key, bool pf);
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32.UI.Shell;
namespace Peek.Common.Models
{
[ComImport]
[Guid("B63EA76D-1F85-456F-A19C-48159EFA858B")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItemArray
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, out IntPtr ppvOut);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetPropertyStore([In] int flags, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetPropertyDescriptionList([In] ref PropertyKey keyType, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int GetCount();
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
Common.Models.IShellItem GetItemAt(int dwIndex);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);
}
}

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.
using System;
using System.Runtime.InteropServices;
namespace Peek.Common.Models
{
[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
{
ResizeToFit = 0x00,
BiggerSizeOk = 0x01,
InMemoryOnly = 0x02,
IconOnly = 0x04,
ThumbnailOnly = 0x08,
InCacheOnly = 0x10,
ScaleUp = 0x100,
}
}

View File

@@ -0,0 +1,18 @@
// 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.Security;
namespace Peek.Common.Models
{
[ComImport]
[Guid("000214E3-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[SuppressUnmanagedCodeSecurity]
public interface IShellView
{
}
}

View File

@@ -0,0 +1,60 @@
// 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.Common.Models
{
[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;
}
}

View File

@@ -0,0 +1,65 @@
// 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.Common.Models
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PropertyKey
{
public Guid FormatId;
public int PropertyId;
public PropertyKey(Guid keyGuid, int propertyId)
{
this.FormatId = keyGuid;
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);
}
}

View File

@@ -0,0 +1,17 @@
// 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.Runtime.InteropServices;
using static Peek.Common.Helpers.PropertyStoreHelper;
namespace Peek.Common.Models
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SHELLDETAILS
{
public int Fmt;
public int CxChar;
public Strret Str;
}
}

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.Runtime.InteropServices;
namespace Peek.Common.Models
{
[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;
}
}

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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Peek.Common.Models
{
[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;
}
}

View File

@@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": true
}

View File

@@ -0,0 +1,5 @@
GETPROPERTYSTOREFLAGS
_SHCONTF
SIGDN
SHGDNF
SIATTRIBFLAGS

View File

@@ -0,0 +1,36 @@
<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>
<COMReference Include="Scripting">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>420b2830-e718-11cf-893d-00a0c9054228</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</ItemGroup>
</Project>

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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Peek.UI.Telemetry.Events
{
[EventData]
public class ClosedEvent : EventBase, IEvent
{
public ClosedEvent()
{
EventName = "Peek_Closed";
}
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

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.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
using Peek.Common.Models;
namespace Peek.UI.Telemetry.Events
{
[EventData]
public class ErrorEvent : EventBase, IEvent
{
public class FailureType
{
public static readonly string PreviewFail = "Preview fail, cannot render file";
public static readonly string FileNotSupported = "Default view shown due to file not supported";
public static readonly string AppCrash = "App crash";
}
public ErrorEvent()
{
EventName = "Peek_Error";
}
public HResult HResult { get; set; }
public string Message { get; set; } = string.Empty;
public string Failure { get; set; } = string.Empty;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Peek.UI.Telemetry.Events
{
[EventData]
public class OpenWithEvent : EventBase, IEvent
{
public OpenWithEvent()
{
EventName = "Peek_OpenWith";
}
public string App { get; set; } = string.Empty;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Peek.UI.Telemetry.Events
{
[EventData]
public class OpenedEvent : EventBase, IEvent
{
public OpenedEvent()
{
EventName = "Peek_Opened";
}
public string ActivationKind { get; set; } = string.Empty;
public string FileExtension { get; set; } = string.Empty;
public bool IsAppToggledOn { get; set; }
public double HotKeyToVisibleTimeMs { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}