Registry Preview: complete feature with integration with Settings and the Launcher UX (#23709)

* Initial src for Registry Preview

Initial collection of files

* Update MainWindow.Utilities.cs

fixing a few spelling items

* Update expect.txt

* Update App.xaml.cs

* Update MainWindow.Events.cs

* Update MainWindow.Utilities.cs

* Update MainWindow.xaml.cs

* Update expect.txt

* Update MainWindow.Events.cs

* Rename AddPreviewtoRegfile.reg to AddPreviewToRegfile.reg

* Rename RemovePreviewtoRegfile.reg to RemovePreviewToRegfile.reg

* Update Resources.resw

* Update MainWindow.xaml

* Turns on self-contained mode

Updates the csproj file to compile the app as self-contained .
Includes fixes for code that expected the app to be in an ApplicationContainer.
Updates WindowsAppSDK from 1.1.5 to 1.2.230118.102.

* Updated to align with StyleCop errors

Multiple changes across the codebase that now aligns with StyleCop guidelines.

Tested again after the changes, to make sure no new bugs crept in.

* Added comments for spell-checker

Unclear if this side step should be done or not, but the kxz comes from a GUID and the other three names are constants.

* Adding code-custom.dic

Comments didn't work; trying a dic file

* Added four new terms to expects.txt file

Cleaning up attempt to update the spell-checker with dic file and moved it to expects.txt file

* Adding one more string

Adding one more string for Spell-Check

* Adding back egfile

Seems this is needed.

* Correcting a variable name

Seems one of the variable names that changed globally got missed in a XAML file.

* Update project to be more PowerToys friendly

Tweaking names and output file name to fit better with PowerToys.

* First pass at integration into Settings and Launcher

This PR is not as large as it seems:
- RegistryPreview's source moved around to a "better" directory that makes it look like the whole app changed.  It didn't.  In fact, I opened it in Beyond Compare and there's not much difference in the RegistryPreview app.
- Added RegistryPreviewExt that produces a DLL that the Launcher can run the EXE
- Changes to Runner calls the Ext DLL rather than the app
- Settings UI got a bunch of changes to enable the Settings page for enable/disable across ViewModels, Views, and string tables.

Still todo:
- Add "Preview" to .REG files, when the app is enable (and remove it when disabled)
- Update the thumbnail-screenshot in the Settings page
- Add support for OOBE

* Update expect.txt

Added REGISTRYPREVIEWEXT for recent changes and corrected an alphabetic sorting error.

* Updating project file for Release mode

Build failed due to a bad Includes path in Release mode.

* Adds REG registration but breaks settings

This update will update the HKCU branch of the Registry for REG files: if the app in PowerToys is enabled, it adds a Preview item to the context menu of REG files and disabling in PowerToys removes the menu item.

While working on this, I noticed that the application settings were broken, after moving to a self-contained EXE: there must have been old settings from past builds, when it was still using containers and family names.

Added TODO's to add a new way to save settings, likely as JSON.

* Re-enabled app settings

Moved from using ApplicationDataContainer for app settings and now use simple JSON.

Also cleaned up handling the scenario where the Launcher send in the PID from PowerToys' main thread.

Fixed past spelling errors as well.

* Update RegistryPreview.png

Captured new screenshot.

* Integration into OOBE

Integrates new page for Registry Preview into OOBE process.

* Removing old comment and unneeded calls

Two bits of source removed as they aren't needed any longer

Removing a chunk of old commented out code that doesn't make sense anymore.

* Merging file from upstream

Updating some files due to three merge conflicts from upstream changes and a couple of other changes to keep up.

* Update .gitignore

Adding two vcxproj files that have local updates for atls.lib locations.

* Update Resources.resw

Fixing a typo that involved a missing closing tag.

* Fix analyzer warnings

* Fix CI build

* Fix ARM64 build
Project file cleanup

* Add to installer

* expect.txt

* Remove unneeded dll

* Update MainWindow.xaml.cs

Added check for current Theme and adjust TextBox Foreground accordingly.

* Update expect.txt

Cleaning up merge cruft.

* Revert wrong .gitignore changes

* Fix ARM installer

* Update Brushes for textBox to use Theme based versions

Finally figured out how to use the built-in, Theme-aware Brushes for the font colors in the onscreen textBox.  Also have it aligning the font color for the hover state.

* Align configuration in PowerToys.sln

* [installer] Add missing files

* Fix bad merge

* Fixes for stefansjfw's review

Includes:
- two new strings for UX localization
- adds compatibility section for Windows 10
- fix to only track successful activations
- Removes two REG files that were there for examples

* Fixes from review from htcfreek

Updates:
- Fixed an issue where TextBox_TextChanged was firing when you simply opened a file.
- Added clamp to prevent files larger than 10MB from being opened.
- Added support in the UX to show Keys and Values that are deleted via the file
- Added support to specially handle Keys that start with - and Values that have =- in them (delete scenario)
- Changed AppBarButton icon for Edit from Rename to NewWindow

* Create deleted-folder32.png

* Added Registry Preview to GPO

* Update expect.txt

Updating spellchecker works

* Updating Size/Move code for better results

- Moved the size/move to the MainWindow layer
- Cleaned up the JSON settings handling to avoid an access denied on first run

* Improving text handling

Changed how special characters are parse, which helps with live entry.

* Updates to parsing and other fixes

- Renamed the value PNG for parity
- Added new error image
- Added check that values have " at start and finish.
- Added support for a new "ERROR" type for Values
- Fixed support for @ versus "@" in values
- Fixed bug where Save wasn't activating in all scernarios

* Fix signing and versioning

* Update src/settings-ui/Settings.UI/ViewModels/RegistryPreviewViewModel.cs

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>

* Apply suggestions from code review

Adds Launch button to the settings page.

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>

* Update Resources.resw

Adding strings for new launch button

* Adding new version for GPO

Moving to 0.69.0

* More parsing bug fixes

- Changes to look for [- instead of -[ for syntax deleting keys (fix for developer's mental hiccup)
- Moved [- to top of the decision making stack, as it needs to come before [
- Added new StripEscapedCharacters function for both sides of a Value line
- Fixed crashing bug for scenario where no Keys are parsed before a Value

* Bug fixes from most recent review.

- Dictionary will now be case insensitive when searching for keys
- Added tool tips (and strings) to the images of the Keys and Values
- Updated delete handling for Keys, so that only the leaf-most node gets marked as deleted; also stops the top most roots from being marked deleted.

* Tweaking for @=-

Forces the UX to take @=- and treat it as @="" since that's what Registry Editor would do.

* Removing unused usings

* Updates app description

* Update src/gpo/assets/PowerToys.admx

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>

* cleanup proj file
launch process only if module is enabled
add process to bugreport process list

* Add context menu icon

* Update src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs

Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>

* Use modulesRegistry.h to apply/unapply registry changes

* Tweaked window settings

Moved the loading of the settings a little later in the initialization code, which gives more time for things to initialize.

* Update registry.h

Moving the definition out of the detail namespace to the registry name space to fix a compilation issue in RegistryPreviewExt.

* Unapply on creation

If module is disabled in settings.json, on startup reg entries should be unnaplied.
TODO: read m_enabled from settings file on creation

* Removing size/move main window

Added a TODO comment that responds to the size/position settings that are being saved out in the JSON blob on close as it doesn't always work on every PC, as the MainWindow initializes at different times.

* Change to always keep Save As active

No reason for this to be disabled, honestly.

---------

Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: Stefan Markovic <stefan@janeasystems.com>
Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
This commit is contained in:
Randy 2023-03-27 06:21:46 -07:00 committed by GitHub
parent 144721ad07
commit 81c09685e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 3376 additions and 21 deletions

View File

@ -65,6 +65,7 @@ applicationframehost
Applist
applog
appmanifest
APPNAME
appref
apps
appwindow
@ -217,6 +218,7 @@ CLIPCHILDREN
CLIPSIBLINGS
Cloneable
clrcall
clrcompression
Cls
CLSCTX
clsid
@ -350,6 +352,7 @@ DCOM
dcommon
dcomp
dcompi
DCompiler
DComposition
DCR
DCs
@ -373,6 +376,7 @@ DEFERERASE
DEFPUSHBUTTON
deinitialization
DELA
DELETEDKEYIMAGE
DELETESCANS
deletethis
Delimarsky
@ -469,6 +473,7 @@ editkeyboardwindow
EDITSHORTCUTS
editshortcutswindow
EFile
egfile
ekus
emmintrin
Emoji
@ -508,6 +513,7 @@ EWXREBOOT
EWXSHUTDOWN
examplehandler
examplepowertoy
EXAND
EXCLUDEFROMCAPTURE
exdisp
executionpolicy
@ -748,6 +754,7 @@ IMAGERESIZEREXT
imageresizerinput
imageresizersettings
imagingdevices
Imc
ime
imeutil
inetcpl
@ -836,6 +843,7 @@ keydown
keydropdowncontrol
keyevent
KEYEVENTF
KEYIMAGE
keynum
keyremaps
Keytool
@ -1293,6 +1301,7 @@ pinfo
pinvoke
pipename
PKBDLLHOOKSTRUCT
Pkcs
PKEY
plib
PLK
@ -1435,7 +1444,10 @@ regfile
REGFILTER
REGFILTERPINS
REGISTERCLASSFAILED
REGISTRYHEADER
registrypath
registrypreview
REGISTRYPREVIEWEXT
regkey
REGPINTYPES
regsvr
@ -2043,6 +2055,7 @@ wox
wparam
wpf
wpfdepsjsonpath
wpfgfx
wpftmp
wpr
wprp
@ -2091,6 +2104,7 @@ XStr
XUP
XVIRTUALSCREEN
YAxis
Yeet
YIncrement
yinle
yinwang

View File

@ -147,6 +147,10 @@
"modules\\PowerRename\\PowerToys.PowerRenameContextMenu.dll",
"modules\\PowerRename\\PowerRenameContextMenuPackage.msix",
"modules\\RegistryPreview\\PowerToys.RegistryPreviewExt.dll",
"modules\\RegistryPreview\\PowerToys.RegistryPreview.dll",
"modules\\RegistryPreview\\PowerToys.RegistryPreview.exe",
"modules\\ShortcutGuide\\ShortcutGuide\\PowerToys.ShortcutGuide.exe",
"modules\\ShortcutGuide\\ShortcutGuideModuleInterface\\PowerToys.ShortcutGuideModuleInterface.dll",
@ -240,6 +244,8 @@
"modules\\PowerAccent\\Vanara.PInvoke.Shell32.dll",
"modules\\PowerAccent\\Vanara.PInvoke.ShlwApi.dll",
"modules\\PowerAccent\\Vanara.PInvoke.User32.dll",
"modules\\RegistryPreview\\clrcompression.dll",
"modules\\RegistryPreview\\Microsoft.Graphics.Canvas.Interop.dll",
"modules\\FileExplorerPreview\\Microsoft.Web.WebView2.Core.dll",
"modules\\FileExplorerPreview\\Microsoft.Web.WebView2.WinForms.dll",
"modules\\FileExplorerPreview\\Microsoft.Web.WebView2.Wpf.dll",

View File

@ -18,7 +18,8 @@ $versionExceptions = @(
"Microsoft.Xaml.Interactions.dll",
"Microsoft.Xaml.Interactivity.dll",
"hyjiacan.py4n.dll",
"Microsoft.WindowsAppRuntime.Release.Net.dll") -join '|';
"Microsoft.WindowsAppRuntime.Release.Net.dll",
"Microsoft.Windows.Widgets.Projection.dll") -join '|';
$nullVersionExceptions = @(
"codicon.ttf",
"e_sqlite3.dll",
@ -32,7 +33,8 @@ $nullVersionExceptions = @(
"MRM.dll",
"PushNotificationsLongRunningTask.ProxyStub.dll",
"WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll",
"System.Diagnostics.EventLog.Messages.dll") -join '|';
"System.Diagnostics.EventLog.Messages.dll",
"Microsoft.Windows.Widgets.dll") -join '|';
$totalFailure = 0;
Write-Host $DirPath;

View File

@ -494,6 +494,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PastePlainModuleInterface",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllExperiments", "src\common\AllExperiments\AllExperiments.csproj", "{9CE59ED5-7087-4353-88EB-788038A73CEC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryPreviewUI", "src\modules\registrypreview\RegistryPreviewUI\RegistryPreviewUI.csproj", "{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RegistryPreviewExt", "src\modules\registrypreview\RegistryPreviewExt\RegistryPreviewExt.vcxproj", "{697C6AF9-0A48-49A9-866C-67DA12384015}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RegistryPreview", "RegistryPreview", "{929C1324-22E8-4412-A9A8-80E85F3985A5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@ -2066,6 +2072,30 @@ Global
{9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x64.Build.0 = Release|x64
{9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x86.ActiveCfg = Release|x64
{9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x86.Build.0 = Release|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|ARM64.Build.0 = Debug|ARM64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|x64.ActiveCfg = Debug|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|x64.Build.0 = Debug|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|x86.ActiveCfg = Debug|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|x86.Build.0 = Debug|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|ARM64.ActiveCfg = Release|ARM64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|ARM64.Build.0 = Release|ARM64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|x64.ActiveCfg = Release|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|x64.Build.0 = Release|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|x86.ActiveCfg = Release|x64
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|x86.Build.0 = Release|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|ARM64.ActiveCfg = Debug|ARM64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|ARM64.Build.0 = Debug|ARM64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|x64.ActiveCfg = Debug|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|x64.Build.0 = Debug|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|x86.ActiveCfg = Debug|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|x86.Build.0 = Debug|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Release|ARM64.ActiveCfg = Release|ARM64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Release|ARM64.Build.0 = Release|ARM64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Release|x64.ActiveCfg = Release|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Release|x64.Build.0 = Release|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Release|x86.ActiveCfg = Release|x64
{697C6AF9-0A48-49A9-866C-67DA12384015}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2240,6 +2270,9 @@ Global
{9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
{9CE59ED5-7087-4353-88EB-788038A73CEC} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{697C6AF9-0A48-49A9-866C-67DA12384015} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{929C1324-22E8-4412-A9A8-80E85F3985A5} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@ -15,6 +15,7 @@
<?define MeasureToolProjectName="MeasureTool"?>
<?define HostsProjectName="Hosts"?>
<?define PastePlainProjectName="PastePlain"?>
<?define RegistryPreviewProjectName="RegistryPreview"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?if $(var.Platform) = x64?>

View File

@ -77,6 +77,7 @@ call "..\..\publish.cmd" arm64
<Compile Include="PastePlain.wxs" />
<Compile Include="PowerAccent.wxs" />
<Compile Include="PowerRename.wxs" />
<Compile Include="RegistryPreview.wxs" />
<Compile Include="Run.wxs" />
<Compile Include="Settings.wxs" />
<Compile Include="ShortcutGuide.wxs" />

View File

@ -68,6 +68,7 @@
<ComponentGroupRef Id="PastePlainComponentGroup" />
<ComponentGroupRef Id="PowerAccentComponentGroup" />
<ComponentGroupRef Id="PowerRenameComponentGroup" />
<ComponentGroupRef Id="RegistryPreviewComponentGroup" />
<ComponentGroupRef Id="RunComponentGroup" />
<ComponentGroupRef Id="SettingsComponentGroup" />
<ComponentGroupRef Id="ShortcutGuideComponentGroup" />
@ -485,6 +486,11 @@
<Directory Id="PastePlainInstallFolder" Name="$(var.PastePlainProjectName)">
</Directory>
<!-- RegistryPreview -->
<Directory Id="RegistryPreviewInstallFolder" Name="$(var.RegistryPreviewProjectName)">
<Directory Id="RegistryPreviewAssetsInstallFolder" Name="Assets" />
</Directory>
<!-- AlwaysOnTop -->
<Directory Id="AlwaysOnTopInstallFolder" Name="$(var.AlwaysOnTopProjectName)">
</Directory>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define RegistryPreviewFiles=ColorCode.Core.dll;ColorCode.WinUI.dll;CommunityToolkit.Common.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;CommunityToolkit.WinUI.dll;ControlzEx.dll;Ijwhost.dll;Microsoft.Graphics.Canvas.Interop.dll;Microsoft.Graphics.Canvas.dll;Microsoft.Graphics.Display.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.WinUI.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.AppNotifications.Builder.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.Security.AccessControl.Projection.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.Windows.Widgets.Projection.dll;Microsoft.Windows.Widgets.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.Xaml.Behaviors.dll;PowerToys.Common.UI.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.RegistryPreview.deps.json;PowerToys.RegistryPreview.dll;PowerToys.RegistryPreview.exe;PowerToys.RegistryPreview.runtimeconfig.json;PowerToys.RegistryPreviewExt.dll;PowerToys.Settings.UI.Lib.dll;System.CodeDom.dll;System.IO.Abstractions.dll;System.Management.dll;WinRT.Runtime.dll;app.ico;resources.pri?>
<?if $(var.Platform) = x64?>
<?define RegistryPreviewFiles=$(var.RegistryPreviewFiles);clrcompression.dll?>
<?endif?>
<?define RegistryPreviewAssets=data32.png;folder32.png;string32.png?>
<Fragment>
<!-- Registry Preview -->
<DirectoryRef Id="RegistryPreviewInstallFolder" FileSource="$(var.BinDir)modules\$(var.RegistryPreviewProjectName)">
<?foreach File in $(var.RegistryPreviewFiles)?>
<Component Id="RegistryPreviewComp_$(var.File)" Win64="yes">
<File Id="RegistryPreviewFile_$(var.File)" Source="$(var.BinDir)modules\$(var.RegistryPreviewProjectName)\$(var.File)" />
</Component>
<?endforeach?>
</DirectoryRef>
<DirectoryRef Id="RegistryPreviewAssetsInstallFolder" FileSource="$(var.BinDir)modules\$(var.RegistryPreviewProjectName)\Assets">
<?foreach File in $(var.RegistryPreviewAssets)?>
<Component Id="RegistryPreviewAssetsComp_$(var.File)" Win64="yes">
<File Id="RegistryPreviewAssetsFile_$(var.File)" Source="$(var.BinDir)modules\$(var.RegistryPreviewProjectName)\Assets\$(var.File)" />
</Component>
<?endforeach?>
</DirectoryRef>
<ComponentGroup Id="RegistryPreviewComponentGroup" Directory="INSTALLFOLDER">
<?foreach File in $(var.RegistryPreviewFiles)?>
<ComponentRef Id="RegistryPreviewComp_$(var.File)" />
<?endforeach?>
<?foreach File in $(var.RegistryPreviewAssets)?>
<ComponentRef Id="RegistryPreviewAssetsComp_$(var.File)" />
<?endforeach?>
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -5,9 +5,9 @@
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define SettingsV2Files=WinUIEx.dll;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;System.Text.Json.dll;WindowsBase.dll;PowerToys.AllExperiments.dll?>
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PastePlain.png;PowerAccent.png;PowerOCR.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ScreenRuler.png;ShortcutGuide.png;VideoConference.png?>
<?define SettingsV2OOBEAssetsModulesFiles=ColorPicker.gif;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;FancyZones.gif;FileExplorer.png;FileLocksmith.gif;ImageResizer.gif;KBM.gif;MouseUtils.gif;PastePlain.gif;PowerAccent.gif;PowerOCR.gif;PowerRename.gif;Run.gif;ScreenRuler.gif;OOBEShortcutGuide.png;VideoConferenceMute.png;OOBEPTHero.png;OOBEPTHeroShort.png?>
<?define SettingsV2OOBEAssetsFluentIconsFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;Awake.png;FileExplorerPreview.png;FindMyMouse.png;Hosts.png;ImageResizer.png;KeyboardManager.png;MouseHighlighter.png;MouseJump.png;MouseCrosshairs.png;MouseUtils.png;PastePlain.png;PowerAccent.png;PowerOcr.png;PowerRename.png;PowerToys.png;PowerToysRun.png;ScreenRuler.png;Settings.png;ShortcutGuide.png;VideoConferenceMute.png?>
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PastePlain.png;PowerAccent.png;PowerOCR.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;RegistryPreview.png;ScreenRuler.png;ShortcutGuide.png;VideoConference.png?>
<?define SettingsV2OOBEAssetsModulesFiles=ColorPicker.gif;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;FancyZones.gif;FileExplorer.png;FileLocksmith.gif;ImageResizer.gif;KBM.gif;MouseUtils.gif;PastePlain.gif;PowerAccent.gif;PowerOCR.gif;PowerRename.gif;RegistryPreview.png;Run.gif;ScreenRuler.gif;OOBEShortcutGuide.png;VideoConferenceMute.png;OOBEPTHero.png;OOBEPTHeroShort.png?>
<?define SettingsV2OOBEAssetsFluentIconsFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;Awake.png;FileExplorerPreview.png;FindMyMouse.png;Hosts.png;ImageResizer.png;KeyboardManager.png;MouseHighlighter.png;MouseJump.png;MouseCrosshairs.png;MouseUtils.png;PastePlain.png;PowerAccent.png;PowerOcr.png;PowerRename.png;PowerToys.png;PowerToysRun.png;RegistryPreview.png;ScreenRuler.png;Settings.png;ShortcutGuide.png;VideoConferenceMute.png?>
<?define SettingsV2MicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
<!-- These files are needed for release builds to contain the experimentation DLLs -->

View File

@ -18,7 +18,7 @@
<?endforeach?>
</DirectoryRef>
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder;HostsInstallFolder;FileLocksmithInstallFolder?>
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder;HostsInstallFolder;FileLocksmithInstallFolder;RegistryPreviewInstallFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<?foreach Language in $(var.WinAppSDKLocLanguageList)?>
<?if $(var.Language) = af-ZA?>
@ -351,6 +351,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="RegistryPreview_WinAppSDKLoc_$(var.IdSafeLanguage)_Component"
Directory="WinAppSDKLoc$(var.IdSafeLanguage)RegistryPreviewInstallFolder"
Guid="$(var.CompGUIDPrefix)06">
<File Id="RegistryPreview_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.RegistryPreviewProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
<File Id="RegistryPreview_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.RegistryPreviewProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>

View File

@ -22,3 +22,5 @@ msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvid
msbuild !PTRoot!\src\modules\MeasureTool\MeasureToolUI\MeasureToolUI.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml
msbuild !PTRoot!\src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml
msbuild !PTRoot!\src\modules\registrypreview\RegistryPreviewUI\RegistryPreviewUI.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml

View File

@ -984,6 +984,7 @@ const std::wstring WinAppSDKConsumers[] =
L"modules\\MeasureTool",
L"modules\\FileLocksmith",
L"modules\\Hosts",
L"modules\\RegistryPreview",
};
UINT __stdcall CreateWinAppSDKHardlinksCA(MSIHANDLE hInstall)
@ -1037,6 +1038,7 @@ const std::wstring PTInteropConsumers[] =
L"modules\\Hosts",
L"modules\\FileExplorerPreview",
L"modules\\MouseUtils\\MouseJumpUI",
L"modules\\RegistryPreview",
};
UINT __stdcall CreatePTInteropHardlinksCA(MSIHANDLE hInstall)
@ -1081,7 +1083,7 @@ UINT __stdcall CreateDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
UINT er = ERROR_SUCCESS;
std::wstring installationFolder, dotnetRuntimeFilesSrcDir, colorPickerDir, powerOCRDir, launcherDir, fancyZonesDir,
imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir, fileExplorerAddOnsDir, hostsDir, fileLocksmithDir,
mouseJumpDir;
mouseJumpDir, registryPreviewDir;
hr = WcaInitialize(hInstall, "CreateDotnetRuntimeHardlinksCA");
ExitOnFailure(hr, "Failed to initialize");
@ -1103,6 +1105,7 @@ UINT __stdcall CreateDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
hostsDir = installationFolder + L"modules\\Hosts\\";
fileLocksmithDir = installationFolder + L"modules\\FileLocksmith\\";
mouseJumpDir = installationFolder + L"modules\\MouseUtils\\MouseJumpUI\\";
registryPreviewDir = installationFolder + L"modules\\RegistryPreview\\";
for (auto file : dotnetRuntimeFiles)
{
@ -1120,6 +1123,7 @@ UINT __stdcall CreateDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (hostsDir + file).c_str(), ec);
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (fileLocksmithDir + file).c_str(), ec);
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (mouseJumpDir + file).c_str(), ec);
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (registryPreviewDir + file).c_str(), ec);
if (ec.value() != S_OK)
{
@ -1144,6 +1148,7 @@ UINT __stdcall CreateDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (fileExplorerAddOnsDir + file).c_str(), ec);
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (hostsDir + file).c_str(), ec);
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (mouseJumpDir + file).c_str(), ec);
std::filesystem::create_hard_link((dotnetRuntimeFilesSrcDir + file).c_str(), (registryPreviewDir + file).c_str(), ec);
if (ec.value() != S_OK)
{
@ -1238,7 +1243,7 @@ UINT __stdcall DeleteDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
UINT er = ERROR_SUCCESS;
std::wstring installationFolder, colorPickerDir, powerOCRDir, launcherDir, fancyZonesDir,
imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir, fileExplorerAddOnsDir,
hostsDir, fileLocksmithDir, mouseJumpDir;
hostsDir, fileLocksmithDir, mouseJumpDir, registryPreviewDir;
hr = WcaInitialize(hInstall, "DeleteDotnetRuntimeHardlinksCA");
ExitOnFailure(hr, "Failed to initialize");
@ -1259,6 +1264,7 @@ UINT __stdcall DeleteDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
hostsDir = installationFolder + L"modules\\Hosts\\";
fileLocksmithDir = installationFolder + L"modules\\FileLocksmith\\";
mouseJumpDir = installationFolder + L"modules\\MouseUtils\\MouseJumpUI\\";
registryPreviewDir = installationFolder + L"modules\\RegistryPreview\\";
try
{
@ -1277,6 +1283,7 @@ UINT __stdcall DeleteDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
DeleteFile((hostsDir + file).c_str());
DeleteFile((fileLocksmithDir + file).c_str());
DeleteFile((mouseJumpDir + file).c_str());
DeleteFile((registryPreviewDir + file).c_str());
}
for (auto file : dotnetRuntimeWPFFiles)
@ -1291,6 +1298,7 @@ UINT __stdcall DeleteDotnetRuntimeHardlinksCA(MSIHANDLE hInstall)
DeleteFile((fileExplorerAddOnsDir + file).c_str());
DeleteFile((hostsDir + file).c_str());
DeleteFile((mouseJumpDir + file).c_str());
DeleteFile((registryPreviewDir + file).c_str());
}
}
catch (std::exception e)
@ -1324,7 +1332,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 10> processesToTerminate = {
std::array<std::wstring_view, 11> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.Awake.exe",
@ -1334,7 +1342,8 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.MouseJumpUI.exe",
L"PowerToys.ColorPickerUI.exe",
L"PowerToys.AlwaysOnTop.exe",
L"PowerToys.exe"
L"PowerToys.exe",
L"PowerToys.RegistryPreview.exe",
};
for (const auto procID : processes)

View File

@ -24,6 +24,7 @@ namespace Common.UI
ShortcutGuide,
VideoConference,
Hosts,
RegistryPreview,
}
private static string SettingsWindowNameToString(SettingsWindow value)
@ -56,6 +57,8 @@ namespace Common.UI
return "VideoConference";
case SettingsWindow.Hosts:
return "Hosts";
case SettingsWindow.RegistryPreview:
return "RegistryPreview";
default:
{
return string.Empty;

View File

@ -100,6 +100,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredQuickAccentEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredRegistryPreviewEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredRegistryPreviewEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredScreenRulerEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredScreenRulerEnabledValue());

