diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 41cbe3a5b4..69df4fe631 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -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
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index 24f0d42852..8ebf519c13 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -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",
diff --git a/.pipelines/versionAndSignCheck.ps1 b/.pipelines/versionAndSignCheck.ps1
index 753ab98897..b0089f17fa 100644
--- a/.pipelines/versionAndSignCheck.ps1
+++ b/.pipelines/versionAndSignCheck.ps1
@@ -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;
diff --git a/PowerToys.sln b/PowerToys.sln
index 8f3f5d3ed5..ec3bdfde46 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -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}
diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi
index 9e5b9ea5bc..c0956226fc 100644
--- a/installer/PowerToysSetup/Common.wxi
+++ b/installer/PowerToysSetup/Common.wxi
@@ -15,6 +15,7 @@
+
diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj
index 256ba27132..131d2dceda 100644
--- a/installer/PowerToysSetup/PowerToysInstaller.wixproj
+++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj
@@ -77,6 +77,7 @@ call "..\..\publish.cmd" arm64
+
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index daed766bcc..88c47e351a 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -68,6 +68,7 @@
+
@@ -485,6 +486,11 @@
+
+
+
+
+
diff --git a/installer/PowerToysSetup/RegistryPreview.wxs b/installer/PowerToysSetup/RegistryPreview.wxs
new file mode 100644
index 0000000000..393ca839d2
--- /dev/null
+++ b/installer/PowerToysSetup/RegistryPreview.wxs
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/PowerToysSetup/Settings.wxs b/installer/PowerToysSetup/Settings.wxs
index 0d2f8a1e8d..0686ccfab6 100644
--- a/installer/PowerToysSetup/Settings.wxs
+++ b/installer/PowerToysSetup/Settings.wxs
@@ -5,9 +5,9 @@
-
-
-
+
+
+
diff --git a/installer/PowerToysSetup/WinAppSDK.wxs b/installer/PowerToysSetup/WinAppSDK.wxs
index b1b7203bc9..998a28dfbf 100644
--- a/installer/PowerToysSetup/WinAppSDK.wxs
+++ b/installer/PowerToysSetup/WinAppSDK.wxs
@@ -18,7 +18,7 @@
-
+
@@ -351,6 +351,13 @@
+
+
+
+
diff --git a/installer/PowerToysSetup/publish.cmd b/installer/PowerToysSetup/publish.cmd
index 58c6ef48aa..ec3658f294 100644
--- a/installer/PowerToysSetup/publish.cmd
+++ b/installer/PowerToysSetup/publish.cmd
@@ -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
diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp
index ed17d9a34c..f68c969c28 100644
--- a/installer/PowerToysSetupCustomActions/CustomAction.cpp
+++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp
@@ -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 processesToTerminate = {
+ std::array 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)
diff --git a/src/common/Common.UI/SettingsDeepLink.cs b/src/common/Common.UI/SettingsDeepLink.cs
index 42da58b8a0..c63f3fc1ad 100644
--- a/src/common/Common.UI/SettingsDeepLink.cs
+++ b/src/common/Common.UI/SettingsDeepLink.cs
@@ -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;
diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp
index 95e228ec3e..9e4a6ef347 100644
--- a/src/common/GPOWrapper/GPOWrapper.cpp
+++ b/src/common/GPOWrapper/GPOWrapper.cpp
@@ -100,6 +100,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast(powertoys_gpo::getConfiguredQuickAccentEnabledValue());
}
+ GpoRuleConfigured GPOWrapper::GetConfiguredRegistryPreviewEnabledValue()
+ {
+ return static_cast(powertoys_gpo::getConfiguredRegistryPreviewEnabledValue());
+ }
GpoRuleConfigured GPOWrapper::GetConfiguredScreenRulerEnabledValue()
{
return static_cast(powertoys_gpo::getConfiguredScreenRulerEnabledValue());
diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h
index 3a0ca75ace..77d6ba72f9 100644
--- a/src/common/GPOWrapper/GPOWrapper.h
+++ b/src/common/GPOWrapper/GPOWrapper.h
@@ -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();
diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl
index 5ab4744702..b0f1633dfc 100644
--- a/src/common/GPOWrapper/GPOWrapper.idl
+++ b/src/common/GPOWrapper/GPOWrapper.idl
@@ -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();
diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp
index a7ec3b47dd..ae63efa64b 100644
--- a/src/common/interop/interop.cpp
+++ b/src/common/interop/interop.cpp
@@ -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);
}
diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h
index 43fc2bad20..7f8c0bbd7e 100644
--- a/src/common/interop/shared_constants.h
+++ b/src/common/interop/shared_constants.h
@@ -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";
diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h
index bfe35315d8..fb64a11eae 100644
--- a/src/common/logger/logger_settings.h
+++ b/src/common/logger/logger_settings.h
@@ -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();
diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h
index 4e54aeda3d..31563a2075 100644
--- a/src/common/utils/gpo.h
+++ b/src/common/utils/gpo.h
@@ -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);
diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h
index 62d0d35cf2..1f58ab55ca 100644
--- a/src/common/utils/modulesRegistry.h
+++ b/src/common/utils/modulesRegistry.h
@@ -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;
+ 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 getAllOnByDefaultModulesChangeSets(const std::wstring installationDir)
{
constexpr bool PER_USER = true;
@@ -201,7 +219,8 @@ inline std::vector 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 getAllModulesChangeSets(const std::wstring installationDir)
@@ -215,5 +234,6 @@ inline std::vector 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) };
}
diff --git a/src/common/utils/registry.h b/src/common/utils/registry.h
index fbb44a23f5..cd4a3fb715 100644
--- a/src/common/utils/registry.h
+++ b/src/common/utils/registry.h
@@ -16,6 +16,9 @@
namespace registry
{
+ template
+ inline constexpr bool always_false_v = false;
+
namespace detail
{
struct on_exit
@@ -27,9 +30,6 @@ namespace registry
~on_exit() { f(); }
};
- template
- inline constexpr bool always_false_v = false;
-
template
struct overloaded : Ts...
{
diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx
index ef5b2c015d..e3f44a5118 100644
--- a/src/gpo/assets/PowerToys.admx
+++ b/src/gpo/assets/PowerToys.admx
@@ -269,7 +269,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml
index d86fbf9a95..915d61f6c9 100644
--- a/src/gpo/assets/en-US/PowerToys.adml
+++ b/src/gpo/assets/en-US/PowerToys.adml
@@ -11,6 +11,7 @@
PowerToys version 0.64.0 or later
PowerToys version 0.68.0 or later
+ PowerToys version 0.69.0 or later
This policy configures the enabled state for a PowerToys utility.
@@ -81,6 +82,7 @@ If this setting is disabled, experimentation is not allowed.
Power Rename: Configure enabled state
PowerToys Run: Configure enabled state
Quick Accent: Configure enabled state
+ Registry Preview: Configure enabled state
Screen Ruler: Configure enabled state
Shortcut Guide: Configure enabled state
diff --git a/src/modules/registrypreview/RegistryPreviewExt/Constants.h b/src/modules/registrypreview/RegistryPreviewExt/Constants.h
new file mode 100644
index 0000000000..5c81392657
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/Constants.h
@@ -0,0 +1,7 @@
+#include
+
+namespace RegistryPreviewConstants
+{
+ // Name of the powertoy module.
+ inline const std::wstring ModuleKey = L"RegistryPreview";
+}
\ No newline at end of file
diff --git a/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.rc b/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.rc
new file mode 100644
index 0000000000..e13a322c4f
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.rc
@@ -0,0 +1,108 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include
+#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
+
diff --git a/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj b/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj
new file mode 100644
index 0000000000..5f9b2fac10
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj
@@ -0,0 +1,134 @@
+
+
+
+
+
+ Debug
+ ARM64
+
+
+ Release
+ ARM64
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {697C6AF9-0A48-49A9-866C-67DA12384015}
+ Win32Proj
+ RegistryPreviewExt
+ 10.0.19041.0
+
+
+
+ DynamicLibrary
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\RegistryPreview\
+ PowerToys.RegistryPreviewExt
+
+
+
+ Level3
+ true
+ _DEBUG;REGISTRYPREVIEWEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ Use
+ ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
+
+
+ Windows
+ true
+ false
+ Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;REGISTRYPREVIEWEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ Use
+ ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
+
+
+ Windows
+ true
+ true
+ true
+ false
+ Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ pch.h
+
+
+
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj.filters b/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj.filters
new file mode 100644
index 0000000000..bca37b946b
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/RegistryPreviewExt.vcxproj.filters
@@ -0,0 +1,53 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Resource Files
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/registrypreview/RegistryPreviewExt/Trace.cpp b/src/modules/registrypreview/RegistryPreviewExt/Trace.cpp
new file mode 100644
index 0000000000..7dda85e43e
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/Trace.cpp
@@ -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));
+}
diff --git a/src/modules/registrypreview/RegistryPreviewExt/Trace.h b/src/modules/registrypreview/RegistryPreviewExt/Trace.h
new file mode 100644
index 0000000000..d2cda345d8
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/Trace.h
@@ -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;
+};
diff --git a/src/modules/registrypreview/RegistryPreviewExt/dllmain.cpp b/src/modules/registrypreview/RegistryPreviewExt/dllmain.cpp
new file mode 100644
index 0000000000..4ddac84299
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/dllmain.cpp
@@ -0,0 +1,265 @@
+// dllmain.cpp : Defines the entry point for the DLL application.
+#include "pch.h"
+#include
+#include
+#include "trace.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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(&__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();
+}
diff --git a/src/modules/registrypreview/RegistryPreviewExt/packages.config b/src/modules/registrypreview/RegistryPreviewExt/packages.config
new file mode 100644
index 0000000000..c92dd4bf0c
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/registrypreview/RegistryPreviewExt/pch.cpp b/src/modules/registrypreview/RegistryPreviewExt/pch.cpp
new file mode 100644
index 0000000000..1d9f38c57d
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/src/modules/registrypreview/RegistryPreviewExt/pch.h b/src/modules/registrypreview/RegistryPreviewExt/pch.h
new file mode 100644
index 0000000000..be72eb015e
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/pch.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN
+#include
+//#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+//#include
+#include
+#include
diff --git a/src/modules/registrypreview/RegistryPreviewExt/resource.h b/src/modules/registrypreview/RegistryPreviewExt/resource.h
new file mode 100644
index 0000000000..a62c618221
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewExt/resource.h
@@ -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
diff --git a/src/modules/registrypreview/RegistryPreviewUI/App.xaml b/src/modules/registrypreview/RegistryPreviewUI/App.xaml
new file mode 100644
index 0000000000..ffd4f5f5ae
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/App.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/registrypreview/RegistryPreviewUI/App.xaml.cs b/src/modules/registrypreview/RegistryPreviewUI/App.xaml.cs
new file mode 100644
index 0000000000..749a5bfcbe
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/App.xaml.cs
@@ -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
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : Application
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// 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.
+ ///
+ /// Details about the launch request and process.
+ 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
+ }
+}
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Assets/data32.png b/src/modules/registrypreview/RegistryPreviewUI/Assets/data32.png
new file mode 100644
index 0000000000..de5afb66e9
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/Assets/data32.png differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Assets/deleted-folder32.png b/src/modules/registrypreview/RegistryPreviewUI/Assets/deleted-folder32.png
new file mode 100644
index 0000000000..ccd53343a6
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/Assets/deleted-folder32.png differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Assets/deleted-value32.png b/src/modules/registrypreview/RegistryPreviewUI/Assets/deleted-value32.png
new file mode 100644
index 0000000000..6bf283e6c9
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/Assets/deleted-value32.png differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Assets/error32.png b/src/modules/registrypreview/RegistryPreviewUI/Assets/error32.png
new file mode 100644
index 0000000000..100bfed840
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/Assets/error32.png differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Assets/folder32.png b/src/modules/registrypreview/RegistryPreviewUI/Assets/folder32.png
new file mode 100644
index 0000000000..ca0bd90c99
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/Assets/folder32.png differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Assets/string32.png b/src/modules/registrypreview/RegistryPreviewUI/Assets/string32.png
new file mode 100644
index 0000000000..499f28daea
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/Assets/string32.png differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Events.cs b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Events.cs
new file mode 100644
index 0000000000..996f745c42
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Events.cs
@@ -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
+ {
+ ///
+ /// Event handler to grab the main window's size and position before it closes
+ ///
+ 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));
+ }
+
+ ///
+ /// Event that is will prevent the app from closing if the "save file" flag is active
+ ///
+ 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);
+ }
+
+ ///
+ /// 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
+ ///
+ 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);
+ }
+
+ ///
+ /// Uses a picker to select a new file to open
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Saves the currently opened file in place
+ ///
+ private void SaveButton_Click(object sender, RoutedEventArgs e)
+ {
+ SaveFile();
+ }
+
+ ///
+ /// Uses a picker to save out a copy of the current reg file
+ ///
+ 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() { ".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));
+ }
+ }
+
+ ///
+ /// Reloads the current REG file from storage
+ ///
+ 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;
+ }
+
+ ///
+ /// Opens the Registry Editor; UAC is handled by the request to open
+ ///
+ private void RegistryButton_Click(object sender, RoutedEventArgs e)
+ {
+ // pass in an empty string as we have no file to open
+ OpenRegistryEditor(string.Empty);
+ }
+
+ ///
+ /// Merges the currently saved file into the Registry Editor; UAC is handled by the request to open
+ ///
+ 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);
+ }
+
+ ///
+ /// Opens the currently saved file in the PC's default REG file editor (often Notepad)
+ ///
+ 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"));
+ }
+ }
+
+ ///
+ /// Trigger that fires when a node in treeView is clicked and which populates dataGrid
+ /// Can also be fired from elsewhere in the code
+ ///
+ 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();
+
+ 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);
+ }
+
+ ///
+ /// When the text in textBox changes, reload treeView and possibly dataGrid and reset the save button
+ ///
+ private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ RefreshRegistryFile();
+ saveButton.IsEnabled = true;
+ }
+
+ ///
+ /// 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!)
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Readonly checkbox is unchecked, set textBox to be editable; also update the font color back to a theme friendly foreground (also the hover state!)
+ ///
+ 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;
+ }
+ }
+ }
+}
diff --git a/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs
new file mode 100644
index 0000000000..62de8f22d2
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs
@@ -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
+ {
+ ///
+ /// Method that opens and processes the passed in file name; expected to be an absolute path and a first time open
+ ///
+ 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;
+ }
+
+ ///
+ /// Method that re-opens and processes the filename the app already knows about; expected to not be a first time open
+ ///
+ 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);
+ }
+
+ ///
+ /// Parses the text that is passed in, which should be the same text that's in textBox
+ ///
+ 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(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;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
+ ///
+ /// Adds the REG file that's being currently being viewed to the app's title bar
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// No REG file was opened, so leave the app's title bar alone
+ ///
+ private void UpdateWindowTitle()
+ {
+ appWindow.Title = APPNAME;
+ }
+
+ ///
+ /// Helper method that assumes everything is enabled/disabled together
+ ///
+ private void UpdateToolBarAndUI(bool enable)
+ {
+ UpdateToolBarAndUI(enable, enable, enable);
+ }
+
+ ///
+ /// Enable command bar buttons and textBox.
+ /// Note that writeButton and textBox all update with the same value on purpose
+ ///
+ private void UpdateToolBarAndUI(bool enableWrite, bool enableRefresh, bool enableEdit)
+ {
+ refreshButton.IsEnabled = enableRefresh;
+ editButton.IsEnabled = enableEdit;
+ writeButton.IsEnabled = enableWrite;
+ }
+
+ ///
+ /// 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
+ ///
+ 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;
+ }
+
+ ///
+ /// Wrapper method that shows a simple one-button message box, parented by the main application window
+ ///
+ 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();
+ }
+
+ ///
+ /// Wrapper method that shows a Save/Don't Save/Cancel message box, parented by the main application window and shown when closing the app
+ ///
+ 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();
+ }
+
+ ///
+ /// 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.
+ ///
+ 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"));
+ }
+ }
+
+ ///
+ /// Utility method that clears out the GridView as there's no other way to do it.
+ ///
+ private void ClearTable()
+ {
+ if (listRegistryValues != null)
+ {
+ listRegistryValues.Clear();
+ }
+
+ dataGrid.ItemsSource = null;
+ }
+
+ ///
+ /// 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.
+ ///
+ 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);
+ }
+
+ ///
+ /// Wrapper method that saves the current file in place, using the current text in textBox.
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Save the settings JSON blob out to a local file
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Rip the first and last character off a string,
+ /// checking that the string is at least 2 characters long to avoid errors
+ ///
+ private string StripFirstAndLast(string line)
+ {
+ if (line.Length > 1)
+ {
+ line = line.Remove(line.Length - 1, 1);
+ line = line.Remove(0, 1);
+ }
+
+ return line;
+ }
+
+ ///
+ /// Replace any escaped characters in the REG file with their counterparts, for the UX
+ ///
+ private string StripEscapedCharacters(string value)
+ {
+ value = value.Replace("\\\\", "\\"); // Replace \\ with \ in the UI
+ value = value.Replace("\\\"", "\""); // Replace \" with " in the UI
+ return value;
+ }
+
+ ///
+ /// Loads and returns a string for a given Key's image in the tree, based off the current set image
+ ///
+ 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;
+ }
+
+ ///
+ /// 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
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/modules/registrypreview/RegistryPreviewUI/MainWindow.xaml b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.xaml
new file mode 100644
index 0000000000..a0157581f5
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.xaml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/registrypreview/RegistryPreviewUI/MainWindow.xaml.cs b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.xaml.cs
new file mode 100644
index 0000000000..8866ca2112
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.xaml.cs
@@ -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 mapRegistryKeys;
+ private List 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"));
+ }
+ }
+ }
+}
diff --git a/src/modules/registrypreview/RegistryPreviewUI/RegistryKey.xaml.cs b/src/modules/registrypreview/RegistryPreviewUI/RegistryKey.xaml.cs
new file mode 100644
index 0000000000..343966fa3d
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/RegistryKey.xaml.cs
@@ -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
+{
+ ///
+ /// 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
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/modules/registrypreview/RegistryPreviewUI/RegistryPreviewUI.csproj b/src/modules/registrypreview/RegistryPreviewUI/RegistryPreviewUI.csproj
new file mode 100644
index 0000000000..35c36b74c6
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/RegistryPreviewUI.csproj
@@ -0,0 +1,67 @@
+
+
+
+ WinExe
+ net7.0-windows10.0.19041.0
+ 10.0.19041.0
+ x86;x64;arm64
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\RegistryPreview
+ true
+ False
+ app.ico
+ app.manifest
+ true
+ false
+ false
+ true
+ None
+ true
+ 10.0.19041.0
+ true
+ $(AssemblyName)
+ PowerToys.RegistryPreview
+ PowerToys.RegistryPreview
+ PowerToys RegistryPreview
+ RegistryPreview
+ true
+
+
+
+
+ win10-x64
+
+
+ win10-arm64
+
+
+
+
+
+
+
+
+ https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Never
+
+
+ Never
+
+
+
+
diff --git a/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs b/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs
new file mode 100644
index 0000000000..ce372281b3
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs
@@ -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
+{
+ ///
+ /// Class representing an each item in the list view, each one a Registry Value.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw b/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw
new file mode 100644
index 0000000000..dc89b61703
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw
@@ -0,0 +1,231 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Read only
+
+
+ Edit file...
+
+
+ Error
+
+
+ The REG file editor could not be opened.
+
+
+ File was not found!
+
+
+ The REG file cannot be written to.
+
+
+ doesn't appear to be a valid registry file!
+
+
+ File was not a Registry file!
+
+
+ is larger than 10MB which is too large for this application.
+
+
+ File is too large!
+
+
+ Name
+
+
+ OK
+
+
+ Open file...
+
+
+ Open
+
+
+ Reload from file
+
+
+ Open Registry Editor...
+
+
+ Save file as...
+
+
+ Save file
+
+
+ Save
+
+
+ New Registry File
+
+
+ Registry file text will appear here...
+
+
+ Key will be added, if needed
+
+
+ Binary value will be updated
+
+
+ Key will be deleted
+
+
+ Value will be deleted
+
+
+ Value has a syntax error
+
+
+ String value will be updated
+
+
+ Type
+
+
+ You must click Yes on the previous popup if you want to run the Registry application.
+
+
+ User Account Control
+
+
+ Value
+
+
+ Write to Registry...
+
+
+ Cancel
+
+
+ Changes were made to the text file. Do you want to save your changes?
+
+
+ Save
+
+
+ Don't save
+
+
+ Registry Preview
+
+
\ No newline at end of file
diff --git a/src/modules/registrypreview/RegistryPreviewUI/app.ico b/src/modules/registrypreview/RegistryPreviewUI/app.ico
new file mode 100644
index 0000000000..77b76593e3
Binary files /dev/null and b/src/modules/registrypreview/RegistryPreviewUI/app.ico differ
diff --git a/src/modules/registrypreview/RegistryPreviewUI/app.manifest b/src/modules/registrypreview/RegistryPreviewUI/app.manifest
new file mode 100644
index 0000000000..0871bb63b2
--- /dev/null
+++ b/src/modules/registrypreview/RegistryPreviewUI/app.manifest
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
+
+
+
+
+
+
diff --git a/src/runner/main.cpp b/src/runner/main.cpp
index 3e0ed200cc..cc07f71126 100644
--- a/src/runner/main.cpp
+++ b/src/runner/main.cpp
@@ -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",
};
diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp
index 06f853713d..99dc0a4ae6 100644
--- a/src/runner/settings_window.cpp
+++ b/src/runner/settings_window.cpp
@@ -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(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));
diff --git a/src/runner/settings_window.h b/src/runner/settings_window.h
index e03a7c0ef0..1497c2382e 100644
--- a/src/runner/settings_window.h
+++ b/src/runner/settings_window.h
@@ -16,7 +16,8 @@ enum class ESettingsWindowNames
FileExplorer,
ShortcutGuide,
VideoConference,
- Hosts
+ Hosts,
+ RegistryPreview,
};
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs
index c501ada3f3..7db56bc0ec 100644
--- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs
+++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs
@@ -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();
diff --git a/src/settings-ui/Settings.UI.Library/RegistryPreviewSettings.cs b/src/settings-ui/Settings.UI.Library/RegistryPreviewSettings.cs
new file mode 100644
index 0000000000..92718d2e91
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/RegistryPreviewSettings.cs
@@ -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;
+ }
+}
diff --git a/src/settings-ui/Settings.UI/App.xaml.cs b/src/settings-ui/Settings.UI/App.xaml.cs
index 75cc05883e..007482ee92 100644
--- a/src/settings-ui/Settings.UI/App.xaml.cs
+++ b/src/settings-ui/Settings.UI/App.xaml.cs
@@ -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;
}
diff --git a/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsRegistryPreview.png b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsRegistryPreview.png
new file mode 100644
index 0000000000..57e7549189
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsRegistryPreview.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Modules/OOBE/RegistryPreview.png b/src/settings-ui/Settings.UI/Assets/Modules/OOBE/RegistryPreview.png
new file mode 100644
index 0000000000..369e117a7e
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Modules/OOBE/RegistryPreview.png differ
diff --git a/src/settings-ui/Settings.UI/Assets/Modules/RegistryPreview.png b/src/settings-ui/Settings.UI/Assets/Modules/RegistryPreview.png
new file mode 100644
index 0000000000..ec1a043536
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Modules/RegistryPreview.png differ
diff --git a/src/settings-ui/Settings.UI/Flyout/LaunchPage.xaml.cs b/src/settings-ui/Settings.UI/Flyout/LaunchPage.xaml.cs
index c921aeb4e0..e16a48461b 100644
--- a/src/settings-ui/Settings.UI/Flyout/LaunchPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/Flyout/LaunchPage.xaml.cs
@@ -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()))
{
diff --git a/src/settings-ui/Settings.UI/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/MainWindow.xaml.cs
index 06ce02f20d..b214590e65 100644
--- a/src/settings-ui/Settings.UI/MainWindow.xaml.cs
+++ b/src/settings-ui/Settings.UI/MainWindow.xaml.cs
@@ -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;
diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
index b7ceb31266..bdfb3e54f6 100644
--- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
+++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
@@ -26,5 +26,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
Hosts,
PastePlain,
WhatsNew,
+ RegistryPreview,
}
}
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeRegistryPreview.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobeRegistryPreview.xaml
new file mode 100644
index 0000000000..0709030bea
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeRegistryPreview.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeRegistryPreview.xaml.cs b/src/settings-ui/Settings.UI/OOBE/Views/OobeRegistryPreview.xaml.cs
new file mode 100644
index 0000000000..5e78f18988
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeRegistryPreview.xaml.cs
@@ -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
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ 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();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
index 73ec67abce..0613becbe9 100644
--- a/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
@@ -84,6 +84,10 @@
x:Uid="Shell_QuickAccent"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsPowerAccent.png}"
Tag="QuickAccent" />
+
$(DefaultXamlRuntime)
+
+ $(DefaultXamlRuntime)
+
diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
index d203708b84..8ae33c5fa1 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -3043,6 +3043,41 @@ Activate by holding the key for the character you want to add an accent to, then
The maximum size, in kilobytes, for files to be displayed. This is a safety mechanism to prevent loading large files into RAM.
"RAM" refers to random access memory; "size" refers to disk space; "bytes" refer to the measurement unit
+
+ A quick little utility to visualize and edit complex Windows Registry files.
+
+
+ Registry Preview
+
+
+ Registry Preview
+ Product name: Navigation view item name for Registry Preview
+
+
+ Enable Registry Preview
+ Registry Preview is the name of the utility
+
+
+ In File Explorer, right-click a .REG file and select **Preview** from the context menu.
+
+
+ You can preview or edit Registry files in File Explorer or by opening the app from the PowerToys launcher.
+
+
+ Registry Preview is a quick little utility to visualize and edit complex Windows Registry files.
+
+
+ Registry Preview
+ Do not localize this string
+
+
+ Learn more about Registry Preview
+ Registry Preview is a product name, do not loc
+
+
+ Launch Registry Preview
+ "Registry Preview" is the name of the utility
+
Quickly move the mouse pointer long distances.
"Mouse Jump" is the name of the utility. Mouse is the hardware mouse.
@@ -3071,4 +3106,13 @@ Activate by holding the key for the character you want to add an accent to, then
Consider loopback addresses as duplicates
+
+ Launch
+
+
+ Launch Registry Preview
+
+
+ Launch Registry Preview
+
diff --git a/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
index 760e6ecba8..783bf244d3 100644
--- a/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
@@ -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;
diff --git a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
index 66490cea04..97062a2dba 100644
--- a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
@@ -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;
}
diff --git a/src/settings-ui/Settings.UI/ViewModels/RegistryPreviewViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/RegistryPreviewViewModel.cs
new file mode 100644
index 0000000000..64f92c6efd
--- /dev/null
+++ b/src/settings-ui/Settings.UI/ViewModels/RegistryPreviewViewModel.cs
@@ -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 settingsRepository, Func 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 SendConfigMSG { get; }
+
+ private GpoRuleConfigured _enabledGpoRuleConfiguration;
+ private bool _enabledStateIsGPOConfigured;
+ private bool _isRegistryPreviewEnabled;
+
+ public void RefreshEnabledState()
+ {
+ InitializeEnabledValue();
+ OnPropertyChanged(nameof(IsRegistryPreviewEnabled));
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/Views/RegistryPreviewPage.xaml b/src/settings-ui/Settings.UI/Views/RegistryPreviewPage.xaml
new file mode 100644
index 0000000000..25961d5781
--- /dev/null
+++ b/src/settings-ui/Settings.UI/Views/RegistryPreviewPage.xaml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/Views/RegistryPreviewPage.xaml.cs b/src/settings-ui/Settings.UI/Views/RegistryPreviewPage.xaml.cs
new file mode 100644
index 0000000000..8f511269ba
--- /dev/null
+++ b/src/settings-ui/Settings.UI/Views/RegistryPreviewPage.xaml.cs
@@ -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.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
+ DataContext = ViewModel;
+ InitializeComponent();
+ }
+
+ public void RefreshEnabledState()
+ {
+ ViewModel.RefreshEnabledState();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/Views/ShellPage.xaml
index 268b25dc15..df36d1e8c5 100644
--- a/src/settings-ui/Settings.UI/Views/ShellPage.xaml
+++ b/src/settings-ui/Settings.UI/Views/ShellPage.xaml
@@ -119,6 +119,11 @@
helpers:NavHelper.NavigateTo="views:PowerAccentPage"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsPowerAccent.png}" />
+
+
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"
};