diff --git a/ContextMenuProfiler.QualityChecks/Program.cs b/ContextMenuProfiler.QualityChecks/Program.cs index fd2173b..3f6af93 100644 --- a/ContextMenuProfiler.QualityChecks/Program.cs +++ b/ContextMenuProfiler.QualityChecks/Program.cs @@ -39,12 +39,6 @@ static string ReadSource(string relativePath) return File.ReadAllText(FindFileUpward(relativePath)); } -static void AssertSourceDoesNotContainAny(string source, string caseName, params string[] forbiddenLiterals) -{ - var hit = forbiddenLiterals.Where(l => source.Contains($"\"{l}\"", StringComparison.Ordinal)).ToList(); - AssertTrue(hit.Count == 0, caseName, hit.Count == 0 ? null : string.Join(", ", hit)); -} - static void AssertSourceContains(string source, string requiredLiteral, string caseName) { AssertTrue(source.Contains(requiredLiteral, StringComparison.Ordinal), caseName); @@ -61,36 +55,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri string requestWithHint = HookIpcClient.BuildRequest("{00000000-0000-0000-0000-000000000000}", @"C:\Temp\a.txt", @"C:\x\h.dll"); AssertEqual("CMP1|AUTO|{00000000-0000-0000-0000-000000000000}|C:\\Temp\\a.txt|C:\\x\\h.dll", requestWithHint, "BuildRequestWithHint"); -AssertEqual( - BenchmarkSemantics.Category.Background, - BenchmarkSemantics.ResolveCategoryFromLocations(new[] { "Directory", "Background" }), - "ResolveCategoryBackgroundPriority" -); - -AssertEqual( - BenchmarkSemantics.Category.Drive, - BenchmarkSemantics.ResolveCategoryFromLocations(new[] { "Drive", "Directory" }), - "ResolveCategoryDrivePriority" -); - -AssertEqual( - BenchmarkSemantics.Category.Folder, - BenchmarkSemantics.ResolveCategoryFromLocations(new[] { "Directory" }), - "ResolveCategoryFolderHint" -); - -AssertEqual( - BenchmarkSemantics.Category.File, - BenchmarkSemantics.ResolveCategoryFromLocations(new[] { "All Files" }), - "ResolveCategoryFileHint" -); - -AssertEqual( - BenchmarkSemantics.Category.File, - BenchmarkSemantics.ResolveCategoryFromLocations(Array.Empty()), - "ResolveCategoryDefault" -); - const BindingFlags instanceNonPublic = BindingFlags.Instance | BindingFlags.NonPublic; var resourcesField = typeof(LocalizationService).GetField("_resources", instanceNonPublic); AssertTrue(resourcesField != null, "LocalizationResourcesFieldExists"); @@ -139,8 +103,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri string benchmarkServiceSource = ReadSource(@"ContextMenuProfiler.UI\Core\BenchmarkService.cs"); string benchmarkSemanticsSource = ReadSource(@"ContextMenuProfiler.UI\Core\BenchmarkSemantics.cs"); -string comRegistrySemanticsSource = ReadSource(@"ContextMenuProfiler.UI\Core\ComRegistrySemantics.cs"); -string packageManifestSemanticsSource = ReadSource(@"ContextMenuProfiler.UI\Core\PackageManifestSemantics.cs"); string benchmarkStatisticsSource = ReadSource(@"ContextMenuProfiler.UI\Core\BenchmarkStatistics.cs"); string hookIpcClientSource = ReadSource(@"ContextMenuProfiler.UI\Core\HookIpcClient.cs"); string hookIpcSemanticsSource = ReadSource(@"ContextMenuProfiler.UI\Core\HookIpcSemantics.cs"); @@ -178,20 +140,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri "BenchmarkSemanticsUsesPriorityBasedLocationResolution" ); -AssertTrue( - benchmarkSemanticsSource.Contains("StaticVerbRegistryShellPrefix = \"Registry (Shell) - \"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("StaticVerbDisabledKeyPrefix = \"-\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildStaticVerbRegistryLocation", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("IsStaticVerbRegistryPathDisabled", StringComparison.Ordinal), - "BenchmarkSemanticsDefinesStaticVerbRegistryHelpers" -); - -AssertTrue( - benchmarkSemanticsSource.Contains("Timeout = \"Timeout\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static bool IsTimeoutLikeError", StringComparison.Ordinal), - "BenchmarkSemanticsDefinesTimeoutErrorHelper" -); - AssertTrue( benchmarkSemanticsSource.Contains("SkipUnstableHandlersEnvVar = \"CMP_SKIP_UNSTABLE_HANDLERS\"", StringComparison.Ordinal) && benchmarkSemanticsSource.Contains("public static bool IsSkipUnstableHandlersEnabled", StringComparison.Ordinal) @@ -199,215 +147,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri "BenchmarkSemanticsDefinesUnstableHandlerHelpers" ); -AssertTrue( - benchmarkSemanticsSource.Contains("public static class RegistryLocationLabel", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("DisabledSuffix = \" [Disabled]\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildDisabledRegistryLocationLabel", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildExtensionRegistryLocationLabel", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildProgIdRegistryLocationLabel", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildRegistryHandlerLocation", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static class RegistryPathPattern", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("AnyAssociationType = \"*\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("DirectoryAssociationType = \"directory\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("FolderAssociationType = \"folder\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("DirectoryBackgroundAssociationType = @\"directory\\background\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("AllFilesHandlers = @\"*\\shellex\\ContextMenuHandlers\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildSystemFileAssociationHandlers", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildProgIdHandlers", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildSystemFileAssociationShell", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("BuildProgIdShell", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("IsDirectoryLikeAssociationType", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static class RegistryToken", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("ExtensionPrefix = \".\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static bool LooksLikeBracedClsid", StringComparison.Ordinal), - "BenchmarkSemanticsDefinesRegistryScannerLocationHelpers" -); - -AssertTrue( - benchmarkSemanticsSource.Contains("public static class StaticVerb", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("UniqueKeySeparator = '|'", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("IconValueName = \"Icon\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static bool IsIgnoredStaticVerbName", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static string BuildStaticVerbUniqueKey", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static bool TryParseStaticVerbUniqueKey", StringComparison.Ordinal), - "BenchmarkSemanticsDefinesStaticVerbKeyHelpers" -); - -AssertTrue( - benchmarkSemanticsSource.Contains("public static class IconSource", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("ManifestAppLogo = \"ManifestAppLogo\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static class IconLocation", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("HintSeparator = '|'", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("MsAppxUriPrefix = \"ms-appx://\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("IndirectStringPrefix = \"@\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("MrtPreferredTargetSizeToken = \"targetsize-48\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("MrtPreferredScaleToken = \"scale-200\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("IconResourceIndexSeparator = ','", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("IndirectStringBufferSize = 1024", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("public static class IconFileExtension", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("Png = \".png\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("Jpg = \".jpg\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("Bmp = \".bmp\"", StringComparison.Ordinal) - && benchmarkSemanticsSource.Contains("Ico = \".ico\"", StringComparison.Ordinal), - "BenchmarkSemanticsDefinesIconSourceMarkers" -); - -AssertTrue( - packageManifestSemanticsSource.Contains("public static class PackageManifestSemantics", StringComparison.Ordinal) - && packageManifestSemanticsSource.Contains("FileName = \"AppxManifest.xml\"", StringComparison.Ordinal) - && packageManifestSemanticsSource.Contains("ContextMenuCategoryToken = \"fileExplorerContextMenus\"", StringComparison.Ordinal) - && packageManifestSemanticsSource.Contains("ContextMenuCategory = \"windows.fileExplorerContextMenus\"", StringComparison.Ordinal), - "PackageManifestSemanticsDefinesManifestTokens" -); - -AssertTrue( - comRegistrySemanticsSource.Contains("public static class ComRegistrySemantics", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("FriendlyNameValueName = \"FriendlyName\"", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("InprocServer32SubKeyName = \"InprocServer32\"", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("ClsidPrefix = \"CLSID\"", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("Wow6432NodeClsidPrefix = @\"WOW6432Node\\CLSID\"", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("BuildPackagedComClassIndexPath", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("BuildClsidPath", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("BuildWow6432NodeClsidPath", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("BuildPackageRepositoryPath", StringComparison.Ordinal) - && comRegistrySemanticsSource.Contains("ExtractPackageIdPrefix", StringComparison.Ordinal), - "ComRegistrySemanticsDefinesRegistryMetadataTokens" -); - -AssertTrue( - registryScannerSource.Contains("BenchmarkSemantics.RegistryLocationLabel.AllFiles", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.BuildDisabledRegistryLocationLabel", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.BuildExtensionRegistryLocationLabel", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.BuildProgIdRegistryLocationLabel", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.BuildRegistryHandlerLocation", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.AllFilesHandlers", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.BuildSystemFileAssociationHandlers", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.BuildProgIdHandlers", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.AllFilesShell", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.BuildSystemFileAssociationShell", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.BuildProgIdShell", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.DirectoryAssociationType", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.RegistryToken.ExtensionPrefix", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.LooksLikeBracedClsid", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.IconLocation.IndirectStringPrefix", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.IsIgnoredStaticVerbName(verbName)", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.StaticVerb.CommandSubKeyName", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.StaticVerb.MuiVerbValueName", StringComparison.Ordinal) - && registryScannerSource.Contains("BenchmarkSemantics.BuildStaticVerbUniqueKey(displayName, command)", StringComparison.Ordinal), - "RegistryScannerUsesSemanticLocationBuilders" -); - -AssertTrue( - !registryScannerSource.Contains("\"All Files (*)\"", StringComparison.Ordinal) - && !registryScannerSource.Contains("\"Directory [Disabled]\"", StringComparison.Ordinal) - && !registryScannerSource.Contains("\"Extension (", StringComparison.Ordinal) - && !registryScannerSource.Contains("\"ProgID (", StringComparison.Ordinal) - && !registryScannerSource.Contains("\"directory\"", StringComparison.Ordinal) - && !registryScannerSource.Contains("@\"*\\shellex\\ContextMenuHandlers\"", StringComparison.Ordinal) - && !registryScannerSource.Contains("@\"Directory\\shell\"", StringComparison.Ordinal) - && !registryScannerSource.Contains("SystemFileAssociations\\", StringComparison.Ordinal) - && !registryScannerSource.Contains("keyName.StartsWith(\".\")", StringComparison.Ordinal) - && !registryScannerSource.Contains("trimmedName.StartsWith(\"{\")", StringComparison.Ordinal) - && !registryScannerSource.Contains("trimmedName.EndsWith(\"}\")", StringComparison.Ordinal) - && !registryScannerSource.Contains("trimmedGuid.StartsWith(\"{\")", StringComparison.Ordinal) - && !registryScannerSource.Contains("displayName.StartsWith(\"@\")", StringComparison.Ordinal) - && !registryScannerSource.Contains("verbName.Equals(\"Attributes\"", StringComparison.Ordinal) - && !registryScannerSource.Contains("verbName.Equals(\"AnyCode\"", StringComparison.Ordinal), - "RegistryScannerNoInlineLocationLabelLiterals" -); - -AssertTrue( - extensionManagerSource.Contains("BenchmarkSemantics.RegistryLocationToken.StaticVerbDisabledKeyPrefix", StringComparison.Ordinal) - && !extensionManagerSource.Contains("keyName.StartsWith(\"-\")", StringComparison.Ordinal) - && !extensionManagerSource.Contains("\"-\" + keyName", StringComparison.Ordinal) - && !extensionManagerSource.Contains("keyName.Substring(1)", StringComparison.Ordinal), - "ExtensionManagerUsesDisabledPrefixSemantics" -); - -AssertTrue( - shellUtilsSource.Contains("BenchmarkSemantics.IconLocation.IndirectStringPrefix", StringComparison.Ordinal) - && shellUtilsSource.Contains("BenchmarkSemantics.IconLocation.IndirectStringBufferSize", StringComparison.Ordinal) - && shellUtilsSource.Contains("ComRegistrySemantics.BuildClsidPath", StringComparison.Ordinal) - && shellUtilsSource.Contains("ComRegistrySemantics.BuildWow6432NodeClsidPath", StringComparison.Ordinal) - && !shellUtilsSource.Contains("StartsWith(\"@\")", StringComparison.Ordinal) - && !shellUtilsSource.Contains("new StringBuilder(1024)", StringComparison.Ordinal) - && !shellUtilsSource.Contains("OpenSubKey($@\"CLSID\\", StringComparison.Ordinal) - && !shellUtilsSource.Contains("OpenSubKey($@\"WOW6432Node\\CLSID\\", StringComparison.Ordinal), - "ShellUtilsUsesSemanticResourceAndClsidHelpers" -); - -AssertTrue( - packageScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.AnyAssociationType", StringComparison.Ordinal) - && packageScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.IsDirectoryLikeAssociationType", StringComparison.Ordinal) - && packageScannerSource.Contains("BenchmarkSemantics.RegistryPathPattern.DirectoryAssociationType", StringComparison.Ordinal), - "PackageScannerUsesAssociationTypeSemantics" -); - -AssertTrue( - packageScannerSource.Contains("PackageManifestSemantics.Manifest.FileName", StringComparison.Ordinal) - && packageScannerSource.Contains("PackageManifestSemantics.Manifest.ContextMenuCategoryToken", StringComparison.Ordinal) - && packageScannerSource.Contains("PackageManifestSemantics.Manifest.ContextMenuCategory", StringComparison.Ordinal) - && packageScannerSource.Contains("PackageManifestSemantics.Manifest.ExtensionElement", StringComparison.Ordinal) - && packageScannerSource.Contains("PackageManifestSemantics.Manifest.CategoryAttribute", StringComparison.Ordinal) - && packageScannerSource.Contains("ComRegistrySemantics.BuildPackagedComClassIndexPath", StringComparison.Ordinal), - "PackageScannerUsesPackageManifestSemantics" -); - -AssertTrue( - !packageScannerSource.Contains("\"directory\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"folder\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"directory\\\\background\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("type == \"*\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"AppxManifest.xml\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"fileExplorerContextMenus\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"windows.fileExplorerContextMenus\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("Name.LocalName == \"Extension\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("Attribute(\"Category\")", StringComparison.Ordinal) - && !packageScannerSource.Contains("OpenSubKey($@\"PackagedCom\\\\ClassIndex\\\\{clsid:B}\")", StringComparison.Ordinal), - "PackageScannerNoInlineAssociationTypeLiterals" -); - -AssertTrue( - packageScannerSource.Contains("BenchmarkSemantics.IconSource.ManifestAppLogo", StringComparison.Ordinal) - && packageScannerSource.Contains("BenchmarkSemantics.IconLocation.HintSeparator", StringComparison.Ordinal) - && packageScannerSource.Contains("BenchmarkSemantics.IconLocation.MsAppxUriPrefix", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"Manifest (App Logo)\"", StringComparison.Ordinal) - && !packageScannerSource.Contains("\"None\"", StringComparison.Ordinal), - "PackageScannerUsesIconSourceSemantics" -); - -AssertTrue( - nullOrEmptyConverterSource.Contains("BenchmarkSemantics.IconSource.ManifestAppLogo", StringComparison.Ordinal) - && nullOrEmptyConverterSource.Contains("Dashboard.Value.ManifestAppLogo", StringComparison.Ordinal), - "NullOrEmptyConverterMapsIconSourceMarkerLocalization" -); - -AssertTrue( - iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.HintSeparator", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.MsAppxUriPrefix", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.IndirectStringPrefix", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.MrtPreferredTargetSizeToken", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.MrtPreferredScaleToken", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.IconResourceIndexSeparator", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconLocation.IndirectStringBufferSize", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconFileExtension.Png", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconFileExtension.Jpg", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("BenchmarkSemantics.IconFileExtension.Bmp", StringComparison.Ordinal) - && iconToImageConverterSource.Contains("HookIpcSemantics.Response.NoIconToken", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("uriStr.IndexOf('|')", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("path == \"NONE\"", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("path.StartsWith(\"@\")", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("path.LastIndexOf(',')", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("path.StartsWith(\"ms-appx://\")", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("targetsize-48", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("scale-200", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("new StringBuilder(1024)", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("ext == \".png\"", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("ext == \".jpg\"", StringComparison.Ordinal) - && !iconToImageConverterSource.Contains("ext == \".bmp\"", StringComparison.Ordinal), - "IconToImageConverterUsesIconProtocolSemantics" -); - AssertTrue( !benchmarkSemanticsSource.Contains("bool hasDrive = false;", StringComparison.Ordinal) && !benchmarkSemanticsSource.Contains("bool hasFolder = false;", StringComparison.Ordinal) @@ -421,62 +160,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri "BenchmarkStatisticsCalculatorExists" ); -AssertTrue( - hookIpcSemanticsSource.Contains("VersionPrefix = \"CMP1\"", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("ModeAuto = \"AUTO\"", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("PipeName = \"ContextMenuProfilerHook\"", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("ProbeFileName = \"ContextMenuProfiler_probe.txt\"", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("ProbeFileContent = \"probe\"", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("FrameHeaderBytes = 4", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("MaxRequestBytes = 16384", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("MaxResponseBytes = 65536", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("MultiValueDelimiter = '|'", StringComparison.Ordinal) - && hookIpcSemanticsSource.Contains("NoIconToken = \"NONE\"", StringComparison.Ordinal), - "HookIpcSemanticsDefinesProtocolAndPipeConstants" -); - -AssertTrue( - hookIpcClientSource.Contains("HookIpcSemantics.Runtime.MaxConcurrentCalls", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.MaxAttempts", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.RetryDelayMs", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Protocol.VersionPrefix", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.ProbeFileName", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.ProbeFileContent", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.FrameHeaderBytes", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.MaxRequestBytes", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Runtime.MaxResponseBytes", StringComparison.Ordinal) - && hookIpcClientSource.Contains("HookIpcSemantics.Response.MultiValueDelimiter", StringComparison.Ordinal) - && hookIpcClientSource.Contains("WriteFrameAsync", StringComparison.Ordinal) - && hookIpcClientSource.Contains("ReadFrameAsync", StringComparison.Ordinal) - && hookIpcClientSource.Contains("ReadExactAsync", StringComparison.Ordinal) - && hookIpcClientSource.Contains("ShouldRetry(attempt)", StringComparison.Ordinal) - && hookIpcClientSource.Contains("private static async Task DelayForRetryAsync", StringComparison.Ordinal) - && hookIpcClientSource.Contains("await DelayForRetryAsync(attempt)", StringComparison.Ordinal) - && hookIpcClientSource.Contains("private static void CompleteRoundTrip", StringComparison.Ordinal) - && hookIpcClientSource.Contains("TryProbeOnceAsync", StringComparison.Ordinal), - "HookIpcClientUsesIpcSemanticsConstants" -); - -AssertTrue( - !hookIpcClientSource.Contains("private const string ProtocolPrefix", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("private const string ProtocolMode", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("private const string PipeName", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("private const int ConnectTimeoutMs", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("private const int RoundTripTimeoutMs", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("Task.Delay(80)", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("attempt == 0", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("ContextMenuProfiler_probe.txt", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("new StringBuilder(1024)", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("new byte[4096]", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("ExtractJsonEnvelope(", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("ReadResponseAsync(", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("client.IsMessageComplete", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("if (ShouldRetry(attempt))", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("data.names.Split('|',", StringComparison.Ordinal) - && !hookIpcClientSource.Contains("result.roundtrip_ms += Math.Max(0, (long)swRoundTrip.Elapsed.TotalMilliseconds);\r\n if (await DelayForRetryAsync(attempt))", StringComparison.Ordinal), - "HookIpcClientNoLegacyInlineProtocolRuntimeLiterals" -); - AssertTrue( benchmarkServiceSource.Contains("BenchmarkSemantics.Runtime.MaxParallelProbeTasks", StringComparison.Ordinal), "BenchmarkServiceUsesMaxParallelProbeTasksConstant" @@ -502,27 +185,11 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri "DashboardViewModelUsesRuntimeRetryTimingConstants" ); -AssertTrue( - !dashboardViewModelSource.Contains("Task.Delay(1000)", StringComparison.Ordinal) - && !dashboardViewModelSource.Contains("for (int i = 0; i < 5; i++)", StringComparison.Ordinal) - && !dashboardViewModelSource.Contains("Task.Delay(100)", StringComparison.Ordinal) - && !dashboardViewModelSource.Contains("0x800401D0", StringComparison.Ordinal), - "DashboardViewModelNoInlineRetryTimingLiterals" -); - AssertTrue( benchmarkServiceSource.Contains("BenchmarkSemantics.Runtime.IpcTimeoutLikeRoundtripThresholdMs", StringComparison.Ordinal), "BenchmarkServiceUsesIpcTimeoutThresholdConstant" ); -AssertTrue( - benchmarkServiceSource.Contains("HookIpcSemantics.Response.MultiValueDelimiter", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("HookIpcSemantics.Response.NoIconToken", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("BenchmarkSemantics.IconLocation.IconResourceIndexSeparator", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("BenchmarkSemantics.IconFileExtension.Ico", StringComparison.Ordinal), - "BenchmarkServiceUsesHookIpcResponseSemantics" -); - AssertTrue( !benchmarkServiceSource.Contains("hookData.icons.Split('|')", StringComparison.Ordinal) && !benchmarkServiceSource.Contains("i != \"NONE\"", StringComparison.Ordinal) @@ -531,76 +198,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri "BenchmarkServiceNoLegacyHookResponseDelimiterLiterals" ); -AssertTrue( - benchmarkServiceSource.Contains("BenchmarkSemantics.TryParseStaticVerbUniqueKey(key, out string name, out string command)", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("private static bool TryParseStaticVerbKey", StringComparison.Ordinal), - "BenchmarkServiceUsesStaticVerbKeyParser" -); - -AssertTrue( - benchmarkServiceSource.Contains("BenchmarkSemantics.StaticVerb.IconValueName", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("GetValue(\"Icon\")", StringComparison.Ordinal), - "BenchmarkServiceUsesStaticVerbIconValueSemantic" -); - -AssertTrue( - benchmarkServiceSource.Contains("ComRegistrySemantics.FriendlyNameValueName", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("ComRegistrySemantics.InprocServer32SubKeyName", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("ComRegistrySemantics.BuildPackagedComClassIndexPath", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("ComRegistrySemantics.BuildPackageRepositoryPath", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("ComRegistrySemantics.ExtractPackageIdPrefix", StringComparison.Ordinal), - "BenchmarkServiceUsesComRegistrySemantics" -); - -AssertTrue( - !benchmarkServiceSource.Contains("key.GetValue(\"FriendlyName\")", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("OpenSubKey(\"InprocServer32\")", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("OpenSubKey(\"TreatAs\")", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("key.GetValue(\"AppID\")", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("GetValue(\"DllSurrogate\")", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("\"dllhost.exe\"", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("@\"PackagedCom\\ClassIndex\\", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("@\"PackagedCom\\Package\"", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("GetValue(\"DisplayName\")", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("Split('_')[0]", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("GetValue(\"DllPath\")", StringComparison.Ordinal), - "BenchmarkServiceNoInlineComRegistryMetadataLiterals" -); - -AssertTrue( - benchmarkServiceSource.Contains("BenchmarkSemantics.BuildStaticVerbRegistryLocation(p)", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("paths.Any(BenchmarkSemantics.IsStaticVerbRegistryPathDisabled)", StringComparison.Ordinal), - "BenchmarkServiceUsesStaticVerbRegistrySemanticHelpers" -); - -AssertTrue( - benchmarkServiceSource.Contains("BenchmarkSemantics.IsTimeoutLikeError(hookData.error)", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("hookData.error.Contains(\"Timeout\"", StringComparison.Ordinal), - "BenchmarkServiceUsesTimeoutErrorSemanticHelper" -); - -AssertTrue( - benchmarkServiceSource.Contains("BenchmarkSemantics.IsSkipUnstableHandlersEnabled()", StringComparison.Ordinal) - && benchmarkServiceSource.Contains("BenchmarkSemantics.ContainsKnownUnstableHandlerToken", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("CMP_SKIP_UNSTABLE_HANDLERS", StringComparison.Ordinal), - "BenchmarkServiceUsesUnstableHandlerSemanticHelpers" -); - -AssertTrue( - !benchmarkServiceSource.Contains("PintoStartScreen", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("NvcplDesktopContext", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("NvAppDesktopContext", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("NVIDIA CPL Context Menu Extension", StringComparison.Ordinal), - "BenchmarkServiceNoInlineUnstableHandlerTokens" -); - -AssertTrue( - !benchmarkServiceSource.Contains("Registry (Shell) -", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("p.Split('\\\\')[0]", StringComparison.Ordinal) - && !benchmarkServiceSource.Contains("p.Split('\\\\').Last().StartsWith(\"-\")", StringComparison.Ordinal), - "BenchmarkServiceNoInlineStaticVerbRegistryLiterals" -); - AssertTrue( !benchmarkServiceSource.Contains("new SemaphoreSlim(8)", StringComparison.Ordinal) && !benchmarkServiceSource.Contains("hookCall.roundtrip_ms >= 1900", StringComparison.Ordinal) @@ -608,22 +205,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri "BenchmarkServiceNoRuntimeMagicNumericLiterals" ); -string[] forbiddenStatusMagicLiterals = -{ - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.RegistryFallback), - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.LoadError), - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.OrphanedMissingDll), - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.IpcTimeout), - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.VerifiedViaHook), - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.HookLoadedNoMenu), - BenchmarkSemantics.GetStatusDisplayText(BenchmarkSemantics.Status.SkippedKnownUnstable) -}; - -AssertSourceDoesNotContainAny(benchmarkServiceSource, "BenchmarkServiceNoStatusMagicLiterals", forbiddenStatusMagicLiterals); -AssertSourceDoesNotContainAny(packageScannerSource, "PackageScannerNoStatusMagicLiterals", forbiddenStatusMagicLiterals); -AssertSourceDoesNotContainAny(dashboardViewModelSource, "DashboardViewModelNoStatusMagicLiterals", forbiddenStatusMagicLiterals); -AssertSourceDoesNotContainAny(statusVisibilityConverterSource, "StatusVisibilityConverterNoStatusMagicLiterals", forbiddenStatusMagicLiterals); - string[] forbiddenDetailedStatusLiterals = { resources["en-US"]["Dashboard.Detail.StaticNotMeasured"], @@ -633,12 +214,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri resources["en-US"]["Dashboard.Detail.HookUnavailableFallback"] }; -AssertSourceDoesNotContainAny( - benchmarkServiceSource, - "BenchmarkServiceNoHardcodedDetailedStatusLiterals", - forbiddenDetailedStatusLiterals -); - string[] requiredDetailLocalizationKeysInBenchmarkService = { "Dashboard.Detail.StaticNotMeasured", @@ -659,25 +234,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri ); } -string[] forbiddenInterfaceAndLocationLiterals = -{ - "Static Verb", - "Skipped", - "Modern Shell (UWP)", - "[Disabled]" -}; - -AssertSourceDoesNotContainAny( - benchmarkServiceSource, - "BenchmarkServiceNoInterfaceLocationMagicLiterals", - forbiddenInterfaceAndLocationLiterals -); - -AssertTrue( - !dashboardViewModelSource.Contains("private static class CategoryTag", StringComparison.Ordinal), - "DashboardViewModelNoCategoryTagDuplication" -); - AssertTrue( dashboardViewModelSource.Contains("BenchmarkSemantics.IsCategoryMatch", StringComparison.Ordinal), "DashboardViewModelUsesCentralizedCategoryMatch" @@ -823,6 +379,6 @@ static void AssertSourceNotContains(string source, string forbiddenLiteral, stri var service = new BenchmarkService(); var results = await service.RunSystemBenchmarkAsync(ScanMode.Targeted); var measured = results.Count(r => r.TotalTime > 0); - var fallback = results.Count(r => r.Status == BenchmarkSemantics.Status.RegistryFallback); + var fallback = results.Count(r => r.Status == BenchmarkStatus.RegistryFallback); Console.WriteLine($"Live probe: total={results.Count}, measured={measured}, fallback={fallback}"); } diff --git a/ContextMenuProfiler.UI/Converters/IconToImageConverter.cs b/ContextMenuProfiler.UI/Converters/IconToImageConverter.cs index a34610a..f6ab243 100644 --- a/ContextMenuProfiler.UI/Converters/IconToImageConverter.cs +++ b/ContextMenuProfiler.UI/Converters/IconToImageConverter.cs @@ -30,7 +30,7 @@ public class IconToImageConverter : IValueConverter // 解析 URI 和可选的 Binary Hint string actualUri = uriStr; string? hintDllPath = null; - int pipeIndex = uriStr.IndexOf(BenchmarkSemantics.IconLocation.HintSeparator); + int pipeIndex = uriStr.IndexOf('|'); if (pipeIndex > 0) { actualUri = uriStr.Substring(0, pipeIndex); hintDllPath = uriStr.Substring(pipeIndex + 1); @@ -74,9 +74,9 @@ public class IconToImageConverter : IValueConverter string ext = Path.GetExtension(fullPath); var files = Directory.GetFiles(dir, $"{fileName}*{ext}"); var best = files.OrderByDescending(f => - f.Contains(BenchmarkSemantics.IconLocation.MrtPreferredTargetSizeToken, StringComparison.OrdinalIgnoreCase)) + f.Contains("targetsize-48", StringComparison.OrdinalIgnoreCase)) .ThenByDescending(f => - f.Contains(BenchmarkSemantics.IconLocation.MrtPreferredScaleToken, StringComparison.OrdinalIgnoreCase)) + f.Contains("scale-200", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (best != null) return best; } @@ -119,7 +119,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn try { // Handle ms-appx:// URIs (UWP resources) - if (path.StartsWith(BenchmarkSemantics.IconLocation.MsAppxUriPrefix, StringComparison.OrdinalIgnoreCase)) + if (path.StartsWith("ms-appx://", StringComparison.OrdinalIgnoreCase)) { var resolvedPath = ResolveMsAppxUri(path); if (string.IsNullOrEmpty(resolvedPath)) return null; @@ -130,9 +130,9 @@ public object Convert(object value, Type targetType, object parameter, CultureIn path = Environment.ExpandEnvironmentVariables(path); // Handle MUI / UWP Resource strings (starts with @) - if (path.StartsWith(BenchmarkSemantics.IconLocation.IndirectStringPrefix, StringComparison.Ordinal)) + if (path.StartsWith("@")) { - StringBuilder sb = new StringBuilder(BenchmarkSemantics.IconLocation.IndirectStringBufferSize); + StringBuilder sb = new StringBuilder(1024); int res = SHLoadIndirectString(path, sb, (uint)sb.Capacity, IntPtr.Zero); if (res == 0) // S_OK { @@ -150,7 +150,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn else { // Remove @ prefix and try parsing as normal path - path = path.Substring(BenchmarkSemantics.IconLocation.IndirectStringPrefix.Length); + path = path.Substring(1); } } @@ -158,7 +158,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn string filePath = path; // Parse resource index (path,index or path,-id) - int commaIndex = path.LastIndexOf(BenchmarkSemantics.IconLocation.IconResourceIndexSeparator); + int commaIndex = path.LastIndexOf(','); if (commaIndex > 0) { string indexStr = path.Substring(commaIndex + 1); @@ -183,9 +183,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn { string ext = Path.GetExtension(filePath).ToLower(); - if (ext == BenchmarkSemantics.IconFileExtension.Png - || ext == BenchmarkSemantics.IconFileExtension.Jpg - || ext == BenchmarkSemantics.IconFileExtension.Bmp) + if (ext == ".png" || ext == ".jpg" || ext == ".bmp") { var bitmap = new BitmapImage(); bitmap.BeginInit(); diff --git a/ContextMenuProfiler.UI/Converters/NullOrEmptyToLocalizedConverter.cs b/ContextMenuProfiler.UI/Converters/NullOrEmptyToLocalizedConverter.cs index 4f465ce..16bc797 100644 --- a/ContextMenuProfiler.UI/Converters/NullOrEmptyToLocalizedConverter.cs +++ b/ContextMenuProfiler.UI/Converters/NullOrEmptyToLocalizedConverter.cs @@ -10,24 +10,22 @@ public class NullOrEmptyToLocalizedConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - string text = value?.ToString() ?? string.Empty; - if (!string.IsNullOrWhiteSpace(text)) + if (value is string str) { - if (string.Equals(text, BenchmarkSemantics.IconSource.ManifestAppLogo, StringComparison.Ordinal)) + if (string.IsNullOrEmpty(str)) { - return LocalizationService.Instance["Dashboard.Value.ManifestAppLogo"]; + return LocalizationService.Instance["Dashboard.Value.None"]; } - return text; - } + if (string.Equals(str, "ManifestAppLogo", StringComparison.OrdinalIgnoreCase)) + { + return LocalizationService.Instance["Dashboard.Value.ManifestAppLogo"]; + } - string key = parameter?.ToString() ?? string.Empty; - if (string.IsNullOrWhiteSpace(key)) - { - return string.Empty; + return str; } - return LocalizationService.Instance[key]; + return LocalizationService.Instance["Dashboard.Value.None"]; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/ContextMenuProfiler.UI/Converters/TypeToIconConverter.cs b/ContextMenuProfiler.UI/Converters/TypeToIconConverter.cs index a1b1e88..2b73979 100644 --- a/ContextMenuProfiler.UI/Converters/TypeToIconConverter.cs +++ b/ContextMenuProfiler.UI/Converters/TypeToIconConverter.cs @@ -13,8 +13,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn if (value is string type) { if (BenchmarkSemantics.IsPackagedExtensionType(type)) return SymbolRegular.AppGeneric24; - if (string.Equals(type, BenchmarkSemantics.Type.Com, StringComparison.OrdinalIgnoreCase)) return SymbolRegular.PuzzlePiece24; // Default generic icon - if (string.Equals(type, BenchmarkSemantics.Type.Static, StringComparison.OrdinalIgnoreCase)) return SymbolRegular.WindowConsole20; + if (string.Equals(type, "COM", StringComparison.OrdinalIgnoreCase)) return SymbolRegular.PuzzlePiece24; // Default generic icon + if (string.Equals(type, "Static", StringComparison.OrdinalIgnoreCase)) return SymbolRegular.WindowConsole20; } return SymbolRegular.PuzzlePiece24; // Default fallback } diff --git a/ContextMenuProfiler.UI/Core/BenchmarkSemantics.cs b/ContextMenuProfiler.UI/Core/BenchmarkSemantics.cs index be5f17c..d5c2bdb 100644 --- a/ContextMenuProfiler.UI/Core/BenchmarkSemantics.cs +++ b/ContextMenuProfiler.UI/Core/BenchmarkSemantics.cs @@ -24,15 +24,15 @@ public static class BenchmarkSemantics { private static readonly string[] FolderLocationHints = { - CategoryLocationHint.Directory, - CategoryLocationHint.Folder + "Directory", + "Folder" }; private static readonly string[] FileLocationHints = { - CategoryLocationHint.AllFiles, - CategoryLocationHint.Extension, - CategoryLocationHint.AllFileSystemObjects + "All Files", + "Extension", + "All File System Objects" }; private static class CategoryPriority @@ -44,210 +44,6 @@ private static class CategoryPriority public const int Background = 4; } - public const BenchmarkStatus StatusRegistryFallback = Status.RegistryFallback; - public const BenchmarkStatus StatusHookLoadedNoMenu = Status.HookLoadedNoMenu; - public const BenchmarkStatus StatusLoadError = Status.LoadError; - public const BenchmarkStatus StatusOrphanedMissingDll = Status.OrphanedMissingDll; - - public static class Type - { - public const string Com = "COM"; - public const string Uwp = "UWP"; - public const string Static = "Static"; - public const string PackagedExtension = "Packaged Extension"; - public const string PackagedCom = "Packaged COM"; - public const string UwpPackagedCom = "UWP / Packaged COM"; - } - - public static class Category - { - public const string File = "File"; - public const string Folder = "Folder"; - public const string Background = "Background"; - public const string Drive = "Drive"; - public const string Uwp = "UWP"; - public const string Static = "Static"; - } - - public static class FilterCategory - { - public const string All = "All"; - } - - public static class Status - { - public const BenchmarkStatus Unknown = BenchmarkStatus.Unknown; - public const BenchmarkStatus Ok = BenchmarkStatus.Ok; - public const BenchmarkStatus VerifiedViaHook = BenchmarkStatus.VerifiedViaHook; - public const BenchmarkStatus HookLoadedNoMenu = BenchmarkStatus.HookLoadedNoMenu; - public const BenchmarkStatus OrphanedMissingDll = BenchmarkStatus.OrphanedMissingDll; - public const BenchmarkStatus IpcTimeout = BenchmarkStatus.IpcTimeout; - public const BenchmarkStatus LoadError = BenchmarkStatus.LoadError; - public const BenchmarkStatus RegistryFallback = BenchmarkStatus.RegistryFallback; - public const BenchmarkStatus StaticNotMeasured = BenchmarkStatus.StaticNotMeasured; - public const BenchmarkStatus SkippedKnownUnstable = BenchmarkStatus.SkippedKnownUnstable; - public const BenchmarkStatus DisabledPendingRestart = BenchmarkStatus.DisabledPendingRestart; - public const BenchmarkStatus EnabledPendingRestart = BenchmarkStatus.EnabledPendingRestart; - } - - public static class StatusToken - { - public const string Fallback = "Fallback"; - public const string Error = "Error"; - public const string Timeout = "Timeout"; - public const string Orphaned = "Orphaned"; - public const string Missing = "Missing"; - public const string Exception = "Exception"; - public const string Failed = "Failed"; - public const string NotRegistered = "Not Registered"; - public const string Invalid = "Invalid"; - public const string NotFound = "Not Found"; - public const string NoMenu = "No Menu"; - public const string NotMeasured = "Not Measured"; - public const string Unsupported = "Unsupported"; - } - - public static class CategoryLocationHint - { - public const string Background = "Background"; - public const string Drive = "Drive"; - public const string Directory = "Directory"; - public const string Folder = "Folder"; - public const string AllFiles = "All Files"; - public const string Extension = "Extension"; - public const string AllFileSystemObjects = "All File System Objects"; - } - - public static class RegistryLocationLabel - { - public const string AllFiles = "All Files (*)"; - public const string Directory = "Directory"; - public const string Folder = "Folder"; - public const string Drive = "Drive"; - public const string AllFileSystemObjects = "All File System Objects"; - public const string DirectoryBackground = "Directory Background"; - public const string DesktopBackground = "Desktop Background"; - public const string Extension = "Extension"; - public const string ProgId = "ProgID"; - } - - public static class RegistryPathPattern - { - public const string AnyAssociationType = "*"; - public const string DirectoryAssociationType = "directory"; - public const string FolderAssociationType = "folder"; - public const string DirectoryBackgroundAssociationType = @"directory\background"; - - public const string AllFilesHandlers = @"*\shellex\ContextMenuHandlers"; - public const string AllFilesHandlersDisabled = @"*\shellex\-ContextMenuHandlers"; - public const string DirectoryHandlers = @"Directory\shellex\ContextMenuHandlers"; - public const string DirectoryHandlersDisabled = @"Directory\shellex\-ContextMenuHandlers"; - public const string FolderHandlers = @"Folder\shellex\ContextMenuHandlers"; - public const string DriveHandlers = @"Drive\shellex\ContextMenuHandlers"; - public const string AllFileSystemObjectsHandlers = @"AllFileSystemObjects\shellex\ContextMenuHandlers"; - public const string DirectoryBackgroundHandlers = @"Directory\Background\shellex\ContextMenuHandlers"; - public const string DesktopBackgroundHandlers = @"DesktopBackground\shellex\ContextMenuHandlers"; - - public const string AllFilesShell = @"*\shell"; - public const string DirectoryShell = @"Directory\shell"; - public const string DirectoryBackgroundShell = @"Directory\Background\shell"; - public const string DriveShell = @"Drive\shell"; - public const string FolderShell = @"Folder\shell"; - - public static string BuildSystemFileAssociationHandlers(string extension, bool disabled) - { - string handlerKey = disabled ? "-ContextMenuHandlers" : "ContextMenuHandlers"; - return $@"SystemFileAssociations\{extension}\shellex\{handlerKey}"; - } - - public static string BuildProgIdHandlers(string progId, bool disabled) - { - string handlerKey = disabled ? "-ContextMenuHandlers" : "ContextMenuHandlers"; - return $@"{progId}\shellex\{handlerKey}"; - } - - public static string BuildSystemFileAssociationShell(string extension) - { - return $@"SystemFileAssociations\{extension}\shell"; - } - - public static string BuildProgIdShell(string progId) - { - return $@"{progId}\shell"; - } - - public static bool IsDirectoryLikeAssociationType(string? type) - { - if (string.IsNullOrWhiteSpace(type)) - { - return false; - } - - return string.Equals(type, DirectoryAssociationType, StringComparison.OrdinalIgnoreCase) - || string.Equals(type, FolderAssociationType, StringComparison.OrdinalIgnoreCase) - || string.Equals(type, DirectoryBackgroundAssociationType, StringComparison.OrdinalIgnoreCase); - } - } - - public static class InterfaceType - { - public const string StaticVerb = "Static Verb"; - public const string Skipped = "Skipped"; - } - - public static class IconSource - { - public const string ManifestAppLogo = "ManifestAppLogo"; - } - - public static class IconLocation - { - public const char HintSeparator = '|'; - public const string MsAppxUriPrefix = "ms-appx://"; - public const string IndirectStringPrefix = "@"; - public const string MrtPreferredTargetSizeToken = "targetsize-48"; - public const string MrtPreferredScaleToken = "scale-200"; - public const char IconResourceIndexSeparator = ','; - public const int IndirectStringBufferSize = 1024; - } - - public static class IconFileExtension - { - public const string Png = ".png"; - public const string Jpg = ".jpg"; - public const string Bmp = ".bmp"; - public const string Ico = ".ico"; - } - - public static class LocationSummary - { - public const string ModernShellUwp = "Modern Shell (UWP)"; - public const string StaticVerbRegistryShellPrefix = "Registry (Shell) - "; - } - - public static class RegistryLocationToken - { - public const string Disabled = "[Disabled]"; - public const string DisabledSuffix = " [Disabled]"; - public const char StaticVerbDisabledKeyPrefixChar = '-'; - public const string StaticVerbDisabledKeyPrefix = "-"; - } - - public static class RegistryToken - { - public const string ExtensionPrefix = "."; - } - - public static class StaticVerb - { - public const string CommandSubKeyName = "command"; - public const string MuiVerbValueName = "MUIVerb"; - public const string IconValueName = "Icon"; - public const string IgnoredVerbAttributes = "Attributes"; - public const string IgnoredVerbAnyCode = "AnyCode"; - public const char UniqueKeySeparator = '|'; - } - public static class Runtime { public const int MaxParallelProbeTasks = 8; @@ -301,10 +97,10 @@ public static bool IsPackagedExtensionType(string? type) return false; } - return string.Equals(type, Type.Uwp, StringComparison.OrdinalIgnoreCase) - || string.Equals(type, Type.PackagedExtension, StringComparison.OrdinalIgnoreCase) - || string.Equals(type, Type.PackagedCom, StringComparison.OrdinalIgnoreCase) - || string.Equals(type, Type.UwpPackagedCom, StringComparison.OrdinalIgnoreCase); + return string.Equals(type, "UWP", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, "Packaged Extension", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, "Packaged COM", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, "UWP / Packaged COM", StringComparison.OrdinalIgnoreCase); } public static bool IsRegistryManagedExtensionType(string? type) @@ -312,107 +108,10 @@ public static bool IsRegistryManagedExtensionType(string? type) return !IsPackagedExtensionType(type); } - public static bool IsDisabledRegistryLocation(string? location) - { - return !string.IsNullOrWhiteSpace(location) - && location.Contains(RegistryLocationToken.Disabled, StringComparison.OrdinalIgnoreCase); - } - - public static string BuildDisabledRegistryLocationLabel(string locationLabel) - { - return locationLabel + RegistryLocationToken.DisabledSuffix; - } - - public static string BuildExtensionRegistryLocationLabel(string extension) - { - return $"{RegistryLocationLabel.Extension} ({extension})"; - } - - public static string BuildProgIdRegistryLocationLabel(string progId, string extension) - { - return $"{RegistryLocationLabel.ProgId} ({progId} for {extension})"; - } - - public static string BuildRegistryHandlerLocation(string locationLabel, string handlerName) - { - return $"{locationLabel} ({handlerName})"; - } - - public static string BuildStaticVerbRegistryLocation(string? registryPath) - { - string hive = ExtractRegistryHive(registryPath); - return LocationSummary.StaticVerbRegistryShellPrefix + hive; - } - - public static bool IsStaticVerbRegistryPathDisabled(string? registryPath) - { - if (string.IsNullOrWhiteSpace(registryPath)) - { - return false; - } - - int lastSeparatorIndex = registryPath.LastIndexOf('\\'); - string terminalSegment = lastSeparatorIndex >= 0 - ? registryPath[(lastSeparatorIndex + 1)..] - : registryPath; - - return terminalSegment.StartsWith(RegistryLocationToken.StaticVerbDisabledKeyPrefix, StringComparison.Ordinal); - } - - public static bool IsIgnoredStaticVerbName(string? verbName) - { - if (string.IsNullOrWhiteSpace(verbName)) - { - return false; - } - - return string.Equals(verbName, StaticVerb.IgnoredVerbAttributes, StringComparison.OrdinalIgnoreCase) - || string.Equals(verbName, StaticVerb.IgnoredVerbAnyCode, StringComparison.OrdinalIgnoreCase); - } - - public static string BuildStaticVerbUniqueKey(string displayName, string command) - { - return $"{displayName}{StaticVerb.UniqueKeySeparator}{command}"; - } - - public static bool TryParseStaticVerbUniqueKey(string key, out string name, out string command) - { - name = string.Empty; - command = string.Empty; - - if (string.IsNullOrWhiteSpace(key)) - { - return false; - } - - int separatorIndex = key.IndexOf(StaticVerb.UniqueKeySeparator); - if (separatorIndex <= 0 || separatorIndex >= key.Length - 1) - { - return false; - } - - name = key[..separatorIndex]; - command = key[(separatorIndex + 1)..]; - return true; - } - - public static bool LooksLikeBracedClsid(string? value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return false; - } - - string trimmed = value.Trim(); - return trimmed.Length > 2 - && trimmed[0] == '{' - && trimmed[^1] == '}'; - } - public static bool IsCategoryMatch(string? selectedCategory, string? resultCategory) { if (string.IsNullOrWhiteSpace(selectedCategory) - || string.Equals(selectedCategory, FilterCategory.All, StringComparison.OrdinalIgnoreCase)) + || string.Equals(selectedCategory, "All", StringComparison.OrdinalIgnoreCase)) { return true; } @@ -440,10 +139,10 @@ public static bool IsFallbackLikeStatus(string? status) return IsFallbackLikeStatus(parsedStatus); } - return status.Contains(StatusToken.Fallback, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Error, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Orphaned, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Missing, StringComparison.OrdinalIgnoreCase); + return status.Contains("Fallback", StringComparison.OrdinalIgnoreCase) + || status.Contains("Error", StringComparison.OrdinalIgnoreCase) + || status.Contains("Orphaned", StringComparison.OrdinalIgnoreCase) + || status.Contains("Missing", StringComparison.OrdinalIgnoreCase); } public static bool IsWarningLikeStatus(BenchmarkStatus status) @@ -468,15 +167,15 @@ public static bool IsWarningLikeStatus(string? status) } return status.StartsWith(GetStatusDisplayText(BenchmarkStatus.LoadError), StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Exception, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Failed, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.NotRegistered, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Invalid, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.NotFound, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Fallback, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.NoMenu, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Orphaned, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Missing, StringComparison.OrdinalIgnoreCase); + || status.Contains("Exception", StringComparison.OrdinalIgnoreCase) + || status.Contains("Failed", StringComparison.OrdinalIgnoreCase) + || status.Contains("Not Registered", StringComparison.OrdinalIgnoreCase) + || status.Contains("Invalid", StringComparison.OrdinalIgnoreCase) + || status.Contains("Not Found", StringComparison.OrdinalIgnoreCase) + || status.Contains("Fallback", StringComparison.OrdinalIgnoreCase) + || status.Contains("No Menu", StringComparison.OrdinalIgnoreCase) + || status.Contains("Orphaned", StringComparison.OrdinalIgnoreCase) + || status.Contains("Missing", StringComparison.OrdinalIgnoreCase); } public static bool IsNotMeasuredLikeStatus(BenchmarkStatus status) @@ -497,9 +196,9 @@ public static bool IsNotMeasuredLikeStatus(string? status) return IsNotMeasuredLikeStatus(parsedStatus); } - return status.Contains(StatusToken.NotMeasured, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.Unsupported, StringComparison.OrdinalIgnoreCase) - || status.Contains(StatusToken.NoMenu, StringComparison.OrdinalIgnoreCase); + return status.Contains("Not Measured", StringComparison.OrdinalIgnoreCase) + || status.Contains("Unsupported", StringComparison.OrdinalIgnoreCase) + || status.Contains("No Menu", StringComparison.OrdinalIgnoreCase); } public static string GetStatusDisplayText(BenchmarkStatus status) @@ -582,14 +281,14 @@ public static bool IsTimeoutLikeError(string? error) return false; } - return error.Contains(StatusToken.Timeout, StringComparison.OrdinalIgnoreCase); + return error.Contains("Timeout", StringComparison.OrdinalIgnoreCase); } public static string ResolveCategoryFromLocations(IEnumerable locations) { if (locations == null) { - return Category.File; + return "File"; } int resolvedPriority = CategoryPriority.Unknown; @@ -604,7 +303,7 @@ public static string ResolveCategoryFromLocations(IEnumerable locations) int locationPriority = ResolveLocationPriority(location); if (locationPriority == CategoryPriority.Background) { - return Category.Background; + return "Background"; } if (locationPriority > resolvedPriority) @@ -615,21 +314,21 @@ public static string ResolveCategoryFromLocations(IEnumerable locations) return resolvedPriority switch { - CategoryPriority.Drive => Category.Drive, - CategoryPriority.Folder => Category.Folder, - CategoryPriority.File => Category.File, - _ => Category.File + CategoryPriority.Drive => "Drive", + CategoryPriority.Folder => "Folder", + CategoryPriority.File => "File", + _ => "File" }; } private static int ResolveLocationPriority(string location) { - if (location.Contains(CategoryLocationHint.Background, StringComparison.OrdinalIgnoreCase)) + if (location.Contains("Background", StringComparison.OrdinalIgnoreCase)) { return CategoryPriority.Background; } - if (location.Contains(CategoryLocationHint.Drive, StringComparison.OrdinalIgnoreCase)) + if (location.Contains("Drive", StringComparison.OrdinalIgnoreCase)) { return CategoryPriority.Drive; } @@ -659,21 +358,5 @@ private static bool ContainsAnyLocationHint(string location, IEnumerable return false; } - - private static string ExtractRegistryHive(string? registryPath) - { - if (string.IsNullOrWhiteSpace(registryPath)) - { - return string.Empty; - } - - int firstSeparatorIndex = registryPath.IndexOf('\\'); - if (firstSeparatorIndex <= 0) - { - return registryPath; - } - - return registryPath[..firstSeparatorIndex]; - } } } diff --git a/ContextMenuProfiler.UI/Core/BenchmarkService.cs b/ContextMenuProfiler.UI/Core/BenchmarkService.cs index a778be4..5e4eb55 100644 --- a/ContextMenuProfiler.UI/Core/BenchmarkService.cs +++ b/ContextMenuProfiler.UI/Core/BenchmarkService.cs @@ -18,33 +18,34 @@ public class BenchmarkResult { public string Name { get; set; } = ""; public Guid? Clsid { get; set; } - public BenchmarkStatus Status { get; set; } = BenchmarkSemantics.Status.Unknown; - public string Type { get; set; } = BenchmarkSemantics.Type.Com; // Legacy COM, UWP, Static + public BenchmarkStatus Status { get; set; } = BenchmarkStatus.Unknown; + public string Type { get; set; } = "COM"; // Legacy COM, UWP, Static public string? Path { get; set; } public List RegistryEntries { get; set; } = new List(); public long TotalTime { get; set; } - public bool IsEnabled { get; set; } = true; - public string? IconLocation { get; set; } - public string? BinaryPath { get; set; } - public string? DetailedStatus { get; set; } - public long InitTime { get; set; } public long CreateTime { get; set; } + public long InitTime { get; set; } public long QueryTime { get; set; } + public long WallClockTime { get; set; } public long LockWaitTime { get; set; } public long ConnectTime { get; set; } public long IpcRoundTripTime { get; set; } public long ScanOrder { get; set; } - - // Extended Info - public string? PackageName { get; set; } + + public string DetailedStatus { get; set; } = ""; + public string InterfaceType { get; set; } = ""; + public bool IsEnabled { get; set; } = true; + public string BinaryPath { get; set; } = ""; public string? Version { get; set; } - public string? InterfaceType { get; set; } // IContextMenu, IExplorerCommand, Static + public string? PackageName { get; set; } + public string? IconLocation { get; set; } public string? ThreadingModel { get; set; } + public string? FriendlyName { get; set; } public string? IconSource { get; set; } public string? LocationSummary { get; set; } - public string Category { get; set; } = BenchmarkSemantics.Category.File; + public string Category { get; set; } = "File"; public List? ObservedMenuDisplayNames { get; set; } } @@ -252,11 +253,10 @@ private async Task ProcessComHandlersAsync( var result = new BenchmarkResult { Clsid = clsid, - Type = BenchmarkSemantics.Type.Com, + Type = "COM", RegistryEntries = handlerInfos.ToList(), Name = meta.Name, BinaryPath = meta.BinaryPath, - ThreadingModel = meta.ThreadingModel, FriendlyName = meta.FriendlyName }; @@ -319,7 +319,7 @@ private async Task ProcessPackagedExtensionsAsync( return; } - uwpResult.Category = BenchmarkSemantics.Category.Uwp; + uwpResult.Category = "UWP"; await ProcessMeasuredResultAsync(uwpResult, hookContextPath, allResults, progress, scanId); })); @@ -350,32 +350,58 @@ private static void AddAndReportResult( private async Task ProcessMeasuredResultAsync( BenchmarkResult result, - string hookContextPath, + string targetPath, ConcurrentBag allResults, IProgress? progress, string? scanId) { - await EnrichBenchmarkResultAsync(result, hookContextPath, scanId); - result.IsEnabled = ResolveEnabledState(result); - result.LocationSummary = ResolveLocationSummary(result); - AddAndReportResult(allResults, result, progress); + try + { + if (!ShouldMeasureHandler(result)) + { + result.TotalTime = 0; + result.Category = DetermineCategory(result.RegistryEntries.Select(e => e.Location)); + result.LocationSummary = ResolveLocationSummary(result); + AddAndReportResult(allResults, result, progress); - LogService.Instance.InfoEvent( - "scan.item_processed", - fields: BuildItemFields(result, scanId, "measured")); + LogService.Instance.InfoEvent( + "scan.item_processed", + fields: BuildItemFields(result, scanId, "com")); + return; + } + + var sw = Stopwatch.StartNew(); + var hookCall = await HookIpcClient.GetHookDataAsync(result.Clsid!.Value.ToString("B"), targetPath, result.BinaryPath, scanId); + HookResponse? hookData = hookCall.data; + sw.Stop(); + + result.Category = DetermineCategory(result.RegistryEntries.Select(e => e.Location)); + result.LocationSummary = ResolveLocationSummary(result); + EnrichBenchmarkResult(result, hookData, hookCall, sw.ElapsedMilliseconds); + + AddAndReportResult(allResults, result, progress); + + LogService.Instance.InfoEvent( + "scan.item_processed", + fields: BuildItemFields(result, scanId, "com")); + } + catch (Exception ex) + { + LogService.Instance.Error($"Error processing result for {result.Name}", ex); + } } - private static bool ResolveEnabledState(BenchmarkResult result) + private static bool ShouldMeasureHandler(BenchmarkResult result) { - bool isBlocked = result.Clsid.HasValue && ExtensionManager.IsExtensionBlocked(result.Clsid.Value); - if (isBlocked) + if (IsKnownUnstableHandler(result)) { + MarkAsSkippedKnownUnstable(result); return false; } if (BenchmarkSemantics.IsRegistryManagedExtensionType(result.Type)) { - return !result.RegistryEntries.Any(e => BenchmarkSemantics.IsDisabledRegistryLocation(e.Location)); + return !result.RegistryEntries.Any(e => e.Location.Contains("[Disabled]")); } return true; @@ -385,7 +411,7 @@ private static string ResolveLocationSummary(BenchmarkResult result) { if (BenchmarkSemantics.IsPackagedExtensionType(result.Type)) { - return BenchmarkSemantics.LocationSummary.ModernShellUwp; + return "Modern Shell (UWP)"; } return string.Join(", ", result.RegistryEntries.Select(e => e.Location).Distinct()); @@ -393,7 +419,16 @@ private static string ResolveLocationSummary(BenchmarkResult result) private BenchmarkResult? CreateStaticVerbResult(string key, List paths) { - if (!BenchmarkSemantics.TryParseStaticVerbUniqueKey(key, out string name, out string command)) + string name = string.Empty; + string command = string.Empty; + int separatorIndex = key.IndexOf('|'); + if (separatorIndex > 0 && separatorIndex < key.Length - 1) + { + name = key[..separatorIndex]; + command = key[(separatorIndex + 1)..]; + } + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(command)) { LogService.Instance.Warning($"Skip malformed static verb entry key: '{key}'"); return null; @@ -402,59 +437,77 @@ private static string ResolveLocationSummary(BenchmarkResult result) var result = new BenchmarkResult { Name = name, - Type = BenchmarkSemantics.Type.Static, - Status = BenchmarkSemantics.Status.StaticNotMeasured, - BinaryPath = ExtractExecutablePath(command), + Type = "Static", + Status = BenchmarkStatus.StaticNotMeasured, + BinaryPath = ExtractExecutablePath(command) ?? string.Empty, RegistryEntries = paths.Select(p => new RegistryHandlerInfo { Path = p, - Location = BenchmarkSemantics.BuildStaticVerbRegistryLocation(p) + Location = "Registry (Shell) - " + ExtractRegistryHive(p) }).ToList(), - InterfaceType = BenchmarkSemantics.InterfaceType.StaticVerb, + InterfaceType = "Static Verb", DetailedStatus = LocalizationService.Instance["Dashboard.Detail.StaticNotMeasured"], TotalTime = 0, - Category = BenchmarkSemantics.Category.Static + Category = "Static" }; - result.IsEnabled = !paths.Any(BenchmarkSemantics.IsStaticVerbRegistryPathDisabled); + result.IsEnabled = !paths.Any(p => p.Split('\\').LastOrDefault()?.StartsWith("-") == true); result.LocationSummary = string.Join(", ", result.RegistryEntries.Select(e => e.Location).Distinct()); result.IconLocation = ResolveStaticVerbIcon(paths.First(), result.BinaryPath); return result; } - private async Task EnrichBenchmarkResultAsync(BenchmarkResult result, string contextPath, string? scanId) + private static string ExtractRegistryHive(string registryPath) + { + int firstSeparatorIndex = registryPath.IndexOf('\\'); + if (firstSeparatorIndex <= 0) + { + return registryPath; + } + return registryPath[..firstSeparatorIndex]; + } + + private void EnrichBenchmarkResult( + BenchmarkResult result, + HookResponse? hookData, + HookCallResult hookCall, + long wallClockMs) { - if (!result.Clsid.HasValue) return; + result.WallClockTime = wallClockMs; + result.LockWaitTime = hookCall.lock_wait_ms; + result.ConnectTime = hookCall.connect_ms; + result.IpcRoundTripTime = hookCall.roundtrip_ms; - if (SkipKnownUnstableHandlers && IsKnownUnstableHandler(result)) + if (!result.IsEnabled) { - MarkAsSkippedKnownUnstable(result); + result.Status = BenchmarkStatus.DisabledPendingRestart; return; } // Check for Orphaned / Missing DLL if (!string.IsNullOrEmpty(result.BinaryPath) && !File.Exists(result.BinaryPath)) { - result.Status = BenchmarkSemantics.Status.OrphanedMissingDll; + result.Status = BenchmarkStatus.OrphanedMissingDll; result.DetailedStatus = string.Format( LocalizationService.Instance["Dashboard.Detail.OrphanedMissingDll"], result.BinaryPath); + result.TotalTime = 0; + result.CreateTime = 0; + result.InitTime = 0; + result.QueryTime = 0; + return; } - var hookCall = await HookIpcClient.GetHookDataAsync(result.Clsid.Value.ToString("B"), contextPath, result.BinaryPath, scanId); - var hookData = hookCall.data; - result.WallClockTime = hookCall.total_ms; - result.LockWaitTime = hookCall.lock_wait_ms; - result.ConnectTime = hookCall.connect_ms; - result.IpcRoundTripTime = hookCall.roundtrip_ms; - - if (hookData?.success == true) - { - ApplyHookSuccessResult(result, hookData); - } - else if (hookData != null) + if (hookData != null) { - ApplyHookErrorResult(result, hookData); + if (hookData.success) + { + ApplyHookSuccessResult(result, hookData); + } + else + { + ApplyHookErrorResult(result, hookData); + } } else { @@ -464,9 +517,9 @@ private async Task EnrichBenchmarkResultAsync(BenchmarkResult result, string con private static void MarkAsSkippedKnownUnstable(BenchmarkResult result) { - result.Status = BenchmarkSemantics.Status.SkippedKnownUnstable; + result.Status = BenchmarkStatus.SkippedKnownUnstable; result.DetailedStatus = LocalizationService.Instance["Dashboard.Detail.SkippedKnownUnstable"]; - result.InterfaceType = BenchmarkSemantics.InterfaceType.Skipped; + result.InterfaceType = "Skipped"; result.CreateTime = 0; result.InitTime = 0; result.QueryTime = 0; @@ -475,7 +528,7 @@ private static void MarkAsSkippedKnownUnstable(BenchmarkResult result) private static void ApplyHookSuccessResult(BenchmarkResult result, HookResponse hookData) { - result.InterfaceType = hookData.@interface; + result.InterfaceType = hookData.@interface ?? string.Empty; if (!string.IsNullOrEmpty(hookData.names)) { result.ObservedMenuDisplayNames = ParseHookMenuDisplayNames(hookData.names); @@ -487,14 +540,14 @@ private static void ApplyHookSuccessResult(BenchmarkResult result, HookResponse result.ObservedMenuDisplayNames); } - if (result.Status == BenchmarkSemantics.Status.Unknown) + if (result.Status == BenchmarkStatus.Unknown) { - result.Status = BenchmarkSemantics.Status.VerifiedViaHook; + result.Status = BenchmarkStatus.VerifiedViaHook; } } - else if (result.Status == BenchmarkSemantics.Status.Unknown || result.Status == BenchmarkSemantics.Status.Ok) + else if (result.Status == BenchmarkStatus.Unknown || result.Status == BenchmarkStatus.Ok) { - result.Status = BenchmarkSemantics.Status.HookLoadedNoMenu; + result.Status = BenchmarkStatus.HookLoadedNoMenu; result.DetailedStatus = LocalizationService.Instance["Dashboard.Detail.HookLoadedNoMenu"]; } @@ -513,7 +566,7 @@ private static void ApplyHookSuccessResult(BenchmarkResult result, HookResponse private static List ParseHookMenuDisplayNames(string names) { return names - .Split(HookIpcSemantics.Response.MultiValueDelimiter) + .Split('|') .Select(n => n.Trim()) .Where(n => !string.IsNullOrWhiteSpace(n)) .Distinct(StringComparer.Ordinal) @@ -523,8 +576,8 @@ private static List ParseHookMenuDisplayNames(string names) private static string? ResolveHookIconLocation(HookResponse hookData) { if (!string.IsNullOrEmpty(hookData.reg_icon) - && (hookData.reg_icon.Contains(BenchmarkSemantics.IconLocation.IconResourceIndexSeparator) - || hookData.reg_icon.EndsWith(BenchmarkSemantics.IconFileExtension.Ico, StringComparison.OrdinalIgnoreCase))) + && (hookData.reg_icon.Contains(',') + || hookData.reg_icon.EndsWith(".ico", StringComparison.OrdinalIgnoreCase))) { return hookData.reg_icon; } @@ -535,10 +588,10 @@ private static List ParseHookMenuDisplayNames(string names) } return hookData.icons - .Split(HookIpcSemantics.Response.MultiValueDelimiter) + .Split('|') .FirstOrDefault(i => !string.IsNullOrEmpty(i) - && !string.Equals(i, HookIpcSemantics.Response.NoIconToken, StringComparison.OrdinalIgnoreCase)); + && !string.Equals(i, "NONE", StringComparison.OrdinalIgnoreCase)); } private static void ApplyHookErrorResult(BenchmarkResult result, HookResponse hookData) @@ -547,14 +600,14 @@ private static void ApplyHookErrorResult(BenchmarkResult result, HookResponse ho if (IsTimeoutLikeHookFailure(hookData)) { - result.Status = BenchmarkSemantics.Status.IpcTimeout; + result.Status = BenchmarkStatus.IpcTimeout; result.DetailedStatus = string.Format( LocalizationService.Instance["Dashboard.Detail.HookProbeTimeoutWithError"], hookError); return; } - result.Status = BenchmarkSemantics.Status.LoadError; + result.Status = BenchmarkStatus.LoadError; result.DetailedStatus = string.Format( LocalizationService.Instance["Dashboard.Detail.HookLoadErrorWithError"], hookError); @@ -562,21 +615,21 @@ private static void ApplyHookErrorResult(BenchmarkResult result, HookResponse ho private static void ApplyHookUnavailableFallback(BenchmarkResult result, long roundTripMs, string? ipcError) { - if (result.Status == BenchmarkSemantics.Status.LoadError || result.Status == BenchmarkSemantics.Status.OrphanedMissingDll) + if (result.Status == BenchmarkStatus.LoadError || result.Status == BenchmarkStatus.OrphanedMissingDll) { return; } if (roundTripMs >= BenchmarkSemantics.Runtime.IpcTimeoutLikeRoundtripThresholdMs) { - result.Status = BenchmarkSemantics.Status.IpcTimeout; + result.Status = BenchmarkStatus.IpcTimeout; result.DetailedStatus = AttachIpcReason( LocalizationService.Instance["Dashboard.Detail.HookResponseTimeoutFallback"], ipcError); return; } - result.Status = BenchmarkSemantics.Status.RegistryFallback; + result.Status = BenchmarkStatus.RegistryFallback; result.DetailedStatus = AttachIpcReason( LocalizationService.Instance["Dashboard.Detail.HookUnavailableFallback"], ipcError); @@ -670,7 +723,7 @@ private ClsidMetadata QueryClsidMetadata(Guid clsid, int depth = 0) var meta = new ClsidMetadata(); meta.Name = key.GetValue("") as string ?? ""; - meta.FriendlyName = key.GetValue(ComRegistrySemantics.FriendlyNameValueName) as string ?? ""; + meta.FriendlyName = key.GetValue("FriendlyName") as string ?? ""; PopulateFromInprocServerKey(key, meta); if (string.IsNullOrEmpty(meta.BinaryPath)) @@ -688,7 +741,7 @@ private ClsidMetadata QueryClsidMetadata(Guid clsid, int depth = 0) private ClsidMetadata? TryQueryPackagedClsidMetadata(Guid clsid, string clsidB) { - using var pkgKey = Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.BuildPackagedComClassIndexPath(clsidB)); + using var pkgKey = Registry.ClassesRoot.OpenSubKey($@"PackagedCom\ClassIndex\{clsidB}"); string? packageFullName = pkgKey?.GetValue("") as string; if (string.IsNullOrEmpty(packageFullName)) { @@ -697,8 +750,9 @@ private ClsidMetadata QueryClsidMetadata(Guid clsid, int depth = 0) return new ClsidMetadata { - BinaryPath = ResolvePackageDllPath(packageFullName, clsid) ?? "", - Name = QueryPackagedDisplayName(clsidB) ?? "" + Name = QueryPackagedDisplayName(clsidB) ?? packageFullName, + FriendlyName = packageFullName, + BinaryPath = ResolvePackageDllPath(packageFullName, clsid) ?? packageFullName }; } @@ -715,61 +769,57 @@ private static ClsidMetadata NormalizeClsidMetadata(ClsidMetadata meta) private static void PopulateFromInprocServerKey(RegistryKey clsidKey, ClsidMetadata meta) { - using var serverKey = clsidKey.OpenSubKey(ComRegistrySemantics.InprocServer32SubKeyName); + using var serverKey = clsidKey.OpenSubKey("InprocServer32"); if (serverKey == null) { return; } meta.BinaryPath = serverKey.GetValue("") as string ?? ""; - meta.ThreadingModel = serverKey.GetValue(ComRegistrySemantics.ThreadingModelValueName) as string ?? ""; + meta.ThreadingModel = serverKey.GetValue("ThreadingModel") as string ?? ""; } private void PopulateFromTreatAsAlias(Guid originalClsid, RegistryKey clsidKey, ClsidMetadata meta, int depth) { - string? treatAs = clsidKey.OpenSubKey(ComRegistrySemantics.TreatAsSubKeyName)?.GetValue("") as string; + string? treatAs = clsidKey.OpenSubKey("TreatAs")?.GetValue("") as string; if (string.IsNullOrEmpty(treatAs) || !Guid.TryParse(treatAs, out Guid otherGuid) || otherGuid == originalClsid) { return; } - var otherMeta = QueryClsidMetadata(otherGuid, depth + 1); - if (string.IsNullOrEmpty(meta.Name)) - { - meta.Name = otherMeta.Name; - } - - meta.BinaryPath = otherMeta.BinaryPath; - meta.ThreadingModel = otherMeta.ThreadingModel; + var aliasMeta = QueryClsidMetadata(otherGuid, depth + 1); + meta.BinaryPath = aliasMeta.BinaryPath; + if (string.IsNullOrEmpty(meta.Name)) meta.Name = aliasMeta.Name; + if (string.IsNullOrEmpty(meta.FriendlyName)) meta.FriendlyName = aliasMeta.FriendlyName; } private static void PopulateFromAppIdSurrogate(RegistryKey clsidKey, ClsidMetadata meta) { - string? appId = clsidKey.GetValue(ComRegistrySemantics.AppIdValueName) as string; + string? appId = clsidKey.GetValue("AppID") as string; if (string.IsNullOrEmpty(appId)) { return; } - using var appKey = Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.BuildAppIdPath(appId)); - string? dllSurrogate = appKey?.GetValue(ComRegistrySemantics.DllSurrogateValueName) as string; + using var appKey = Registry.ClassesRoot.OpenSubKey($@"AppID\{appId}"); + string? dllSurrogate = appKey?.GetValue("DllSurrogate") as string; meta.BinaryPath = dllSurrogate != null && string.IsNullOrEmpty(dllSurrogate) - ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), ComRegistrySemantics.DllHostExecutableName) + ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "dllhost.exe") : (dllSurrogate ?? ""); } - private string? QueryPackagedDisplayName(string clsidB) + private static string? QueryPackagedDisplayName(string clsidB) { try { - using (var pkgKey = Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.PackagedComPackagePrefix)) + using (var pkgKey = Registry.ClassesRoot.OpenSubKey(@"PackagedCom\Package")) { if (pkgKey == null) return null; foreach (var pkgName in pkgKey.GetSubKeyNames()) { - using (var clsKey = pkgKey.OpenSubKey(ComRegistrySemantics.BuildPackagedComPackageClassPath(pkgName, clsidB))) + using (var clsKey = pkgKey.OpenSubKey($@"{pkgName}\Class\{clsidB}")) { - string? name = clsKey?.GetValue(ComRegistrySemantics.DisplayNameValueName) as string; + string? name = clsKey?.GetValue("DisplayName") as string; if (!string.IsNullOrEmpty(name)) return name; } } @@ -777,7 +827,7 @@ private static void PopulateFromAppIdSurrogate(RegistryKey clsidKey, ClsidMetada } catch (Exception ex) { - LogService.Instance.Error($"Failed to resolve packaged display name for CLSID {clsidB}", ex); + LogService.Instance.Warning($"Failed to query packaged COM display name for {clsidB}: {ex.Message}"); } return null; } @@ -807,7 +857,7 @@ private static void PopulateFromAppIdSurrogate(RegistryKey clsidKey, ClsidMetada { using (var key = Registry.ClassesRoot.OpenSubKey(regPath)) { - string? icon = key?.GetValue(BenchmarkSemantics.StaticVerb.IconValueName) as string; + string? icon = key?.GetValue("Icon") as string; if (!string.IsNullOrEmpty(icon)) return icon; } } @@ -823,15 +873,15 @@ private static void PopulateFromAppIdSurrogate(RegistryKey clsidKey, ClsidMetada try { // Look up package installation path - using (var key = Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.BuildPackageRepositoryPath(packageFullName))) + using (var key = Registry.ClassesRoot.OpenSubKey($@"Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\PackageRepository\Packages\{packageFullName}")) { - string? installPath = key?.GetValue(ComRegistrySemantics.PackageInstallPathValueName) as string; + string? installPath = key?.GetValue("Path") as string; if (string.IsNullOrEmpty(installPath)) return null; // Now find the relative DLL path from PackagedCom\Package // We need the short name (Package Family Name or part of full name) - string packageId = ComRegistrySemantics.ExtractPackageIdPrefix(packageFullName); - using (var pkgKey = Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.PackagedComPackagePrefix)) + string packageId = ExtractPackageIdPrefix(packageFullName); + using (var pkgKey = Registry.ClassesRoot.OpenSubKey(@"PackagedCom\Package")) { if (pkgKey != null) { @@ -839,9 +889,9 @@ private static void PopulateFromAppIdSurrogate(RegistryKey clsidKey, ClsidMetada { if (name.StartsWith(packageId, StringComparison.OrdinalIgnoreCase)) { - using (var clsKey = pkgKey.OpenSubKey(ComRegistrySemantics.BuildPackagedComPackageClassPath(name, clsid.ToString("B")))) + using (var clsKey = pkgKey.OpenSubKey($@"{name}\Class\{clsid:B}")) { - string? relDllPath = clsKey?.GetValue(ComRegistrySemantics.DllPathValueName) as string; + string? relDllPath = clsKey?.GetValue("DllPath") as string; if (!string.IsNullOrEmpty(relDllPath)) { return Path.Combine(installPath, relDllPath); @@ -861,6 +911,17 @@ private static void PopulateFromAppIdSurrogate(RegistryKey clsidKey, ClsidMetada } } + private static string ExtractPackageIdPrefix(string packageFullName) + { + int separatorIndex = packageFullName.IndexOf('_'); + if (separatorIndex <= 0) + { + return packageFullName; + } + + return packageFullName[..separatorIndex]; + } + public long RunRealShellBenchmark(string? filePath = null) => BenchmarkSemantics.Runtime.RealShellBenchmarkUnsupportedMs; diff --git a/ContextMenuProfiler.UI/Core/ComRegistrySemantics.cs b/ContextMenuProfiler.UI/Core/ComRegistrySemantics.cs deleted file mode 100644 index 6363743..0000000 --- a/ContextMenuProfiler.UI/Core/ComRegistrySemantics.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; - -namespace ContextMenuProfiler.UI.Core -{ - public static class ComRegistrySemantics - { - public const string FriendlyNameValueName = "FriendlyName"; - public const string InprocServer32SubKeyName = "InprocServer32"; - public const string ThreadingModelValueName = "ThreadingModel"; - public const string TreatAsSubKeyName = "TreatAs"; - public const string AppIdValueName = "AppID"; - public const string AppIdSubKeyPrefix = "AppID"; - public const string DllSurrogateValueName = "DllSurrogate"; - public const string DllHostExecutableName = "dllhost.exe"; - public const string DisplayNameValueName = "DisplayName"; - public const string PackageInstallPathValueName = "Path"; - public const string DllPathValueName = "DllPath"; - public const string ClsidPrefix = "CLSID"; - public const string Wow6432NodeClsidPrefix = @"WOW6432Node\CLSID"; - - public const string PackagedComClassIndexPrefix = @"PackagedCom\ClassIndex"; - public const string PackagedComPackagePrefix = @"PackagedCom\Package"; - public const string PackagedComClassSubKeyName = "Class"; - public const string PackageRepositoryPrefix = @"Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\PackageRepository\Packages"; - public const char PackageNameSeparator = '_'; - - public static string BuildPackagedComClassIndexPath(string clsidB) - { - return $@"{PackagedComClassIndexPrefix}\{clsidB}"; - } - - public static string BuildClsidPath(string clsidB) - { - return $@"{ClsidPrefix}\{clsidB}"; - } - - public static string BuildWow6432NodeClsidPath(string clsidB) - { - return $@"{Wow6432NodeClsidPrefix}\{clsidB}"; - } - - public static string BuildPackagedComPackageClassPath(string packageName, string clsidB) - { - return $@"{packageName}\{PackagedComClassSubKeyName}\{clsidB}"; - } - - public static string BuildAppIdPath(string appId) - { - return $@"{AppIdSubKeyPrefix}\{appId}"; - } - - public static string BuildPackageRepositoryPath(string packageFullName) - { - return $@"{PackageRepositoryPrefix}\{packageFullName}"; - } - - public static string ExtractPackageIdPrefix(string packageFullName) - { - int separatorIndex = packageFullName.IndexOf(PackageNameSeparator); - if (separatorIndex <= 0) - { - return packageFullName; - } - - return packageFullName[..separatorIndex]; - } - } -} diff --git a/ContextMenuProfiler.UI/Core/ExtensionManager.cs b/ContextMenuProfiler.UI/Core/ExtensionManager.cs index eee6e17..e4be0f3 100644 --- a/ContextMenuProfiler.UI/Core/ExtensionManager.cs +++ b/ContextMenuProfiler.UI/Core/ExtensionManager.cs @@ -53,31 +53,32 @@ public static void SetExtensionBlockStatus(Guid clsid, string name, bool block) public static void DisableRegistryKey(string registryPath) { - // Rename key: "Name" -> "-Name" - // We need to parse parent and key name + if (string.IsNullOrEmpty(registryPath)) return; + int lastSlash = registryPath.LastIndexOf('\\'); if (lastSlash < 0) return; string parentPath = registryPath.Substring(0, lastSlash); string keyName = registryPath.Substring(lastSlash + 1); - if (keyName.StartsWith(BenchmarkSemantics.RegistryLocationToken.StaticVerbDisabledKeyPrefix, StringComparison.Ordinal)) return; // Already disabled + if (keyName.StartsWith("-", StringComparison.Ordinal)) return; // Already disabled - RenameRegistryKey(parentPath, keyName, BenchmarkSemantics.RegistryLocationToken.StaticVerbDisabledKeyPrefix + keyName); + RenameRegistryKey(parentPath, keyName, "-" + keyName); } public static void EnableRegistryKey(string registryPath) { - // Rename key: "-Name" -> "Name" + if (string.IsNullOrEmpty(registryPath)) return; + int lastSlash = registryPath.LastIndexOf('\\'); if (lastSlash < 0) return; string parentPath = registryPath.Substring(0, lastSlash); string keyName = registryPath.Substring(lastSlash + 1); - if (!keyName.StartsWith(BenchmarkSemantics.RegistryLocationToken.StaticVerbDisabledKeyPrefix, StringComparison.Ordinal)) return; // Already enabled + if (!keyName.StartsWith("-", StringComparison.Ordinal)) return; // Already enabled - RenameRegistryKey(parentPath, keyName, keyName.Substring(BenchmarkSemantics.RegistryLocationToken.StaticVerbDisabledKeyPrefix.Length)); + RenameRegistryKey(parentPath, keyName, keyName.Substring(1)); } public static void DeleteRegistryKey(string registryPath) diff --git a/ContextMenuProfiler.UI/Core/Helpers/ShellUtils.cs b/ContextMenuProfiler.UI/Core/Helpers/ShellUtils.cs index 4553329..444067e 100644 --- a/ContextMenuProfiler.UI/Core/Helpers/ShellUtils.cs +++ b/ContextMenuProfiler.UI/Core/Helpers/ShellUtils.cs @@ -17,12 +17,12 @@ public static class ShellUtils public static string ResolveMuiString(string? muiString) { if (string.IsNullOrEmpty(muiString) - || !muiString.StartsWith(BenchmarkSemantics.IconLocation.IndirectStringPrefix, StringComparison.Ordinal)) + || !muiString.StartsWith("@", StringComparison.Ordinal)) { return muiString ?? ""; } - var sb = new StringBuilder(BenchmarkSemantics.IconLocation.IndirectStringBufferSize); + var sb = new StringBuilder(1024); if (SHLoadIndirectString(muiString, sb, (uint)sb.Capacity, IntPtr.Zero) == 0) { return sb.ToString(); @@ -37,8 +37,8 @@ public static string ResolveMuiString(string? muiString) { // HKCR is a merged view of HKLM\SOFTWARE\Classes and HKCU\SOFTWARE\Classes. // We only need to check HKCR and then WOW6432Node specifically if not found. - return Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.BuildClsidPath(clsidB)) - ?? Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.BuildWow6432NodeClsidPath(clsidB)); + return Registry.ClassesRoot.OpenSubKey($@"CLSID\{clsidB}") + ?? Registry.ClassesRoot.OpenSubKey($@"WOW6432Node\CLSID\{clsidB}"); } } } diff --git a/ContextMenuProfiler.UI/Core/HookIpcClient.cs b/ContextMenuProfiler.UI/Core/HookIpcClient.cs index 43f7fb3..665f67b 100644 --- a/ContextMenuProfiler.UI/Core/HookIpcClient.cs +++ b/ContextMenuProfiler.UI/Core/HookIpcClient.cs @@ -39,9 +39,7 @@ public class HookCallResult public static class HookIpcClient { - internal static readonly SemaphoreSlim IpcLock = new SemaphoreSlim( - HookIpcSemantics.Runtime.MaxConcurrentCalls, - HookIpcSemantics.Runtime.MaxConcurrentCalls); + internal static readonly SemaphoreSlim IpcLock = new SemaphoreSlim(8, 8); public static async Task GetHookDataAsync(string clsid, string? contextPath = null, string? dllHint = null, string? scanId = null) { @@ -51,13 +49,13 @@ public static async Task GetHookDataAsync(string clsid, string? try { // Default bait path if none provided - string path = contextPath ?? Path.Combine(Path.GetTempPath(), HookIpcSemantics.Runtime.ProbeFileName); + string path = contextPath ?? Path.Combine(Path.GetTempPath(), "ContextMenuProfiler_probe.txt"); if (!File.Exists(path) && !Directory.Exists(path)) { - try { File.WriteAllText(path, HookIpcSemantics.Runtime.ProbeFileContent); } catch {} + try { File.WriteAllText(path, "probe"); } catch {} } - for (int attempt = 0; attempt < HookIpcSemantics.Runtime.MaxAttempts; attempt++) + for (int attempt = 0; attempt < 3; attempt++) { attempts = attempt + 1; try diff --git a/ContextMenuProfiler.UI/Core/PackageManifestSemantics.cs b/ContextMenuProfiler.UI/Core/PackageManifestSemantics.cs deleted file mode 100644 index c794abc..0000000 --- a/ContextMenuProfiler.UI/Core/PackageManifestSemantics.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Xml.Linq; - -namespace ContextMenuProfiler.UI.Core -{ - public static class PackageManifestSemantics - { - public static class NamespaceUri - { - public const string Default = "http://schemas.microsoft.com/appx/manifest/foundation/windows10"; - public const string Uap = "http://schemas.microsoft.com/appx/manifest/uap/windows10"; - public const string Desktop4 = "http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"; - public const string Desktop5 = "http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"; - public const string Com = "http://schemas.microsoft.com/appx/manifest/com/windows10"; - } - - public static class Namespaces - { - public static readonly XNamespace Default = NamespaceUri.Default; - public static readonly XNamespace Uap = NamespaceUri.Uap; - public static readonly XNamespace Desktop4 = NamespaceUri.Desktop4; - public static readonly XNamespace Desktop5 = NamespaceUri.Desktop5; - public static readonly XNamespace Com = NamespaceUri.Com; - } - - public static class Manifest - { - public const string FileName = "AppxManifest.xml"; - public const string ContextMenuCategoryToken = "fileExplorerContextMenus"; - public const string ContextMenuCategory = "windows.fileExplorerContextMenus"; - - public const string ExtensionElement = "Extension"; - public const string ItemTypeElement = "ItemType"; - public const string VerbElement = "Verb"; - public const string VisualElementsElement = "VisualElements"; - public const string LogoElement = "Logo"; - public const string ClassElement = "Class"; - - public const string CategoryAttribute = "Category"; - public const string TypeAttribute = "Type"; - public const string ClsidAttribute = "Clsid"; - public const string IdAttribute = "Id"; - public const string PathAttribute = "Path"; - public const string Square44LogoAttribute = "Square44x44Logo"; - public const string Square150LogoAttribute = "Square150x150Logo"; - } - - } -} diff --git a/ContextMenuProfiler.UI/Core/PackageScanner.cs b/ContextMenuProfiler.UI/Core/PackageScanner.cs index d60360d..ac18a40 100644 --- a/ContextMenuProfiler.UI/Core/PackageScanner.cs +++ b/ContextMenuProfiler.UI/Core/PackageScanner.cs @@ -11,7 +11,7 @@ namespace ContextMenuProfiler.UI.Core { public class PackageScanner { - private static readonly XNamespace NS_COM = PackageManifestSemantics.Namespaces.Com; + private static readonly XNamespace NS_COM = "http://schemas.microsoft.com/appx/manifest/com/windows10"; public static IEnumerable ScanPackagedExtensions(string? targetPath) { @@ -47,11 +47,11 @@ private static void ProcessPackage(Package package, List result string? installPath = package.InstalledLocation?.Path; if (string.IsNullOrEmpty(installPath)) return; - string manifestPath = Path.Combine(installPath, PackageManifestSemantics.Manifest.FileName); + string manifestPath = Path.Combine(installPath, "AppxManifest.xml"); if (!File.Exists(manifestPath)) return; string manifestContent = File.ReadAllText(manifestPath); - if (!manifestContent.Contains(PackageManifestSemantics.Manifest.ContextMenuCategoryToken)) return; + if (!manifestContent.Contains("fileExplorerContextMenus")) return; // Handle Sparse Packages (like VS Code) // Deferred: only resolve EffectiveLocation for packages that actually have context menu extensions @@ -64,8 +64,8 @@ private static void ProcessPackage(Package package, List result var clsidToPath = MapClsidToBinaryPath(doc, effectivePath); var extensions = doc.Descendants().Where(e => - e.Name.LocalName == PackageManifestSemantics.Manifest.ExtensionElement - && e.Attribute(PackageManifestSemantics.Manifest.CategoryAttribute)?.Value == PackageManifestSemantics.Manifest.ContextMenuCategory); + e.Name.LocalName == "Extension" + && e.Attribute("Category")?.Value == "windows.fileExplorerContextMenus"); foreach (var extElement in extensions) { @@ -76,16 +76,16 @@ private static void ProcessPackage(Package package, List result private static void ProcessExtensionElement(Package package, XElement extElement, List results, Dictionary clsidToPath, string installPath, string targetExt, bool scanAll) { - var itemTypes = extElement.Descendants().Where(e => e.Name.LocalName == PackageManifestSemantics.Manifest.ItemTypeElement); + var itemTypes = extElement.Descendants().Where(e => e.Name.LocalName == "ItemType"); foreach (var itemType in itemTypes) { - string? type = itemType.Attribute(PackageManifestSemantics.Manifest.TypeAttribute)?.Value?.ToLowerInvariant(); + string? type = itemType.Attribute("Type")?.Value?.ToLowerInvariant(); if (string.IsNullOrEmpty(type)) continue; if (!scanAll && !IsTypeMatch(type, targetExt)) continue; - var verbs = itemType.Descendants().Where(e => e.Name.LocalName == PackageManifestSemantics.Manifest.VerbElement); + var verbs = itemType.Descendants().Where(e => e.Name.LocalName == "Verb"); foreach (var verb in verbs) { if (TryParseVerb(package, verb, clsidToPath, installPath, out var result) && result != null) @@ -101,13 +101,13 @@ private static bool TryParseVerb(Package package, XElement verb, Dictionary e.Name.LocalName == PackageManifestSemantics.Manifest.VisualElementsElement); + var visualElements = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "VisualElements"); if (visualElements != null) { - return visualElements.Attribute(PackageManifestSemantics.Manifest.Square44LogoAttribute)?.Value - ?? visualElements.Attribute(PackageManifestSemantics.Manifest.Square150LogoAttribute)?.Value - ?? visualElements.Attribute(PackageManifestSemantics.Manifest.LogoElement)?.Value; + return visualElements.Attribute("Square44x44Logo")?.Value + ?? visualElements.Attribute("Square150x150Logo")?.Value + ?? visualElements.Attribute("Logo")?.Value; } - return doc.Descendants().FirstOrDefault(e => e.Name.LocalName == PackageManifestSemantics.Manifest.LogoElement)?.Value; + return doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "Logo")?.Value; } private static Dictionary MapClsidToBinaryPath(XDocument doc, string installPath) { var map = new Dictionary(); var classes = doc.Descendants().Where(e => - e.Name.LocalName == PackageManifestSemantics.Manifest.ClassElement + e.Name.LocalName == "Class" && e.Name.Namespace == NS_COM); foreach (var cls in classes) { - string? idStr = cls.Attribute(PackageManifestSemantics.Manifest.IdAttribute)?.Value; - string? path = cls.Attribute(PackageManifestSemantics.Manifest.PathAttribute)?.Value; + string? idStr = cls.Attribute("Id")?.Value; + string? path = cls.Attribute("Path")?.Value; if (Guid.TryParse(idStr, out Guid guid) && !string.IsNullOrEmpty(path)) { map[guid] = path; @@ -203,20 +203,24 @@ private static IEnumerable SafeFindPackages(PackageManager pm) private static string DetermineTargetExtension(string? path) { if (path == null) return string.Empty; - if (Directory.Exists(path)) return BenchmarkSemantics.RegistryPathPattern.DirectoryAssociationType; + if (Directory.Exists(path)) return "directory"; return Path.GetExtension(path).ToLowerInvariant(); } private static bool IsTypeMatch(string type, string targetExt) { - if (string.Equals(type, BenchmarkSemantics.RegistryPathPattern.AnyAssociationType, StringComparison.Ordinal)) + if (string.Equals(type, "*", StringComparison.Ordinal)) { return true; } - if (BenchmarkSemantics.RegistryPathPattern.IsDirectoryLikeAssociationType(type)) + bool isDirectoryLike = string.Equals(type, "directory", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, "folder", StringComparison.OrdinalIgnoreCase) + || string.Equals(type, @"directory\background", StringComparison.OrdinalIgnoreCase); + + if (isDirectoryLike) { - return string.Equals(targetExt, BenchmarkSemantics.RegistryPathPattern.DirectoryAssociationType, StringComparison.OrdinalIgnoreCase); + return string.Equals(targetExt, "directory", StringComparison.OrdinalIgnoreCase); } return string.Equals(type, targetExt, StringComparison.OrdinalIgnoreCase); @@ -226,7 +230,7 @@ private static bool IsTypeMatch(string type, string targetExt) { try { - using var key = Registry.ClassesRoot.OpenSubKey(ComRegistrySemantics.BuildPackagedComClassIndexPath(clsid.ToString("B"))); + using var key = Registry.ClassesRoot.OpenSubKey($@"PackagedCom\ClassIndex\{clsid:B}"); return key?.GetValue("") as string; } catch { return null; } diff --git a/ContextMenuProfiler.UI/Core/RegistryScanner.cs b/ContextMenuProfiler.UI/Core/RegistryScanner.cs index b48a6f4..b6ce9a7 100644 --- a/ContextMenuProfiler.UI/Core/RegistryScanner.cs +++ b/ContextMenuProfiler.UI/Core/RegistryScanner.cs @@ -5,8 +5,7 @@ using System.Threading.Tasks; using Microsoft.Win32; -using System.Runtime.InteropServices; -using System.Text; +using System.Linq; using ContextMenuProfiler.UI.Core.Helpers; using ContextMenuProfiler.UI.Core.Services; @@ -27,34 +26,19 @@ public class RegistryHandlerInfo public class RegistryScanner { - private static readonly (string Path, string Location)[] GlobalHandlerLocations = + private static readonly (string BasePath, string Location)[] GlobalTargets = { - (BenchmarkSemantics.RegistryPathPattern.AllFilesHandlers, BenchmarkSemantics.RegistryLocationLabel.AllFiles), - (BenchmarkSemantics.RegistryPathPattern.AllFilesHandlersDisabled, BenchmarkSemantics.BuildDisabledRegistryLocationLabel(BenchmarkSemantics.RegistryLocationLabel.AllFiles)) + ("*", "All Files (*)") }; - private static readonly (string Path, string Location)[] DirectoryScopedHandlerLocations = + private static readonly (string BasePath, string Location)[] DirectoryTargets = { - (BenchmarkSemantics.RegistryPathPattern.DirectoryHandlers, BenchmarkSemantics.RegistryLocationLabel.Directory), - (BenchmarkSemantics.RegistryPathPattern.DirectoryHandlersDisabled, BenchmarkSemantics.BuildDisabledRegistryLocationLabel(BenchmarkSemantics.RegistryLocationLabel.Directory)), - (BenchmarkSemantics.RegistryPathPattern.FolderHandlers, BenchmarkSemantics.RegistryLocationLabel.Folder), - (BenchmarkSemantics.RegistryPathPattern.DriveHandlers, BenchmarkSemantics.RegistryLocationLabel.Drive), - (BenchmarkSemantics.RegistryPathPattern.AllFileSystemObjectsHandlers, BenchmarkSemantics.RegistryLocationLabel.AllFileSystemObjects), - (BenchmarkSemantics.RegistryPathPattern.DirectoryBackgroundHandlers, BenchmarkSemantics.RegistryLocationLabel.DirectoryBackground), - (BenchmarkSemantics.RegistryPathPattern.DesktopBackgroundHandlers, BenchmarkSemantics.RegistryLocationLabel.DesktopBackground) - }; - - private static readonly (string Path, string Location)[] GlobalShellLocations = - { - (BenchmarkSemantics.RegistryPathPattern.AllFilesShell, BenchmarkSemantics.RegistryLocationLabel.AllFiles) - }; - - private static readonly (string Path, string Location)[] DirectoryScopedShellLocations = - { - (BenchmarkSemantics.RegistryPathPattern.DirectoryShell, BenchmarkSemantics.RegistryLocationLabel.Directory), - (BenchmarkSemantics.RegistryPathPattern.DirectoryBackgroundShell, BenchmarkSemantics.RegistryLocationLabel.DirectoryBackground), - (BenchmarkSemantics.RegistryPathPattern.DriveShell, BenchmarkSemantics.RegistryLocationLabel.Drive), - (BenchmarkSemantics.RegistryPathPattern.FolderShell, BenchmarkSemantics.RegistryLocationLabel.Folder) + ("Directory", "Directory"), + ("Folder", "Folder"), + ("Drive", "Drive"), + ("AllFileSystemObjects", "All File System Objects"), + (@"Directory\Background", "Directory Background"), + ("DesktopBackground", "Desktop Background") }; private readonly struct TargetAssociationContext @@ -73,15 +57,13 @@ public static Dictionary> ScanHandlers(ScanMode { var handlers = new ConcurrentDictionary>(); - ScanHandlerLocations(handlers, GlobalHandlerLocations); - ScanHandlerLocations(handlers, DirectoryScopedHandlerLocations); + ScanHandlerTargets(handlers, GlobalTargets); + ScanHandlerTargets(handlers, DirectoryTargets); - // 2. Scan Extensions (Only if Full mode) if (mode == ScanMode.Full) { string[] rootKeys = Registry.ClassesRoot.GetSubKeyNames(); - // Bound parallelism to avoid saturating CPU during deep scans. var options = new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2) @@ -89,27 +71,13 @@ public static Dictionary> ScanHandlers(ScanMode Parallel.ForEach(rootKeys, options, keyName => { - if (keyName.StartsWith(BenchmarkSemantics.RegistryToken.ExtensionPrefix, StringComparison.Ordinal)) + if (keyName.StartsWith(".")) { - // It's an extension - // Check SystemFileAssociations - string extensionLocation = BenchmarkSemantics.BuildExtensionRegistryLocationLabel(keyName); - ScanLocation(handlers, BenchmarkSemantics.RegistryPathPattern.BuildSystemFileAssociationHandlers(keyName, disabled: false), extensionLocation); - ScanLocation(handlers, BenchmarkSemantics.RegistryPathPattern.BuildSystemFileAssociationHandlers(keyName, disabled: true), BenchmarkSemantics.BuildDisabledRegistryLocationLabel(extensionLocation)); - - // Get ProgID - string? progId = GetProgID(keyName); - if (!string.IsNullOrEmpty(progId)) - { - string progIdLocation = BenchmarkSemantics.BuildProgIdRegistryLocationLabel(progId, keyName); - ScanLocation(handlers, BenchmarkSemantics.RegistryPathPattern.BuildProgIdHandlers(progId, disabled: false), progIdLocation); - ScanLocation(handlers, BenchmarkSemantics.RegistryPathPattern.BuildProgIdHandlers(progId, disabled: true), BenchmarkSemantics.BuildDisabledRegistryLocationLabel(progIdLocation)); - } + ScanFileAssociationHandlers(handlers, keyName); } }); } - // Convert back to regular Dictionary return new Dictionary>(handlers); } @@ -118,20 +86,31 @@ public static Dictionary> ScanHandlersForPath(st var handlers = new ConcurrentDictionary>(); var context = ResolveTargetAssociationContext(targetPath); - ScanHandlerLocations(handlers, GlobalHandlerLocations); + ScanHandlerTargets(handlers, GlobalTargets); if (context.IsDirectory) { - ScanHandlerLocations(handlers, DirectoryScopedHandlerLocations); - return new Dictionary>(handlers); + ScanHandlerTargets(handlers, DirectoryTargets); + } + else + { + ScanFileAssociationHandlers(handlers, context.AssociationType); } - - ScanFileAssociationHandlers(handlers, context.AssociationType); return new Dictionary>(handlers); } - // Updated signature to accept ConcurrentDictionary + private static void ScanHandlerTargets( + ConcurrentDictionary> handlers, + IEnumerable<(string BasePath, string Location)> targets) + { + foreach (var target in targets) + { + ScanLocation(handlers, $@"{target.BasePath}\shellex\ContextMenuHandlers", target.Location); + ScanLocation(handlers, $@"{target.BasePath}\shellex\-ContextMenuHandlers", target.Location + " [Disabled]"); + } + } + private static void ScanLocation(ConcurrentDictionary> handlers, string subKeyPath, string locationName) { try @@ -149,7 +128,7 @@ private static void ScanLocation(ConcurrentDictionary new List()); @@ -190,7 +169,9 @@ private static bool TryParseBracedClsid(string? value, out Guid clsid) } string trimmed = value.Trim(); - return BenchmarkSemantics.LooksLikeBracedClsid(trimmed) + return trimmed.Length > 2 + && trimmed[0] == '{' + && trimmed[^1] == '}' && Guid.TryParse(trimmed, out clsid); } @@ -214,8 +195,8 @@ public static Dictionary> ScanStaticVerbs() { var verbs = new ConcurrentDictionary>(); - ScanShellLocations(verbs, GlobalShellLocations); - ScanShellLocations(verbs, DirectoryScopedShellLocations); + ScanShellTargets(verbs, GlobalTargets); + ScanShellTargets(verbs, DirectoryTargets); return new Dictionary>(verbs); } @@ -225,15 +206,16 @@ public static Dictionary> ScanStaticVerbsForPath(string tar var verbs = new ConcurrentDictionary>(); var context = ResolveTargetAssociationContext(targetPath); - ScanShellLocations(verbs, GlobalShellLocations); + ScanShellTargets(verbs, GlobalTargets); if (context.IsDirectory) { - ScanShellLocations(verbs, DirectoryScopedShellLocations); - return new Dictionary>(verbs); + ScanShellTargets(verbs, DirectoryTargets); + } + else + { + ScanFileAssociationShellVerbs(verbs, context.AssociationType); } - - ScanFileAssociationShellVerbs(verbs, context.AssociationType); return new Dictionary>(verbs); } @@ -242,29 +224,19 @@ private static TargetAssociationContext ResolveTargetAssociationContext(string t { bool isDirectory = Directory.Exists(targetPath); string associationType = isDirectory - ? BenchmarkSemantics.RegistryPathPattern.DirectoryAssociationType + ? "directory" : Path.GetExtension(targetPath).ToLowerInvariant(); return new TargetAssociationContext(isDirectory, associationType); } - private static void ScanHandlerLocations( - ConcurrentDictionary> handlers, - IEnumerable<(string Path, string Location)> locations) - { - foreach (var location in locations) - { - ScanLocation(handlers, location.Path, location.Location); - } - } - - private static void ScanShellLocations( + private static void ScanShellTargets( ConcurrentDictionary> verbs, - IEnumerable<(string Path, string Location)> locations) + IEnumerable<(string BasePath, string Location)> targets) { - foreach (var location in locations) + foreach (var target in targets) { - ScanShellKey(verbs, location.Path, location.Location); + ScanShellKey(verbs, $@"{target.BasePath}\shell", target.Location); } } @@ -275,19 +247,17 @@ private static void ScanFileAssociationHandlers(ConcurrentDictionary> verbs, string extension) @@ -297,15 +267,24 @@ private static void ScanFileAssociationShellVerbs(ConcurrentDictionary> verbs, string subKeyPath, string locationName) @@ -317,45 +296,37 @@ private static void ScanShellKey(ConcurrentDictionary> verb if (key == null) return; foreach (var verbName in key.GetSubKeyNames()) { - // Ignore some system defaults that are usually not interesting or dangerous to touch - if (BenchmarkSemantics.IsIgnoredStaticVerbName(verbName)) continue; + if (IsIgnoredStaticVerbName(verbName)) continue; using (var verbKey = key.OpenSubKey(verbName)) { if (verbKey == null) continue; - // Get Command string command = ""; - using (var commandKey = verbKey.OpenSubKey(BenchmarkSemantics.StaticVerb.CommandSubKeyName)) + using (var commandKey = verbKey.OpenSubKey("command")) { command = commandKey?.GetValue("") as string ?? ""; } - // If no command, it's likely a sub-menu or invalid, but we might still want to see it - // However, for "Static Verb" type, the command is the main identity if (string.IsNullOrEmpty(command)) continue; - // Get Display Name (MUIVerb > Default) - string? displayName = verbKey.GetValue(BenchmarkSemantics.StaticVerb.MuiVerbValueName) as string; + string? displayName = verbKey.GetValue("MUIVerb") as string; if (string.IsNullOrEmpty(displayName)) { - displayName = verbKey.GetValue("") as string; // Default value + displayName = verbKey.GetValue("") as string; } - // Resolve MUI string if necessary - if (!string.IsNullOrEmpty(displayName) - && displayName.StartsWith(BenchmarkSemantics.IconLocation.IndirectStringPrefix, StringComparison.Ordinal)) + if (!string.IsNullOrEmpty(displayName) && displayName.StartsWith("@", StringComparison.Ordinal)) { displayName = ShellUtils.ResolveMuiString(displayName); } if (string.IsNullOrEmpty(displayName)) { - displayName = verbName.TrimStart(BenchmarkSemantics.RegistryLocationToken.StaticVerbDisabledKeyPrefixChar); // Fallback to key name + displayName = verbName.TrimStart('-'); } - // Use unique key: "Name|Command" to distinguish same name but different command - string uniqueKey = BenchmarkSemantics.BuildStaticVerbUniqueKey(displayName, command); + string uniqueKey = $"{displayName}|{command}"; var list = verbs.GetOrAdd(uniqueKey, _ => new List()); lock (list) diff --git a/ContextMenuProfiler.UI/ViewModels/DashboardViewModel.cs b/ContextMenuProfiler.UI/ViewModels/DashboardViewModel.cs index f623cb1..3911725 100644 --- a/ContextMenuProfiler.UI/ViewModels/DashboardViewModel.cs +++ b/ContextMenuProfiler.UI/ViewModels/DashboardViewModel.cs @@ -68,7 +68,7 @@ partial void OnSearchTextChanged(string value) } [ObservableProperty] - private string _selectedCategory = BenchmarkSemantics.FilterCategory.All; + private string _selectedCategory = "All"; [ObservableProperty] private int _selectedCategoryIndex = 0; @@ -627,13 +627,13 @@ private void ApplyLocalizedCategoryNames() { Categories = new ObservableCollection { - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.All"], Tag = BenchmarkSemantics.FilterCategory.All, Icon = SymbolRegular.TableMultiple20, IsActive = true }, - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Files"], Tag = BenchmarkSemantics.Category.File, Icon = SymbolRegular.Document20 }, - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Folders"], Tag = BenchmarkSemantics.Category.Folder, Icon = SymbolRegular.Folder20 }, - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Background"], Tag = BenchmarkSemantics.Category.Background, Icon = SymbolRegular.Image20 }, - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Drives"], Tag = BenchmarkSemantics.Category.Drive, Icon = SymbolRegular.HardDrive20 }, - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.UwpModern"], Tag = BenchmarkSemantics.Category.Uwp, Icon = SymbolRegular.Box20 }, - new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.StaticVerbs"], Tag = BenchmarkSemantics.Category.Static, Icon = SymbolRegular.PuzzlePiece20 } + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.All"], Tag = "All", Icon = SymbolRegular.TableMultiple20, IsActive = true }, + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Files"], Tag = "File", Icon = SymbolRegular.Document20 }, + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Folders"], Tag = "Folder", Icon = SymbolRegular.Folder20 }, + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Background"], Tag = "Background", Icon = SymbolRegular.Image20 }, + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.Drives"], Tag = "Drive", Icon = SymbolRegular.HardDrive20 }, + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.UwpModern"], Tag = "UWP", Icon = SymbolRegular.Box20 }, + new CategoryItem { Name = LocalizationService.Instance["Dashboard.Category.StaticVerbs"], Tag = "Static", Icon = SymbolRegular.PuzzlePiece20 } }; if (SelectedCategoryIndex < 0 || SelectedCategoryIndex >= Categories.Count) { @@ -684,8 +684,8 @@ private void ToggleExtension(BenchmarkResult item) } item.Status = shouldEnable - ? BenchmarkSemantics.Status.EnabledPendingRestart - : BenchmarkSemantics.Status.DisabledPendingRestart; + ? BenchmarkStatus.EnabledPendingRestart + : BenchmarkStatus.DisabledPendingRestart; UpdateStats(); } diff --git a/ContextMenuProfiler.UI/Views/Pages/DashboardPage.xaml b/ContextMenuProfiler.UI/Views/Pages/DashboardPage.xaml index 1563435..66787a4 100644 --- a/ContextMenuProfiler.UI/Views/Pages/DashboardPage.xaml +++ b/ContextMenuProfiler.UI/Views/Pages/DashboardPage.xaml @@ -344,16 +344,16 @@ - + - + - + - + @@ -386,10 +386,10 @@