View File

@ -31,6 +31,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredPowerRenameEnabledValue();
static GpoRuleConfigured GetConfiguredPowerLauncherEnabledValue();
static GpoRuleConfigured GetConfiguredQuickAccentEnabledValue();
static GpoRuleConfigured GetConfiguredRegistryPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredScreenRulerEnabledValue();
static GpoRuleConfigured GetConfiguredShortcutGuideEnabledValue();
static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue();

View File

@ -35,6 +35,7 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredPowerRenameEnabledValue();
static GpoRuleConfigured GetConfiguredPowerLauncherEnabledValue();
static GpoRuleConfigured GetConfiguredQuickAccentEnabledValue();
static GpoRuleConfigured GetConfiguredRegistryPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredScreenRulerEnabledValue();
static GpoRuleConfigured GetConfiguredShortcutGuideEnabledValue();
static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue();

View File

@ -215,10 +215,14 @@ public
return gcnew String(CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT);
}
static String
^ MeasureToolTriggerEvent() {
static String ^ RegistryPreviewTriggerEvent() {
return gcnew String(CommonSharedConstants::REGISTRY_PREVIEW_TRIGGER_EVENT);
}
static String ^ MeasureToolTriggerEvent() {
return gcnew String(CommonSharedConstants::MEASURE_TOOL_TRIGGER_EVENT);
}
static String ^ GcodePreviewResizeEvent() {
return gcnew String(CommonSharedConstants::GCODE_PREVIEW_RESIZE_EVENT);
}

View File

@ -46,6 +46,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 by RegistryPreview
const wchar_t REGISTRY_PREVIEW_TRIGGER_EVENT[] = L"Local\\RegistryPreviewEvent-4C559468-F75A-4E7F-BC4F-9C9688316687";
// Path to the event used by MeasureTool
const wchar_t MEASURE_TOOL_TRIGGER_EVENT[] = L"Local\\MeasureToolEvent-3d46745f-09b3-4671-a577-236be7abd199";

View File

@ -57,6 +57,8 @@ struct LogSettings
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt";
inline const static std::string hostsLoggerName = "hosts";
inline const static std::wstring hostsLogPath = L"Logs\\hosts-log.txt";
inline const static std::string registryPreviewLoggerName = "registrypreview";
inline const static std::wstring registryPreviewLogPath = L"Logs\\registryPreview-log.txt";
inline const static int retention = 30;
std::wstring logLevel;
LogSettings();

View File

@ -48,6 +48,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_CONFIGURE_ENABLED_TEXT_EXTRACTOR = L"ConfigureEnabledUtilityTextExtractor";
const std::wstring POLICY_CONFIGURE_ENABLED_PASTE_PLAIN = L"ConfigureEnabledUtilityPastePlain";
const std::wstring POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE = L"ConfigureEnabledUtilityVideoConferenceMute";
const std::wstring POLICY_CONFIGURE_ENABLED_REGISTRY_PREVIEW = L"ConfigureEnabledUtilityRegistryPreview";
// The registry value names for PowerToys installer and update policies.
const std::wstring POLICY_DISABLE_AUTOMATIC_UPDATE_DOWNLOAD = L"AutomaticUpdateDownloadDisabled";
@ -255,6 +256,10 @@ namespace powertoys_gpo {
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE);
}
inline gpo_rule_configured_t getConfiguredRegistryPreviewEnabledValue()
{
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_REGISTRY_PREVIEW);
}
inline gpo_rule_configured_t getDisableAutomaticUpdateDownloadValue()
{
return getConfiguredValue(POLICY_DISABLE_AUTOMATIC_UPDATE_DOWNLOAD);

View File

@ -192,6 +192,24 @@ inline registry::ChangeSet getStlThumbnailHandlerChangeSet(const std::wstring in
NonLocalizable::ExtSTL);
}
inline registry::ChangeSet getRegistryPreviewChangeSet(const std::wstring installationDir,const bool perUser)
{
const HKEY scope = perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
using vec_t = std::vector<registry::ValueChange>;
vec_t changes;
std::wstring command = installationDir;
command.append(L"\\modules\\RegistryPreview\\PowerToys.RegistryPreview.exe \"%1\"");
changes.push_back({ scope, L"Software\\Classes\\regfile\\shell\\preview\\command", std::nullopt, command });
std::wstring icon_path = installationDir;
icon_path.append(L"\\modules\\RegistryPreview\\app.ico");
changes.push_back({ scope, L"Software\\Classes\\regfile\\shell\\preview", L"icon", icon_path });
return { changes };
}
inline std::vector<registry::ChangeSet> getAllOnByDefaultModulesChangeSets(const std::wstring installationDir)
{
constexpr bool PER_USER = true;
@ -201,7 +219,8 @@ inline std::vector<registry::ChangeSet> getAllOnByDefaultModulesChangeSets(const
getGcodePreviewHandlerChangeSet(installationDir, PER_USER),
getSvgThumbnailHandlerChangeSet(installationDir, PER_USER),
getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER),
getStlThumbnailHandlerChangeSet(installationDir, PER_USER) };
getStlThumbnailHandlerChangeSet(installationDir, PER_USER),
getRegistryPreviewChangeSet(installationDir, PER_USER) };
}
inline std::vector<registry::ChangeSet> getAllModulesChangeSets(const std::wstring installationDir)
@ -215,5 +234,6 @@ inline std::vector<registry::ChangeSet> getAllModulesChangeSets(const std::wstri
getSvgThumbnailHandlerChangeSet(installationDir, PER_USER),
getPdfThumbnailHandlerChangeSet(installationDir, PER_USER),
getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER),
getStlThumbnailHandlerChangeSet(installationDir, PER_USER) };
getStlThumbnailHandlerChangeSet(installationDir, PER_USER),
getRegistryPreviewChangeSet(installationDir, PER_USER) };
}

View File

@ -16,6 +16,9 @@
namespace registry
{
template<class>
inline constexpr bool always_false_v = false;
namespace detail
{
struct on_exit
@ -27,9 +30,6 @@ namespace registry
~on_exit() { f(); }
};
template<class>
inline constexpr bool always_false_v = false;
template<class... Ts>
struct overloaded : Ts...
{

View File

@ -268,6 +268,16 @@
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityRegistryPreview" class="Both" displayName="$(string.ConfigureEnabledUtilityRegistryPreview)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityRegistryPreview">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_69_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityScreenRuler" class="Both" displayName="$(string.ConfigureEnabledUtilityScreenRuler)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityScreenRuler">
<parentCategory ref="PowerToys" />

View File

@ -11,6 +11,7 @@
<string id="SUPPORTED_POWERTOYS_0_64_0">PowerToys version 0.64.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_68_0">PowerToys version 0.68.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_69_0">PowerToys version 0.69.0 or later</string>
<string id="ConfigureEnabledUtilityDescription">This policy configures the enabled state for a PowerToys utility.
@ -81,6 +82,7 @@ If this setting is disabled, experimentation is not allowed.
<string id="ConfigureEnabledUtilityPowerRename">Power Rename: Configure enabled state</string>
<string id="ConfigureEnabledUtilityPowerLauncher">PowerToys Run: Configure enabled state</string>
<string id="ConfigureEnabledUtilityQuickAccent">Quick Accent: Configure enabled state</string>
<string id="ConfigureEnabledUtilityRegistryPreview">Registry Preview: Configure enabled state</string>
<string id="ConfigureEnabledUtilityScreenRuler">Screen Ruler: Configure enabled state</string>
<string id="ConfigureEnabledUtilityShortcutGuide">Shortcut Guide: Configure enabled state</string>
<string id="ConfigureEnabledUtilityTextExtractor">Text Extractor: Configure enabled state</string>

View File

@ -0,0 +1,7 @@
#include <string>
namespace RegistryPreviewConstants
{
// Name of the powertoy module.
inline const std::wstring ModuleKey = L"RegistryPreview";
}

View File

@ -0,0 +1,108 @@
// Microsoft Visual C++ generated resource script.
//
#include <windows.h>
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_REGISTRYPREVIEW_NAME "Registry Preview"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{697C6AF9-0A48-49A9-866C-67DA12384015}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>RegistryPreviewExt</RootNamespace>
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup>
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\RegistryPreview\</OutDir>
<TargetName>PowerToys.RegistryPreviewExt</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;REGISTRYPREVIEWEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;REGISTRYPREVIEWEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Constants.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="RegistryPreviewExt.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Constants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="RegistryPreviewExt.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,40 @@
#include "pch.h"
#include "trace.h"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider()
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider()
{
TraceLoggingUnregister(g_hProvider);
}
// Log if the user has enabled or disabled the app
void Trace::EnableRegistryPreview(_In_ bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"RegistryPreview_EnableRegistryPreview",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
// Log that the user tried to activate the app
void Trace::ActivateEditor() noexcept
{
TraceLoggingWrite(
g_hProvider,
"RegistryPreview_Activate",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@ -0,0 +1,14 @@
#pragma once
class Trace
{
public:
static void RegisterProvider();
static void UnregisterProvider();
// Log if the user has enabled or disabled the app
static void EnableRegistryPreview(const bool enabled) noexcept;
// Log that the user tried to activate the app
static void ActivateEditor() noexcept;
};

View File

@ -0,0 +1,265 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include "trace.h"
#include <common/interop/shared_constants.h>
#include <common/utils/string_utils.h>
#include <common/utils/winapi_error.h>
#include <common/utils/logger_helper.h>
#include <common/utils/EventWaiter.h>
#include <common/utils/resources.h>
#include <common/utils/modulesRegistry.h>
#include <common/utils/process_path.h>
#include "resource.h"
#include "Constants.h"
extern "C" IMAGE_DOS_HEADER __ImageBase;
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
const static wchar_t* MODULE_NAME = L"RegistryPreview";
const static wchar_t* MODULE_DESC = L"A quick little utility to visualize and edit complex Windows Registry files.";
class RegistryPreviewModule : public PowertoyModuleIface
{
private:
bool m_enabled = false;
//Hotkey m_hotkey;
HANDLE m_hProcess;
HANDLE triggerEvent;
EventWaiter triggerEventWaiter;
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void launch_process()
{
if (m_enabled)
{
Logger::trace(L"Starting Registry Preview process");
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"modules\\RegistryPreview\\PowerToys.RegistryPreview.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei))
{
Logger::trace("Successfully started the Registry Preview process");
}
else
{
Logger::error(L"Registry Preview failed to start. {}", get_last_error_or_default(GetLastError()));
}
m_hProcess = sei.hProcess;
}
}
void terminate_process()
{
TerminateProcess(m_hProcess, 1);
}
public:
RegistryPreviewModule()
{
LoggerHelpers::init_logger(GET_RESOURCE_STRING(IDS_REGISTRYPREVIEW_NAME), L"ModuleInterface", "RegistryPreview");
Logger::info("Registry Preview object is constructing");
if (!m_enabled)
{
const std::wstring installationDir = get_module_folderpath();
auto regChanges = getRegistryPreviewChangeSet(installationDir, true);
if (!regChanges.unApply())
{
Logger::error(L"Unapplying registry changes failed");
}
}
triggerEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::REGISTRY_PREVIEW_TRIGGER_EVENT);
triggerEventWaiter = EventWaiter(CommonSharedConstants::REGISTRY_PREVIEW_TRIGGER_EVENT, [this](int) {
on_hotkey(0);
});
}
~RegistryPreviewModule()
{
if (m_enabled)
{
terminate_process();
}
m_enabled = false;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
delete this;
}
// Return the localized display name of the powertoy
virtual const wchar_t* get_name() override
{
return MODULE_NAME;
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return MODULE_NAME;
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredRegistryPreviewEnabledValue();
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(MODULE_DESC);
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Pop open the app, if the OOBE page asks it to
virtual void call_custom_action(const wchar_t* action) override
{
try
{
PowerToysSettings::CustomActionObject action_object =
PowerToysSettings::CustomActionObject::from_json_string(action);
if (action_object.get_name() == L"Launch")
{
launch_process();
Trace::ActivateEditor();
}
}
catch (std::exception&)
{
Logger::error(L"Failed to parse action. {}", action);
}
}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
// If you don't need to do any custom processing of the settings, proceed
// to persists the values.
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
// Enable the powertoy
virtual void enable()
{
const std::wstring installationDir = get_module_folderpath();
if (!getRegistryPreviewChangeSet(installationDir, true).apply())
{
Logger::error(L"Applying registry changes failed");
}
// let the DLL enable the app
m_enabled = true;
Trace::EnableRegistryPreview(true);
};
virtual void disable()
{
if (m_enabled)
{
// let the DLL disable the app
terminate_process();
Trace::EnableRegistryPreview(false);
Logger::trace(L"Disabling Registry Preview...");
// Yeet the Registry setting so preview doesn't work anymore
const std::wstring installationDir = get_module_folderpath();
if (!getRegistryPreviewChangeSet(installationDir, true).unApply())
{
Logger::error(L"Unapplying registry changes failed");
}
}
m_enabled = false;
}
// Returns if the powertoys is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Respond to a "click" from the launcher
virtual bool on_hotkey(size_t /*hotkeyId*/) override
{
if (m_enabled)
{
Logger::trace(L"Registry Preview hotkey pressed");
if (is_process_running())
{
terminate_process();
}
else
{
launch_process();
}
return true;
}
return false;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new RegistryPreviewModule();
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.220929.3" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" />
</packages>

View File

@ -0,0 +1 @@
#include "pch.h"

View File

@ -0,0 +1,16 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
//#include <winrt/Windows.Foundation.h>
#include <strsafe.h>
#include <hIdUsage.h>
#include <shellapi.h>
#include <thread>
#include <ProjectTelemetry.h>
#include <winrt/Windows.Foundation.Collections.h>
//#include <Shlwapi.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger.h>

View File

@ -0,0 +1,21 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Awake.rc
//
#define IDS_REGISTRYPREVIEW_NAME 101
#define FILE_DESCRIPTION "PowerToys Registry Preview Module"
#define INTERNAL_NAME "PowerToys.RegistryPreview"
#define ORIGINAL_FILENAME "PowerToys.RegistryPreview.dll"
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@ -0,0 +1,15 @@
<Application
x:Class="RegistryPreview.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RegistryPreview">
<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,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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.UI.Xaml;
using Windows.ApplicationModel.Activation;
using LaunchActivatedEventArgs = Windows.ApplicationModel.Activation.LaunchActivatedEventArgs;
namespace RegistryPreview
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
// Grab the command line parameters directly from the Environment since this is expected to be run
// via Context Menu of a REG file.
string[] cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs == null)
{
// Covers the double click exe scenario and treated as no file loaded
AppFilename = string.Empty;
}
else if (cmdArgs.Length == 2)
{
// GetCommandLineArgs() send in the called EXE as 0 and the selected filename as 1
AppFilename = cmdArgs[1];
}
else
{
// Anything else should be treated as no file loaded
AppFilename = string.Empty;
}
// Start the application
appWindow = new MainWindow();
appWindow.Activate();
}
private Window appWindow;
#pragma warning disable SA1401 // Fields should be private
#pragma warning disable CA2211 // Non-constant fields should not be visible. TODO: consider making it a property
public static string AppFilename;
#pragma warning restore CA2211 // Non-constant fields should not be visible
#pragma warning restore SA1401 // Fields should be private
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

View File

@ -0,0 +1,385 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using CommunityToolkit.WinUI.UI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media;
using Windows.Data.Json;
using Windows.Foundation.Metadata;
using Windows.Storage;
using Windows.Storage.Pickers;
using WinRT.Interop;
namespace RegistryPreview
{
public sealed partial class MainWindow : Window
{
/// <summary>
/// Event handler to grab the main window's size and position before it closes
/// </summary>
private void AppWindow_Closing(Microsoft.UI.Windowing.AppWindow sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs args)
{
jsonSettings.SetNamedValue("appWindow.Position.X", JsonValue.CreateNumberValue(appWindow.Position.X));
jsonSettings.SetNamedValue("appWindow.Position.Y", JsonValue.CreateNumberValue(appWindow.Position.Y));
jsonSettings.SetNamedValue("appWindow.Size.Width", JsonValue.CreateNumberValue(appWindow.Size.Width));
jsonSettings.SetNamedValue("appWindow.Size.Height", JsonValue.CreateNumberValue(appWindow.Size.Height));
}
/// <summary>
/// Event that is will prevent the app from closing if the "save file" flag is active
/// </summary>
public void Window_Closed(object sender, WindowEventArgs args)
{
// Only block closing if the REG file has been edited but not yet saved
if (saveButton.IsEnabled)
{
// if true, the app will not close
args.Handled = true;
// ask the user if they want to save, discard or cancel the close; strings must be loaded here and passed to avoid timing issues
HandleDirtyClosing(
resourceLoader.GetString("YesNoCancelDialogTitle"),
resourceLoader.GetString("YesNoCancelDialogContent"),
resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"),
resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"),
resourceLoader.GetString("YesNoCancelDialogCloseButtonText"));
}
// Save app settings
jsonSettings.SetNamedValue("checkBoxTextBox.Checked", JsonValue.CreateBooleanValue(checkBoxTextBox.IsChecked.Value));
SaveSettingsFile(settingsFolder, settingsFile);
}
/// <summary>
/// Event that gets fired after the visual tree has been fully loaded; the app opens the reg file from here so it can show a message box successfully
/// </summary>
private void GridPreview_Loaded(object sender, RoutedEventArgs e)
{
// static flag to track whether the Visual Tree is ready - if the main Grid has been loaded, the tree is ready.
visualTreeReady = true;
// Load and restore app settings
if (jsonSettings.ContainsKey("checkBoxTextBox.Checked"))
{
checkBoxTextBox.IsChecked = jsonSettings.GetNamedBoolean("checkBoxTextBox.Checked");
}
// Check to see if the REG file was opened and parsed successfully
if (OpenRegistryFile(App.AppFilename) == false)
{
if (File.Exists(App.AppFilename))
{
// Allow Refresh and Edit to be enabled because a broken Reg file might be fixable
UpdateToolBarAndUI(false, true, true);
UpdateWindowTitle(resourceLoader.GetString("InvalidRegistryFileTitle"));
textBox.TextChanged += TextBox_TextChanged;
return;
}
else
{
UpdateToolBarAndUI(false, false, false);
UpdateWindowTitle();
}
}
else
{
textBox.TextChanged += TextBox_TextChanged;
}
textBox.Focus(FocusState.Programmatic);
}
/// <summary>
/// Uses a picker to select a new file to open
/// </summary>
private async void OpenButton_Click(object sender, RoutedEventArgs e)
{
// Check to see if the current file has been saved
if (saveButton.IsEnabled)
{
ContentDialog contentDialog = new ContentDialog()
{
Title = resourceLoader.GetString("YesNoCancelDialogTitle"),
Content = resourceLoader.GetString("YesNoCancelDialogContent"),
PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"),
SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"),
CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"),
DefaultButton = ContentDialogButton.Primary,
};
// Use this code to associate the dialog to the appropriate AppWindow by setting
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
contentDialog.XamlRoot = this.Content.XamlRoot;
}
ContentDialogResult contentDialogResult = await contentDialog.ShowAsync();
switch (contentDialogResult)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
SaveFile();
break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
saveButton.IsEnabled = false;
break;
default:
// Don't open the new file!
return;
}
}
// Pull in a new REG file
FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.ViewMode = PickerViewMode.List;
fileOpenPicker.CommitButtonText = resourceLoader.GetString("OpenButtonText");
fileOpenPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
fileOpenPicker.FileTypeFilter.Add(".reg");
// Get the HWND so we an open the modal
IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
InitializeWithWindow.Initialize(fileOpenPicker, hWnd);
StorageFile storageFile = await fileOpenPicker.PickSingleFileAsync();
if (storageFile != null)
{
// mute the TextChanged handler to make for clean UI
textBox.TextChanged -= TextBox_TextChanged;
App.AppFilename = storageFile.Path;
UpdateToolBarAndUI(OpenRegistryFile(App.AppFilename));
// disable the Save button as it's a new file
saveButton.IsEnabled = false;
// Restore the event handler as we're loaded
textBox.TextChanged += TextBox_TextChanged;
}
}
/// <summary>
/// Saves the currently opened file in place
/// </summary>
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
SaveFile();
}
/// <summary>
/// Uses a picker to save out a copy of the current reg file
/// </summary>
private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
{
// Save out a new REG file and then open it
FileSavePicker fileSavePicker = new FileSavePicker();
fileSavePicker.CommitButtonText = resourceLoader.GetString("SaveButtonText");
fileSavePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
fileSavePicker.FileTypeChoices.Add("Registry file", new List<string>() { ".reg" });
fileSavePicker.SuggestedFileName = resourceLoader.GetString("SuggestFileName");
// Get the HWND so we an save the modal
IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
InitializeWithWindow.Initialize(fileSavePicker, hWnd);
StorageFile storageFile = await fileSavePicker.PickSaveFileAsync();
if (storageFile != null)
{
App.AppFilename = storageFile.Path;
SaveFile();
UpdateToolBarAndUI(OpenRegistryFile(App.AppFilename));
}
}
/// <summary>
/// Reloads the current REG file from storage
/// </summary>
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
textBox.TextChanged -= TextBox_TextChanged;
// reload the current Registry file and update the toolbar accordingly.
UpdateToolBarAndUI(OpenRegistryFile(App.AppFilename), true, true);
saveButton.IsEnabled = false;
// restore the TextChanged handler
textBox.TextChanged += TextBox_TextChanged;
}
/// <summary>
/// Opens the Registry Editor; UAC is handled by the request to open
/// </summary>
private void RegistryButton_Click(object sender, RoutedEventArgs e)
{
// pass in an empty string as we have no file to open
OpenRegistryEditor(string.Empty);
}
/// <summary>
/// Merges the currently saved file into the Registry Editor; UAC is handled by the request to open
/// </summary>
private async void WriteButton_Click(object sender, RoutedEventArgs e)
{
// Check to see if the current file has been saved
if (saveButton.IsEnabled)
{
ContentDialog contentDialog = new ContentDialog()
{
Title = resourceLoader.GetString("YesNoCancelDialogTitle"),
Content = resourceLoader.GetString("YesNoCancelDialogContent"),
PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"),
SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"),
CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"),
DefaultButton = ContentDialogButton.Primary,
};
// Use this code to associate the dialog to the appropriate AppWindow by setting
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
contentDialog.XamlRoot = this.Content.XamlRoot;
}
ContentDialogResult contentDialogResult = await contentDialog.ShowAsync();
switch (contentDialogResult)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
SaveFile();
break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
saveButton.IsEnabled = false;
break;
default:
// Don't open the new file!
return;
}
}
// pass in the filename so we can edit the current file
OpenRegistryEditor(App.AppFilename);
}
/// <summary>
/// Opens the currently saved file in the PC's default REG file editor (often Notepad)
/// </summary>
private void EditButton_Click(object sender, RoutedEventArgs e)
{
// use the REG file's filename and verb so we can respect the selected editor
Process process = new Process();
process.StartInfo.FileName = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", App.AppFilename);
process.StartInfo.Verb = "Edit";
process.StartInfo.UseShellExecute = true;
try
{
process.Start();
}
catch
{
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
resourceLoader.GetString("FileEditorError"),
resourceLoader.GetString("OkButtonText"));
}
}
/// <summary>
/// Trigger that fires when a node in treeView is clicked and which populates dataGrid
/// Can also be fired from elsewhere in the code
/// </summary>
private void TreeView_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args)
{
TreeViewItemInvokedEventArgs localArgs = args as TreeViewItemInvokedEventArgs;
TreeViewNode treeViewNode = null;
// if there are no args, the mouse didn't get clicked but we want to believe it did
if (args != null)
{
treeViewNode = args.InvokedItem as TreeViewNode;
}
else
{
treeViewNode = treeView.SelectedNode;
}
// Grab the object that has Registry data in it from the currently selected treeView node
RegistryKey registryKey = (RegistryKey)treeViewNode.Content;
// no matter what happens, clear the ListView of items on each click
ClearTable();
// if there's no ListView items stored for the selected node, dataGrid is clear so get out now
if (registryKey.Tag == null)
{
return;
}
// if there WAS something in the Tag property, cast it to a list and Populate the ListView
ArrayList arrayList = (ArrayList)registryKey.Tag;
listRegistryValues = new List<RegistryValue>();
for (int i = 0; i < arrayList.Count; i++)
{
RegistryValue listViewItem = (RegistryValue)arrayList[i];
listRegistryValues.Add(listViewItem);
}
// create a new binding for dataGrid and reattach it, updating the rows
Binding listRegistryValuesBinding = new Binding { Source = listRegistryValues };
dataGrid.SetBinding(DataGrid.ItemsSourceProperty, listRegistryValuesBinding);
}
/// <summary>
/// When the text in textBox changes, reload treeView and possibly dataGrid and reset the save button
/// </summary>
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
RefreshRegistryFile();
saveButton.IsEnabled = true;
}
/// <summary>
/// Readonly checkbox is checked, set textBox to read only; also update the font color so it has a hint of being "disabled" (also the hover state!)
/// </summary>
private void CheckBoxTextBox_Checked(object sender, RoutedEventArgs e)
{
textBox.IsReadOnly = true;
SolidColorBrush brush = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 120, 120, 120)); // (SolidColorBrush)Application.Current.Resources["TextBoxDisabledForegroundThemeBrush"];
if (brush != null)
{
textBox.Foreground = brush;
textBox.Resources["TextControlForegroundPointerOver"] = brush;
}
}
/// <summary>
/// Readonly checkbox is unchecked, set textBox to be editable; also update the font color back to a theme friendly foreground (also the hover state!)
/// </summary>
private void CheckBoxTextBox_Unchecked(object sender, RoutedEventArgs e)
{
textBox.IsReadOnly = false;
SolidColorBrush brush = (SolidColorBrush)Application.Current.Resources["TextControlForeground"];
if (brush != null)
{
textBox.Foreground = brush;
textBox.Resources["TextControlForegroundPointerOver"] = brush;
}
}
}
}

View File

@ -0,0 +1,896 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation.Metadata;
using Windows.Storage;
namespace RegistryPreview
{
public sealed partial class MainWindow : Window
{
/// <summary>
/// Method that opens and processes the passed in file name; expected to be an absolute path and a first time open
/// </summary>
private bool OpenRegistryFile(string filename)
{
// clamp to prevent attempts to open a file larger than 10MB
try
{
long fileLength = new System.IO.FileInfo(filename).Length;
if (fileLength > 1048576)
{
ShowMessageBox(resourceLoader.GetString("LargeRegistryFileTitle"), App.AppFilename + resourceLoader.GetString("LargeRegistryFile"), resourceLoader.GetString("OkButtonText"));
ChangeCursor(gridPreview, false);
return false;
}
}
catch
{
// Do nothing here - a missing or invalid file will be caught below
}
// Disable parts of the UI that can cause trouble when loading
ChangeCursor(gridPreview, true);
textBox.Text = string.Empty;
// clear the treeView and dataGrid no matter what
treeView.RootNodes.Clear();
ClearTable();
// update the current window's title with the current filename
UpdateWindowTitle(filename);
// Load in the whole file in one call and plop it all into textBox
FileStream fileStream = null;
try
{
FileStreamOptions fileStreamOptions = new FileStreamOptions();
fileStreamOptions.Access = FileAccess.Read;
fileStreamOptions.Share = FileShare.ReadWrite;
fileStreamOptions.Mode = FileMode.Open;
fileStream = new FileStream(filename, fileStreamOptions);
StreamReader streamReader = new StreamReader(fileStream);
string filenameText = streamReader.ReadToEnd();
textBox.Text = filenameText;
streamReader.Close();
}
catch
{
// restore TextChanged handler to make for clean UI
textBox.TextChanged += TextBox_TextChanged;
// Reset the cursor but leave textBox disabled as no content got loaded
ChangeCursor(gridPreview, false);
return false;
}
finally
{
// clean up no matter what
if (fileStream != null)
{
fileStream.Dispose();
}
}
// now that the file is loaded and in textBox, parse the data
ParseRegistryFile(textBox.Text);
// Getting here means that the entire REG file was parsed without incident
// so select the root of the tree and celebrate
if (treeView.RootNodes.Count > 0)
{
treeView.SelectedNode = treeView.RootNodes[0];
treeView.Focus(FocusState.Programmatic);
}
// reset the cursor
ChangeCursor(gridPreview, false);
return true;
}
/// <summary>
/// Method that re-opens and processes the filename the app already knows about; expected to not be a first time open
/// </summary>
private void RefreshRegistryFile()
{
// Disable parts of the UI that can cause trouble when loading
ChangeCursor(gridPreview, true);
// Get the current selected node so we can return focus to an existing node
TreeViewNode currentNode = treeView.SelectedNode;
// clear the treeView and dataGrid no matter what
treeView.RootNodes.Clear();
ClearTable();
// the existing text is still in textBox so parse the data again
ParseRegistryFile(textBox.Text);
// check to see if there was a key in treeView before the refresh happened
if (currentNode != null)
{
// since there is a valid node, get the FullPath of the key that was selected
string selectedFullPath = ((RegistryKey)currentNode.Content).FullPath;
// check to see if we still have the key in the new Dictionary of keys
if (mapRegistryKeys.ContainsKey(selectedFullPath))
{
// we found it! select it in the tree and pretend it was selected
TreeViewNode treeViewNode;
mapRegistryKeys.TryGetValue(selectedFullPath, out treeViewNode);
treeView.SelectedNode = treeViewNode;
TreeView_ItemInvoked(treeView, null);
}
else
{
// we failed to find an existing node; it could have been deleted in the edit
if (treeView.RootNodes.Count > 0)
{
treeView.SelectedNode = treeView.RootNodes[0];
}
}
}
else
{
// no node was previously selected so check for a RootNode and select it
if (treeView.RootNodes.Count > 0)
{
treeView.SelectedNode = treeView.RootNodes[0];
}
}
// enable the UI
ChangeCursor(gridPreview, false);
}
/// <summary>
/// Parses the text that is passed in, which should be the same text that's in textBox
/// </summary>
private bool ParseRegistryFile(string filenameText)
{
// if this is a not-first open, clear out the Dictionary of nodes
if (mapRegistryKeys != null)
{
mapRegistryKeys.Clear();
mapRegistryKeys = null;
}
// set up a new dictionary
mapRegistryKeys = new Dictionary<string, TreeViewNode>(StringComparer.InvariantCultureIgnoreCase);
// As we'll be processing the text one line at a time, this string will be the current line
string registryLine;
// Brute force editing: for textBox to show Cr-Lf corrected, we need to strip out the \n's
filenameText = filenameText.Replace("\r\n", "\r");
// split apart all of the text in textBox, where one element in the array represents one line
string[] registryLines = filenameText.Split("\r");
if (registryLines.Length <= 1)
{
// after the split, we have no lines so get out
ChangeCursor(gridPreview, false);
return false;
}
// REG files have to start with one of two headers and it's case insensitive
registryLine = registryLines[0];
registryLine = registryLine.ToLowerInvariant();
// make sure that this is a valid REG file, based on the first line of the file
switch (registryLine)
{
case REGISTRYHEADER4:
case REGISTRYHEADER5:
break;
default:
ShowMessageBox(APPNAME, App.AppFilename + resourceLoader.GetString("InvalidRegistryFile"), resourceLoader.GetString("OkButtonText"));
ChangeCursor(gridPreview, false);
return false;
}
// these are used for populating the tree as we read in one line at a time
TreeViewNode treeViewNode = null;
RegistryValue registryValue = null;
// start with the first element of the array
int index = 1;
registryLine = registryLines[index];
while (index < registryLines.Length)
{
// special case for when the registryLine begins with a @ - make some tweaks and
// let the regular processing handle the rest.
if (registryLine.StartsWith("@=-", StringComparison.InvariantCulture))
{
// REG file has a callout to delete the @ Value which won't work *but* the Registry Editor will
// clear the value of the @ Value instead, so it's still a valid line.
registryLine = registryLine.Replace("@=-", "\"(Default)\"=\"\"");
}
else if (registryLine.StartsWith("@=", StringComparison.InvariantCulture))
{
// This is the a Value called "(Default)" so we tweak the line for the UX
registryLine = registryLine.Replace("@=", "\"(Default)\"=");
}
// continue until we have nothing left to read
// switch logic, based off what the current line we're reading is
if (registryLine.StartsWith("[-", StringComparison.InvariantCulture))
{
// remove the - as we won't need it but it will get special treatment in the UI
registryLine = registryLine.Remove(1, 1);
// this is a key, so remove the first [ and last ]
registryLine = StripFirstAndLast(registryLine);
// do not track the result of this node, since it should have no children
AddTextToTree(registryLine, DELETEDKEYIMAGE);
}
else if (registryLine.StartsWith("[", StringComparison.InvariantCulture))
{
// this is a key, so remove the first [ and last ]
registryLine = StripFirstAndLast(registryLine);
treeViewNode = AddTextToTree(registryLine, KEYIMAGE);
}
else if (registryLine.StartsWith("\"", StringComparison.InvariantCulture) && registryLine.EndsWith("=-", StringComparison.InvariantCulture))
{
// this line deletes this value so it gets special treatment for the UI
registryLine = registryLine.Replace("=-", string.Empty);
// remove the "'s without removing all of them
registryLine = StripFirstAndLast(registryLine);
// Create a new listview item that will be used to display the delete value and store it
registryValue = new RegistryValue(registryLine, string.Empty, string.Empty);
SetValueToolTip(registryValue);
// store the ListViewItem, if we have a valid Key to attach to
if (treeViewNode != null)
{
StoreTheListValue((RegistryKey)treeViewNode.Content, registryValue);
}
}
else if (registryLine.StartsWith("\"", StringComparison.InvariantCulture))
{
// this is a named value
// split up the name from the value by looking for the first found =
int equal = registryLine.IndexOf('=');
if ((equal < 0) || (equal > registryLine.Length - 1))
{
// something is very wrong
Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "SOMETHING WENT WRONG: {0}", registryLine));
break;
}
// set the name and the value
string name = registryLine.Substring(0, equal);
name = StripFirstAndLast(name);
// Clean out any escaped characters in the value, only for the preview
name = StripEscapedCharacters(name);
string value = registryLine.Substring(equal + 1);
// Create a new listview item that will be used to display the value
registryValue = new RegistryValue(name, "REG_SZ", string.Empty);
// if the first and last character is a " then this is a string value; get rid of the first and last "
if (value.StartsWith("\"", StringComparison.InvariantCulture) && value.EndsWith("\"", StringComparison.InvariantCulture))
{
value = StripFirstAndLast(value);
}
else
{
// this is an invalid value as there are no "s in the right side of the =
registryValue.Type = "ERROR";
}
if (value.StartsWith("dword:", StringComparison.InvariantCultureIgnoreCase))
{
registryValue.Type = "REG_DWORD";
value = value.Replace("dword:", string.Empty);
}
else if (value.StartsWith("hex(b):", StringComparison.InvariantCultureIgnoreCase))
{
registryValue.Type = "REG_QWORD";
value = value.Replace("hex(b):", string.Empty);
}
else if (value.StartsWith("hex:", StringComparison.InvariantCultureIgnoreCase))
{
registryValue.Type = "REG_BINARY";
value = value.Replace("hex:", string.Empty);
}
else if (value.StartsWith("hex(2):", StringComparison.InvariantCultureIgnoreCase))
{
registryValue.Type = "REG_EXAND_SZ";
value = value.Replace("hex(2):", string.Empty);
}
else if (value.StartsWith("hex(7):", StringComparison.InvariantCultureIgnoreCase))
{
registryValue.Type = "REG_MULTI_SZ";
value = value.Replace("hex(7):", string.Empty);
}
// If the end of a decimal line ends in a \ then you have to keep
// reading the block as a single value!
while (value.EndsWith(@",\", StringComparison.InvariantCulture))
{
value = value.TrimEnd('\\');
index++;
if (index >= registryLines.Length)
{
ChangeCursor(gridPreview, false);
return false;
}
registryLine = registryLines[index];
registryLine = registryLine.TrimStart();
value += registryLine;
}
// Clean out any escaped characters in the value, only for the preview
value = StripEscapedCharacters(value);
// update the ListViewItem with this information
if (registryValue.Type != "ERROR")
{
registryValue.Value = value;
}
// update the ToolTip
SetValueToolTip(registryValue);
// store the ListViewItem, if we have a valid Key to attach to
if (treeViewNode != null)
{
StoreTheListValue((RegistryKey)treeViewNode.Content, registryValue);
}
}
// if we get here, it's not a Key (starts with [) or Value (starts with ") so it's likely waste (comments that start with ; fall out here)
// read the next line from the REG file
index++;
// if we've gone too far, escape the proc!
if (index >= registryLines.Length)
{
// check to see if anything got parsed!
if (treeView.RootNodes.Count <= 0)
{
ShowMessageBox(APPNAME, App.AppFilename + resourceLoader.GetString("InvalidRegistryFile"), resourceLoader.GetString("OkButtonText"));
}
ChangeCursor(gridPreview, false);
return false;
}
// carry on with the next line
registryLine = registryLines[index];
}
// last check, to see if anything got parsed!
if (treeView.RootNodes.Count <= 0)
{
ShowMessageBox(APPNAME, App.AppFilename + resourceLoader.GetString("InvalidRegistryFile"), resourceLoader.GetString("OkButtonText"));
ChangeCursor(gridPreview, false);
return false;
}
return true;
}
/// <summary>
/// We're going to store this ListViewItem in an ArrayList which will then
/// be attached to the most recently returned TreeNode that came back from
/// AddTextToTree. If there's already a list there, we will use that list and
/// add our new node to it.
/// </summary>
private void StoreTheListValue(RegistryKey registryKey, RegistryValue registryValue)
{
ArrayList arrayList = null;
if (registryKey.Tag == null)
{
arrayList = new ArrayList();
}
else
{
arrayList = (ArrayList)registryKey.Tag;
}
arrayList.Add(registryValue);
// shove the updated array into the Tag property
registryKey.Tag = arrayList;
}
/// <summary>
/// Adds the REG file that's being currently being viewed to the app's title bar
/// </summary>
private void UpdateWindowTitle(string filename)
{
string[] file = filename.Split('\\');
if (file.Length > 0)
{
appWindow.Title = file[file.Length - 1] + " - " + APPNAME;
}
else
{
appWindow.Title = filename + " - " + APPNAME;
}
}
/// <summary>
/// No REG file was opened, so leave the app's title bar alone
/// </summary>
private void UpdateWindowTitle()
{
appWindow.Title = APPNAME;
}
/// <summary>
/// Helper method that assumes everything is enabled/disabled together
/// </summary>
private void UpdateToolBarAndUI(bool enable)
{
UpdateToolBarAndUI(enable, enable, enable);
}
/// <summary>
/// Enable command bar buttons and textBox.
/// Note that writeButton and textBox all update with the same value on purpose
/// </summary>
private void UpdateToolBarAndUI(bool enableWrite, bool enableRefresh, bool enableEdit)
{
refreshButton.IsEnabled = enableRefresh;
editButton.IsEnabled = enableEdit;
writeButton.IsEnabled = enableWrite;
}
/// <summary>
/// Helper method that creates a new TreeView node, attaches it to a parent if any, and then passes the new node back to the caller
/// mapRegistryKeys is a collection of all of the [] lines in the file
/// keys comes from the REG file and represents a bunch of nodes
/// </summary>
private TreeViewNode AddTextToTree(string keys, string image)
{
string[] individualKeys = keys.Split('\\');
string fullPath = keys;
TreeViewNode returnNewNode = null, newNode = null, previousNode = null;
// Walk the list of keys backwards
for (int i = individualKeys.Length - 1; i >= 0; i--)
{
// when a Key is marked for deletion, make sure it only sets the icon for the bottom most leaf
if (image == DELETEDKEYIMAGE)
{
if (i < individualKeys.Length - 1)
{
image = KEYIMAGE;
}
else
{
// special casing for Registry roots
switch (individualKeys[i])
{
case "HKEY_CLASSES_ROOT":
case "HKEY_CURRENT_USER":
case "HKEY_LOCAL_MACHINE":
case "HKEY_USERS":
case "HKEY_CURRENT_CONFIG":
image = KEYIMAGE;
break;
}
}
}
// First check the dictionary, and return the current node if it already exists
if (mapRegistryKeys.ContainsKey(fullPath))
{
// was a new node created?
if (returnNewNode == null)
{
// if no new nodes have been created, send out the node we should have already
mapRegistryKeys.TryGetValue(fullPath, out returnNewNode);
}
else
{
// as a new node was created, hook it up to this found parent
mapRegistryKeys.TryGetValue(fullPath, out newNode);
newNode.Children.Add(previousNode);
}
// return the new node no matter what
return returnNewNode;
}
// Since the path is not in the tree, create a new node and add it to the dictionary
RegistryKey registryKey = new RegistryKey(individualKeys[i], fullPath, image, GetFolderToolTip(image));
newNode = new TreeViewNode() { Content = registryKey, IsExpanded = true };
mapRegistryKeys.Add(fullPath, newNode);
// if this is the first new node we're creating, we need to return it to the caller
if (previousNode == null)
{
// capture the first node so it can be returned
returnNewNode = newNode;
}
else
{
// The newly created node is a parent to the previously created node, as add it here.
newNode.Children.Add(previousNode);
}
// before moving onto the next node, tag the previous node and update the path
previousNode = newNode;
fullPath = fullPath.Replace(string.Format(CultureInfo.InvariantCulture, @"\{0}", individualKeys[i]), string.Empty);
// One last check: if we get here, the parent of this node is not yet in the tree, so we need to add it as a RootNode
if (i == 0)
{
treeView.RootNodes.Add(newNode);
treeView.UpdateLayout();
}
}
return returnNewNode;
}
/// <summary>
/// Wrapper method that shows a simple one-button message box, parented by the main application window
/// </summary>
private async void ShowMessageBox(string title, string content, string closeButtonText)
{
ContentDialog contentDialog = new ContentDialog()
{
Title = title,
Content = content,
CloseButtonText = closeButtonText,
};
// Use this code to associate the dialog to the appropriate AppWindow by setting
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
contentDialog.XamlRoot = this.Content.XamlRoot;
}
await contentDialog.ShowAsync();
}
/// <summary>
/// Wrapper method that shows a Save/Don't Save/Cancel message box, parented by the main application window and shown when closing the app
/// </summary>
private async void HandleDirtyClosing(string title, string content, string primaryButtonText, string secondaryButtonText, string closeButtonText)
{
ContentDialog contentDialog = new ContentDialog()
{
Title = title,
Content = content,
PrimaryButtonText = primaryButtonText,
SecondaryButtonText = secondaryButtonText,
CloseButtonText = closeButtonText,
DefaultButton = ContentDialogButton.Primary,
};
// Use this code to associate the dialog to the appropriate AppWindow by setting
// the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow.
if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8))
{
contentDialog.XamlRoot = this.Content.XamlRoot;
}
ContentDialogResult contentDialogResult = await contentDialog.ShowAsync();
switch (contentDialogResult)
{
case ContentDialogResult.Primary:
// Save, then close
SaveFile();
break;
case ContentDialogResult.Secondary:
// Don't save, and then close!
saveButton.IsEnabled = false;
break;
default:
// Cancel closing!
return;
}
// if we got here, we should try to close again
App.Current.Exit();
}
/// <summary>
/// Method will open the Registry Editor or merge the current REG file into the Registry via the Editor
/// Process will prompt for elevation if it needs it.
/// </summary>
private void OpenRegistryEditor(string fileMerge)
{
Process process = new Process();
process.StartInfo.FileName = "regedit.exe";
process.StartInfo.UseShellExecute = true;
if (File.Exists(fileMerge))
{
// If Merge was called, pass in the filename as a param to the Editor
process.StartInfo.Arguments = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", fileMerge);
}
try
{
process.Start();
}
catch
{
ShowMessageBox(
resourceLoader.GetString("UACDialogTitle"),
resourceLoader.GetString("UACDialogError"),
resourceLoader.GetString("OkButtonText"));
}
}
/// <summary>
/// Utility method that clears out the GridView as there's no other way to do it.
/// </summary>
private void ClearTable()
{
if (listRegistryValues != null)
{
listRegistryValues.Clear();
}
dataGrid.ItemsSource = null;
}
/// <summary>
/// Change the current app cursor at the grid level to be a wait cursor. Sort of works, sort of doesn't, but it's a nice attempt.
/// </summary>
public void ChangeCursor(UIElement uiElement, bool wait)
{
// You can only change the Cursor if the visual tree is loaded
if (!visualTreeReady)
{
return;
}
InputCursor cursor = InputSystemCursor.Create(wait ? InputSystemCursorShape.Wait : InputSystemCursorShape.Arrow);
System.Type type = typeof(UIElement);
type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }, CultureInfo.InvariantCulture);
}
/// <summary>
/// Wrapper method that saves the current file in place, using the current text in textBox.
/// </summary>
private void SaveFile()
{
ChangeCursor(gridPreview, true);
// set up the FileStream for all writing
FileStream fileStream = null;
try
{
// attempt to open the existing file for writing
FileStreamOptions fileStreamOptions = new FileStreamOptions();
fileStreamOptions.Access = FileAccess.Write;
fileStreamOptions.Share = FileShare.Write;
fileStreamOptions.Mode = FileMode.OpenOrCreate;
fileStream = new FileStream(App.AppFilename, fileStreamOptions);
StreamWriter streamWriter = new StreamWriter(fileStream, System.Text.Encoding.Unicode);
// if we get here, the file is open and writable so dump the whole contents of textBox
string filenameText = textBox.Text;
streamWriter.Write(filenameText);
streamWriter.Flush();
streamWriter.Close();
// only change when the save is successful
saveButton.IsEnabled = false;
}
catch (UnauthorizedAccessException ex)
{
// this exception is thrown if the file is there but marked as read only
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
ex.Message,
resourceLoader.GetString("OkButtonText"));
}
catch
{
// this catch handles all other exceptions thrown when trying to write the file out
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
resourceLoader.GetString("FileSaveError"),
resourceLoader.GetString("OkButtonText"));
}
finally
{
// clean up no matter what
if (fileStream != null)
{
fileStream.Dispose();
}
}
// restore the cursor
ChangeCursor(gridPreview, false);
}
private async void OpenSettingsFile(string path, string filename)
{
StorageFolder storageFolder = null;
StorageFile storageFile = null;
string fileContents = string.Empty;
try
{
storageFolder = await StorageFolder.GetFolderFromPathAsync(path);
}
catch
{
}
try
{
if (storageFolder != null)
{
storageFile = await storageFolder.GetFileAsync(filename);
}
}
catch
{
}
try
{
if (storageFile != null)
{
fileContents = await Windows.Storage.FileIO.ReadTextAsync(storageFile);
}
}
catch
{
}
try
{
jsonSettings = Windows.Data.Json.JsonObject.Parse(fileContents);
}
catch
{
// set up default JSON blob
fileContents = "{ }";
jsonSettings = Windows.Data.Json.JsonObject.Parse(fileContents);
}
}
/// <summary>
/// Save the settings JSON blob out to a local file
/// </summary>
private async void SaveSettingsFile(string path, string filename)
{
StorageFolder storageFolder = null;
StorageFile storageFile = null;
string fileContents = string.Empty;
try
{
storageFolder = await StorageFolder.GetFolderFromPathAsync(path);
}
catch (FileNotFoundException ex)
{
Debug.WriteLine(ex.Message);
Directory.CreateDirectory(path);
storageFolder = await StorageFolder.GetFolderFromPathAsync(path);
}
try
{
storageFile = await storageFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists);
}
catch (FileNotFoundException ex)
{
Debug.WriteLine(ex.Message);
storageFile = await storageFolder.CreateFileAsync(filename);
}
try
{
fileContents = jsonSettings.Stringify();
await Windows.Storage.FileIO.WriteTextAsync(storageFile, fileContents);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
/// <summary>
/// Rip the first and last character off a string,
/// checking that the string is at least 2 characters long to avoid errors
/// </summary>
private string StripFirstAndLast(string line)
{
if (line.Length > 1)
{
line = line.Remove(line.Length - 1, 1);
line = line.Remove(0, 1);
}
return line;
}
/// <summary>
/// Replace any escaped characters in the REG file with their counterparts, for the UX
/// </summary>
private string StripEscapedCharacters(string value)
{
value = value.Replace("\\\\", "\\"); // Replace \\ with \ in the UI
value = value.Replace("\\\"", "\""); // Replace \" with " in the UI
return value;
}
/// <summary>
/// Loads and returns a string for a given Key's image in the tree, based off the current set image
/// </summary>
private string GetFolderToolTip(string key)
{
string value = string.Empty;
switch (key)
{
case DELETEDKEYIMAGE:
value = resourceLoader.GetString("ToolTipDeletedKey");
break;
case KEYIMAGE:
value = resourceLoader.GetString("ToolTipAddedKey");
break;
}
return value;
}
/// <summary>
/// Loads a string for a given Value's image in the grid, based off the current type and updates the RegistryValue that's passed in
/// </summary>
private void SetValueToolTip(RegistryValue registryValue)
{
string value = string.Empty;
switch (registryValue.Type)
{
case "REG_SZ":
case "REG_EXAND_SZ":
case "REG_MULTI_SZ":
value = resourceLoader.GetString("ToolTipStringValue");
break;
case "ERROR":
value = resourceLoader.GetString("ToolTipErrorValue");
break;
case "":
value = resourceLoader.GetString("ToolTipDeletedValue");
break;
default:
value = resourceLoader.GetString("ToolTipBinaryValue");
break;
}
registryValue.ToolTipText = value;
}
}
}

View File

@ -0,0 +1,212 @@
<Window
x:Class="RegistryPreview.MainWindow"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RegistryPreview"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Closed="Window_Closed"
>
<Grid
x:Name="gridPreview"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Width="Auto"
Height="Auto"
TabFocusNavigation="Cycle"
x:FieldModifier="public"
Loaded="GridPreview_Loaded"
>
<Grid.ColumnDefinitions>
<!-- Left, Splitter, Right -->
<ColumnDefinition Width="*" />
<ColumnDefinition Width="6"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!-- CommandBar, Tree, Splitter, List, StackPanel -->
<RowDefinition Height="48"/>
<RowDefinition Height="*" />
<RowDefinition Height="6" />
<RowDefinition Height="*" />
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<CommandBar
Name="commandBar"
Background="Transparent"
IsOpen="True"
DefaultLabelPosition="Right"
HorizontalAlignment="Left"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
IsTabStop="False"
>
<AppBarButton x:Name="openButton" x:Uid="OpenButton" Icon="OpenFile" IsTabStop="False" Click="OpenButton_Click" HorizontalAlignment="Left">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="O" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton x:Name="saveButton" x:Uid="SaveButton" Icon="Save" IsTabStop="False" Click="SaveButton_Click" HorizontalAlignment="Left" IsEnabled="False">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="None" Key="F2" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton x:Name="saveAsButton" x:Uid="SaveAsButton" Icon="SaveLocal" IsTabStop="False" Click="SaveAsButton_Click" HorizontalAlignment="Left" IsEnabled="True">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Shift" Key="F2" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton x:Name="editButton" x:Uid="EditButton" Icon="NewWindow" IsTabStop="False" Click="EditButton_Click" HorizontalAlignment="Left">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="E" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton x:Name="refreshButton" x:Uid="RefreshButton" Icon="Refresh" IsTabStop="False" Click="RefreshButton_Click" HorizontalAlignment="Left">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="F5" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton x:Name="writeButton" x:Uid="WriteButton" Icon="Share" IsTabStop="False" Click="WriteButton_Click" HorizontalAlignment="Left">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="W" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton x:Name="registryButton" x:Uid="RegistryButton" Icon="Go" IsTabStop="False" Click="RegistryButton_Click" HorizontalAlignment="Left">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Modifiers="Control" Key="R" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
</CommandBar>
<TextBox
x:Name="textBox"
x:Uid="textBox"
Grid.Column="0"
Grid.Row="1"
Grid.RowSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsTabStop="True"
TabIndex="0"
IsTextPredictionEnabled="False"
IsSpellCheckEnabled="False"
AcceptsReturn="True"
PlaceholderText="{Binding PlaceholderText}"
TextWrapping="NoWrap"
CanBeScrollAnchor="False"
ScrollViewer.IsVerticalRailEnabled ="True"
ScrollViewer.IsHorizontalRailEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
FontFamily="Cascadia Mono, Consolas, Courier New"
/>
<StackPanel
x:Name="stackPanelTextBox"
Grid.Column="0"
Grid.Row="4"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsTabStop="False"
Background="Transparent"
>
<CheckBox
x:Name="checkBoxTextBox"
x:Uid="checkBoxTextBox"
Content="{Binding Content}"
IsChecked="True"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsTabStop="True"
TabIndex="1"
Checked="CheckBoxTextBox_Checked"
Unchecked="CheckBoxTextBox_Unchecked"
/>
</StackPanel>
<TreeView
x:Name="treeView"
Grid.Column="2"
Grid.Row="1"
Background="Transparent"
CanDragItems="False"
AllowDrop="False"
AllowFocusOnInteraction="True"
CanReorderItems="False"
IsEnabled="True"
ScrollViewer.BringIntoViewOnFocusChange="True"
ScrollViewer.VerticalScrollMode="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollMode="Enabled"
ItemInvoked="TreeView_ItemInvoked"
Padding="0,0,0,0"
IsTabStop="False"
TabIndex="2"
>
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Padding="0,0,0,0" IsTabStop="False">
<Image Source="{Binding Path=Content.Image}" MaxHeight="16" MaxWidth="16" ToolTipService.ToolTip="{Binding Path=Content.ToolTipText}"/>
<TextBlock Padding="5,0,0,0" Text="{Binding Path=Content.Name}" />
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<controls:DataGrid
x:Name="dataGrid"
Background="Transparent"
Grid.Column="2"
Grid.Row="3"
Grid.RowSpan="2"
CanDrag="False"
SelectionMode="Single"
AllowDrop="False"
AreRowDetailsFrozen="True"
HeadersVisibility="Column"
RowDetailsVisibilityMode="Collapsed"
IsReadOnly="True"
AutoGenerateColumns="False"
ItemsSource="{x:Bind listRegistryValues}"
IsTabStop="true"
TabIndex="3"
>
<controls:DataGrid.Columns>
<controls:DataGridTemplateColumn
x:Uid="NameColumn"
Width="Auto"
IsReadOnly="True"
>
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Padding="10,0,0,0" Text=" " FontSize="14" IsTabStop="False"/>
<Image Source="{Binding ImageUri}" MaxHeight="16" MaxWidth="16" IsTabStop="False" ToolTipService.ToolTip="{Binding ToolTipText}" />
<TextBlock Padding="5, 0, 10, 0" Text="{Binding Name}" FontSize="14" IsTabStop="False"/>
</StackPanel>
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
<controls:DataGridTextColumn
x:Uid="TypeColumn"
Width="Auto"
Binding="{Binding Type}"
FontSize="14"
/>
<controls:DataGridTextColumn
x:Uid="ValueColumn"
Width="Auto"
Binding="{Binding Value}"
FontSize="14"
/>
</controls:DataGrid.Columns>
</controls:DataGrid>
<controls:GridSplitter x:Name="verticalSplitter" Grid.Column="1" Grid.Row="1" Grid.RowSpan="4" CursorBehavior="ChangeOnSplitterHover" Background="{ThemeResource TextControlBorderBrush}" VerticalAlignment="Stretch" GripperCursor="SizeWestEast" IsTabStop="False"/>
<controls:GridSplitter x:Name="horizontalSplitter" Grid.Column="2" Grid.Row="2" CursorBehavior="ChangeOnSplitterHover" Background="{ThemeResource TextControlBorderBrush}" HorizontalAlignment="Stretch" GripperCursor="SizeNorthSouth" IsTabStop="False"/>
</Grid>
</Window>

View File

@ -0,0 +1,93 @@
// 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 Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.ApplicationModel.Resources;
using Windows.Data.Json;
using Windows.Graphics;
namespace RegistryPreview
{
public sealed partial class MainWindow : Window
{
// Const values
private const string REGISTRYHEADER4 = "regedit4";
private const string REGISTRYHEADER5 = "windows registry editor version 5.00";
private const string APPNAME = "Registry Preview";
private const string KEYIMAGE = "ms-appx:///Assets/folder32.png";
private const string DELETEDKEYIMAGE = "ms-appx:///Assets/deleted-folder32.png";
// private members
private Microsoft.UI.Windowing.AppWindow appWindow;
private ResourceLoader resourceLoader;
private bool visualTreeReady;
private Dictionary<string, TreeViewNode> mapRegistryKeys;
private List<RegistryValue> listRegistryValues;
private JsonObject jsonSettings;
private string settingsFolder = string.Empty;
private string settingsFile = string.Empty;
internal MainWindow()
{
this.InitializeComponent();
// Initialize the string table
resourceLoader = ResourceLoader.GetForViewIndependentUse();
// Removed this on 2/15/23 as it doesn't seem to be doing anything any more
// Attempts to force the visual tree to load faster
// this.Activate();
// Update the Win32 looking window with the correct icon (and grab the appWindow handle for later)
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("app.ico");
appWindow.Closing += AppWindow_Closing;
// Open settings file; this moved to after the window tweak because it gives the window time to start up
settingsFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerToys\" + APPNAME;
settingsFile = APPNAME + "_settings.json";
OpenSettingsFile(settingsFolder, settingsFile);
// TODO: figure out a way to only call this once after MainWindow is initialized but before it shows itself
// Calling it from here only successfully resizes/moves the window and it seems to be based off timing, which is horrible.
// Calling it from GridPreview_Loaded() works 100% of the time, but the initial state of the window flashes before sizing/moving it
//
// // if have settings, update the location of the window
// if (jsonSettings != null)
// {
// // resize the window
// if (jsonSettings.ContainsKey("appWindow.Size.Width") && jsonSettings.ContainsKey("appWindow.Size.Height"))
// {
// SizeInt32 size;
// size.Width = (int)jsonSettings.GetNamedNumber("appWindow.Size.Width");
// size.Height = (int)jsonSettings.GetNamedNumber("appWindow.Size.Height");
// appWindow.Resize(size);
// }
//
// // reposition the window
// if (jsonSettings.ContainsKey("appWindow.Position.X") && jsonSettings.ContainsKey("appWindow.Position.Y"))
// {
// PointInt32 point;
// point.X = (int)jsonSettings.GetNamedNumber("appWindow.Position.X");
// point.Y = (int)jsonSettings.GetNamedNumber("appWindow.Position.Y");
// appWindow.Move(point);
// }
// }
// Update Toolbar
if ((App.AppFilename == null) || (File.Exists(App.AppFilename) != true))
{
UpdateToolBarAndUI(false);
UpdateWindowTitle(resourceLoader.GetString("FileNotFound"));
}
}
}
}

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 RegistryPreview
{
/// <summary>
/// Class representing an each item in the tree view, each one a Registry Key;
/// FullPath is so we can re-select the node after a live update
/// Tag is an Array of ListViewItems that stores all the children for the current object
/// </summary>
public class RegistryKey
{
public string Name { get; set; }
public string FullPath { get; set; }
public string Image { get; set; }
public string ToolTipText { get; set; }
public object Tag { get; set; }
public RegistryKey(string name, string fullPath, string image, string toolTipText)
{
this.Name = name;
this.FullPath = fullPath;
this.Image = image;
this.ToolTipText = toolTipText;
}
}
}

View File

@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<Platforms>x86;x64;arm64</Platforms>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\RegistryPreview</OutputPath>
<UseWinUI>true</UseWinUI>
<UseWindowsForms>False</UseWindowsForms>
<ApplicationIcon>app.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<SelfContained>true</SelfContained>
<Product>$(AssemblyName)</Product>
<AssemblyName>PowerToys.RegistryPreview</AssemblyName>
<AssemblyTitle>PowerToys.RegistryPreview</AssemblyTitle>
<AssemblyDescription>PowerToys RegistryPreview</AssemblyDescription>
<RootNamespace>RegistryPreview</RootNamespace>
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
</PropertyGroup>
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
<PropertyGroup Condition="'$(Platform)'=='x64'">
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='ARM64'">
<RuntimeIdentifier>win10-arm64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<Content Include="app.ico" />
</ItemGroup>
<!-- Needed for CommunityToolkit.Labs.WinUI.SettingsControls, on local builds and PR CI. -->
<PropertyGroup Condition="'$(IsPipeline)' == ''">
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\data32.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
<Content Update="Assets\string32.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
<!--TargetFramework>net6.0-windows10.0.19041.0</TargetFramework-->

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.
using System;
namespace RegistryPreview
{
/// <summary>
/// Class representing an each item in the list view, each one a Registry Value.
/// </summary>
public class RegistryValue
{
// Static members
private static Uri uriStringValue = new Uri("ms-appx:///Assets/string32.png");
private static Uri uriBinaryValue = new Uri("ms-appx:///Assets/data32.png");
private static Uri uriDeleteValue = new Uri("ms-appx:///Assets/deleted-value32.png");
private static Uri uriErrorValue = new Uri("ms-appx:///Assets/error32.png");
public string Name { get; set; }
public string Type { get; set; }
public string Value { get; set; }
public string ToolTipText { get; set; }
public Uri ImageUri
{
// Based off the Type of the item, pass back the correct image Uri used by the Binding of the DataGrid
get
{
switch (Type)
{
case "REG_SZ":
case "REG_EXAND_SZ":
case "REG_MULTI_SZ":
return uriStringValue;
case "ERROR":
return uriErrorValue;
case "":
return uriDeleteValue;
}
return uriBinaryValue;
}
}
public RegistryValue(string name, string type, string value)
{
this.Name = name;
this.Type = type;
this.Value = value;
this.ToolTipText = string.Empty;
}
}
}

View File

@ -0,0 +1,231 @@
<?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="checkBoxTextBox.Content" xml:space="preserve">
<value>Read only</value>
</data>
<data name="EditButton.Label" xml:space="preserve">
<value>Edit file...</value>
</data>
<data name="ErrorDialogTitle" xml:space="preserve">
<value>Error</value>
</data>
<data name="FileEditorError" xml:space="preserve">
<value>The REG file editor could not be opened.</value>
</data>
<data name="FileNotFound" xml:space="preserve">
<value>File was not found!</value>
</data>
<data name="FileSaveError" xml:space="preserve">
<value>The REG file cannot be written to.</value>
</data>
<data name="InvalidRegistryFile" xml:space="preserve">
<value> doesn't appear to be a valid registry file!</value>
</data>
<data name="InvalidRegistryFileTitle" xml:space="preserve">
<value>File was not a Registry file!</value>
</data>
<data name="LargeRegistryFile" xml:space="preserve">
<value> is larger than 10MB which is too large for this application.</value>
</data>
<data name="LargeRegistryFileTitle" xml:space="preserve">
<value>File is too large!</value>
</data>
<data name="NameColumn.Header" xml:space="preserve">
<value>Name</value>
</data>
<data name="OkButtonText" xml:space="preserve">
<value>OK</value>
</data>
<data name="OpenButton.Label" xml:space="preserve">
<value>Open file...</value>
</data>
<data name="OpenButtonText" xml:space="preserve">
<value>Open</value>
</data>
<data name="RefreshButton.Label" xml:space="preserve">
<value>Reload from file</value>
</data>
<data name="RegistryButton.Label" xml:space="preserve">
<value>Open Registry Editor...</value>
</data>
<data name="SaveAsButton.Label" xml:space="preserve">
<value>Save file as...</value>
</data>
<data name="SaveButton.Label" xml:space="preserve">
<value>Save file</value>
</data>
<data name="SaveButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="SuggestFileName" xml:space="preserve">
<value>New Registry File</value>
</data>
<data name="textBox.PlaceholderText" xml:space="preserve">
<value>Registry file text will appear here...</value>
</data>
<data name="ToolTipAddedKey" xml:space="preserve">
<value>Key will be added, if needed</value>
</data>
<data name="ToolTipBinaryValue" xml:space="preserve">
<value>Binary value will be updated</value>
</data>
<data name="ToolTipDeletedKey" xml:space="preserve">
<value>Key will be deleted</value>
</data>
<data name="ToolTipDeletedValue" xml:space="preserve">
<value>Value will be deleted</value>
</data>
<data name="ToolTipErrorValue" xml:space="preserve">
<value>Value has a syntax error</value>
</data>
<data name="ToolTipStringValue" xml:space="preserve">
<value>String value will be updated</value>
</data>
<data name="TypeColumn.Header" xml:space="preserve">
<value>Type</value>
</data>
<data name="UACDialogError" xml:space="preserve">
<value>You must click Yes on the previous popup if you want to run the Registry application.</value>
</data>
<data name="UACDialogTitle" xml:space="preserve">
<value>User Account Control</value>
</data>
<data name="ValueColumn.Header" xml:space="preserve">
<value>Value</value>
</data>
<data name="WriteButton.Label" xml:space="preserve">
<value>Write to Registry...</value>
</data>
<data name="YesNoCancelDialogCloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="YesNoCancelDialogContent" xml:space="preserve">
<value>Changes were made to the text file. Do you want to save your changes?</value>
</data>
<data name="YesNoCancelDialogPrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="YesNoCancelDialogSecondaryButtonText" xml:space="preserve">
<value>Don't save</value>
</data>
<data name="YesNoCancelDialogTitle" xml:space="preserve">
<value>Registry Preview</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

View File

@ -0,0 +1,21 @@
<?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="RegistryPreview.app"/>
<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>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@ -165,6 +165,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"modules/PowerOCR/PowerToys.PowerOCRModuleInterface.dll",
L"modules/PastePlain/PowerToys.PastePlainModuleInterface.dll",
L"modules/FileLocksmith/PowerToys.FileLocksmithExt.dll",
L"modules/RegistryPreview/PowerToys.RegistryPreviewExt.dll",
L"modules/MeasureTool/PowerToys.MeasureToolModuleInterface.dll",
L"modules/Hosts/PowerToys.HostsModuleInterface.dll",
};

View File

@ -663,6 +663,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
return "VideoConference";
case ESettingsWindowNames::Hosts:
return "Hosts";
case ESettingsWindowNames::RegistryPreview:
return "RegistryPreview";
default:
{
Logger::error(L"Can't convert ESettingsWindowNames value={} to string", static_cast<int>(value));
@ -726,6 +728,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
{
return ESettingsWindowNames::Hosts;
}
else if (value == "RegistryPreview")
{
return ESettingsWindowNames::RegistryPreview;
}
else
{
Logger::error(L"Can't convert string value={} to ESettingsWindowNames", winrt::to_hstring(value));

View File

@ -16,7 +16,8 @@ enum class ESettingsWindowNames
FileExplorer,
ShortcutGuide,
VideoConference,
Hosts
Hosts,
RegistryPreview,
};
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);

View File

@ -362,6 +362,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool registryPreview = true;
[JsonPropertyName("RegistryPreview")]
public bool RegistryPreview
{
get => registryPreview;
set
{
if (registryPreview != value)
{
LogTelemetryEvent(value);
registryPreview = value;
}
}
}
private void NotifyChange()
{
notifyEnabledChangedAction?.Invoke();

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.
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class RegistryPreviewSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "RegistryPreview";
public RegistryPreviewSettings()
{
Version = "1";
Name = ModuleName;
}
public string GetModuleName()
=> Name;
// This can be utilized in the future if the settings.json file is to be modified/deleted.
public bool UpgradeSettingsConfiguration()
=> false;
}
}

View File

@ -153,6 +153,7 @@ namespace Microsoft.PowerToys.Settings.UI
case "VideoConference": StartupPage = typeof(Views.VideoConferencePage); break;
case "MeasureTool": StartupPage = typeof(Views.MeasureToolPage); break;
case "Hosts": StartupPage = typeof(Views.HostsPage); break;
case "RegistryPreview": StartupPage = typeof(Views.RegistryPreviewPage); break;
case "PastePlain": StartupPage = typeof(Views.PastePlainPage); break;
default: Debug.Assert(false, "Unexpected SettingsWindow argument value"); break;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -65,6 +65,13 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
break;
case "RegistryPreview": // Launch Registry Preview
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent()))
{
eventHandle.Set();
}
break;
case "MeasureTool": // Launch Screen Ruler
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent()))
{

View File

@ -138,6 +138,9 @@ namespace Microsoft.PowerToys.Settings.UI
case "PowerAccent":
needToUpdate = generalSettingsConfig.Enabled.PowerAccent != isEnabled;
generalSettingsConfig.Enabled.PowerAccent = isEnabled; break;
case "RegistryPreview":
needToUpdate = generalSettingsConfig.Enabled.RegistryPreview != isEnabled;
generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break;
case "MeasureTool":
needToUpdate = generalSettingsConfig.Enabled.MeasureTool != isEnabled;
generalSettingsConfig.Enabled.MeasureTool = isEnabled; break;

View File

@ -26,5 +26,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
Hosts,
PastePlain,
WhatsNew,
RegistryPreview,
}
}

View File

@ -0,0 +1,56 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeRegistryPreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:toolkitcontrols="using:CommunityToolkit.WinUI.UI.Controls"
mc:Ignorable="d">
<controls:OOBEPageControl
x:Uid="Oobe_RegistryPreview"
HeroImage="ms-appx:///Assets/Modules/OOBE/RegistryPreview.png">
<controls:OOBEPageControl.PageContent>
<StackPanel Orientation="Vertical">
<TextBlock
x:Uid="Oobe_HowToUse"
Style="{ThemeResource OobeSubtitleStyle}" />
<toolkitcontrols:MarkdownTextBlock
x:Uid="Oobe_RegistryPreview_HowToUse"
Background="Transparent" />
<TextBlock
x:Uid="Oobe_TipsAndTricks"
Style="{ThemeResource OobeSubtitleStyle}" />
<toolkitcontrols:MarkdownTextBlock
x:Uid="Oobe_RegistryPreview_TipsAndTricks"
Background="Transparent" />
<StackPanel
Margin="0,24,0,0"
Orientation="Horizontal"
Spacing="12">
<Button
x:Uid="Launch_RegistryPreview"
Click="Launch_RegistryPreview_Click"
Style="{StaticResource AccentButtonStyle}" />
<Button
x:Uid="OOBE_Settings"
Click="SettingsLaunchButton_Click" />
<HyperlinkButton
NavigateUri="https://aka.ms/PowerToysOverview_RegistryPreview"
Style="{StaticResource TextButtonStyle}">
<TextBlock
x:Uid="LearnMore_RegistryPreview"
TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</StackPanel>
</controls:OOBEPageControl.PageContent>
</controls:OOBEPageControl>
</Page>

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 Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class OobeRegistryPreview : Page
{
public OobePowerToysModule ViewModel { get; set; }
public OobeRegistryPreview()
{
this.InitializeComponent();
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.RegistryPreview]);
DataContext = ViewModel;
}
private void Launch_RegistryPreview_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ShellPage.SendDefaultIPCMessage("{\"action\":{\"RegistryPreview\":{\"action_name\":\"Launch\", \"value\":\"\"}}}");
}
private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (OobeShellPage.OpenMainWindowCallback != null)
{
OobeShellPage.OpenMainWindowCallback(typeof(RegistryPreviewPage));
}
ViewModel.LogOpeningSettingsEvent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
ViewModel.LogClosingModuleEvent();
}
}
}

View File

@ -84,6 +84,10 @@
x:Uid="Shell_QuickAccent"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsPowerAccent.png}"
Tag="QuickAccent" />
<NavigationViewItem
x:Uid="Shell_RegistryPreview"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsRegistryPreview.png}"
Tag="RegistryPreview" />
<NavigationViewItem
x:Uid="Shell_MeasureTool"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsScreenRuler.png}"

View File

@ -170,6 +170,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
ModuleName = "WhatsNew",
IsNew = false,
});
Modules.Insert((int)PowerToysModules.RegistryPreview, new OobePowerToysModule()
{
ModuleName = "RegistryPreview",
IsNew = true,
});
}
public void OnClosing()
@ -240,6 +246,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
case "MouseUtils": NavigationFrame.Navigate(typeof(OobeMouseUtils)); break;
case "MeasureTool": NavigationFrame.Navigate(typeof(OobeMeasureTool)); break;
case "Hosts": NavigationFrame.Navigate(typeof(OobeHosts)); break;
case "RegistryPreview": NavigationFrame.Navigate(typeof(OobeRegistryPreview)); break;
case "PastePlain": NavigationFrame.Navigate(typeof(OobePastePlain)); break;
}
}

View File

@ -139,6 +139,9 @@
<Page Update="OOBE\Views\OobeOverviewAlternate.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="Views\RegistryPreviewPage.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
<ItemGroup>

View File

@ -3043,6 +3043,41 @@ Activate by holding the key for the character you want to add an accent to, then
<value>The maximum size, in kilobytes, for files to be displayed. This is a safety mechanism to prevent loading large files into RAM.</value>
<comment>"RAM" refers to random access memory; "size" refers to disk space; "bytes" refer to the measurement unit</comment>
</data>
<data name="RegistryPreview.ModuleDescription" xml:space="preserve">
<value>A quick little utility to visualize and edit complex Windows Registry files.</value>
</data>
<data name="RegistryPreview.ModuleTitle" xml:space="preserve">
<value>Registry Preview</value>
</data>
<data name="Shell_RegistryPreview.Content" xml:space="preserve">
<value>Registry Preview</value>
<comment>Product name: Navigation view item name for Registry Preview</comment>
</data>
<data name="RegistryPreview_Enable_RegistryPreview.Header" xml:space="preserve">
<value>Enable Registry Preview</value>
<comment>Registry Preview is the name of the utility</comment>
</data>
<data name="Oobe_RegistryPreview_HowToUse.Text" xml:space="preserve">
<value>In File Explorer, right-click a .REG file and select **Preview** from the context menu.</value>
</data>
<data name="Oobe_RegistryPreview_TipsAndTricks.Text" xml:space="preserve">
<value>You can preview or edit Registry files in File Explorer or by opening the app from the PowerToys launcher.</value>
</data>
<data name="Oobe_RegistryPreview.Description" xml:space="preserve">
<value>Registry Preview is a quick little utility to visualize and edit complex Windows Registry files.</value>
</data>
<data name="Oobe_RegistryPreview.Title" xml:space="preserve">
<value>Registry Preview</value>
<comment>Do not localize this string</comment>
</data>
<data name="LearnMore_RegistryPreview.Text" xml:space="preserve">
<value>Learn more about Registry Preview</value>
<comment>Registry Preview is a product name, do not loc</comment>
</data>
<data name="Launch_RegistryPreview.Content" xml:space="preserve">
<value>Launch Registry Preview</value>
<comment>"Registry Preview" is the name of the utility</comment>
</data>
<data name="MouseUtils_MouseJump.Description" xml:space="preserve">
<value>Quickly move the mouse pointer long distances.</value>
<comment>"Mouse Jump" is the name of the utility. Mouse is the hardware mouse.</comment>
@ -3071,4 +3106,13 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="Hosts_Toggle_LoopbackDuplicates.Header" xml:space="preserve">
<value>Consider loopback addresses as duplicates</value>
</data>
<data name="RegistryPreview_Launch_GroupSettings.Header" xml:space="preserve">
<value>Launch</value>
</data>
<data name="RegistryPreview_LaunchButton_Accessible.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Launch Registry Preview</value>
</data>
<data name="RegistryPreview_LaunchButtonControl.Header" xml:space="preserve">
<value>Launch Registry Preview</value>
</data>
</root>

View File

@ -113,6 +113,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
FlyoutMenuItems.Add(new FlyoutMenuItem() { Label = resourceLoader.GetString("QuickAccent/ModuleTitle"), IsEnabled = generalSettingsConfig.Enabled.PowerAccent, Tag = "PowerAccent", Icon = "ms-appx:///Assets/FluentIcons/FluentIconsPowerAccent.png", EnabledChangedCallback = EnabledChangedOnUI });
}
if ((gpo = GPOWrapper.GetConfiguredRegistryPreviewEnabledValue()) != GpoRuleConfigured.Disabled && gpo != GpoRuleConfigured.Enabled)
{
FlyoutMenuItems.Add(new FlyoutMenuItem() { Label = resourceLoader.GetString("RegistryPreview/ModuleTitle"), IsEnabled = generalSettingsConfig.Enabled.RegistryPreview, Tag = "RegistryPreview", Icon = "ms-appx:///Assets/FluentIcons/FluentIconsRegistryPreview.png", EnabledChangedCallback = EnabledChangedOnUI });
}
if ((gpo = GPOWrapper.GetConfiguredScreenRulerEnabledValue()) != GpoRuleConfigured.Disabled && gpo != GpoRuleConfigured.Enabled)
{
FlyoutMenuItems.Add(new FlyoutMenuItem() { Label = resourceLoader.GetString("MeasureTool/ModuleTitle"), IsEnabled = generalSettingsConfig.Enabled.MeasureTool, Tag = "MeasureTool", Icon = "ms-appx:///Assets/FluentIcons/FluentIconsScreenRuler.png", EnabledChangedCallback = EnabledChangedOnUI });
@ -164,6 +169,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
case "PowerRename": item.IsEnabled = generalSettingsConfig.Enabled.PowerRename; break;
case "PowerLauncher": item.IsEnabled = generalSettingsConfig.Enabled.PowerLauncher; break;
case "PowerAccent": item.IsEnabled = generalSettingsConfig.Enabled.PowerAccent; break;
case "RegistryPreview": item.IsEnabled = generalSettingsConfig.Enabled.RegistryPreview; break;
case "MeasureTool": item.IsEnabled = generalSettingsConfig.Enabled.MeasureTool; break;
case "ShortcutGuide": item.IsEnabled = generalSettingsConfig.Enabled.ShortcutGuide; break;
case "PowerOCR": item.IsEnabled = generalSettingsConfig.Enabled.PowerOCR; break;

View File

@ -8,6 +8,7 @@ using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.ApplicationModel.Resources;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
@ -93,6 +94,17 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
});
}
if (GPOWrapper.GetConfiguredRegistryPreviewEnabledValue() != GpoRuleConfigured.Disabled)
{
FlyoutMenuItems.Add(new FlyoutMenuItem()
{
Label = resourceLoader.GetString("RegistryPreview/ModuleTitle"),
Tag = "RegistryPreview",
Visible = generalSettingsConfig.Enabled.RegistryPreview,
Icon = "ms-appx:///Assets/FluentIcons/FluentIconsRegistryPreview.png",
});
}
if (GPOWrapper.GetConfiguredScreenRulerEnabledValue() != GpoRuleConfigured.Disabled)
{
FlyoutMenuItems.Add(new FlyoutMenuItem()
@ -147,6 +159,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
case "Hosts": item.Visible = generalSettingsConfig.Enabled.Hosts; break;
case "PowerLauncher": item.Visible = generalSettingsConfig.Enabled.PowerLauncher; break;
case "PowerOCR": item.Visible = generalSettingsConfig.Enabled.PowerOCR; break;
case "RegistryPreview": item.Visible = generalSettingsConfig.Enabled.RegistryPreview; break;
case "MeasureTool": item.Visible = generalSettingsConfig.Enabled.MeasureTool; break;
case "ShortcutGuide": item.Visible = generalSettingsConfig.Enabled.ShortcutGuide; break;
}

View File

@ -0,0 +1,98 @@
// 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 global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class RegistryPreviewViewModel : Observable
{
private GeneralSettings GeneralSettingsConfig { get; set; }
public ButtonClickCommand LaunchEventHandler => new ButtonClickCommand(Launch);
public RegistryPreviewViewModel(ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc)
{
// To obtain the general settings configurations of PowerToys Settings.
if (settingsRepository == null)
{
throw new ArgumentNullException(nameof(settingsRepository));
}
GeneralSettingsConfig = settingsRepository.SettingsConfig;
InitializeEnabledValue();
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredRegistryPreviewEnabledValue();
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true;
_isRegistryPreviewEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
}
else
{
_isRegistryPreviewEnabled = GeneralSettingsConfig.Enabled.RegistryPreview;
}
}
public bool IsRegistryPreviewEnabled
{
get => _isRegistryPreviewEnabled;
set
{
if (_enabledStateIsGPOConfigured)
{
// If it's GPO configured, shouldn't be able to change this state.
return;
}
if (_isRegistryPreviewEnabled != value)
{
_isRegistryPreviewEnabled = value;
OnPropertyChanged(nameof(IsRegistryPreviewEnabled));
GeneralSettingsConfig.Enabled.RegistryPreview = value;
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public void Launch()
{
var actionName = "Launch";
SendConfigMSG("{\"action\":{\"RegistryPreview\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
}
private Func<string, int> SendConfigMSG { get; }
private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured;
private bool _isRegistryPreviewEnabled;
public void RefreshEnabledState()
{
InitializeEnabledValue();
OnPropertyChanged(nameof(IsRegistryPreviewEnabled));
}
}
}

View File

@ -0,0 +1,54 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Views.RegistryPreviewPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI.UI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<controls:SettingsPageControl
x:Uid="RegistryPreview"
ModuleImageSource="ms-appx:///Assets/Modules/RegistryPreview.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel Orientation="Vertical" ChildrenTransitions="{StaticResource SettingsCardsAnimations}">
<labs:SettingsCard
x:Uid="RegistryPreview_Enable_RegistryPreview"
HeaderIcon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsRegistryPreview.png}"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsRegistryPreviewEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</labs:SettingsCard>
<InfoBar
x:Uid="GPO_IsSettingForced"
IsClosable="False"
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured}"
IsTabStop="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured}"
Severity="Informational" />
<controls:SettingsGroup x:Uid="RegistryPreview_Launch_GroupSettings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsRegistryPreviewEnabled}">
<labs:SettingsCard
x:Uid="RegistryPreview_LaunchButtonControl"
ActionIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily},
FontSize=14,
Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.LaunchEventHandler}"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily},
Glyph=&#xEA37;}"
IsClickEnabled="True" />
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>
<controls:SettingsPageControl.PrimaryLinks>
<controls:PageLink
x:Uid="LearnMore_RegistryPreview"
Link="https://aka.ms/PowerToysOverview_RegistryPreview" />
</controls:SettingsPageControl.PrimaryLinks>
</controls:SettingsPageControl>
</Page>

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 Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class RegistryPreviewPage : Page, IRefreshablePage
{
private RegistryPreviewViewModel ViewModel { get; set; }
public RegistryPreviewPage()
{
var settingsUtils = new SettingsUtils();
ViewModel = new RegistryPreviewViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
}
public void RefreshEnabledState()
{
ViewModel.RefreshEnabledState();
}
}
}

View File

@ -119,6 +119,11 @@
helpers:NavHelper.NavigateTo="views:PowerAccentPage"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsPowerAccent.png}" />
<NavigationViewItem
x:Uid="Shell_RegistryPreview"
helpers:NavHelper.NavigateTo="views:RegistryPreviewPage"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsRegistryPreview.png}" />
<NavigationViewItem
x:Uid="Shell_MeasureTool"
helpers:NavHelper.NavigateTo="views:MeasureToolPage"

View File

@ -32,5 +32,6 @@ std::vector<std::wstring> processes =
L"PowerToys.PdfThumbnailProvider.exe",
L"PowerToys.StlThumbnailProvider.exe",
L"PowerToys.SvgPreviewHandler.exe",
L"PowerToys.SvgThumbnailProvider.exe"
L"PowerToys.SvgThumbnailProvider.exe",
L"PowerToys.RegistryPreview.exe"
};