From 5213ff12e13abb29a233579114e57d0a7d1ecc82 Mon Sep 17 00:00:00 2001 From: Jason Holmes Date: Fri, 10 May 2024 15:35:58 -0700 Subject: [PATCH 01/11] Bringing Project Ironsides into Dev Home --- DevHome.sln | 18 + Packaging/Packaging.wapproj | 116 + common/DevHome.Common.csproj | 2 +- common/Helpers/RuntimeHelper.cs | 7 + common/Services/StringResource.cs | 7 +- .../Strings/en-us/Resources.resw | 66 +- src/App.xaml.cs | 2 + src/DevHome.csproj | 3 +- src/NavConfig.jsonc | 21 + src/Package.appxmanifest | 21 +- src/Program.cs | 49 +- src/Strings/en-us/Resources.resw | 54 +- tools/PI/DevHome.PI/App.config | 91 + .../DevHome.PI/Assets/LargeTile.scale-100.png | Bin 0 -> 3610 bytes .../DevHome.PI/Assets/LargeTile.scale-125.png | Bin 0 -> 4401 bytes .../DevHome.PI/Assets/LargeTile.scale-150.png | Bin 0 -> 5237 bytes .../DevHome.PI/Assets/LargeTile.scale-200.png | Bin 0 -> 7534 bytes .../DevHome.PI/Assets/LargeTile.scale-400.png | Bin 0 -> 18165 bytes .../DevHome.PI/Assets/SmallTile.scale-100.png | Bin 0 -> 1244 bytes .../DevHome.PI/Assets/SmallTile.scale-125.png | Bin 0 -> 1500 bytes .../DevHome.PI/Assets/SmallTile.scale-150.png | Bin 0 -> 1713 bytes .../DevHome.PI/Assets/SmallTile.scale-200.png | Bin 0 -> 2157 bytes .../DevHome.PI/Assets/SmallTile.scale-400.png | Bin 0 -> 4166 bytes .../Assets/SplashScreen.scale-100.png | Bin 0 -> 3977 bytes .../Assets/SplashScreen.scale-125.png | Bin 0 -> 4822 bytes .../Assets/SplashScreen.scale-150.png | Bin 0 -> 6019 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 8710 bytes .../Assets/SplashScreen.scale-400.png | Bin 0 -> 23152 bytes .../Assets/Square150x150Logo.scale-100.png | Bin 0 -> 1838 bytes .../Assets/Square150x150Logo.scale-125.png | Bin 0 -> 2134 bytes .../Assets/Square150x150Logo.scale-150.png | Bin 0 -> 2553 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 3535 bytes .../Assets/Square150x150Logo.scale-400.png | Bin 0 -> 7182 bytes ...go.altform-lightunplated_targetsize-16.png | Bin 0 -> 581 bytes ...go.altform-lightunplated_targetsize-24.png | Bin 0 -> 817 bytes ...o.altform-lightunplated_targetsize-256.png | Bin 0 -> 5932 bytes ...go.altform-lightunplated_targetsize-32.png | Bin 0 -> 972 bytes ...go.altform-lightunplated_targetsize-48.png | Bin 0 -> 1363 bytes ...x44Logo.altform-unplated_targetsize-16.png | Bin 0 -> 581 bytes ...x44Logo.altform-unplated_targetsize-24.png | Bin 0 -> 817 bytes ...44Logo.altform-unplated_targetsize-256.png | Bin 0 -> 5932 bytes ...x44Logo.altform-unplated_targetsize-32.png | Bin 0 -> 972 bytes ...x44Logo.altform-unplated_targetsize-48.png | Bin 0 -> 1363 bytes .../Assets/Square44x44Logo.scale-100.png | Bin 0 -> 1029 bytes .../Assets/Square44x44Logo.scale-125.png | Bin 0 -> 1249 bytes .../Assets/Square44x44Logo.scale-150.png | Bin 0 -> 1455 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 1789 bytes .../Assets/Square44x44Logo.scale-400.png | Bin 0 -> 3359 bytes .../Assets/Square44x44Logo.targetsize-16.png | Bin 0 -> 475 bytes .../Assets/Square44x44Logo.targetsize-24.png | Bin 0 -> 698 bytes .../Assets/Square44x44Logo.targetsize-256.png | Bin 0 -> 4786 bytes .../Assets/Square44x44Logo.targetsize-32.png | Bin 0 -> 841 bytes .../Assets/Square44x44Logo.targetsize-48.png | Bin 0 -> 1125 bytes .../PI/DevHome.PI/Assets/StoreLogo.backup.png | Bin 0 -> 1451 bytes .../DevHome.PI/Assets/StoreLogo.scale-100.png | Bin 0 -> 1379 bytes .../DevHome.PI/Assets/StoreLogo.scale-125.png | Bin 0 -> 1660 bytes .../DevHome.PI/Assets/StoreLogo.scale-150.png | Bin 0 -> 1909 bytes .../DevHome.PI/Assets/StoreLogo.scale-200.png | Bin 0 -> 2515 bytes .../DevHome.PI/Assets/StoreLogo.scale-400.png | Bin 0 -> 4738 bytes .../Assets/Wide310x150Logo.scale-100.png | Bin 0 -> 1978 bytes .../Assets/Wide310x150Logo.scale-125.png | Bin 0 -> 2303 bytes .../Assets/Wide310x150Logo.scale-150.png | Bin 0 -> 2841 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 3977 bytes .../Assets/Wide310x150Logo.scale-400.png | Bin 0 -> 8710 bytes tools/PI/DevHome.PI/BarWindow.xaml | 157 + tools/PI/DevHome.PI/BarWindow.xaml.cs | 722 ++ .../Contracts/ViewModels/INavigationAware.cs | 18 + .../PI/DevHome.PI/Controls/AppStatusBar.xaml | 15 + .../DevHome.PI/Controls/AppStatusBar.xaml.cs | 22 + .../Controls/ExpandedViewControl.xaml | 126 + .../Controls/ExpandedViewControl.xaml.cs | 40 + tools/PI/DevHome.PI/Controls/GlowButton.xaml | 15 + .../PI/DevHome.PI/Controls/GlowButton.xaml.cs | 73 + .../Controls/ProcessSelectionButton.cs | 68 + tools/PI/DevHome.PI/Controls/SystemBar.cs | 150 + .../Controls/SystemBarHorizontal.xaml | 60 + .../Controls/SystemBarHorizontal.xaml.cs | 13 + .../Controls/SystemBarVertical.xaml | 36 + .../Controls/SystemBarVertical.xaml.cs | 13 + tools/PI/DevHome.PI/DevHome.PI.csproj | 214 + tools/PI/DevHome.PI/Helpers/CommonHelper.cs | 59 + tools/PI/DevHome.PI/Helpers/CommonInterop.cs | 21 + tools/PI/DevHome.PI/Helpers/DebugMonitor.cs | 153 + .../DevHome.PI/Helpers/DesktopBridgeHelper.cs | 39 + tools/PI/DevHome.PI/Helpers/ETWHelper.cs | 168 + .../DevHome.PI/Helpers/EnumStringConverter.cs | 35 + .../DevHome.PI/Helpers/ErrorLookupHelper.cs | 90 + .../DevHome.PI/Helpers/EventViewerHelper.cs | 82 + tools/PI/DevHome.PI/Helpers/ExternalTool.cs | 147 + .../DevHome.PI/Helpers/ExternalToolsHelper.cs | 100 + tools/PI/DevHome.PI/Helpers/HotKeyHelper.cs | 80 + tools/PI/DevHome.PI/Helpers/InsightsHelper.cs | 194 + .../Helpers/RestartManagerHelper.cs | 111 + tools/PI/DevHome.PI/Helpers/WatsonHelper.cs | 182 + tools/PI/DevHome.PI/Helpers/WinLogsHelper.cs | 276 + tools/PI/DevHome.PI/Helpers/WindowHelper.cs | 526 ++ tools/PI/DevHome.PI/Helpers/WindowHooker`1.cs | 92 + tools/PI/DevHome.PI/Images/PI.ico | Bin 0 -> 140512 bytes tools/PI/DevHome.PI/Models/AppRuntimeInfo.cs | 65 + .../PI/DevHome.PI/Models/ClipboardContents.cs | 26 + .../PI/DevHome.PI/Models/ClipboardMonitor.cs | 201 + tools/PI/DevHome.PI/Models/Insight.cs | 39 + tools/PI/DevHome.PI/Models/NavLink.cs | 22 + tools/PI/DevHome.PI/Models/PerfCounters.cs | 214 + tools/PI/DevHome.PI/Models/RestoreState.cs | 21 + tools/PI/DevHome.PI/Models/TargetAppData.cs | 171 + tools/PI/DevHome.PI/Models/ThemeName.cs | 23 + tools/PI/DevHome.PI/Models/WatsonReport.cs | 32 + tools/PI/DevHome.PI/Models/WinLogsEntry.cs | 79 + tools/PI/DevHome.PI/NativeMethods.json | 4 + tools/PI/DevHome.PI/NativeMethods.txt | 105 + tools/PI/DevHome.PI/PI.ico | Bin 0 -> 140512 bytes tools/PI/DevHome.PI/PIApp.xaml | 32 + tools/PI/DevHome.PI/PIApp.xaml.cs | 109 + tools/PI/DevHome.PI/Pages/AppDetailsPage.xaml | 90 + .../DevHome.PI/Pages/AppDetailsPage.xaml.cs | 29 + tools/PI/DevHome.PI/Pages/InsightsPage.xaml | 30 + .../PI/DevHome.PI/Pages/InsightsPage.xaml.cs | 29 + tools/PI/DevHome.PI/Pages/ModulesPage.xaml | 63 + tools/PI/DevHome.PI/Pages/ModulesPage.xaml.cs | 29 + .../PI/DevHome.PI/Pages/ProcessListPage.xaml | 75 + .../DevHome.PI/Pages/ProcessListPage.xaml.cs | 29 + .../DevHome.PI/Pages/ResourceUsagePage.xaml | 59 + .../Pages/ResourceUsagePage.xaml.cs | 36 + tools/PI/DevHome.PI/Pages/WatsonsPage.xaml | 57 + tools/PI/DevHome.PI/Pages/WatsonsPage.xaml.cs | 36 + tools/PI/DevHome.PI/Pages/WinLogsPage.xaml | 87 + tools/PI/DevHome.PI/Pages/WinLogsPage.xaml.cs | 36 + tools/PI/DevHome.PI/PrimaryWindow.xaml | 13 + tools/PI/DevHome.PI/PrimaryWindow.xaml.cs | 111 + tools/PI/DevHome.PI/Program.cs | 215 + .../PublishProfiles/win-arm64.pubxml | 16 + .../Properties/PublishProfiles/win-x64.pubxml | 16 + .../Properties/PublishProfiles/win-x86.pubxml | 16 + .../Properties/Settings.Designer.cs | 280 + .../DevHome.PI/Properties/Settings.settings | 82 + .../DevHome.PI/Properties/launchSettings.json | 8 + .../Services/PINavigationService.cs | 167 + tools/PI/DevHome.PI/Services/PIPageService.cs | 66 + .../DevHome.PI/SettingsUi/AddToolControl.xaml | 100 + .../SettingsUi/AddToolControl.xaml.cs | 119 + .../SettingsUi/EditToolsControl.xaml | 43 + .../SettingsUi/EditToolsControl.xaml.cs | 27 + .../SettingsUi/SettingsToolWindow.xaml | 316 + .../SettingsUi/SettingsToolWindow.xaml.cs | 223 + .../DevHome.PI/Strings/en-us/Resources.resw | 780 ++ tools/PI/DevHome.PI/Telemetry/FeatureState.cs | 74 + .../Telemetry/FocusStartEventData.cs | 19 + .../Telemetry/FocusStopEventData.cs | 16 + .../Telemetry/LogStateChangedEventData.cs | 30 + .../DevHome.PI/Telemetry/TelemetryReporter.cs | 182 + .../Telemetry/TimedStartEventBase.cs | 30 + .../Telemetry/TimedStopEventBase.cs | 31 + .../PI/DevHome.PI/Telemetry/UsageEventData.cs | 21 + .../Telemetry/VisibilityStartEventData.cs | 19 + .../Telemetry/VisibilityStopEventData.cs | 20 + .../Telemetry/WindowEventGenerator.cs | 171 + tools/PI/DevHome.PI/ToolWindows/ToolWindow.cs | 60 + .../ViewModels/AppDetailsPageViewModel.cs | 154 + .../ViewModels/AppStatusBarViewModel.cs | 95 + .../ViewModels/BarWindowViewModel.cs | 64 + .../ExpandedViewControlViewModel.cs | 237 + .../ViewModels/InsightsPageViewModel.cs | 55 + .../ViewModels/ModulesPageViewModel.cs | 104 + .../ViewModels/ProcessListPageViewModel.cs | 144 + .../ViewModels/ResourceUsagePageViewModel.cs | 110 + .../ViewModels/WatsonPageViewModel.cs | 140 + .../ViewModels/WinLogsPageViewModel.cs | 241 + tools/PI/DevHome.PI/app.manifest | 19 + tools/PI/DevHome.PI/appsettings_pi.json | 31 + tools/PI/DevHome.PI/errors.db | Bin 0 -> 856064 bytes tools/PI/DevHome.PI/errors.db.txt | 7434 +++++++++++++++++ .../ErrorDBPopulateTool.csproj | 34 + .../ErrorDBPopulateTool.sln | 25 + tools/PI/ErrorDBPopulateTool/HRESULTS.txt | 5856 +++++++++++++ tools/PI/ErrorDBPopulateTool/NTStatus.txt | 3590 ++++++++ tools/PI/ErrorDBPopulateTool/Program.cs | 91 + tools/PI/ErrorDBPopulateTool/Win32Errors.txt | 5408 ++++++++++++ tools/PI/ErrorDBPopulateTool/readme.md | 21 + tools/PI/FeatureExploration/ExternalTools.png | Bin 0 -> 104945 bytes tools/PI/FeatureExploration/FullWindow.png | Bin 0 -> 101978 bytes tools/PI/FeatureExploration/InvokeWinDbg.png | Bin 0 -> 73329 bytes tools/PI/FeatureExploration/MinBarCentred.png | Bin 0 -> 7143 bytes tools/PI/FeatureExploration/MinBarDocked.png | Bin 0 -> 54792 bytes tools/PI/FeatureExploration/WindbgInBar.png | Bin 0 -> 12707 bytes .../WindowPI-FeatureExploration.md | 140 + tools/PI/PI.ico | Bin 0 -> 140512 bytes tools/PI/PI.png | Bin 0 -> 5664 bytes .../src/Strings/en-us/Resources.resw | 6 + .../ViewModels/UtilitiesMainPageViewModel.cs | 10 +- .../src/ViewModels/UtilityViewModel.cs | 28 +- tools/Utilities/src/Views/UtilityView.xaml | 11 +- 192 files changed, 33625 insertions(+), 76 deletions(-) create mode 100644 Packaging/Packaging.wapproj create mode 100644 tools/PI/DevHome.PI/App.config create mode 100644 tools/PI/DevHome.PI/Assets/LargeTile.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/LargeTile.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/LargeTile.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/LargeTile.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/LargeTile.scale-400.png create mode 100644 tools/PI/DevHome.PI/Assets/SmallTile.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/SmallTile.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/SmallTile.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/SmallTile.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/SmallTile.scale-400.png create mode 100644 tools/PI/DevHome.PI/Assets/SplashScreen.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/SplashScreen.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/SplashScreen.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/SplashScreen.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/SplashScreen.scale-400.png create mode 100644 tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-400.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-16.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-24.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-256.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-32.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-48.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.scale-400.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-16.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-24.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-256.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-32.png create mode 100644 tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-48.png create mode 100644 tools/PI/DevHome.PI/Assets/StoreLogo.backup.png create mode 100644 tools/PI/DevHome.PI/Assets/StoreLogo.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/StoreLogo.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/StoreLogo.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/StoreLogo.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/StoreLogo.scale-400.png create mode 100644 tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-100.png create mode 100644 tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-125.png create mode 100644 tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-150.png create mode 100644 tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-200.png create mode 100644 tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-400.png create mode 100644 tools/PI/DevHome.PI/BarWindow.xaml create mode 100644 tools/PI/DevHome.PI/BarWindow.xaml.cs create mode 100644 tools/PI/DevHome.PI/Contracts/ViewModels/INavigationAware.cs create mode 100644 tools/PI/DevHome.PI/Controls/AppStatusBar.xaml create mode 100644 tools/PI/DevHome.PI/Controls/AppStatusBar.xaml.cs create mode 100644 tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml create mode 100644 tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml.cs create mode 100644 tools/PI/DevHome.PI/Controls/GlowButton.xaml create mode 100644 tools/PI/DevHome.PI/Controls/GlowButton.xaml.cs create mode 100644 tools/PI/DevHome.PI/Controls/ProcessSelectionButton.cs create mode 100644 tools/PI/DevHome.PI/Controls/SystemBar.cs create mode 100644 tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml create mode 100644 tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml.cs create mode 100644 tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml create mode 100644 tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml.cs create mode 100644 tools/PI/DevHome.PI/DevHome.PI.csproj create mode 100644 tools/PI/DevHome.PI/Helpers/CommonHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/CommonInterop.cs create mode 100644 tools/PI/DevHome.PI/Helpers/DebugMonitor.cs create mode 100644 tools/PI/DevHome.PI/Helpers/DesktopBridgeHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/ETWHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/EnumStringConverter.cs create mode 100644 tools/PI/DevHome.PI/Helpers/ErrorLookupHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/EventViewerHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/ExternalTool.cs create mode 100644 tools/PI/DevHome.PI/Helpers/ExternalToolsHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/HotKeyHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/InsightsHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/RestartManagerHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/WatsonHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/WinLogsHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/WindowHelper.cs create mode 100644 tools/PI/DevHome.PI/Helpers/WindowHooker`1.cs create mode 100644 tools/PI/DevHome.PI/Images/PI.ico create mode 100644 tools/PI/DevHome.PI/Models/AppRuntimeInfo.cs create mode 100644 tools/PI/DevHome.PI/Models/ClipboardContents.cs create mode 100644 tools/PI/DevHome.PI/Models/ClipboardMonitor.cs create mode 100644 tools/PI/DevHome.PI/Models/Insight.cs create mode 100644 tools/PI/DevHome.PI/Models/NavLink.cs create mode 100644 tools/PI/DevHome.PI/Models/PerfCounters.cs create mode 100644 tools/PI/DevHome.PI/Models/RestoreState.cs create mode 100644 tools/PI/DevHome.PI/Models/TargetAppData.cs create mode 100644 tools/PI/DevHome.PI/Models/ThemeName.cs create mode 100644 tools/PI/DevHome.PI/Models/WatsonReport.cs create mode 100644 tools/PI/DevHome.PI/Models/WinLogsEntry.cs create mode 100644 tools/PI/DevHome.PI/NativeMethods.json create mode 100644 tools/PI/DevHome.PI/NativeMethods.txt create mode 100644 tools/PI/DevHome.PI/PI.ico create mode 100644 tools/PI/DevHome.PI/PIApp.xaml create mode 100644 tools/PI/DevHome.PI/PIApp.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/AppDetailsPage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/AppDetailsPage.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/InsightsPage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/InsightsPage.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/ModulesPage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/ModulesPage.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/ProcessListPage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/ProcessListPage.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/WatsonsPage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/WatsonsPage.xaml.cs create mode 100644 tools/PI/DevHome.PI/Pages/WinLogsPage.xaml create mode 100644 tools/PI/DevHome.PI/Pages/WinLogsPage.xaml.cs create mode 100644 tools/PI/DevHome.PI/PrimaryWindow.xaml create mode 100644 tools/PI/DevHome.PI/PrimaryWindow.xaml.cs create mode 100644 tools/PI/DevHome.PI/Program.cs create mode 100644 tools/PI/DevHome.PI/Properties/PublishProfiles/win-arm64.pubxml create mode 100644 tools/PI/DevHome.PI/Properties/PublishProfiles/win-x64.pubxml create mode 100644 tools/PI/DevHome.PI/Properties/PublishProfiles/win-x86.pubxml create mode 100644 tools/PI/DevHome.PI/Properties/Settings.Designer.cs create mode 100644 tools/PI/DevHome.PI/Properties/Settings.settings create mode 100644 tools/PI/DevHome.PI/Properties/launchSettings.json create mode 100644 tools/PI/DevHome.PI/Services/PINavigationService.cs create mode 100644 tools/PI/DevHome.PI/Services/PIPageService.cs create mode 100644 tools/PI/DevHome.PI/SettingsUi/AddToolControl.xaml create mode 100644 tools/PI/DevHome.PI/SettingsUi/AddToolControl.xaml.cs create mode 100644 tools/PI/DevHome.PI/SettingsUi/EditToolsControl.xaml create mode 100644 tools/PI/DevHome.PI/SettingsUi/EditToolsControl.xaml.cs create mode 100644 tools/PI/DevHome.PI/SettingsUi/SettingsToolWindow.xaml create mode 100644 tools/PI/DevHome.PI/SettingsUi/SettingsToolWindow.xaml.cs create mode 100644 tools/PI/DevHome.PI/Strings/en-us/Resources.resw create mode 100644 tools/PI/DevHome.PI/Telemetry/FeatureState.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/FocusStartEventData.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/FocusStopEventData.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/LogStateChangedEventData.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/TelemetryReporter.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/TimedStartEventBase.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/TimedStopEventBase.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/UsageEventData.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/VisibilityStartEventData.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/VisibilityStopEventData.cs create mode 100644 tools/PI/DevHome.PI/Telemetry/WindowEventGenerator.cs create mode 100644 tools/PI/DevHome.PI/ToolWindows/ToolWindow.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/AppDetailsPageViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/AppStatusBarViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/BarWindowViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/ExpandedViewControlViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/InsightsPageViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/ModulesPageViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/ProcessListPageViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/ResourceUsagePageViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/WatsonPageViewModel.cs create mode 100644 tools/PI/DevHome.PI/ViewModels/WinLogsPageViewModel.cs create mode 100644 tools/PI/DevHome.PI/app.manifest create mode 100644 tools/PI/DevHome.PI/appsettings_pi.json create mode 100644 tools/PI/DevHome.PI/errors.db create mode 100644 tools/PI/DevHome.PI/errors.db.txt create mode 100644 tools/PI/ErrorDBPopulateTool/ErrorDBPopulateTool.csproj create mode 100644 tools/PI/ErrorDBPopulateTool/ErrorDBPopulateTool.sln create mode 100644 tools/PI/ErrorDBPopulateTool/HRESULTS.txt create mode 100644 tools/PI/ErrorDBPopulateTool/NTStatus.txt create mode 100644 tools/PI/ErrorDBPopulateTool/Program.cs create mode 100644 tools/PI/ErrorDBPopulateTool/Win32Errors.txt create mode 100644 tools/PI/ErrorDBPopulateTool/readme.md create mode 100644 tools/PI/FeatureExploration/ExternalTools.png create mode 100644 tools/PI/FeatureExploration/FullWindow.png create mode 100644 tools/PI/FeatureExploration/InvokeWinDbg.png create mode 100644 tools/PI/FeatureExploration/MinBarCentred.png create mode 100644 tools/PI/FeatureExploration/MinBarDocked.png create mode 100644 tools/PI/FeatureExploration/WindbgInBar.png create mode 100644 tools/PI/FeatureExploration/WindowPI-FeatureExploration.md create mode 100644 tools/PI/PI.ico create mode 100644 tools/PI/PI.png diff --git a/DevHome.sln b/DevHome.sln index dac61696af..c02985c1ea 100644 --- a/DevHome.sln +++ b/DevHome.sln @@ -122,6 +122,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevSetupAgent.Test", "exten EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperVExtension.HostGuestCommunication", "extensions\HyperVExtension\src\HyperVExtension.HostGuestCommunication\HyperVExtension.HostGuestCommunication.csproj", "{D759CD66-494C-4A00-8075-8B65A9891349}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.PI", "tools\PI\DevHome.PI\DevHome.PI.csproj", "{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PI", "PI", "{DB3D0F2C-1A7F-44B4-B408-B21A56212985}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Customization", "Customization", "{623998FD-B0A6-4980-95D5-A5072301CA10}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Customization", "tools\Customization\DevHome.Customization\DevHome.Customization.csproj", "{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}" @@ -614,6 +618,18 @@ Global {D759CD66-494C-4A00-8075-8B65A9891349}.Release|x64.Build.0 = Release|x64 {D759CD66-494C-4A00-8075-8B65A9891349}.Release|x86.ActiveCfg = Release|x86 {D759CD66-494C-4A00-8075-8B65A9891349}.Release|x86.Build.0 = Release|x86 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|arm64.ActiveCfg = Debug|ARM64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|arm64.Build.0 = Debug|ARM64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x64.ActiveCfg = Debug|x64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x64.Build.0 = Debug|x64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x86.ActiveCfg = Debug|x86 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x86.Build.0 = Debug|x86 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|arm64.ActiveCfg = Release|ARM64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|arm64.Build.0 = Release|ARM64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x64.ActiveCfg = Release|x64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x64.Build.0 = Release|x64 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x86.ActiveCfg = Release|x86 + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x86.Build.0 = Release|x86 {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug|arm64.ActiveCfg = Debug|arm64 {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug|arm64.Build.0 = Debug|arm64 {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug|x64.ActiveCfg = Debug|x64 @@ -745,6 +761,8 @@ Global {F4095FD3-6A3F-490B-966D-E63059612EE6} = {3E3791DF-070D-4ADE-96E8-93D6FBD53953} {0E05A442-BDC7-43D4-A000-F8C986826716} = {3E3791DF-070D-4ADE-96E8-93D6FBD53953} {D759CD66-494C-4A00-8075-8B65A9891349} = {81AACED5-CFB5-47A6-AFD6-4625AADCFFA3} + {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725} = {DB3D0F2C-1A7F-44B4-B408-B21A56212985} + {DB3D0F2C-1A7F-44B4-B408-B21A56212985} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD} {623998FD-B0A6-4980-95D5-A5072301CA10} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD} {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C} = {623998FD-B0A6-4980-95D5-A5072301CA10} {FAB6FAA7-ADF4-4B65-9831-0C819915E6E1} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD} diff --git a/Packaging/Packaging.wapproj b/Packaging/Packaging.wapproj new file mode 100644 index 0000000000..73464ab621 --- /dev/null +++ b/Packaging/Packaging.wapproj @@ -0,0 +1,116 @@ + + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + 05d4c6e5-d33d-4c15-91fe-3dd2b6a5d61c + + 10.0.22621.0 + 10.0.19041.0 + en-US + True + $(NoWarn);NU1702 + ..\src\DevHome.csproj + False + SHA256 + False + True + x86|x64 + 0 + Dev + False + + + + 100 + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index c22fee997c..aa13bfe193 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -53,7 +53,7 @@ - + diff --git a/common/Helpers/RuntimeHelper.cs b/common/Helpers/RuntimeHelper.cs index 7ab541f6f2..1fb3af8d43 100644 --- a/common/Helpers/RuntimeHelper.cs +++ b/common/Helpers/RuntimeHelper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Security.Principal; using Windows.Win32; using Windows.Win32.Foundation; @@ -27,4 +28,10 @@ public static bool IsOnWindows11 return version.Major >= 10 && version.Build >= 22000; } } + + public static bool IsCurrentProcessRunningAsAdmin() + { + var identity = WindowsIdentity.GetCurrent(); + return identity.Owner?.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) ?? false; + } } diff --git a/common/Services/StringResource.cs b/common/Services/StringResource.cs index 068f7b5f78..c4628b65f2 100644 --- a/common/Services/StringResource.cs +++ b/common/Services/StringResource.cs @@ -48,7 +48,12 @@ public string GetLocalized(string key, params object[] args) try { value = _resourceLoader.GetString(key); - value = string.Format(CultureInfo.CurrentCulture, value, args); + + // only replace the placeholders if args is not empty + if (args.Length > 0) + { + value = string.Format(CultureInfo.CurrentCulture, value, args); + } } catch { diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw index 812608237b..f13444365a 100644 --- a/settings/DevHome.Settings/Strings/en-us/Resources.resw +++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw @@ -1,17 +1,17 @@  - @@ -547,11 +547,19 @@ Quiet background processes - Name of experimental feature 'Quiet background processes' on the 'Settings -> Experiments' page where you enable it. + Name of experimental feature 'Quiet background processes' on the 'Settings -> Experiments' page where you enable it. Quiet background processes allows you to free up resources while developing - Inline description of the Quiet background processes experimental feature on the 'Settings -> Experiments' page where you enable it. + Inline description of the Quiet background processes experimental feature on the 'Settings -> Experiments' page where you enable it. + + + Project Ironsides + Name of experimental feature 'Project Ironsides' on the 'Settings -> Experiments' page where you enable it. + + + Project Ironsides is a utlity to provide deeper insights into your applications + Inline description of the Project Ironsides experimental feature on the 'Settings -> Experiments' page where you enable it. Quickstart Playground diff --git a/src/App.xaml.cs b/src/App.xaml.cs index bcaf2fa4ab..49dd47ec9a 100644 --- a/src/App.xaml.cs +++ b/src/App.xaml.cs @@ -209,6 +209,8 @@ await Task.WhenAll( private async void OnActivated(object? sender, AppActivationArguments args) { + ShowMainWindow(); + // Note: Keep the reference to 'args.Data' object, as 'args' may be // disposed before the async operation completes (RpcCallFailed: 0x800706be) var localArgsDataReference = args.Data; diff --git a/src/DevHome.csproj b/src/DevHome.csproj index 2185386a5a..18555a5eb2 100644 --- a/src/DevHome.csproj +++ b/src/DevHome.csproj @@ -77,7 +77,7 @@ - + @@ -86,6 +86,7 @@ + diff --git a/src/NavConfig.jsonc b/src/NavConfig.jsonc index 948f31682c..3c47f1a159 100644 --- a/src/NavConfig.jsonc +++ b/src/NavConfig.jsonc @@ -86,6 +86,27 @@ "visible": true } ] + }, + { + "identity": "ProjectIronsidesExperiment", + "enabledByDefault": false, + "buildTypeOverrides": [ + { + "buildType": "dev", + "enabledByDefault": true, + "visible": true + }, + { + "buildType": "canary", + "enabledByDefault": true, + "visible": true + }, + { + "buildType": "stable", + "enabledByDefault": false, + "visible": true + } + ] } ] } diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest index 9d26a5e0c8..80015ddf47 100644 --- a/src/Package.appxmanifest +++ b/src/Package.appxmanifest @@ -81,7 +81,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -286,6 +286,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Program.cs b/src/Program.cs index 259b62e784..16b112d5ed 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,17 +1,26 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; +using DevHome.Services; +using Microsoft.Extensions.Configuration; using Microsoft.UI.Dispatching; +using Serilog; namespace DevHome; public static class Program { + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(Program)); private static App? _app; [STAThread] public static void Main(string[] args) { + // Be sure to parse these args in this instance of the exe... don't redirect this to another instance for parsing which + // may be running in a different security context. + ParseCommandLine(args); + WinRT.ComWrappersSupport.InitializeComWrappers(); var isRedirect = DecideRedirection().GetAwaiter().GetResult(); @@ -34,11 +43,7 @@ private static async Task DecideRedirection() var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs(); var isRedirect = false; - if (mainInstance.IsCurrent) - { - mainInstance.Activated += OnActivated; - } - else + if (!mainInstance.IsCurrent) { // Redirect the activation (and args) to the "main" instance, and exit. await mainInstance.RedirectActivationToAsync(activatedEventArgs); @@ -48,8 +53,38 @@ private static async Task DecideRedirection() return isRedirect; } - private static void OnActivated(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments e) + // Currently DevHome supports one set of command line arguments, most useful when debugging different apps within the DevHome package. + // + // For example: + // --utilitylaunch DevHome.PI.Exe --utilityLaunchArgs "--application problemapp2" + // + // --utilityLaunch is the name of the utility to launch + // --utilityLaunchArgs are the arguments to pass to the utility. This is optional, but be sure to include the quotes if you have spaces in the arguments. + private static void ParseCommandLine(string[] args) { - _app?.ShowMainWindow(); + var builder = new ConfigurationBuilder(); + builder.AddCommandLine(args); + var config = builder.Build(); + + var utilityToLaunch = config["utilitylaunch"]; + var utilityLaunchArgs = config["utilitylaunchargs"]; + + if (!string.IsNullOrEmpty(utilityToLaunch)) + { + try + { + var processStartInfo = new ProcessStartInfo + { + FileName = utilityToLaunch, + Arguments = utilityLaunchArgs, + }; + + Process.Start(processStartInfo); + } + catch (Exception ex) + { + _log.Error($"Error launching utility: {ex.Message}"); + } + } } } diff --git a/src/Strings/en-us/Resources.resw b/src/Strings/en-us/Resources.resw index 1e15ef3671..a03ca81571 100644 --- a/src/Strings/en-us/Resources.resw +++ b/src/Strings/en-us/Resources.resw @@ -1,17 +1,17 @@  - diff --git a/tools/PI/DevHome.PI/App.config b/tools/PI/DevHome.PI/App.config new file mode 100644 index 0000000000..a0e35ba70a --- /dev/null +++ b/tools/PI/DevHome.PI/App.config @@ -0,0 +1,91 @@ + + + + +
+ + + + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + + + + + + + + + + + + 50 + 350 + 964 + 680 + + + + + Default + + + True + + + False + + + + + 70 + 942 + 640 + 222 + + + + + True + + + + + DevHome.PI + DevEnv + + + + + 0, 0 + + + + \ No newline at end of file diff --git a/tools/PI/DevHome.PI/Assets/LargeTile.scale-100.png b/tools/PI/DevHome.PI/Assets/LargeTile.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..21b4ce23b0c3b4d0376aa5ffcd3926ad2dcb6c59 GIT binary patch literal 3610 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/LargeTile.scale-150.png b/tools/PI/DevHome.PI/Assets/LargeTile.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..e371b9c48c7f7ff5ea6c235c731892ce2e9524c3 GIT binary patch literal 5237 zcmeHL`CHOi`?hJywA3-JW?UyLw{o=7T#y(`Un@;R#a+xX*TN+iLQs;6QoO7P%+~+y>^PErSH5W%E zMGZwcIXR`vm+al-h#va-B%YBgN;KtqvgI&fW*m}cj zS9aT7GJ#a;DKGr={G;-V>V1k1=xKFh-`gJ*9$H=LDf(&t9X|=qN^0QNt0}AIi08{% z%XjYTFCfn1C#9)s2-0>%@&?pkm)y~^76UoC{*$VE{`~b{{L8^VJ_bS{?YF0t!VXQ2 zksTvEJn7-Wrc)shteY{%ses2W&!seDu_^Omz6f$01{(w;Z9Jc3esMIpc>36$8i})UCld5vCcJDITGSM#rimZwKbM`DhcF zl_>#I_D}=xo89b8!Y8HKzwz_?Lo||4kRO}o>?LZNz7-JwpLX2XW*;DFq^)>INA zGNSPb7QM}Q*}`aRfVu_xg;u&Mxrqih;L&f1i_201GxwWHO`l}$kam@XOBdg`eZxWi z-u5N?pgkreiGOnHO@xEZDR~g@^i{3)zOl59zHh;t)&6rJEA`$!zgb=uHMuQk1(}rA zm#fwP(gPK)r)YALlb>kKt_@0tnZKNH83Bf1QIo!));B3je$mPAS<(b=NHjyj$47<3 zTe?)#z}_Ln7M$nGk#RtcF-&0T8V))4RPy48|67=Jo0kU_lrl;{V|?3Ps_Br`@M}o& zgr2F*S;nR-wzcA0!Q=A+(-3-x_-zMua+a?ZWTFPPa>*J1W4$WLq>BNTZ{{ML(pFSn zwTDY@l80W9TS<$Stf_KcoP(ZXe-RU2=a?k#ZonNHa;extr>A^v3viv4PHQ7A11I>? z*Xvbft63n0I#8P@p|$63eQ@Ij16rX1v=hH{<3S5+&&|w?p4eB2Ot@^1jj_yK4W!$g zPWM(W;)~N%xfPETiW~>vr16i`OZl`%Z8ean29Gzx9@|YTbQY4U96Qyx4O@ zej_3PzH<@|y|Y><3z_MgpUTyojaAbX%#GzSf7?W00r!UJ1 z8?o2|l_RJ}VtNB9Mw)6XWmDj9fCi}JdWy?y&%&fzX&|2j>eV-T8`cF$z3DQa-_ev! zXTIV@YlmP|dHu|c3)($Vr=Rg`T|tIn`JJifS9L{gmFzE}6bn}e&r_h$71b%+Rf^{e8ItlEjqrh+ zy>vpc7J*ZAMk^wKniSmb!g3C%kyl5=x)nic>D2QC$*tN%08NE`m=D@s1&RETBD=W8#YY+SX=&_^o*j7$-e$qC!LgH(&zHty74y~bVDD}^u4QOW# z`f*B_qHY6_u(%Kyv`|FqH21!f2z-|wM=N0-7CrPuVp>7|mmFrS;S&y)I|Dhb23*bb z;NR_o(h1v)BY9kxW!fgQ77h%Sf=u`<(~{*uAi|sPo;ubcC@bnHznrQ?%w;dksmCFKJwVK&K9XcoI6LCUfO=kzqnE+ z;t4Y!_uf~~M-Ts(Y)Zvi!aC;qdT*~kWiC0=QOX0ugg^^2{w!3ol&fBt%oC4z?@gC& zYZ(0)9xYrNf=#6Ukqlo5e*Z=TmSXA)p&J=gh`N}>Cgn>ig0zeaWS;TrhYj)R1r6@n zA@1%DhZMJi;I=?}b>DLu${=%6e0Y<4{CM`l@Sj)h0S>JI4Zg11><;^p%04w``xhaF zgav{WxzUmwxT5YJmTQ29uoG*F@sv`+d|LN?HE_63oPJS&pJ0{yg@H!~Yn*K`g$zt& zM_8e}^%P&)L6a@Am%F}E{QP;iOFkCr;I;~TbHCq5oO2K5{Dml{4^%o*E4cTkV@R2XuTPPiYi}T)zK_G97k8Dkwuz~C_NIN1MFZM0 zAilTvv4?GwWep@8FCUZIhT;*n8k!F5Q**I@;o###j86>h9lXo^?ERkeg)ydkYUW(( z@l{RopJsEi^S!+|p3H*V(5U`P)LhtL)M*CJzF`M1CsFO3Fe&~OaaUn9Xj8glif=Yw8RaWk8uV!czfXn9Kq%l9(cUSxkO5foyQ*tt@t z+K|2%=EcS}02;>9CMa*|T z)pXz1vm5J7^&rrS0ddKfu@e?&#F6uY2lfjS^DA5nLfi4n!=v{|h@o;kXo7$1`oT=Y zvAvaFwb!p}6?a9W&L7oY)82ZQbGAj?+Xr*qr=OS6^A0oWEF(@SXZkb_>x3ucPnYTJ z9oLbiTbfwsHE!T`P}}+-iworL|DN%RWcfQHrw>3cy8N(ES&Yyg7PJC&QSo|;YG{R} zc8t>U@cccmz-Gbp$xr1fxIMD|=YInt+Rv6K3)b$^`;b%#r-ilqX1c7vsJ~KnH&S?F zT3DTCO1i@>@OlkA=Ivy!p5rIWJ9sSCd={`(xT*Nf0!H08uA!Q~FgE>7Ap(>7WxP8) zcDpZ8?=Q~`L#;3y1RNocKISA38rR%}+j7|(oujh+UVl(lk8$`AdDZj7{GX_rOK7cF8y zD-1I&z9eXS#-u|wZGE?Fn)A~mWF+HU!CuE#u9zfPL2@*gPh`1{gLy$e$Q0@aN+jNI zaBKp@{BedTx&Czl0wJyz_(hSJ&93F3NMuSEQFaOK@XxM2O`v&%wa*Ndl%32vjZK03APv$@K5Eqbn!b9OYolTjR|Miy>igl2J*_GIBq0CIK-n4EOY? zXcJ~lf#N1ZJj-HiRl#)$k1&hGKrSSat1`V2ruJdQF>1pD5@PJ0&(1bC+>S73>;9Z2 znjPE_$6;_$>@SRDMAOz}7-<#UT0iZz2WPIDZgaO6%$@2NuMaBpz�OKDG1LuTPNg94ub2H9Q(x89WAF?fGVH2^w3X=1)-jj-{b^E z(OPmp9Y_DufV+Yyhs$cvYOwVs2;|jAE1!Z|JxS+wlGwQkR%VWM(1lrEpfYO;swr=csTT=Y`t7_Rp zN5|;qZxz0CcAw9yf<3F91m}4*B4#FxnpUwo&+vne@U@JdyoJl)LQUV*l#G^}qfH`mCuR literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/LargeTile.scale-200.png b/tools/PI/DevHome.PI/Assets/LargeTile.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0bc6d103d591b2eff0c6aa0c23ee29dabeacd2 GIT binary patch literal 7534 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 NU^^66952oPMvaOZ?o^n(v;Pja7H9d`Q& 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SmallTile.scale-125.png b/tools/PI/DevHome.PI/Assets/SmallTile.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..d6fb697270ab6a13e4db479bbcfd1f34b74eb8b8 GIT binary patch literal 1500 zcmbu9Yd8}M0ES0$i(Jnfqzz-{t|cUwWwITP``k6HHHTKFiX+46JPGE859bxT7!uRq{eO$Fb@y^iLHyYU3i->MM4n*e#YX z9FU&Ik;GFAaf{C@?r9^Blu@GGz(G0cWWYBm)CqBj zx)?1RfIgQ9(RHLLfd{4Y@Dl%R_JoL*9C-9=%B-_9;#O`liglkOYd1>MnTi*DTULDi zlH;kQ_aAX^E&1X?0%qnFQA7Mu;2i^imtd+R zblXbRR|PD<1JUY9_8QEMeLs;5!cnpC@!^YdG(RCQU9bAxacf&4R906(>$TVV7~O3s zh-=!)`YSCtGWjNX&Zn=W-pUcSrcy@qC>f>C}}J+zbTuOV64 z_?nz4{)PbuNKOBhWzs1G$GThEWhoO?dQAfb$nsjuVeLcvJkz$Jl!ziN^=h9g8PFXa zg-=`;l7%b(7>VFuHGt?4u{0`x9FV}D3o>fA>p)L2)^{A?H z`9oiDKtq?4KDxZIFBhSC^O&%g@I1paYkjR$?7Q*!&J$b_i8DS~NisTr&q!f#rliRZ z9xT~UpiZNm;sh!JH?~p_` z^Nk-XO(3r3exWj&T19Ud&2TGUMjnd9xnD9mZ(n0+lwHcsBPKRh?>;Du3L+z7#$@_c zw*+Z3ZQ}G?{W{KYD~?~{Z4Y;vUb^|y z+gG!LD`piE(~u_D$;ATpu9td9!8NT+*9$D`?ODC6+;Wp6%h-bl+$`of%FwDoJ;u0W z0c%Th*c-p0X*P+iFo7(wKEFs6GZKkltPV-``0(&a<8u2-+{|&6KPl0RVIrtS>a_V4 z?P8;(SkbC@Ecf0MxH7QPpahePrOZ$z(xUMrw!z^zxmFks+rrf=0(Im@n|nwq0>U5s zrXyzq4qt%q(!RVN$Q>%CnJDUvXB2TSN>_@KTbn)?9g20b`QHlr dM|epNVv0t~dCkdnumfEK(9Z72M`y#5{{{i`uIm5* literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SmallTile.scale-150.png b/tools/PI/DevHome.PI/Assets/SmallTile.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..94ae71b65ee2888c9fbf248c099e801548778d4f GIT binary patch literal 1713 zcmcJQ`!^E`0LP~y&nzmBq*N}$j8^h!+}N;9HgEEb&1BTNGtZKWAt5D?^2nnjW?^2L zc2qcd6!W-gVri<)T9h^p+Fbns_s7ope81m6;QKx2n~imMQB&4c1^@tRNLRS$RuX?p zQDMtTJ(pFtLW$%WKn4JI_5POJtBa3u0DwXS5`Nk{vFuw}aF(}^-t$oypW6j(ZG|## z?!ar{!s-p{;X1qbI6~HdO7|ZO9zJc|^}vo*DT@W>qtn*Dw8a#e;$NN{Ms% z>Yq2)=1Q-%7LIGCXF)pkgOisdM~!gJR>aYiN~A9=^2YAa#6EsTf3)QC0fW&ju2!5MwFbG}AKrhTpJrX5ZQiu)C}rPamMB;%T0MPvLs1I zjp?$-D<4QpG2YYEji~Db%V>XKZqu-c9iH^bG!KqgR4jdPmNZ2=|NTsRJ=u4A1tJ`b z4vnhZxIs1W!Q%$9O>XMIBaKh+aUz>@MD`D2XSF0rdBmdSOiIcWbz! z^3TprHI9zChMUs1L34ROEA11dZ!QFYouX~%MG5K`*FPiPyRp6~e6~QiQ}-9f>0hZP z@{wfBYD3t*-ip295cIt=P=rVnys}=ETc?G)L#{ZG;9>+D;a4zq>T95#gE2Vz#$I( zj4J(H+pR_-yS31FX^Ix6O+{rKov2OIMWF|esd`k5TyhlZBNE#R%_#ee${R5L4R$9? zKKqjoN1K5S(b>$*;c4(c=GzW~y4YoDy6E8nVaTR*oAaAYG3zbvv9wl{-~AfbV`bS! z9XrNQHI+Eq7Q{ADD({6MJ$Vs?j;l{ao|;ZX?Uv$xo2Jq!F88W=!2!vxcfQFqee1@I z{un%^v9n6fs-R@pGgmGr@gc42|*;Tw0Vi<@kCWfopc zXad&yG{(!ZQvP>gi4aAX)Qy;pX$W;OwHWWLJ?wvcEb_s59yxOob>-vN$s_f76zQ@` zcRsz*K2kmH-OzZ{ITXqTe~QNNadRvDF^|lrS4RH7n z$7b#A`gHl8jJgg9)|0an_#3P+giadehbdn%|9WN0g4*Jyu7%WaLEbG@k8nN;X6=s| zCsQw-8t9o^8#~ra@A;a|G6Sd0u zv&#Ty5xz7g{N-!7Mz_^&r}SJ^^Tbk3`#wC0%U@J?yxjz`-u~fPvS?x5l&P&57{C+> z#(4@OsqOJMJsxF22crbTS_-D`2m7K1ja8QfAvSbwyr9pw$hpqiX?!Vu1-uCo`t$y7 z#4pqmfE+8yrA_s4bCorSu?q12@EBrDi}WnjT7~dpQp^x)w}bHsurnx~9--V58X!OG zr=vJ(LUO0+TMWZjF=)lLZo#JwiPp@Ht{*BSZ`i4i1&5lS90&i^`>RIqW#bh~=rvt( zr(a%c@OeINoq}V;i@6CX7(GWQrHyeqe?3jM)u-s)y++x02>Zv7$%Dklua~zFwYEM8 NfJC^%pE!nH{|`nw4&nd+ literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SmallTile.scale-200.png b/tools/PI/DevHome.PI/Assets/SmallTile.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..d3f373fa7fb8f64d31d84ba8ac6f8f483f4adbb3 GIT binary patch literal 2157 zcmchZYdjMQAIH~0?uJ52=sXn7HHXYKnI`u`B{L>tZYPnDEy-ob(hcR(rkpIZ zYA)4gid@!a&Jr_oDVJr)9_Q`z>Uniu{C>X||5yLt=l}V9|Al@&P-TVv3IG5=`LsL4 ze&F}ABW{;3k|ruyR`8iF2i&05%bHJa_;v|XPz(NGG;PoRqiH8kHHU;1nW4RFgLifNU@|DQ>C9)R2qz zbzBW)no7e@=Ja6KUstLmiqS9O7b+PA0sLm_n3$W7!k&wx)@o7tB*~HUX4xpe!|>pS(7m z+$=6t@*v*uPgi2DQIpSWm?9FBP0(ekB7VHfTE-x@OAJ3ac7BlHv@ud<@3<^wG}1U~ zP6~Zm6{Y#{MuB1}^EmtP&Z-_3o^`=)Cb4RYUob0nD>T%?P(nxG3vcsXB1y`i(ANRE z3WM4?T#VJPmCzjW#r2VIX9sg9P_RUz!#0C^d`Y1TEpkj*wrbHj2rfXX$3d($Ax=V& z^1SH5XNQi>)d*be`g%$Of6gGU?BdA7s^gxNyu}q9!DfrcKX_Iq z!6QJGJa-M~hE*hm;SCyaMzYng3wALFD+W$dWJaztf8 z;%3LKKlfdQtXkIYqSaGEwtG|OF`Ll-d*qPu=6y|+vV|92+#4jlb3A8d- z-1EJv!J+@?Y}i@qUGraS5%BE08x1VXWOv8r*vkPF)0~Nc-; z+u|2*vvQF^nbWf(yen;wuc%WBcA8e|Pr5f85C_>VQR0PDblBp~u__7Ha6b8#)|r9f zdxkoi{oFo~MdTy>T2Iao&Zwdk-22rCac`oR+i^wj>V zn^!dbc0EJjecM}(ukM`wZRFPDCev7nmwGIex7@9>hPw@=>*S&2d2GU5Qd?)M?qU0` zV7X%P6+X1Wu5vjJe-*jSz8~9bE3m`DG(z?k!1Z+Xsy)(M87{UU6=kX@F0nqjVJz5v z%er;CdKew)mDJ22&(YE|zn(dNpS<|}Uc}ZTJrl!iyF-_P5*X0%q9@CY0!}_GD0nSk zKo+4}Lo@DrKk6M$98BufJ4&8*K{3m`(*C%cepGlFbcssHSX05{yK-z?X6i0D1$|Q2*1c^6IzBpdDRtF|445(EJ3oH z3|V|5e{VO)_F-7WlQiLHYM3K;yh}x+?=|_VB}2WXe*uQ#u8yq^&1BlgE}rUC?7yTT z^E*Y1zvdwbwK-d(J@(O|{Jm64At%2~v;Bbpr2N!P@}*dU$q;S(3=UtAYj`*fLtqqt zHiuHvqLY(j*V*PdcH($-a6U5B0`4u1k--q1Hpiu)9VMxly5Fm(BSoV3Bq%vdS9~)>c-v zrVd*;6hfE1@O>KHJw&W3=LYvOfKN)X(&!^OqSqmG2Z?T!_ zW1gcfyqk{Kg~>vJW&}HK9a>eof`(Kw49pbZ%kL{4a9yaNBdcrNZa&VuCe`Hn|DppA zJaPkUjtqR7&(16u47x-gkxU0|l(k6l-!Njj3#5-H=#K7pOL~N}qi5Qh0b!0a`4))1 zQ__jV(M(jlp@5oZEmPD)u8U{ZrgA)1wCL4HNZz>_44B)^6E&6aeSm%CPFYQBxjj0% zic|T4F;GOm5=E$RL$ncZ91tl~aplVXmXoleADg lP?jYBuM+To@cY#{s-avVEBf#KJO2aVw3`p4^<-4;e*s6r5t#r0 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SmallTile.scale-400.png b/tools/PI/DevHome.PI/Assets/SmallTile.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..4207b09585e7d3fd4738fba547e984e243ccb0a4 GIT binary patch literal 4166 zcmeHLS5OmLx6TocK~YdVh!jyl6hTBlA<|Kbkt$tUY?Kh1QUU~m1_9|u0qGzhMLZ$W zYXSiQsX-7VKuC_#6cWIggb3>+WXV$m(>@{nz_U&Xl8;ipSWex%W zfWzRMruG29A9BA%WWPWo`Ad`{SPq2Tyb}rli1q!JKL&$q9s&U3J77~2$H*coN%U{e z@YtShK3|_k!gdytL>$*@66&>X9*DZ9yk@L-sM-1LT)Ev%?ISl8hTcTfL{!;S+Timu z7+0so6v2^8&1e2Ho2zmd%s~6yP8oBDhEyF<*?)eiK9i%1iR1GdpHDoz@sY+Z#u7J^ zHWsl`OdcQnQD?OXG0xwA5b#Xc+ZNC>Y7zo?eg*{ia6}GpUqlR`Aq;$S0wrX82>-|F zL<-=@jsL*^t6Rq3NHm4sDyG%P&K2OZT%sSEu2YEX1XL zigG7LS~Cr!Z#aLT6NH6*U((tiZT37bjr}IHsKgy=?97?jU0#bZMW0&G;xX=5&JbnM z@ciV`w{~weR9af=iy=k%7wh8`GNIJ&n&#ao|AreSnn^izaw6$S-uc3$45WE%)j#be zE#dJ9a#_i|mg6SYwfc_kGJ%h@RV9o5;sh;*uBRLhg=JR$J}9sh%RW z26${W+E?-RKg9i@I+H|WPS%WC#fB_kJlC8Yu?CYy%eIC$jue4^)IN-`?7_6YG*=rc zZ>SEPTR3&jmQ%9I_62%{v~Uae>^YQhunh4?a|X$%jRp1Y;(zQ4=){P{#t{eJ&3hBr ze0ti@98|FpznyZ>=7-Yo+CJ@49}!f1Q9Z}QypsJ2|E}+QOMJkS{rkEfn^6*6nzB~T zzx-9iWvLT6*3@@_BibH6O_Qh0lg@xZ7jnzarqabNV73V+jNTM-_&rs5&TPD8+#Uv6 z5X&%j=k|GKjwV}{T&lW;ir4u-Ypzj-uAC}Kw{@8fP2V27Diw()vBYVd4Rqm3fEpqr z4<{mKiq>bW_cROjzn=o_3Q@3H$!?Y0bed035JauIYP0Y zeGcz_VFwK#Gh*Wryj`NTcwEOZh?m795wWT?QCkBUXPnf2gXyPwWZbU-Ggu?c1-%f% z4)rD+o)W_2|C&-6lejvgTf5ug)7>hK;NzH8#iu;3HQj|1wQjIQ#13jr>Xt{VG$O5K zx$3M-Y=Y6=+R6wH!Ys18go^KNd!WnkGo9{T4DECzGn-T6b`YwqUGy}AytFSCA$H1A z#pe1x6z!>#%TU*RZWVEKYZaV^9Xiwq%5{Im{@vt!1bPNf(- z7WCJ;m{+ooAkAG=2L5Lg2-bL8(@g< zmh;{v$=&I_MfKv1m-6(VFOTIven)Q4+FnS=HMdSuJv@LhZaucUQ;peUPC#N={xiAx zXMUf2H);k(SY)^G+*QA|52kIWO%C|E96>jBsST^`Umh1{vbNw*bHU+iBazNiV1^Yu_rC8u)`nB?V_|AAPO>I{8{}!wDP{*7p-l?~c(W z^Lr+XxzJj2(b=(7x1re_w`tpzun&DJg#L+2|<@y51MNyCHe%~hEu*qBU}UaE?QCt3VDAkfVB4E zO@jI-Q0HmxE#F!TslV#oyk|NW4n#}LU%NdQk<=j-;L)&NR9U!sO5%isk^(5QKT!N) z43w0_@|>T1U>lZ%-Sj)K;Z=F=cFt>Ez47wY4Np{Pq9Q0VT{3g@T1Uh-&3*4m^a86l zMQ_~xEIi1rAJd^$oWP`gOAt$(JSrzQsP+W8X|0EwB*)GSlK#w1m}0{oh^C$r@m2(Z z%0AN#-Gjx+645!0Us2@SVS5+-!jWb1cim14z-fV1Zboe|*wuf~$@BgD2eI~*P3-Av zXMM?wU+<4=$>8zN^eb@km19@g()e?#0dnx<1kzhw7^?=JfknK3|I|(w6@MVz2Yo7H z@JGM@M0n7PT%#_zMneau)eERlb45_zMCl<pqmBvEi{JdG0RFnnlMr~d%i;Z1(jqt zh$MFbIdrMjF(HEE+}xW~urC4@p!?7LnHK_7Maw*>UXCm!dvN(QC{KO?nFE&fwiW7E z0A;OsxxRpC*rrs6H?L_d&>ZhCu#wWi8E}EG1(!_{&D0(Kt$XuW6uUlZ6grYFhsO^| zb7#tPYbJ(6zc}>I(U+Wg*Dr{Oc_r}}3hJAX#@G`{E!YGx6!a;D^31R`(q%01ZZe{xOQC(XO(V^jtQvOON>mtl{xjw?8avIS zry@u4V&E!J`CFR7zGXu9en0EDkj^Heap-fgSuhDy261{cx!4}P?jd%901yTaf+v@F zi^Dyb&lO*7Qy;BTW(i%bJBLg{wq({{ZS1~GALA^@{wt}rr8;a*byeN!_S(6dcU(m5 zG41sB9j_2ucqMDad}$GY*^xZafhv%(bf5nckjJ9;0{fbbLtYCCdxff2h2a;6@0_vv zwLHzLo!Q8_5lHb*>-TkA_{)JuilhU$l#&sHO8)H1a%QbbuFjmHigtv{?E3g7duCWo z6sM@Ie9J*69UP*wNKd6*e;Xj+-tH-I=L`*uGmHLM2S469D{o7)9G&;lkhLBo^@ZSMJ>wPTL zGHz9!kejkK776V-b`th>$#=5@Ey6KiC%mBLrHQ5?EJ+^*EWa+(lAj@S{Y@(dRlK&U zE3Nlco&lZgq1v}ADZJeHOMInZ+m@{my$_>ZtRmH$a(LV?W{2P+=DBO+O#uEBu}S*a z3KjM;P)R9_f`l6%))D77rjKC-m>{f`i-t1iM;kgHD{{Cg?d==v*ZP9K^SJ)|!Dz?q zrp%VcwOuD!DV(FAo5>5hS=wM#m1b6x8+1L<$^AI(0>VaF3U6`-d9?8Jn{;!JZX{zu zuiHtm*$@@K@5J&rBYBGHCm3*Aqy3K7n`BV#4l^8qj@eh@<}%zg?*!FelnVW2~~-p1{G4gZU}KNEz;xTRg|FV<6I-vPtU zqT<1iQvKRO-IlEg7|WI&sAdJHdw*(=2lScA)Y z6GW$E@pxHHoSB2em|xhNMS0>wtKH>g_Z0&NX4izYASrY8PIw&dptAtMW{ke{$N5?NhQJpR$b^!FfW>g5_02r%ecGU%2Y`RZH%Y<;KHIZ!aBHLN}#Hx(58 z7^>Xz%=iH+%|0)9tc@iR+|N+Flg-Ut{T}Cl<7bY@clkkxp=PU!pp(4`J!-XAHH!I> z)7ZU6(cvA5%H5oVb}w#ZkvZ)wRvGNBZVH8UrxS2iN@Po`pG#Et<5<@v!T~LDK`dQ5 z1bubIq0&9-e3}fuEWQ4MMv&(vI85>W=!oU&thyElUC-5QXnn!eW_{8ekgI2j&ygzP zweJC`I$d^e6OH8M!4Oya$D-&VBd4N&1>L*GY1a{Zb??s{C00#A?CUbeTyo%#Dw^0z zfpgztG^t9$XFx5%3;o$@a(IL5V;}qs9POoK$g0-Ml9k7vQRS?n^3y3{0P2jA^X^tk z(j@~|rpJ?^)1Z@QAZ}MKB98YwJo-OrQ2rIx^(%v+PaXdMiINuBWC=PIxZBa^CFx7O zJ`h4Mn9+B>urx$CdydiB>!m*N^p>^mvMr|Bxy;4s0r~+i+lVcwa_69DKRv=slA)#* zLeS%qnu!=vaMm@uk6EQAn$*mu@&R%EK)!_XX;2GDWm}QMDa*9sW)TkkW+RaS`1jbT yU?TmA{VCFAcsI7ad?V`a{r`}{{{u2ikCVGHiZ1sFCks9X0AMp4)4J<-QU3*JL8cu5 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SplashScreen.scale-100.png b/tools/PI/DevHome.PI/Assets/SplashScreen.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..419dc47eb8b828e9cc0926602ab461453a8bc132 GIT binary patch literal 3977 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 br7G&tjZ~8LMR{O&1zoYdcDeeJU+RAVZ0_yG literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SplashScreen.scale-125.png b/tools/PI/DevHome.PI/Assets/SplashScreen.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..964d494333aa01a7842d15bc83fdab0e22f411bb GIT binary patch literal 4822 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SplashScreen.scale-150.png b/tools/PI/DevHome.PI/Assets/SplashScreen.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..88f5a5c310af0b771a5b4da3183c0a428c4cca59 GIT binary patch literal 6019 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 F`5$9s#-7 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 E1D?IjdjJ3c literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/SplashScreen.scale-400.png b/tools/PI/DevHome.PI/Assets/SplashScreen.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..cc930ad3fe3d41b20a336df505163cf87f6958a3 GIT binary patch literal 23152 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-100.png b/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..b245597f1fba4e6495e6f99bc3ef24154320a061 GIT binary patch literal 1838 zcmd5->pv3;7@kXvBFQC}PIQveI?Gn+-DodjB6Nh;o`f9n_ zbK>1@%pItlm1lFjzq=j|GA5`28u712&Tc?MC!}@|R%j1(0lYZgMWsT+w*i#Og-R!a z9T6`S(F&VyZvtuQu}^&8qR_N;sHQ}c>L1p_!-ei6kB5IIN14MXHEfqDDJF}B4u?%( zA0;nzWRFJf`#04fSGggi?p~cod=x=zng}mWouCM3ZI)hw6Jj^c2(r|2LhlJ%Txo%v=zEGEMdr%oW?%9?!)4Q{6bV297?`IhvK&k$Op^$&*t% z%Mu1}xwtzK;=xmmwB#>I3DWAiiY2EhOn>E^c+@v=e^za@y*Q$@=SLUqmV&*5;4-)L zi;Rg1&3&(v*0>RAybxu>W#cdkB$mm%=oI{yI*DHtu2`;9|Ru zk=hP}N(&$zhq}_8k#-2HOl9P>c4zh}VgBmWc7BEG$lyJ)%KpoA?rZ=yEP7(>?fOuc z9rPuHb?;G81N7N2dEKtjSj9_Sy_?9sh}vs=3d8cm*K*@7NdNcO4$PWSZl|Q<2n4X5lCge0i#wU?D$}-l2Bt{A}`O>DU-z955%t|XyrEhH>AccEJnr9rneI$KnJ4H2MHfe5F zcy)*VTMUh=i1zr{Di~!HleDV$ z!z^7KFkD*!w-br4q$X7Q`bPsv>QO@I>-qA9OF0B~R8hFcJeHe_Yn=48ms#Y#ev@x4 z?SDlxR}DMn_qh!MlKpPd!S;+ylGHDD@5wM4xH?~YkkqtPmdpw^;MRkK^@rI|A5d#- zL*csCHRp3{y-$;mgaVyZOwG19V12G;v>a*%J?I)?*jL7+Txer1J(nP#QweUAUsMgM z7@8ZI@Qe_IS=EUSSV{L$@5sT*=Xt01DfP`io2jvDud1qL5LbhZMM_p=uehkWYh|5A z{tLQ1_LBG5S2cXw9k*_Ki&~ih97!2ouoxoBtO4Y_ytoNyyNr ziK25)21_ft3^Pg0qZxJ-$10NJjw1#1i-r)8>sY@2v2#+kr~?=lutMp1i`n6D&bCU3BZnA1G;2H2?qr literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-125.png b/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-125.png new file mode 100644 index 0000000000000000000000000000000000000000..d089b82dd508326f8e9585438b0e1008c66f00fd GIT binary patch literal 2134 zcmds3`8ykk7LBQ?RI93{3?5<7j-fhADJprimQ*ZL?UbNt)zY!Vo>-GXRh4P0mX0OV ztH_|J35nPuQdBk4T9QT)EhF`ih!7DHFY_O~pXdATx#ySropXM;-*<1mueZCJs;(*k z08oQ^T)Lv*p1%P+sOSUGS4|2!6z36;2ml;i&Mja{FOk`L(X*kuiR#7*@&M)_hXV!Bxow zOWnm0V^!r0oqu~E-yHx1e3#;3^v@{CH3I{P>{HQrvY^3L)IhD8?#GerUgo|_YN@#w ze-joukTgQdp_r&yKD@M?%XGr{&9nx9IQy&q4d*iwn<-~kO0PbklQd4t!agsyoRV&} zabE*5C-m+jbSz_K?T4f<+icFQwa371ZJ~~l7GjXezJ;x>sWHBA@N9enh)g!nYB~&5^sHSL(5s= zJsI3dn#f1R_Pji#$!cq>Ja|-Zj?}RgDf+90hzkF)p0z-TV;D)iO1co{rlahFn-* z-KNHE$PoIQ(QJoo&`YCmW0O^_o*-x}+{Jmh)F zx@4Ir$Vg&3=M#>HbE!3?1k#r$j z;EX>X(#Y6R$+2;Ygwag+u<^?i8Q>$uKil-oAp1tYrm|&_^^w|v*Xau=A~A1?+6`pi zWF)lTQKf7Q`^=z_Jbj`aZe&sOxXZc|ForiO9(eV(8iM%rgWMVK=YFb2>Nah&dr=eY zJ9eEkwE$;-k)>fg`$DwfXENu4O(bANYRO>Xmfc$Lzrd)(Q5;O4yFW}Q)R)ItSpL2IuzOx%BEq=Ky3S>KQhcWRG6?%E|)^VTvDFQ7~sqnuJ)#g#Nx?G6aE4U+$b|(SYry}=HDV{#Z zK54xW;r={GVs(3>wNyk0y>Kn4|7i5JTvj@8KOj0tI;9LT-dZl($HqoZLq}ghL(85wE@PVHs{6WU6FUxXgQ{f!CTiM5;X|#sT#|3e_txoecESj)hnuXX4YlTTOU7289SY3Y0>FO z^gO)vhr_iPOYl10FCFpiX7pu6aE$#EbyeJbMBATYarYOQ!wlrt#Yg!xJ<@`#+V8#*dlho3+4%H-IYJbZOo+hd!tz|1`&kmsxx6WtQ7-V zYe>#~kW)(^QDchLQ;lt#=%1!W*s#Jl{d;tVbQejK@|azMMRQq-m%TFe&EiN~`s_$= wPW5f4lLWL6rMN-qk?`Kd^~Wy%l-dA&MYMQ2nebDV;!yxVUA-^0xZKSCAHuXiIsgCw literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-150.png b/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-150.png new file mode 100644 index 0000000000000000000000000000000000000000..d3494b147ba7746daaa5c6c5c77c843549ccdf9d GIT binary patch literal 2553 zcmd^B`#Teg8z#`vo0Z(-$HD@q1TXa6&?ot-njS-T%#P!K};*cEKr_wD{FE-YH19ePwTt1i_6U z(j{bEt%uC zz@{SQP66}U%txJUU-W;b)0cXBx^wh#yE*!JnSze)rEY#;Iv3Ll#_i?Ca&u}p1YMy* zy*Xi_`g5wH|HMg2sacrE1B7+|tAs?RDzMg>DJ4DhE$ zJq{^~2NALv9NI=;^R*M}(GBnOSmidsMy?&sDq}bfwz1vF6*y(kaAq9AoPlBj zOvygkUCnzPX_GZ@o*R=;`kXTWOi`0K2GuJ*OpPe+9@IOoKYu8qs{h5E z_Y+v;h5~gY(iiDfm?!=(>$Z=R{PuSJ4P}>V_Ath~LYYXc;sRhwDY3R7UR^yRK8w$XXLeKRiD9p8pLPegR`d9It+DW`B59#7@=$uqDXIXM1EPqaW4E1WuJBE162$2YIxrk6Dw8h_LT1Eu8c{ zM^in11kCtgLfUD0WIFw${XRNeeX2?VoJ##-W_8M}xz8q2ynQS06T&NNK*jmwI$jw;{UxY;?Jb8Iumm`|CHlj>NAhbhGKK6!*^5MQ)%4-hsTLp<##pc4;*7220AFjKGRaAQ>N{1vMcV+ z$&#BT3TX&6dcPn%Zy9D>H{5~h;s_8N_0w1WFgX&?=-_scII-4N=a*qzdjuGG3}Z`a z*g12syXTHruvGsrU~TEo?W@+5x%>~T+n4d4iAyBY;$6> zeHG=&ae&4SigN@X+r#2sNh-uzhAen$DVp|dqR|?v3x~G{bKGBc_qqf4!at2q?k3-w6S_zZ}It8Zs~MOC-}8}E792QHbbpCq6~Y-FE8r7dT; zeoF9``_cL`z`&d8^7bsly^{D0$g4pPqFx#=5z5xYCmuCB(=1^_5+zc^bnI;0(fL_I zU#O_901O^i2)s&xl#C|_K4iH13EqBqJ*`IBcm2|Ta-K5UVnB(6*1ury8xar(M zt64l>)y6K4CVeRyBERuZmm|u#)O%q!vy8%|;L!j~$^)RmSH+Z^T3>C&ugRhtr`9s> z;b6x2RWFVP{%!9~i2O+H%(CW9g;Djms(DDN)e*DDPa(4qOZ#|rVJLL9bEv<-f*n_d z7Ynaz7SC5aQkh0i;pV?h$9ooxx7uS8kZ1JeEmecUoH-V@=Kl z*D4B3+v$($xs-yxnp!_!98uz5Sv5FNIrS1o(7rbMe426P@Knrn9?q(+@+gb6?lW1E zZ~&^OnhmJ^?Kkva!&g^5!uUkYv pkpu4YgaP7!$NsYX&yr!9kS3ViFsS`QRPf6QSzWS&Q7mpf{U3GugF^rS literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-200.png b/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..e1fd5f5f3ee115d846a1c45d0d6e519824bc12fd GIT binary patch literal 3535 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-400.png b/tools/PI/DevHome.PI/Assets/Square150x150Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..5025a1dabd9cd134e10e65725aa7da3cf57cc450 GIT binary patch literal 7182 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png b/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..516a72eff269fb52141a6b742499ef845babf5ac GIT binary patch literal 5932 zcmeG=_dnF}|L@~6vkPS%PEz)cWRJV7L`fVS;e!iaL6>e;(%RqOT4gdfKgr1fO06@TB2tY#xE|#x~ zpMndmv)%(&0AOtS_dz<)#dZL|jzegvn|fptXXu^z+I*?EW*QpESU9$ljm||6kqr;C z_<2PW7W)=K8T?J=Jk`4=n$O@|SJ~_fjp>AFnTrzHLrSOv`4Zj+2uXJSf=h~M2Jp+$ zHn*ol^H9*n{$>vQ{N&-#nOp442G07*mESrk1!rq7v3!W*v$NqARjJ8M;tY=XiUJs$ zFQf+mS7uNkO#m=I4*_hXpg=&||H_7r2$1h37*whN=*8Tj4N#Wwb1|V#oW)KZ^|eaU z**TTLl1y&wYUsWjsTh5Ff|}Cl!Wnf;t>WMlh4WZ zYef9w_lX-Sj@2tNIyw6-l(yOve%yaF)4`Nlj-%?CeD6}^GdX$*^SmVJ;`ZFEEr#GB zOLy{tH-6l5Xw*_^WRou$=nNKRdzyPAxHkx)Zq74_?0TyNt=?yIP=PD$qPz{=hDuUR zA!-6F%&Mi#{8|s3B<><^o81$4`?jA#^F;Y7%hV_bwmFsj1wL2C!apT5H06me9oS7f zof)#JL`!ESC0jdn>MPCUyesUv#2#zkwc$**kt0m!qta;u;%KXCQYeM|ZA>_i^0o!V zOzA0+oO3aWc}YtnDI#kwS(?|6P#g%FLehn1#w@Cb$}3KjrGd5&wenOIduOyh^oCT- z`W`d7VsA_H+)2HX7j|KER;mQ5J-l@OcJZ6brNR?N+HbT}*fXKr3Z}zjQpntQyvOw% zRM*75FBrOUj`Io2thF{!k5ZO}6@UBswaPt`ly1!;f_Wc>t^Ra8RHkrX$vLv({x)9? zfcJU}BTOxV)+*IEA^SrO=h`zxi*SQ$M}tvcDri_rY_4K=!l%O}+^Szrw_f%ACj0he ztyDQ9;dA6WsbD~lrDQ;Gm5H^nCc0fExHrC>WNhuMBPVK|hG#-GkcawIS=4t;&1w8< zD<9v(Hh*b`LDjnPY*D0oed6?aq0;+*ybL#>16`TMtd!&5M=jAE^#<5%jdI)?_L!TI zZ9NdEjNo+&3kwrq)iTQ+;PQwW*XN$J+^3c}vzyEB0PVJREn>)s-7PC3N0%N3h!(bF zv#!PqBDvdnHau2}S5_7a)`_WD{H+qNbM$pj>z~vaaw^jRFmG?FQjTmU7QL}Pg)*nU z#*coQdo&w@0;|smnA*|T$&w^yVCxnsFyFyEB9zOjLpek_jSG3&q}}sS0mo}fzK0rh zrP5(CfkFUsK`7TrF*2}In0LSj>0~kgCD!uSMBO%avhL||>ucXFztL>jT>2L*fU~g+ zb5OE}q7*^ir;+tM#_Ql%jEM^V-KE*L4=ZEE$IS}`&PVbZijpZwcKicd)0UUIB}5%( zJX)5lWH#iixT3fz07e8{Bex>?z~iNo&>WNUveUDPkt9gSEKomP)Q#NTGfGnoAQmWvn>PGf@CK+Gaeg9)|Lo=d5vqb^PPF(Jwz= z8*I%n1CkEAKG5>SF2uYjF|~$&wy|^xYcLL_4g~`46riPF@x&s!&{jB-hM((5@v0l@3xKZ;y)j*i%-7V2&6VVj>f0|IRPaoyk;A|*g%^VrgipL$Tdrunk_m?0z`2o z@djp2ZF$c1JZ#(vE;>Cbv>%AlABW2Q2Nv_w!Ib|J#>!)4w@dYIhSz@q#q|6d^RHqR zlV+S(>T&kvmZQhz_;fJ+yXB){-S(I5m}QRU#1t+|SwC1CzIj985Nz5IX=@cPoza!jikOBTida6jpPC${5Qo0A;g=;KaU zmarPr#zfNE6sv)>lrQnJ~#Bv%D1=XogVW2--4 z46bg3FT{*WF4al(5yvSkcqIXvw7){n|_m3E7B{X@-bUF$i149Tg4>l zJ@K?(Q(?G53`;vSXD!}X*Cg#uc-)=YjZ%4(F+|e9_vIJTi#$?$xC2=>bi3pn`q4DQ zSTrobmYY#NBoO7^l+%C1-MZQ{#jCdewTk=~dC)JW-i*|fbr}_W1b;&FM8*v9n_@wn z1otKATt|2A*{WVQn@Dm|iH4zb*l^qd9XyJHHO%2=UkZaMkeE6C+kBgPik6~@#*s2x zM|xC|o?~A&8vpA4+6qCa=1g|?v&-iG=*pS&Lx*RII$F*6cYcRLqqH|>+@!Niz1zOK z1eJz5S5n5yh6z|7gq+$g9TCn>2#$t0+dAGUb6((S-viflUP^z@)rIo@S_z+e*5I#} z9Svb7Z|?OwrDl6Wa~$B$3pD#}H*_^64kNrKoIpGn&>o+2(POWprRSk_=0&QLPf?0H zrp)@IJ49`lSeVsrZJfqa0WRhr{Q@)rb(%obUHTt}LmjPzlztR8aA;^R>MyLtIg8oRz6xtUXK_vPM>tAwYCIiUPnbp5Wwzu>gx-UwnF6wD3Zrx&MCbW^lCG9&3 z2tI+4JV?S2+xyJDh?>>yg<2JhDsD`&a5r2nbTIFB>ls`f7&%7)6XGn0 zysp0eqQ4$dmD9X9SfkQb^IZ3Idkrt4KmM2!s)j5^HKl+=_?BbHMhFIg2a+Uxk;1MH zkNl@Ki(zhopLu!<(;f;Xtp85Bda6Tmr-sD99pqKN^+eIOx zvqomkXY#dJDAJnxkMVsK`mcqKpCKSS_FGC{r=`#f0gdIiO0LfRmmsUzk0G37$=%?w zD>TVBhTY(Z_P*HF7ZMb(0@H9GF0|kw)vSA>m+1=47rIo)
mJ9Mg9v;EkUpR4w+yW66Yza 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png b/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ccd38cb652da47febd4fd23335e835b025c18762 GIT binary patch literal 972 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-256.png new file mode 100644 index 0000000000000000000000000000000000000000..516a72eff269fb52141a6b742499ef845babf5ac GIT binary patch literal 5932 zcmeG=_dnF}|L@~6vkPS%PEz)cWRJV7L`fVS;e!iaL6>e;(%RqOT4gdfKgr1fO06@TB2tY#xE|#x~ zpMndmv)%(&0AOtS_dz<)#dZL|jzegvn|fptXXu^z+I*?EW*QpESU9$ljm||6kqr;C z_<2PW7W)=K8T?J=Jk`4=n$O@|SJ~_fjp>AFnTrzHLrSOv`4Zj+2uXJSf=h~M2Jp+$ zHn*ol^H9*n{$>vQ{N&-#nOp442G07*mESrk1!rq7v3!W*v$NqARjJ8M;tY=XiUJs$ zFQf+mS7uNkO#m=I4*_hXpg=&||H_7r2$1h37*whN=*8Tj4N#Wwb1|V#oW)KZ^|eaU z**TTLl1y&wYUsWjsTh5Ff|}Cl!Wnf;t>WMlh4WZ zYef9w_lX-Sj@2tNIyw6-l(yOve%yaF)4`Nlj-%?CeD6}^GdX$*^SmVJ;`ZFEEr#GB zOLy{tH-6l5Xw*_^WRou$=nNKRdzyPAxHkx)Zq74_?0TyNt=?yIP=PD$qPz{=hDuUR zA!-6F%&Mi#{8|s3B<><^o81$4`?jA#^F;Y7%hV_bwmFsj1wL2C!apT5H06me9oS7f zof)#JL`!ESC0jdn>MPCUyesUv#2#zkwc$**kt0m!qta;u;%KXCQYeM|ZA>_i^0o!V zOzA0+oO3aWc}YtnDI#kwS(?|6P#g%FLehn1#w@Cb$}3KjrGd5&wenOIduOyh^oCT- z`W`d7VsA_H+)2HX7j|KER;mQ5J-l@OcJZ6brNR?N+HbT}*fXKr3Z}zjQpntQyvOw% zRM*75FBrOUj`Io2thF{!k5ZO}6@UBswaPt`ly1!;f_Wc>t^Ra8RHkrX$vLv({x)9? zfcJU}BTOxV)+*IEA^SrO=h`zxi*SQ$M}tvcDri_rY_4K=!l%O}+^Szrw_f%ACj0he ztyDQ9;dA6WsbD~lrDQ;Gm5H^nCc0fExHrC>WNhuMBPVK|hG#-GkcawIS=4t;&1w8< zD<9v(Hh*b`LDjnPY*D0oed6?aq0;+*ybL#>16`TMtd!&5M=jAE^#<5%jdI)?_L!TI zZ9NdEjNo+&3kwrq)iTQ+;PQwW*XN$J+^3c}vzyEB0PVJREn>)s-7PC3N0%N3h!(bF zv#!PqBDvdnHau2}S5_7a)`_WD{H+qNbM$pj>z~vaaw^jRFmG?FQjTmU7QL}Pg)*nU z#*coQdo&w@0;|smnA*|T$&w^yVCxnsFyFyEB9zOjLpek_jSG3&q}}sS0mo}fzK0rh zrP5(CfkFUsK`7TrF*2}In0LSj>0~kgCD!uSMBO%avhL||>ucXFztL>jT>2L*fU~g+ zb5OE}q7*^ir;+tM#_Ql%jEM^V-KE*L4=ZEE$IS}`&PVbZijpZwcKicd)0UUIB}5%( zJX)5lWH#iixT3fz07e8{Bex>?z~iNo&>WNUveUDPkt9gSEKomP)Q#NTGfGnoAQmWvn>PGf@CK+Gaeg9)|Lo=d5vqb^PPF(Jwz= z8*I%n1CkEAKG5>SF2uYjF|~$&wy|^xYcLL_4g~`46riPF@x&s!&{jB-hM((5@v0l@3xKZ;y)j*i%-7V2&6VVj>f0|IRPaoyk;A|*g%^VrgipL$Tdrunk_m?0z`2o z@djp2ZF$c1JZ#(vE;>Cbv>%AlABW2Q2Nv_w!Ib|J#>!)4w@dYIhSz@q#q|6d^RHqR zlV+S(>T&kvmZQhz_;fJ+yXB){-S(I5m}QRU#1t+|SwC1CzIj985Nz5IX=@cPoza!jikOBTida6jpPC${5Qo0A;g=;KaU zmarPr#zfNE6sv)>lrQnJ~#Bv%D1=XogVW2--4 z46bg3FT{*WF4al(5yvSkcqIXvw7){n|_m3E7B{X@-bUF$i149Tg4>l zJ@K?(Q(?G53`;vSXD!}X*Cg#uc-)=YjZ%4(F+|e9_vIJTi#$?$xC2=>bi3pn`q4DQ zSTrobmYY#NBoO7^l+%C1-MZQ{#jCdewTk=~dC)JW-i*|fbr}_W1b;&FM8*v9n_@wn z1otKATt|2A*{WVQn@Dm|iH4zb*l^qd9XyJHHO%2=UkZaMkeE6C+kBgPik6~@#*s2x zM|xC|o?~A&8vpA4+6qCa=1g|?v&-iG=*pS&Lx*RII$F*6cYcRLqqH|>+@!Niz1zOK z1eJz5S5n5yh6z|7gq+$g9TCn>2#$t0+dAGUb6((S-viflUP^z@)rIo@S_z+e*5I#} z9Svb7Z|?OwrDl6Wa~$B$3pD#}H*_^64kNrKoIpGn&>o+2(POWprRSk_=0&QLPf?0H zrp)@IJ49`lSeVsrZJfqa0WRhr{Q@)rb(%obUHTt}LmjPzlztR8aA;^R>MyLtIg8oRz6xtUXK_vPM>tAwYCIiUPnbp5Wwzu>gx-UwnF6wD3Zrx&MCbW^lCG9&3 z2tI+4JV?S2+xyJDh?>>yg<2JhDsD`&a5r2nbTIFB>ls`f7&%7)6XGn0 zysp0eqQ4$dmD9X9SfkQb^IZ3Idkrt4KmM2!s)j5^HKl+=_?BbHMhFIg2a+Uxk;1MH zkNl@Ki(zhopLu!<(;f;Xtp85Bda6Tmr-sD99pqKN^+eIOx zvqomkXY#dJDAJnxkMVsK`mcqKpCKSS_FGC{r=`#f0gdIiO0LfRmmsUzk0G37$=%?w zD>TVBhTY(Z_P*HF7ZMb(0@H9GF0|kw)vSA>m+1=47rIo)
mJ9Mg9v;EkUpR4w+yW66Yza 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/tools/PI/DevHome.PI/Assets/Square44x44Logo.altform-unplated_targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ccd38cb652da47febd4fd23335e835b025c18762 GIT binary patch literal 972 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 AApigX literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-16.png b/tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-16.png new file mode 100644 index 0000000000000000000000000000000000000000..b348a6af40cfbd059e96f3f064785aa64fc0666c GIT binary patch literal 475 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-32.png b/tools/PI/DevHome.PI/Assets/Square44x44Logo.targetsize-32.png new file mode 100644 index 0000000000000000000000000000000000000000..b922287fed9a6c784543b05c49d03ea1fd3f4c35 GIT binary patch literal 841 zcmV-P1GfB$P)pH`$2qL1F z9xAn<(y{_ydapmBhafKxS-IAOy@e1_y%))fzT_o(5GzRA-Pzf>o;iIpmMkW@V~Rq4 z!(7gpGvD`{^Igu7V3S4K2G|DJk^lo2#~T!;oh~_sDDU^dCyX$k5^546&8n15oFCXb zyONIH_R*H4Qggtx3PMv-V7U32j={zk6#;PW9ZFW!-D6Z)kvgFAg$I^rmL6UD_{y;J zcW*p9oGl;2fL^YS)m>otRHar!$tk?X?#ciJ0Y|qM@FP8J6Fd|t7Cs5#gXcR4-~*#l zT$AHxD1j4wZP`(KvNRU zmRkyEDyh}f1I?YpPlneO;I~*xDh$Z${mmZUKJ>L`TPbJblrYs7JaiKge)EO_$iiaf z?an8SQ+=(Yb1^Bg$Kf2^B=97)QH)0dST1kw2=-}uOcSt}sX?sZ6cjfG0E7SA?03Dq zYi2roPE(RQZH$2>@%?w}4j^!9(Yx}l`r4M>wo#n|O))}`6JXsgw}avYak&aGZNHSj z`6JK&8G!pPUP-m8s4s&y<~~T68K$d)$34JI2;K+9<7)TN5&iGl)&Q_5A}m1YXf)~| zln1I5#|ozgwP4Qh@;AFO>5-_$qX76GcTExeCkcuX)e&e4T`*!f#>1|s>G`!7uLwY5 zGPvByIn+DGd_NZlnVXtjywaaG*J?Bpz~V{(+$`<*eEa!sYdwrc1PB)v5?6A;YP2~3 z8!1~8fCx~Mv09)+0$EaE!T4GyySVB{ld3>R5JSO+q-g(0RSdudOzPH;9^A!%?8(?8 z48O*T=6@kZ3g&#laPq%uh#0FFpmV4x7~B=f;4pJ7fD1xRCt{2SW9dhMHv2PW_P{L!)+b4Y6BE1BoL~I62t)n2b2qN z;liPUrYTXXL^&f4NN_?3!G&88IW#m)T5+gAa6ufnjH7uh|*? zaTB+Wovaz#+7S7Z)?<%%_W!<_`5&_e{Lf*;O`utH1jLPwfVj~S5I6R|fa#H|2h(Qi zNIopcsC#M1xk0VMVbPa)ap6d*Q4;4yuBRDGKIB!|upkhwcFv)>|9r`D*WR4^{Mp~?j;m`!eJ3Ky zZlnN7q!dzsx*7uBr`9_NeQ*LLib8MVz^gd7&&|Ak<)w)WLqF8ofEpWjzegL@c4C}s zNSIyawoC#9@Gt-_f@*TeVcoq0J=XriT=1`EN3RTw%?+*V+T98*0cxw@l=-e-T*me1 zcZotb_8;xCQhhx1zzf}qIMo*XEF4H4I=bfk^A>i!6RQA5O>|a2%V_3>vAJPyv;WiA zzVGvb!plJDhqlqxkNdw$L@CHCp>NEN{`%h7+%a!k1ML>D0b=k6Eo>`9CNDmn!DjxG zlS`MoAhl#rGZhM68KC?kVI&^#gVp2sbGZrI6*QH|xKX6_`llA2S(!Pxd@*JBO%}Xt zrC<4j#gf2z@F?sC0Xu;ZV+otd+J5vOD$=d8)4o!INKbAP!!ZCvCzS@#J^?};!%SS; zEkkr?daG4t+PH<-37@-FeM(R%C~#(oYpt@QEkNb|dI@QE^t*%8M%6jNt!2=5FvRAYntOy&{vXH&*MeETAL~<%9kYX zw}klyv;?Twjs;uFwtD6^>&G%Vpx}oNY$dO-KY0)`#lOx@Tp0YO8Nyr9#1@j^`1#1Y zRMzgb(~xy;fAPofs}s8RwnJN-Sd|S1vvAM%fa@3hTq*O>`23Uaw!@ZwHHrzT+uB#D zbv7Fn;e9ORaGTw5g8bKk@TSkq5B}5&vh|=PK!rmj=wO*}4!2S{R^q`Ypp@pYxc?J< z`Z^Q-wb6yahL4rH&=WxMpeaP|%>2Mqs|o#A==%W1Ogxz0^-KMHQT9gH{Lpx00000NkvXXu0mjf438fA literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/StoreLogo.backup.png b/tools/PI/DevHome.PI/Assets/StoreLogo.backup.png new file mode 100644 index 0000000000000000000000000000000000000000..7385b56c0e4d3c6b0efe3324aa1194157d837826 GIT binary patch literal 1451 zcmaJ>eN5D57_Z|bH;{0+1#mbl)eTU3{h)Wf7EZV?;HD@XL@{B`Ui%(2aMxQ~xdXSv z5nzWi(LW)U2=Vc-cY@s7nPt{i0hc6!7xN4NNHI#EQl>YNBy8l4%x9gr_W-j zEZMQmmTIy(>;lblRfh`dIyTgc9W5d!VP$L4(kKrN1c5G~(O_#xG zAJCNTstD^5SeXFB+&$h=ToJP2H>xr$iqPs-#O*;4(!Fjw25-!gEb*)mU}=)J;Iu>w zxK(5XoD0wrPSKQ~rbL^Cw6O_03*l*}i=ydbu7adJ6y;%@tjFeXIXT+ms30pmbOP%Q zX}S;+LBh8Tea~TSkHzvX6$rYb)+n&{kSbIqh|c7hmlxmwSiq5iVhU#iEQ<>a18|O^Sln-8t&+t`*{qBWo5M?wFM(JuimAOb5!K#D}XbslM@#1ZVz_;!9U zpfEpLAOz=0g@bd6Xj_ILi-x^!M}73h^o@}hM$1jflTs|Yuj9AL@A3<-?MV4!^4q`e z)fO@A;{9K^?W?DbnesnPr6kK>$zaKo&;FhFd(GYFCIU^T+OIMb%Tqo+P%oq(IdX7S zf6+HLO?7o0m+p>~Tp5UrXWh!UH!wZ5kv!E`_w)PTpI(#Iw{AS`gH4^b(bm^ZCq^FZ zY9DD7bH}rq9mg88+KgA$Zp!iWncuU2n1AuIa@=sWvUR-s`Qb{R*kk(SPU^`$6BXz8 zn#7yaFOIK%qGxyi`dYtm#&qqox0$h=pNi#u=M8zUG@bpiZ=3sT=1}Trr}39cC)H|v zbL?W)=&s4zrh)7>L(|cc%$1#!zfL?HjpeP%T+x_a+jZ16b^iKOHxFEX$7d|8${H-* zIrOJ5w&i$>*D>AKaIoYg`;{L@jM((Kt?$N$5OnuPqVvq**Nm}(f0wwOF%iX_Pba;V z;m@wxX&NcV3?<1+u?A{y_DIj7#m3Af1rCE)o`D&Y3}0%7E;iX1yMDiS)sh0wKi!36 zL!Wmq?P^Ku&rK~HJd97KkLTRl>ScGFYZNlYytWnhmuu|)L&ND8_PmkayQb{HOY640 bno1(wj@u8DCVuFR|31B*4ek@pZJqxCDDe1x literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/StoreLogo.scale-100.png b/tools/PI/DevHome.PI/Assets/StoreLogo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..09db289b3b12cedee2ccced28fab2daa1cdfb621 GIT binary patch literal 1379 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 literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/StoreLogo.scale-400.png b/tools/PI/DevHome.PI/Assets/StoreLogo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..027cfdd1821a15a8a96c6821bc9693be7dad6393 GIT binary patch literal 4738 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 Tp~jV2RAMnRvNXhAcYXYSris-n literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-100.png b/tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-100.png new file mode 100644 index 0000000000000000000000000000000000000000..e0be5f18a9ac823d5a7085d5d14925cec7039ec3 GIT binary patch literal 1978 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 br7G&tjZ~8LMR{O&1zoYdcDeeJU+RAVZ0_yG literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-400.png b/tools/PI/DevHome.PI/Assets/Wide310x150Logo.scale-400.png new file mode 100644 index 0000000000000000000000000000000000000000..ca6ba215374100c5d1d92068843b3a5b7b9806aa GIT binary patch literal 8710 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 E1D?IjdjJ3c literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/BarWindow.xaml b/tools/PI/DevHome.PI/BarWindow.xaml new file mode 100644 index 0000000000..8b5ab53646 --- /dev/null +++ b/tools/PI/DevHome.PI/BarWindow.xaml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/BarWindow.xaml.cs b/tools/PI/DevHome.PI/BarWindow.xaml.cs new file mode 100644 index 0000000000..0a25336942 --- /dev/null +++ b/tools/PI/DevHome.PI/BarWindow.xaml.cs @@ -0,0 +1,722 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using DevHome.Common.Extensions; +using DevHome.PI.Controls; +using DevHome.PI.Helpers; +using DevHome.PI.Models; +using DevHome.PI.Properties; +using DevHome.PI.Telemetry; +using DevHome.PI.ViewModels; +using Microsoft.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Windows.UI.WindowManagement; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Accessibility; +using Windows.Win32.UI.WindowsAndMessaging; +using WinRT.Interop; +using WinUIEx; +using static DevHome.PI.Helpers.WindowHelper; + +namespace DevHome.PI; + +public partial class BarWindow : WindowEx, INotifyPropertyChanged +{ + private readonly Settings settings = Settings.Default; + private readonly string errorTitleText = CommonHelper.GetLocalizedString("ToolLaunchErrorTitle"); + private readonly string errorMessageText = CommonHelper.GetLocalizedString("ToolLaunchErrorMessage"); + private readonly BarWindowViewModel viewModel = new(); + + // Constants that control window sizes + private const int WindowPositionOffsetY = 30; + private const int FloatingHorizontalBarWidth = 700; + private const int FloatingHorizontalBarHeight = 70; + private const int FloatingVerticalBarWidth = 70; + private const int FloatingVerticalBarHeight = 700; + private const int DefaultExpandedViewTop = 30; + private const int DefaultExpandedViewLeft = 100; + private const int RightSideGap = 10; + + private readonly GridLength _gridLengthStar = new(1, GridUnitType.Star); + private int cursorPosX; // = 0; + private int cursorPosY; // = 0; + private int appWindowPosX; // = 0; + private int appWindowPosY; // = 0; + private bool isWindowMoving; // = false; + + private Orientation _barOrientation = Orientation.Horizontal; + + public Orientation BarOrientation + { + get => _barOrientation; + set + { + _barOrientation = value; + + if (value == Orientation.Horizontal) + { + SBarHorizontal.Visibility = Visibility.Visible; + SBarVertical.Visibility = Visibility.Collapsed; + ExternalToolsRepeater.Layout = Application.Current.Resources["ExternalToolsHorizontalLayout"] as StackLayout; + MainPanelMiddleRowDefinition.Height = GridLength.Auto; + MainPanelLastRowDefinition.Height = _gridLengthStar; + SystemResourceStackPanel.SetValue(Grid.RowProperty, 0); + SystemResourceStackPanel.SetValue(Grid.ColumnProperty, 2); + TopGrid.HorizontalAlignment = HorizontalAlignment.Stretch; + } + else + { + SBarHorizontal.Visibility = Visibility.Collapsed; + SBarVertical.Visibility = Visibility.Visible; + ExternalToolsRepeater.Layout = Application.Current.Resources["ExternalToolsVerticalLayout"] as StackLayout; + MainPanelMiddleRowDefinition.Height = _gridLengthStar; + MainPanelLastRowDefinition.Height = GridLength.Auto; + SystemResourceStackPanel.SetValue(Grid.RowProperty, 2); + SystemResourceStackPanel.SetValue(Grid.ColumnProperty, 0); + TopGrid.HorizontalAlignment = HorizontalAlignment.Center; + } + + OnPropertyChanged(nameof(BarOrientation)); + } + } + + private RECT monitorRect; + + private RestoreState restoreState = new() + { + Top = DefaultExpandedViewTop, + Left = DefaultExpandedViewLeft, + BarOrientation = Orientation.Horizontal, + IsLargePanelVisible = true, + }; + + private const int UnsnapGap = 9; + private double dpiScale = 1.0; + + private bool _isSnapped; + + private bool IsSnapped + { + get => _isSnapped; + set + { + _isSnapped = value; + SBarHorizontal.IsSnapped = value; + SBarVertical.IsSnapped = value; + } + } + + private bool _isMaximized; + + private bool IsMaximized + { + get => _isMaximized; + set + { + _isMaximized = value; + SBarHorizontal.IsMaximized = value; + SBarVertical.IsMaximized = value; + + if (value) + { + WindowState = WindowState.Maximized; + } + } + } + + private readonly ObservableCollection + + + + + + diff --git a/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml.cs b/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml.cs new file mode 100644 index 0000000000..3b0db0c8fc --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using DevHome.Common.Extensions; +using DevHome.PI.Properties; +using DevHome.PI.SettingsUi; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using WinUIEx; + +namespace DevHome.PI.Controls; + +public sealed partial class ExpandedViewControl : UserControl +{ + private readonly ExpandedViewControlViewModel viewModel = new(); + + public ExpandedViewControl() + { + InitializeComponent(); + viewModel.NavigationService.Frame = PageFrame; + } + + public Frame GetPageFrame() + { + return PageFrame; + } + + public void NavigateTo(Type viewModelType) + { + viewModel.NavigateTo(viewModelType); + } + + private void SettingsButton_Click(object sender, RoutedEventArgs e) + { + SettingsToolWindow settingsTool = new(Settings.Default.SettingsToolPosition); + settingsTool.Show(); + } +} diff --git a/tools/PI/DevHome.PI/Controls/GlowButton.xaml b/tools/PI/DevHome.PI/Controls/GlowButton.xaml new file mode 100644 index 0000000000..ebc6dc6635 --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/GlowButton.xaml @@ -0,0 +1,15 @@ + + + + + diff --git a/tools/PI/DevHome.PI/Controls/GlowButton.xaml.cs b/tools/PI/DevHome.PI/Controls/GlowButton.xaml.cs new file mode 100644 index 0000000000..9a416ee19d --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/GlowButton.xaml.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Windows.Input; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Hosting; + +namespace DevHome.PI.Controls; + +public sealed partial class GlowButton : UserControl +{ +#pragma warning disable CA2211 // Non-constant fields should not be visible +#pragma warning disable SA1401 // Fields should be private + public static DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(GlowButton), new PropertyMetadata(string.Empty)); +#pragma warning restore SA1401 // Fields should be private +#pragma warning restore CA2211 // Non-constant fields should not be visible + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public ICommand Command + { + get => (ICommand)GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } + + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register("Command", typeof(ICommand), typeof(GlowButton), new PropertyMetadata(null)); + + private readonly Compositor compositor; + private readonly ContainerVisual buttonVisual; + private readonly ScalarKeyFrameAnimation opacityAnimation; + + public GlowButton() + { + InitializeComponent(); + compositor = ElementCompositionPreview.GetElementVisual(this).Compositor; + buttonVisual = (ContainerVisual)ElementCompositionPreview.GetElementVisual(this); + + var result = RegisterPropertyChangedCallback(VisibilityProperty, VisibilityChanged); + opacityAnimation = CreatePulseAnimation("Opacity", 0.4f, 1.0f, TimeSpan.FromSeconds(5)); + } + + private ScalarKeyFrameAnimation CreatePulseAnimation(string property, float from, float to, TimeSpan duration) + { + var animation = compositor.CreateScalarKeyFrameAnimation(); + animation.InsertKeyFrame(0.0f, from); + animation.InsertKeyFrame(0.1f, to); + animation.InsertKeyFrame(0.3f, from); + animation.InsertKeyFrame(0.4f, to); + animation.InsertKeyFrame(0.6f, from); + animation.InsertKeyFrame(0.7f, to); + animation.InsertKeyFrame(0.8f, from); + animation.InsertKeyFrame(0.9f, to); + animation.Duration = duration; + animation.Target = property; + return animation; + } + + private void VisibilityChanged(DependencyObject sender, DependencyProperty dp) + { + if (Visibility == Visibility.Visible) + { + buttonVisual.StartAnimation("Opacity", opacityAnimation); + } + } +} diff --git a/tools/PI/DevHome.PI/Controls/ProcessSelectionButton.cs b/tools/PI/DevHome.PI/Controls/ProcessSelectionButton.cs new file mode 100644 index 0000000000..9fa1a49175 --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/ProcessSelectionButton.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; +using DevHome.PI.Helpers; +using DevHome.PI.Models; +using Microsoft.UI.Input; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; + +namespace DevHome.PI.Controls; + +public class ProcessSelectionButton : Button +{ + public ProcessSelectionButton() + { + } + + protected override void OnPointerEntered(PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + + // When the mouse cursor is over the button, change to the default cursor + ResetCursor(); + } + + protected override void OnPointerExited(PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + + // When the mouse cursor leaves the button, change the cursor to the cross + ChangeCursor(); + } + + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + base.OnPointerReleased(e); + + // Were we showing the select cursor? + if (this.ProtectedCursor == null) + { + return; + } + + Process? p; + Windows.Win32.Foundation.HWND hwnd; + + // Grab the window under the cursor and attach to that process + WindowHelper.GetAppInfoUnderMouseCursor(out p, out hwnd); + + if (p != null) + { + TargetAppData.Instance.SetNewAppData(p, hwnd); + } + + ResetCursor(); + } + + public void ChangeCursor() + { + this.ProtectedCursor = InputSystemCursor.Create(InputSystemCursorShape.Cross); + } + + public void ResetCursor() + { + this.ProtectedCursor = null; + } +} diff --git a/tools/PI/DevHome.PI/Controls/SystemBar.cs b/tools/PI/DevHome.PI/Controls/SystemBar.cs new file mode 100644 index 0000000000..afb963aeb0 --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/SystemBar.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.ComponentModel; +using System.Diagnostics; +using DevHome.Common.Extensions; +using DevHome.PI.Models; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Windows.Win32.Foundation; + +namespace DevHome.PI.Controls; + +// This is the base class for a system/chrome/title bar. There are at least 2 consumers of this, +// one which implements a system bar in the horizontal position, another in a vertical position +public partial class SystemBar : UserControl, INotifyPropertyChanged +{ + private bool _isSnapped; + + // This is used to toggle the "snap to an app" icon to have different values + // based on if we are snapped or not + public bool IsSnapped + { + get => _isSnapped; + set + { + _isSnapped = value; + CurrentSnapButtonText = _isSnapped ? UnsnapButtonText : SnapButtonText; + } + } + + private bool _isSnappingEnabled; + + // This is used to enable the "snap to an app" icon if we're allowed to snap + public bool IsSnappingEnabled + { + get => _isSnappingEnabled; + set + { + _isSnappingEnabled = value; + OnPropertyChanged(nameof(IsSnappingEnabled)); + } + } + + private bool _isMaximized; + + // This is used determine if the system bar should treat the window as maximized or not. That + // allows us to enable/disable the "Maximize" button and the "Restore" button in the titlebar + public bool IsMaximized + { + get => _isMaximized; + set + { + _isMaximized = value; + MaximizeButtonVisibility = _isMaximized ? Visibility.Collapsed : Visibility.Visible; + RestoreButtonVisibility = _isMaximized ? Visibility.Visible : Visibility.Collapsed; + OnPropertyChanged(nameof(MaximizeButtonVisibility)); + OnPropertyChanged(nameof(RestoreButtonVisibility)); + } + } + + protected Visibility MaximizeButtonVisibility { get; private set; } = Visibility.Visible; + + protected Visibility RestoreButtonVisibility { get; private set; } = Visibility.Collapsed; + + private const string UnsnapButtonText = "\ue89f"; + private const string SnapButtonText = "\ue8a0"; + + private string _currentSnapButtonText = SnapButtonText; + + protected string CurrentSnapButtonText + { + get => _currentSnapButtonText; + private set + { + if (_currentSnapButtonText != value) + { + _currentSnapButtonText = value; + OnPropertyChanged(nameof(CurrentSnapButtonText)); + } + } + } + + public SystemBar() + { + } + + public void Initialize() + { + IsSnappingEnabled = TargetAppData.Instance.HWnd != HWND.Null; + TargetAppData.Instance.PropertyChanged += TargetApp_PropertyChanged; + } + + private void TargetApp_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TargetAppData.HWnd)) + { + IsSnappingEnabled = TargetAppData.Instance.HWnd != HWND.Null; + } + } + + protected void SnapButton_Click(object sender, RoutedEventArgs e) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + barWindow.PerformSnapAction(); + } + + protected void MinimizeButton_Click(object sender, RoutedEventArgs e) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + barWindow.HandleMinimizeRequest(); + } + + protected void MaximizeButton_Click(object sender, RoutedEventArgs e) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + barWindow.HandleMaximizeRequest(); + } + + protected void RestoreButton_Click(object sender, RoutedEventArgs e) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + barWindow.HandleRestoreRequest(); + } + + protected void CloseButton_Click(object sender, RoutedEventArgs e) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + barWindow.HandleCloseRequest(); + } + + protected void CloseAllMenuItem_Click(object sender, RoutedEventArgs e) + { + var barWindow = Application.Current.GetService().DBarWindow; + Debug.Assert(barWindow != null, "BarWindow should not be null."); + barWindow.HandleCloseAllRequest(); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml b/tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml new file mode 100644 index 0000000000..6679b54fcb --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml.cs b/tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml.cs new file mode 100644 index 0000000000..49f3f04239 --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/SystemBarHorizontal.xaml.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.PI.Controls; + +// This is the class for a horiztonal system/chrome/title bar. +public sealed partial class SystemBarHorizontal : SystemBar +{ + public SystemBarHorizontal() + { + this.InitializeComponent(); + } +} diff --git a/tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml b/tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml new file mode 100644 index 0000000000..7e2605c99b --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml.cs b/tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml.cs new file mode 100644 index 0000000000..f5741c7977 --- /dev/null +++ b/tools/PI/DevHome.PI/Controls/SystemBarVertical.xaml.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.PI.Controls; + +// This is the class for a vertical system/chrome/title bar. +public sealed partial class SystemBarVertical : SystemBar +{ + public SystemBarVertical() + { + this.InitializeComponent(); + } +} diff --git a/tools/PI/DevHome.PI/DevHome.PI.csproj b/tools/PI/DevHome.PI/DevHome.PI.csproj new file mode 100644 index 0000000000..8df6514f97 --- /dev/null +++ b/tools/PI/DevHome.PI/DevHome.PI.csproj @@ -0,0 +1,214 @@ + + + + WinExe + DevHome.PI + app.manifest + x86;x64;ARM64 + $(Platform) + win-x86;win-x64;win-arm64 + Properties\PublishProfiles\win-$(Platform).pubxml + true + false + enable + 12.0 + DevHome.PI.Program + true + false + $(DefineConstants);DISABLE_XAML_GENERATED_MAIN + + + + + 10.0.19041.0 + PI.ico + PI.ico + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + $(DefaultXamlRuntime) + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + all + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + Always + + + MSBuild:Compile + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + 7 + + + 7 + + + 7 + + + 7 + + + 7 + + + 7 + + diff --git a/tools/PI/DevHome.PI/Helpers/CommonHelper.cs b/tools/PI/DevHome.PI/Helpers/CommonHelper.cs new file mode 100644 index 0000000000..4d22de4d5c --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/CommonHelper.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.IO; +using DevHome.Common.Extensions; +using DevHome.Common.Services; +using Microsoft.UI.Xaml; +using Serilog; +using Windows.ApplicationModel; + +namespace DevHome.PI.Helpers; + +internal sealed class CommonHelper +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(CommonHelper)); + + internal static string GetLocalizedString(string stringName, params object[] args) + { + var stringResource = new StringResource(); + var localizedString = stringResource.GetLocalized(stringName, args); + Debug.Assert(!string.IsNullOrEmpty(localizedString), stringName + " is empty. Check if " + stringName + " is present in Resources.resw."); + return localizedString; + } + + internal static void RunAsAdmin(int pid, string pageName) + { + var startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + + var aliasSubDirectoryPath = $"Microsoft\\WindowsApps\\{Package.Current.Id.FamilyName}\\devhome.pi.exe"; + var aliasPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), aliasSubDirectoryPath); + startInfo.FileName = aliasPath; + + // Pass pid and the page from where the admin request came from + startInfo.Arguments = $"--pid {pid} --expandWindow {pageName}"; + startInfo.UseShellExecute = true; + startInfo.Verb = "runas"; + + var process = new Process(); + process.StartInfo = startInfo; + + // Since a UAC prompt will be shown, we need to wait for the process to exit + // This can also be cancelled by the user which will result in an exception + try + { + process.Start(); + + // Close the primary window for this instance and exit + var primaryWindow = Application.Current.GetService(); + primaryWindow.Close(); + } + catch (Exception ex) + { + _log.Error(ex, "UAC to run PI as admin was denied"); + } + } +} diff --git a/tools/PI/DevHome.PI/Helpers/CommonInterop.cs b/tools/PI/DevHome.PI/Helpers/CommonInterop.cs new file mode 100644 index 0000000000..a3270345a2 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/CommonInterop.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace DevHome.PI.Helpers; + +internal sealed class CommonInterop +{ + // CSWin32 will not produce these methods for x86 so we need to define them here. + [DllImport("user32.dll", ExactSpelling = true, EntryPoint = "SetWindowLongPtrW", SetLastError = true)] + internal static extern nint SetWindowLongPtr64(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong); + + [DllImport("user32.dll", ExactSpelling = true, EntryPoint = "GetWindowLongPtrW", SetLastError = true)] + internal static extern nint GetWindowLongPtr64(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex); + + [DllImport("user32.dll", ExactSpelling = true, EntryPoint = "GetClassLongPtrW", SetLastError = true)] + internal static extern nint GetClassLongPtr64(HWND hWnd, GET_CLASS_LONG_INDEX nIndex); +} diff --git a/tools/PI/DevHome.PI/Helpers/DebugMonitor.cs b/tools/PI/DevHome.PI/Helpers/DebugMonitor.cs new file mode 100644 index 0000000000..18caa503ee --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/DebugMonitor.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Threading; +using DevHome.PI.Models; + +namespace DevHome.PI.Helpers; + +public sealed class DebugMonitor : IDisposable +{ + private readonly Process targetProcess; + private readonly ObservableCollection output; + private readonly EventWaitHandle stopEvent; + private readonly string errorMessageText = CommonHelper.GetLocalizedString("WinLogsAlreadyRunningErrorMessage"); + + private const string MutexName = "DevHome.PI.DebugMonitor.SingletonMutex"; + private const string StopEventName = "DebugMonitorStopEvent"; + private const string DBWinBufferReadyName = "DBWIN_BUFFER_READY"; + private const string DBWinDataReadyName = "DBWIN_DATA_READY"; + private const string DBWinBufferName = "DBWIN_BUFFER"; + + private static readonly List IgnoreLogList = []; + + public DebugMonitor(Process targetProcess, ObservableCollection output) + { + this.targetProcess = targetProcess; + this.targetProcess.Exited += TargetProcess_Exited; + this.output = output; + + stopEvent = new EventWaitHandle(false, EventResetMode.AutoReset, StopEventName); + } + + public void Start() + { + stopEvent.Reset(); + + // Don't initiate if debugger is attached. It makes debugging very slow. + if (Debugger.IsAttached) + { + return; + } + + // Check for multiple instances. It is possible to have multiple debug monitors listen on OutputDebugString, + // but the message would be randomly distributed among all running instances. + using var singletonMutex = new Mutex(false, MutexName, out var createdNew); + if (!createdNew) + { + throw new InvalidOperationException($"Failed to get the {MutexName} mutex."); + } + + bool isNewBufferReadyEvent; + using var bufferReadyEvent = new EventWaitHandle(false, EventResetMode.AutoReset, DBWinBufferReadyName, out isNewBufferReadyEvent); + bool isNewDataReadyEvent; + using var dataReadyEvent = new EventWaitHandle(false, EventResetMode.AutoReset, DBWinDataReadyName, out isNewDataReadyEvent); + + // Don't initiate if there is an existing OutputDebugString monitor running + if (!isNewBufferReadyEvent || !isNewDataReadyEvent) + { + WinLogsEntry entry = new(DateTime.Now, WinLogCategory.Error, errorMessageText, WinLogsHelper.DebugOutputLogsName); + output.Add(entry); + return; + } + + using var memoryMappedFile = MemoryMappedFile.CreateNew(DBWinBufferName, 4096); + while (true) + { + bufferReadyEvent.Set(); + var waitResult = WaitHandle.WaitAny(new[] { stopEvent, dataReadyEvent }); + + // Stop listenting to OutputDebugString if the debugger is attached. It makes debugging very slow. + if (Debugger.IsAttached) + { + break; + } + + // Stop event is triggered. + if (waitResult == 0) + { + break; + } + + if (waitResult == 1) + { + var timeGenerated = DateTime.Now; + + // The first DWORD of the shared memory buffer contains + // the process ID of the client that sent the debug string. + using var viewStream = memoryMappedFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); + using BinaryReader binaryReader = new BinaryReader(viewStream); + var pid = binaryReader.ReadUInt32(); + + if (pid == targetProcess.Id) + { + // Get the message from the stream. + var stringBuilder = new StringBuilder(); + while (binaryReader.PeekChar() != '\0') + { + stringBuilder.Append(binaryReader.ReadChar()); + } + + var entryMessage = stringBuilder.ToString(); + + if (!string.IsNullOrWhiteSpace(entryMessage)) + { + var hasIgnoreLog = false; + foreach (var ignoreLog in IgnoreLogList) + { + if (entryMessage.Contains(ignoreLog)) + { + hasIgnoreLog = true; + } + } + + if (!hasIgnoreLog) + { + WinLogsEntry entry = new(timeGenerated, WinLogCategory.Debug, entryMessage, WinLogsHelper.DebugOutputLogsName); + output.Add(entry); + } + } + } + } + } + } + + public void Stop() + { + if (!stopEvent.SafeWaitHandle.IsClosed) + { + stopEvent.Set(); + } + } + + public void Dispose() + { + stopEvent.Close(); + stopEvent.Dispose(); + + GC.SuppressFinalize(this); + } + + private void TargetProcess_Exited(object? sender, EventArgs e) + { + Stop(); + Dispose(); + } +} diff --git a/tools/PI/DevHome.PI/Helpers/DesktopBridgeHelper.cs b/tools/PI/DevHome.PI/Helpers/DesktopBridgeHelper.cs new file mode 100644 index 0000000000..4ef0a368ef --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/DesktopBridgeHelper.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Windows.Win32; +using Windows.Win32.Foundation; + +namespace DevHome.PI.Helpers; + +public class DesktopBridgeHelper +{ + public static unsafe bool IsMsixPackagedApp() + { + if (IsWindows7OrLower) + { + return false; + } + else + { + uint length = 0; + var empty = stackalloc char[0]; + WIN32_ERROR result = PInvoke.GetCurrentPackageFullName(ref length, empty); + var packageName = stackalloc char[(int)length]; + result = PInvoke.GetCurrentPackageFullName(ref length, packageName); + return result != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE; + } + } + + private static bool IsWindows7OrLower + { + get + { + var versionMajor = Environment.OSVersion.Version.Major; + var versionMinor = Environment.OSVersion.Version.Minor; + var version = versionMajor + ((double)versionMinor / 10); + return version <= 6.1; + } + } +} diff --git a/tools/PI/DevHome.PI/Helpers/ETWHelper.cs b/tools/PI/DevHome.PI/Helpers/ETWHelper.cs new file mode 100644 index 0000000000..33b529a75d --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/ETWHelper.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Security.Principal; +using DevHome.PI.Models; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Session; +using Serilog; + +namespace DevHome.PI.Helpers; + +internal sealed class ETWHelper : IDisposable +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(ETWHelper)); + private readonly Process targetProcess; + private readonly ObservableCollection output; + + // TODO Add providers + private static readonly List ProviderList = ["1AFF6089-E863-4D36-BDFD-3581F07440BE" /*COM Tracelog*/]; + private TraceEventSession? session; + + // From: https://learn.microsoft.com/windows-server/identity/ad-ds/manage/understand-security-identifiers + private const string PerformanceLogUsersSid = "S-1-5-32-559"; + + public ETWHelper(Process targetProcess, ObservableCollection output) + { + this.targetProcess = targetProcess; + this.targetProcess.Exited += TargetProcess_Exited; + this.output = output; + } + + public void Start() + { + var isUserInPerformanceLogUsersGroup = IsUserInPerformanceLogUsersGroup(); + + if (!isUserInPerformanceLogUsersGroup) + { + isUserInPerformanceLogUsersGroup = TryAddUserToPerformanceLogUsersGroup(); + } + + if (isUserInPerformanceLogUsersGroup) + { + var sessionName = "DevHomePITrace" + Process.GetCurrentProcess().SessionId; + + // Stop and dispose any existing session + session = TraceEventSession.GetActiveSession(sessionName); + if (session is not null) + { + session.Stop(); + session.Dispose(); + } + + using (session = new TraceEventSession(sessionName)) + { + // Filter the provider events based on processId + var providerOptions = new TraceEventProviderOptions { ProcessIDFilter = [targetProcess.Id] }; + foreach (var provider in ProviderList) + { + session.EnableProvider(provider, TraceEventLevel.Always, options: providerOptions); + } + + session.Source.Dynamic.All += EventsHandler; + session.Source.UnhandledEvents += UnHandledEventsHandler; + session.Source.Process(); + } + } + } + + public void Stop() + { + session?.Stop(); + } + + public void Dispose() + { + session?.Dispose(); + GC.SuppressFinalize(this); + } + + private void EventsHandler(TraceEvent traceEvent) + { + ETWEventHandler(traceEvent.ProcessID, traceEvent.TimeStamp, traceEvent.Level, traceEvent.ToString(CultureInfo.CurrentCulture)); + } + + private void UnHandledEventsHandler(TraceEvent traceEvent) + { + var errorMessage = CommonHelper.GetLocalizedString("UnhandledTraceEventErrorMessage", traceEvent.Dump()); + ETWEventHandler(traceEvent.ProcessID, traceEvent.TimeStamp, traceEvent.Level, errorMessage); + } + + private void ETWEventHandler(int processId, DateTime timeStamp, TraceEventLevel level, string message) + { + if (processId != targetProcess.Id) + { + return; + } + + var category = WinLogsHelper.ConvertTraceEventLevelToWinLogCategory(level); + var entry = new WinLogsEntry(timeStamp, category, message, WinLogsHelper.EtwLogsName); + output.Add(entry); + } + + private void TargetProcess_Exited(object? sender, EventArgs e) + { + Stop(); + Dispose(); + } + + public static bool IsUserInPerformanceLogUsersGroup() + { + WindowsIdentity processUserIdentity = WindowsIdentity.GetCurrent(); + var isPerformanceLogSidFound = processUserIdentity.Groups?.Any(sid => sid.Value == PerformanceLogUsersSid); + return isPerformanceLogSidFound ?? false; + } + + public static bool TryAddUserToPerformanceLogUsersGroup() + { + WindowsIdentity processUserIdentity = WindowsIdentity.GetCurrent(); + var userName = processUserIdentity.Name; + if (userName is null) + { + _log.Error("Unable to get the current user name"); + return false; + } + + var startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = Environment.SystemDirectory + "\\net.exe"; + + // Add the user to the Performance Log Users group + startInfo.Arguments = $"localgroup \"Performance Log Users\" {userName} /add"; + startInfo.UseShellExecute = true; + startInfo.Verb = "runas"; + + var process = new Process(); + process.StartInfo = startInfo; + + // Since a UAC prompt will be shown, we need to wait for the process to exit + // This can also be cancelled by the user which will result in an exception + try + { + process.Start(); + process.WaitForExit(); + + if (process.ExitCode == 0) + { + return true; + } + else + { + _log.Error("Unable to add the user to the Performance Log Users group"); + return false; + } + } + catch (Exception ex) + { + _log.Error(ex, "UAC to add the user to the Performance Log Users group was denied"); + } + + return false; + } +} diff --git a/tools/PI/DevHome.PI/Helpers/EnumStringConverter.cs b/tools/PI/DevHome.PI/Helpers/EnumStringConverter.cs new file mode 100644 index 0000000000..a89262890f --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/EnumStringConverter.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DevHome.PI.Helpers; + +#pragma warning disable SA1649 // File name should match first type name +public class EnumStringConverter : JsonConverter +#pragma warning restore SA1649 // File name should match first type name + where TEnum : struct +{ + public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException(); + } + + var enumString = reader.GetString(); + if (Enum.TryParse(enumString, ignoreCase: true, out TEnum result)) + { + return result; + } + + throw new JsonException($"Unable to convert \"{enumString}\" to enum type {typeof(TEnum)}."); + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } +} diff --git a/tools/PI/DevHome.PI/Helpers/ErrorLookupHelper.cs b/tools/PI/DevHome.PI/Helpers/ErrorLookupHelper.cs new file mode 100644 index 0000000000..65ff1586e0 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/ErrorLookupHelper.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.Data.Sqlite; +using Serilog; + +namespace DevHome.PI.Helpers; + +internal sealed class ErrorLookupHelper +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(ErrorLookupHelper)); + + private static SqliteConnectionStringBuilder? _dbConnectionString; + + private static SqliteConnectionStringBuilder DbConnectionString + { + get + { + if (_dbConnectionString == null) + { + _dbConnectionString = new SqliteConnectionStringBuilder(); + var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); + var dbPath = Path.Combine(path ?? string.Empty, "errors.db"); + + _dbConnectionString.DataSource = dbPath; + _dbConnectionString.Mode = SqliteOpenMode.ReadOnly; + } + + return _dbConnectionString; + } + } + + public static AppError[]? LookupError(int error) + { + try + { + using SqliteConnection connection = new(DbConnectionString.ConnectionString); + connection.Open(); + AppError[]? errors = LookupErrors(connection, error); + connection.Close(); + return errors; + } + catch + { + _log.Error("Failed to look up errors: {AppError}", error.ToString(CultureInfo.CurrentCulture)); + } + + return null; + } + + private static AppError[]? LookupErrors(SqliteConnection connection, int hresult) + { + // Look up a solution for an error. + SqliteCommand errorCommand = connection.CreateCommand(); + errorCommand.CommandText = @"select Name, Help from tblErrors WHERE code=@code"; + + SqliteParameter errorParam = new("@code", hresult.ToString(CultureInfo.CurrentCulture)); + errorCommand.Parameters.Add(errorParam); + SqliteDataReader errorReader = errorCommand.ExecuteReader(); + IList errors = []; + + while (errorReader.Read()) + { + AppError error = new() + { + Code = hresult, + Name = errorReader.GetString(0), + Help = errorReader.GetString(1), + }; + errors.Add(error); + } + + errorReader.Close(); + return errors.ToArray(); + } +} + +public class AppError +{ + public int Code { get; set; } + + public string Name { get; set; } = string.Empty; + + public string Help { get; set; } = string.Empty; +} diff --git a/tools/PI/DevHome.PI/Helpers/EventViewerHelper.cs b/tools/PI/DevHome.PI/Helpers/EventViewerHelper.cs new file mode 100644 index 0000000000..1ac44fe9c0 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/EventViewerHelper.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; +using DevHome.PI.Models; + +namespace DevHome.PI.Helpers; + +internal sealed class EventViewerHelper : IDisposable +{ + private readonly Process targetProcess; + private readonly ObservableCollection output; + private readonly EventLogWatcher? eventLogWatcher; + + public EventViewerHelper(Process targetProcess, ObservableCollection output) + { + this.targetProcess = targetProcess; + this.targetProcess.Exited += TargetProcess_Exited; + this.output = output; + + try + { + // Subscribe for Application events matching the processName. + var filterQuery = "*[System[Provider[@Name=\"" + targetProcess.ProcessName + "\"]]]"; + EventLogQuery subscriptionQuery = new("Application", PathType.LogName, filterQuery); + eventLogWatcher = new EventLogWatcher(subscriptionQuery); + eventLogWatcher.EventRecordWritten += new EventHandler(EventLogEventRead); + } + catch (EventLogReadingException) + { + var message = CommonHelper.GetLocalizedString("UnableToStartEventViewerErrorMessage"); + WinLogsEntry entry = new(DateTime.Now, WinLogCategory.Error, message, WinLogsHelper.EventViewerName); + output.Add(entry); + } + } + + public void Start() + { + if (eventLogWatcher is not null) + { + eventLogWatcher.Enabled = true; + } + } + + public void Stop() + { + if (eventLogWatcher is not null) + { + eventLogWatcher.Enabled = false; + } + } + + public void Dispose() + { + if (eventLogWatcher is not null) + { + eventLogWatcher.Dispose(); + } + + GC.SuppressFinalize(this); + } + + public void EventLogEventRead(object? obj, EventRecordWrittenEventArgs eventArg) + { + if (eventArg.EventRecord != null) + { + WinLogCategory category = WinLogsHelper.ConvertStandardEventLevelToWinLogCategory(eventArg.EventRecord.Level); + var message = eventArg.EventRecord.FormatDescription(); + WinLogsEntry entry = new(eventArg.EventRecord.TimeCreated, category, message, WinLogsHelper.EventViewerName); + output.Add(entry); + } + } + + private void TargetProcess_Exited(object? sender, EventArgs e) + { + Stop(); + Dispose(); + } +} diff --git a/tools/PI/DevHome.PI/Helpers/ExternalTool.cs b/tools/PI/DevHome.PI/Helpers/ExternalTool.cs new file mode 100644 index 0000000000..9bfc24db8d --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/ExternalTool.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Text.Json.Serialization; +using Microsoft.UI.Xaml.Media.Imaging; +using Serilog; +using Windows.Win32.Foundation; +using static DevHome.PI.Helpers.WindowHelper; + +namespace DevHome.PI.Helpers; + +public enum ExternalToolArgType +{ + None, + ProcessId, + Hwnd, +} + +// ExternalTool represents an imported tool +public class ExternalTool : INotifyPropertyChanged +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(ExternalTool)); + + public string ID { get; private set; } + + public string Name { get; private set; } + + public string Executable { get; private set; } + + [JsonConverter(typeof(EnumStringConverter))] + public ExternalToolArgType ArgType { get; private set; } = ExternalToolArgType.None; + + public string ArgPrefix + { + get; private set; + } + + public string OtherArgs + { + get; private set; + } + + [JsonIgnore] + private SoftwareBitmapSource? _toolIcon; + + [JsonIgnore] + public SoftwareBitmapSource? ToolIcon + { + get => _toolIcon; + private set + { + _toolIcon = value; + OnPropertyChanged(nameof(ToolIcon)); + } + } + + public ExternalTool( + string name, + string executable, + ExternalToolArgType argtype, + string argprefix = "", + string otherArgs = "") + { + Name = name; + Executable = executable; + ArgType = argtype; + ArgPrefix = argprefix; + OtherArgs = otherArgs; + + ID = Guid.NewGuid().ToString(); + + if (!string.IsNullOrEmpty(executable)) + { + GetToolImage(); + } + } + + private async void GetToolImage() + { + try + { + var toolIcon = System.Drawing.Icon.ExtractAssociatedIcon(Executable); + + // Fall back to Windows default app icon. + toolIcon ??= System.Drawing.Icon.FromHandle(LoadDefaultAppIcon()); + + if (toolIcon is not null) + { + ToolIcon = await WindowHelper.GetWinUI3BitmapSourceFromGdiBitmap(toolIcon.ToBitmap()); + } + } + catch (Exception ex) + { + _log.Debug(ex, "Unable to fetch tool image."); + } + } + + internal string CreateFullCommandLine(int? pid, HWND? hwnd) + { + return "\"" + Executable + "\"" + CreateCommandLine(pid, hwnd); + } + + internal string CreateCommandLine(int? pid, HWND? hwnd) + { + var commandLine = $" {OtherArgs}"; + + if (ArgType == ExternalToolArgType.Hwnd && hwnd is not null) + { + commandLine = $" {ArgPrefix} {hwnd:D} {OtherArgs}"; + } + else if (ArgType == ExternalToolArgType.ProcessId && pid is not null) + { + commandLine = $" {ArgPrefix} {pid:D} {OtherArgs}"; + } + + return commandLine; + } + + internal virtual Process? Invoke(int? pid, HWND? hwnd) + { + try + { + var toolProcess = new Process(); + toolProcess.StartInfo.FileName = Executable; + toolProcess.StartInfo.Arguments = CreateCommandLine(pid, hwnd); + toolProcess.StartInfo.UseShellExecute = false; + toolProcess.StartInfo.RedirectStandardOutput = true; + toolProcess.Start(); + return toolProcess; + } + catch (Exception ex) + { + _log.Error(ex, "Tool launched failed"); + return null; + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/tools/PI/DevHome.PI/Helpers/ExternalToolsHelper.cs b/tools/PI/DevHome.PI/Helpers/ExternalToolsHelper.cs new file mode 100644 index 0000000000..a65bcdb74e --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/ExternalToolsHelper.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Reflection; +using System.Text.Json; +using Serilog; +using Windows.Storage; + +namespace DevHome.PI.Helpers; + +internal sealed class ExternalToolsHelper +{ + private readonly JsonSerializerOptions serializerOptions = new() { WriteIndented = true }; + private readonly ObservableCollection externalTools = []; + private readonly string toolInfoFileName; + + public static readonly ExternalToolsHelper Instance = new(); + + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(ExternalToolsHelper)); + + public ReadOnlyObservableCollection ExternalTools { get; set; } + + private ExternalToolsHelper() + { + string localFolder; + if (DesktopBridgeHelper.IsMsixPackagedApp()) + { + localFolder = ApplicationData.Current.LocalFolder.Path; + } + else + { + localFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? string.Empty; + } + + toolInfoFileName = Path.Combine(localFolder, "externaltools.json"); + ExternalTools = new(externalTools); + } + + internal void Init() + { + if (File.Exists(toolInfoFileName)) + { + try + { + var jsonData = File.ReadAllText(toolInfoFileName); + var existingData = JsonSerializer.Deserialize(jsonData) ?? []; + foreach (var data in existingData) + { + externalTools.Add(data); + } + } + catch (Exception ex) + { + // TODO If we failed parsing the JSON file... should we just delete it? + _log.Error(ex, "Failed to parse {tool}", toolInfoFileName); + } + } + } + + public ExternalTool AddExternalTool(ExternalTool tool) + { + externalTools.Add(tool); + + // Write out to JSON file + var updatedJson = JsonSerializer.Serialize(externalTools, serializerOptions); + + try + { + File.WriteAllText(toolInfoFileName, updatedJson); + } + catch (Exception ex) + { + // TODO What should we do if we're unable to write to the file? + _log.Error(ex, "AddExternalTool unable to write to file"); + } + + return tool; + } + + public void RemoveExternalTool(ExternalTool tool) + { + if (externalTools.Remove(tool)) + { + // Write out to JSON file + var updatedJson = JsonSerializer.Serialize(externalTools, serializerOptions); + + try + { + File.WriteAllText(toolInfoFileName, updatedJson); + } + catch (Exception ex) + { + _log.Error(ex, "RemoveExternalTool unable to write to file"); + } + } + } +} diff --git a/tools/PI/DevHome.PI/Helpers/HotKeyHelper.cs b/tools/PI/DevHome.PI/Helpers/HotKeyHelper.cs new file mode 100644 index 0000000000..20d09e9b36 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/HotKeyHelper.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Microsoft.UI.Xaml; +using Windows.System; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using WinUIEx.Messaging; + +namespace DevHome.PI.Helpers; + +// Note: instead of making this class disposable, we're disposing the WindowMessageMonitor in +// UnregisterHotKey, and MainWindow calls this in its Closing event handler. +#pragma warning disable CA1001 // Types that own disposable fields should be disposable +public class HotKeyHelper// : IDisposable +#pragma warning restore CA1001 // Types that own disposable fields should be disposable +{ + internal ushort HotkeyID { get; private set; } + + private const string NoWindowHandleException = "Cannot get window handle: are you doing this too early?"; + private readonly HWND windowHandle; + private readonly Action onHotKeyPressed; + private readonly WindowMessageMonitor windowMessageMonitor; + + public HotKeyHelper(Window handlerWindow, Action hotKeyHandler) + { + onHotKeyPressed = hotKeyHandler; + + // Create a unique Id for this class in this instance. + var atomName = $"{Environment.CurrentManagedThreadId:X8}{GetType().FullName}"; + HotkeyID = PInvoke.GlobalAddAtom(atomName); + + // Set up the window message hook to listen for hot keys. + windowHandle = (HWND)WinRT.Interop.WindowNative.GetWindowHandle(handlerWindow); + if (windowHandle.IsNull) + { + throw new InvalidOperationException(NoWindowHandleException); + } + + windowMessageMonitor = new WindowMessageMonitor(windowHandle); + windowMessageMonitor.WindowMessageReceived += OnWindowMessageReceived; + } + + private void OnWindowMessageReceived(object? sender, WindowMessageEventArgs e) + { + if (e.Message.MessageId == PInvoke.WM_HOTKEY) + { + var keyId = (int)e.Message.WParam; + if (keyId == HotkeyID) + { + onHotKeyPressed?.Invoke((int)e.Message.LParam); + e.Handled = true; + } + } + } + + internal void RegisterHotKey(VirtualKey key, HOT_KEY_MODIFIERS modifiers) + { + PInvoke.RegisterHotKey(windowHandle, HotkeyID, modifiers, (uint)key); + } + + internal void UnregisterHotKey() + { + if (HotkeyID != 0) + { + _ = PInvoke.UnregisterHotKey(windowHandle, HotkeyID); + PInvoke.GlobalDeleteAtom(HotkeyID); + windowMessageMonitor.Dispose(); + HotkeyID = 0; + } + } + + /*public void Dispose() + { + UnregisterHotKey(); + GC.SuppressFinalize(this); + }*/ +} diff --git a/tools/PI/DevHome.PI/Helpers/InsightsHelper.cs b/tools/PI/DevHome.PI/Helpers/InsightsHelper.cs new file mode 100644 index 0000000000..9d6eafc5f2 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/InsightsHelper.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Text.RegularExpressions; +using DevHome.PI.Models; +using Serilog; + +namespace DevHome.PI.Helpers; + +internal sealed partial class InsightsHelper +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(InsightsHelper)); + + // TODO: Add more patterns for different insights. + // TODO: Insights patterns should be in a database of some kind. + // TODO: Pattern texts should be extracted from localized windows builds. + [GeneratedRegex( + @"The process cannot access the file '(.+?)' because it is being used by another process", + RegexOptions.IgnoreCase, "en-US")] + private static partial Regex LockedFileErrorRegex(); + + // TODO The following are examples of a simple pattern where we map error code to some help text. + // This is temporary: longer-term, we should update the errors.db + // to map the error code to a description, plus any existing documented solution options. + [GeneratedRegex(@"0xc0000409", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex BufferOverflowErrorRegex(); + + [GeneratedRegex(@"0xc0000005", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex MemoryErrorRegex(); + + private static readonly List RegexList = []; + + static InsightsHelper() + { + RegexList.Add(new InsightRegex(InsightType.LockedFile, LockedFileErrorRegex())); + RegexList.Add(new InsightRegex(InsightType.Security, BufferOverflowErrorRegex())); + RegexList.Add(new InsightRegex(InsightType.MemoryViolation, MemoryErrorRegex())); + } + + internal static Insight? FindPattern(string errorText) + { + Insight? newInsight = null; + + foreach (var insightRegex in RegexList) + { + var match = insightRegex.Regex.Match(errorText); + if (match.Success) + { + newInsight = new Insight + { + InsightType = insightRegex.InsightType, + }; + + // Once we flesh out our error database, we should have a more structured way to + // handle different types of insights, rather than a switch statement. + switch (insightRegex.InsightType) + { + case InsightType.LockedFile: + { + // Extract the file path from the matched group. + var pattern = string.Empty; + if (match.Groups != null && match.Groups.Count > 1) + { + pattern = match.Groups[1].Value; + } + + newInsight.Title = CommonHelper.GetLocalizedString("LockedFileInsightTitle"); + var processName = GetLockingProcess(pattern); + if (!string.IsNullOrEmpty(processName)) + { + newInsight.Description = + CommonHelper.GetLocalizedString("LockedFileInsightSpecificDescription", pattern, processName); + } + else + { + newInsight.Description = + CommonHelper.GetLocalizedString("LockedFileInsightUnknownDescription", pattern); + } + } + + break; + + case InsightType.Security: + { + var hexValue = match.Value; + var intConverter = new Int32Converter(); + var errorAsInt = (int?)intConverter.ConvertFromString(hexValue); + if (errorAsInt is not null) + { + var errors = ErrorLookupHelper.LookupError((int)errorAsInt); + if (errors is not null && errors.Length > 0) + { + var error = errors[0]; + { + newInsight.Description = + CommonHelper.GetLocalizedString("GenericInsightDescription", error.Name, error.Help); + } + } + } + + newInsight.Title = CommonHelper.GetLocalizedString("SecurityInsightTitle"); + } + + break; + + case InsightType.MemoryViolation: + { + var hexValue = match.Value; + var intConverter = new Int32Converter(); + var errorAsInt = (int?)intConverter.ConvertFromString(hexValue); + if (errorAsInt is not null) + { + var errors = ErrorLookupHelper.LookupError((int)errorAsInt); + if (errors is not null && errors.Length > 0) + { + var error = errors[0]; + { + if (IsPythonCtypesError(errorText, out var description)) + { + newInsight.Description = description; + } + else + { + newInsight.Description = + CommonHelper.GetLocalizedString("GenericInsightDescription", error.Name, error.Help); + } + } + } + } + + newInsight.Title = CommonHelper.GetLocalizedString("MemoryInsightTitle"); + } + + break; + + default: + break; + } + + break; + } + } + + return newInsight; + } + + // This is an example of an error that requires additional runtime processing to + // determine the locking process, so it cannot be handled in the error database alone. + private static string GetLockingProcess(string lockedFilePath) + { + var lockingProcess = string.Empty; + + try + { + // Determines if the specified file is locked by another process. + _ = RestartManagerHelper.GetLockingProcesses(lockedFilePath, out var processes); + if (processes != null && processes.Count > 0) + { + var process = processes[0]; + lockingProcess = process.ProcessName; + } + } + catch (Exception ex) + { + _log.Debug(ex, "Unable to determine if process is locked."); + } + + return lockingProcess; + } + + // We're special-casing Python ctypes errors here, just to exercise this type of issue + // pattern, but longer-term this should be handled by some data relationship in the errors.db. + private static bool IsPythonCtypesError(string errorText, out string description) + { + var result = false; + description = string.Empty; + var appPathPattern = @"Faulting application path: .*\\python\.exe"; + var modulePathPattern = @"Faulting module path: .*\\_ctypes\.pyd"; + var hasAppPath = Regex.IsMatch(errorText, appPathPattern); + var hasModulePath = Regex.IsMatch(errorText, modulePathPattern); + + if (hasAppPath && hasModulePath) + { + description = CommonHelper.GetLocalizedString("PythonCtypesDescription"); + result = true; + } + + return result; + } +} diff --git a/tools/PI/DevHome.PI/Helpers/RestartManagerHelper.cs b/tools/PI/DevHome.PI/Helpers/RestartManagerHelper.cs new file mode 100644 index 0000000000..ce298db6eb --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/RestartManagerHelper.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.RestartManager; + +namespace DevHome.PI.Helpers; + +internal static class RestartManagerHelper +{ + // Find out what process(es) have a lock on the specified file. + internal static WIN32_ERROR GetLockingProcesses(string filePath, out List processes) + { + var key = Guid.NewGuid().ToString(); + processes = []; + + // Start a Restart Manager session. + var result = WIN32_ERROR.ERROR_SUCCESS; + uint handle; + unsafe + { + fixed (char* p = key) + { + PInvoke.RmStartSession(out handle, p); + } + } + + if (result != 0) + { + return result; + } + + try + { + uint pnProcInfo = 0; + var lpdwRebootReasons = (uint)RM_REBOOT_REASON.RmRebootReasonNone; + + unsafe + { + fixed (char* p = filePath) + { + var filePathStr = new PCWSTR(p); + var resources = new ReadOnlySpan(&filePathStr, 1); + var uniqueProcesses = default(Span); + var serviceNames = default(ReadOnlySpan); + + // Specify the given file as a resource to be managed by the Restart Manager. + result = PInvoke.RmRegisterResources(handle, resources, uniqueProcesses, serviceNames); + if (result != 0) + { + return result; + } + } + } + + // Note: there's a race here - the first call to RmGetList returns the count of processes, + // but when we call RmGetList again to get them this number might have changed. + unsafe + { + result = PInvoke.RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, out lpdwRebootReasons); + if (result == WIN32_ERROR.ERROR_MORE_DATA) + { + var processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + + fixed (RM_PROCESS_INFO* processArrayPtr = processInfo) + { + pnProcInfo = pnProcInfoNeeded; + + // Get the list of running processes that are using the given resource (file). + result = PInvoke.RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processArrayPtr, out lpdwRebootReasons); + if (result == 0) + { + // Enumerate all of the returned PIDS, get a Process for each one, and add it to the list. + processes = new List((int)pnProcInfo); + for (var i = 0; i < pnProcInfo; i++) + { + try + { + processes.Add(Process.GetProcessById((int)processInfo[i].Process.dwProcessId)); + } + catch (ArgumentException) + { + // The process might have died before we got to look at it. + } + } + } + else + { + return result; + } + } + } + else if (result != 0) + { + return result; + } + } + } + finally + { + _ = PInvoke.RmEndSession(handle); + } + + return 0; + } +} diff --git a/tools/PI/DevHome.PI/Helpers/WatsonHelper.cs b/tools/PI/DevHome.PI/Helpers/WatsonHelper.cs new file mode 100644 index 0000000000..4a7d8e308b --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/WatsonHelper.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; +using System.Globalization; +using System.IO; +using System.Linq; +using DevHome.PI.Models; + +namespace DevHome.PI.Helpers; + +internal sealed class WatsonHelper : IDisposable +{ + private const string WatsonQueryPart1 = "(*[System[Provider[@Name=\"Application Error\"]]] and *[System[EventID=1000]])"; + private const string WatsonQueryPart2 = "(*[System[Provider[@Name=\"Windows Error Reporting\"]]] and *[System[EventID=1001]])"; + + private readonly Process targetProcess; + private readonly EventLogWatcher? eventLogWatcher; + private readonly ObservableCollection? watsonOutput; + private readonly ObservableCollection? winLogsPageOutput; + + public WatsonHelper(Process targetProcess, ObservableCollection? watsonOutput, ObservableCollection? winLogsPageOutput) + { + this.targetProcess = targetProcess; + this.targetProcess.Exited += TargetProcess_Exited; + this.watsonOutput = watsonOutput; + this.winLogsPageOutput = winLogsPageOutput; + + try + { + // Subscribe for Application events matching the processName. + var filterQuery = string.Format(CultureInfo.CurrentCulture, "{0} or {1}", WatsonQueryPart1, WatsonQueryPart2); + EventLogQuery subscriptionQuery = new("Application", PathType.LogName, filterQuery); + eventLogWatcher = new EventLogWatcher(subscriptionQuery); + eventLogWatcher.EventRecordWritten += new EventHandler(EventLogEventRead); + } + catch (EventLogReadingException) + { + var message = CommonHelper.GetLocalizedString("WatsonStartErrorMessage"); + WinLogsEntry entry = new(DateTime.Now, WinLogCategory.Error, message, WinLogsHelper.WatsonName); + winLogsPageOutput?.Add(entry); + } + } + + public void Start() + { + if (eventLogWatcher is not null) + { + eventLogWatcher.Enabled = true; + } + } + + public void Stop() + { + if (eventLogWatcher is not null) + { + eventLogWatcher.Enabled = false; + } + } + + public void Dispose() + { + if (eventLogWatcher is not null) + { + eventLogWatcher.Dispose(); + } + + GC.SuppressFinalize(this); + } + + public void EventLogEventRead(object? obj, EventRecordWrittenEventArgs eventArg) + { + var eventRecord = eventArg.EventRecord; + if (eventRecord != null) + { + if (eventRecord.Id == 1000 && eventRecord.ProviderName.Equals("Application Error", StringComparison.OrdinalIgnoreCase)) + { + var filePath = eventRecord.Properties[10].Value.ToString() ?? string.Empty; + if (filePath.Contains(targetProcess.ProcessName, StringComparison.OrdinalIgnoreCase)) + { + var timeGenerated = eventRecord.TimeCreated ?? DateTime.Now; + var moduleName = eventRecord.Properties[3].Value.ToString() ?? string.Empty; + var executable = eventRecord.Properties[0].Value.ToString() ?? string.Empty; + var eventGuid = eventRecord.Properties[12].Value.ToString() ?? string.Empty; + var report = new WatsonReport(timeGenerated, moduleName, executable, eventGuid); + watsonOutput?.Add(report); + + WinLogsEntry entry = new(timeGenerated, WinLogCategory.Error, eventRecord.FormatDescription(), WinLogsHelper.WatsonName); + winLogsPageOutput?.Add(entry); + } + } + else if (eventRecord.Id == 1001 && eventRecord.ProviderName.Equals("Windows Error Reporting", StringComparison.OrdinalIgnoreCase)) + { + // See if we've already put this into our Collection. + for (var i = 0; i < watsonOutput?.Count; i++) + { + var existingReport = watsonOutput[i]; + if (existingReport.EventGuid.Equals(eventRecord.Properties[19].Value.ToString(), StringComparison.OrdinalIgnoreCase)) + { + existingReport.WatsonLog = eventRecord.FormatDescription(); + try + { + // List files available in the archive. + var directoryPath = eventRecord.Properties[16].Value.ToString(); + if (Directory.Exists(directoryPath)) + { + IEnumerable files = Directory.EnumerateFiles(directoryPath); + foreach (var file in files) + { + existingReport.WatsonReportFile = File.ReadAllText(file); + } + } + } + catch + { + } + + break; + } + } + } + } + } + + public List GetWatsonReports() + { + Dictionary reports = []; + EventLog eventLog = new("Application"); + + foreach (EventLogEntry entry in eventLog.Entries) + { + if (entry.InstanceId == 1000 + && entry.Source.Equals("Application Error", StringComparison.OrdinalIgnoreCase) + && entry.ReplacementStrings[10].Contains(targetProcess.ProcessName, StringComparison.OrdinalIgnoreCase)) + { + var timeGenerated = entry.TimeGenerated; + var moduleName = entry.ReplacementStrings[3]; + var executable = entry.ReplacementStrings[0]; + var eventGuid = entry.ReplacementStrings[12]; + var report = new WatsonReport(timeGenerated, moduleName, executable, eventGuid); + reports.Add(entry.ReplacementStrings[12], report); + } + else if (entry.InstanceId == 1001 + && entry.Source.Equals("Windows Error Reporting", StringComparison.OrdinalIgnoreCase)) + { + // See if we've already put this into our Dictionary. + if (reports.TryGetValue(entry.ReplacementStrings[19], out WatsonReport? report)) + { + report.WatsonLog = entry.Message; + + try + { + // List files available in the archive. + if (Directory.Exists(entry.ReplacementStrings[16])) + { + var files = Directory.EnumerateFiles(entry.ReplacementStrings[16]); + foreach (var file in files) + { + report.WatsonReportFile = File.ReadAllText(file); + } + } + } + catch + { + } + } + } + } + + return reports.Values.ToList(); + } + + private void TargetProcess_Exited(object? sender, EventArgs e) + { + Stop(); + Dispose(); + } +} diff --git a/tools/PI/DevHome.PI/Helpers/WinLogsHelper.cs b/tools/PI/DevHome.PI/Helpers/WinLogsHelper.cs new file mode 100644 index 0000000000..967cfe66c2 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/WinLogsHelper.cs @@ -0,0 +1,276 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using DevHome.PI.Models; +using Microsoft.Diagnostics.Tracing; +using Microsoft.UI; +using Microsoft.UI.Xaml.Media; + +namespace DevHome.PI.Helpers; + +public class WinLogsHelper : IDisposable +{ + public const string EtwLogsName = "ETW Logs"; + public const string DebugOutputLogsName = "DebugOutput"; + public const string EventViewerName = "EventViewer"; + public const string WatsonName = "Watson"; + + private readonly ETWHelper etwHelper; + private readonly DebugMonitor debugMonitor; + private readonly EventViewerHelper eventViewerHelper; + private readonly WatsonHelper watsonHelper; + private readonly ObservableCollection output; + private readonly Process targetProcess; + + private Thread? etwThread; + private Thread? debugMonitorThread; + private Thread? eventViewerThread; + private Thread? watsonThread; + + public bool IsETWEnabled { get; } + + public WinLogsHelper(Process targetProcess, ObservableCollection output) + { + this.targetProcess = targetProcess; + this.output = output; + IsETWEnabled = ETWHelper.IsUserInPerformanceLogUsersGroup(); + + // Initialize ETW logs + etwHelper = new ETWHelper(targetProcess, output); + + // Initialize DebugMonitor + debugMonitor = new DebugMonitor(targetProcess, output); + + // Initialize EventViewer + eventViewerHelper = new EventViewerHelper(targetProcess, output); + + // Initialize Watson + watsonHelper = new WatsonHelper(targetProcess, null, output); + + Start(); + } + + public void Start() + { + if (IsETWEnabled) + { + StartETWLogsThread(); + } + + StartEventViewerThread(); + StartWatsonThread(); + } + + public void Stop() + { + // Stop ETW logs + StopETWLogsThread(); + + // Stop Debug Outputs + StopDebugOutputsThread(); + + // Stop Event Viewer + StopEventViewerThread(); + + // Stop Watson + StopWatsonThread(); + } + + public void Dispose() + { + etwHelper.Dispose(); + debugMonitor.Dispose(); + eventViewerHelper.Dispose(); + watsonHelper.Dispose(); + GC.SuppressFinalize(this); + } + + private void StartETWLogsThread() + { + // Stop and close existing thread if any + StopETWLogsThread(); + + // Start a new thread + etwThread = new Thread(() => + { + etwHelper.Start(); + }); + etwThread.Name = EtwLogsName + " Thread"; + etwThread.Start(); + } + + private void StopETWLogsThread() + { + etwHelper.Stop(); + + if (Thread.CurrentThread != etwThread) + { + etwThread?.Join(); + } + } + + private void StartDebugOutputsThread() + { + // Stop and close existing thread if any + StopDebugOutputsThread(); + + // Start a new thread + debugMonitorThread = new Thread(() => + { + // Start Debug Outputs + debugMonitor.Start(); + }); + debugMonitorThread.Name = DebugOutputLogsName + " Thread"; + debugMonitorThread.Start(); + } + + private void StopDebugOutputsThread() + { + debugMonitor.Stop(); + + if (Thread.CurrentThread != debugMonitorThread) + { + debugMonitorThread?.Join(); + } + } + + private void StartEventViewerThread() + { + // Stop and close existing thread if any + StopEventViewerThread(); + + // Start a new thread + eventViewerThread = new Thread(() => + { + // Start EventViewer logs + eventViewerHelper.Start(); + }); + eventViewerThread.Name = EventViewerName + " Thread"; + eventViewerThread.Start(); + } + + private void StopEventViewerThread() + { + eventViewerHelper.Stop(); + + if (Thread.CurrentThread != eventViewerThread) + { + eventViewerThread?.Join(); + } + } + + private void StartWatsonThread() + { + // Stop and close existing thread if any + StopWatsonThread(); + + // Start a new thread + watsonThread = new Thread(() => + { + // Start Watson logs + watsonHelper.Start(); + }); + watsonThread.Name = WatsonName + " Thread"; + watsonThread.Start(); + } + + private void StopWatsonThread() + { + watsonHelper.Stop(); + + if (Thread.CurrentThread != watsonThread) + { + watsonThread?.Join(); + } + } + + public void LogStateChanged(WinLogsTool logType, bool isEnabled) + { + if (isEnabled) + { + switch (logType) + { + case WinLogsTool.ETWLogs: + StartETWLogsThread(); + break; + case WinLogsTool.DebugOutput: + StartDebugOutputsThread(); + break; + case WinLogsTool.EventViewer: + StartEventViewerThread(); + break; + case WinLogsTool.Watson: + StartWatsonThread(); + break; + } + } + else + { + switch (logType) + { + case WinLogsTool.ETWLogs: + StopETWLogsThread(); + break; + case WinLogsTool.DebugOutput: + StopDebugOutputsThread(); + break; + case WinLogsTool.EventViewer: + StopEventViewerThread(); + break; + case WinLogsTool.Watson: + StopWatsonThread(); + break; + } + } + } + + public static WinLogCategory ConvertTraceEventLevelToWinLogCategory(TraceEventLevel level) + { + var category = WinLogCategory.Information; + + switch (level) + { + case TraceEventLevel.Error: + case TraceEventLevel.Critical: + category = WinLogCategory.Error; + break; + case TraceEventLevel.Warning: + category = WinLogCategory.Warning; + break; + } + + return category; + } + + public static WinLogCategory ConvertStandardEventLevelToWinLogCategory(byte? level) + { + var category = WinLogCategory.Information; + + if (level.HasValue) + { + StandardEventLevel standardEventLevel = (StandardEventLevel)level.Value; + switch (standardEventLevel) + { + case StandardEventLevel.Error: + case StandardEventLevel.Critical: + category = WinLogCategory.Error; + break; + case StandardEventLevel.Warning: + category = WinLogCategory.Warning; + break; + } + } + + return category; + } +} diff --git a/tools/PI/DevHome.PI/Helpers/WindowHelper.cs b/tools/PI/DevHome.PI/Helpers/WindowHelper.cs new file mode 100644 index 0000000000..c9f927d040 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/WindowHelper.cs @@ -0,0 +1,526 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +using Windows.Devices.Display; +using Windows.Devices.Enumeration; +using Windows.Graphics; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Dwm; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.Accessibility; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace DevHome.PI.Helpers; + +public class WindowHelper +{ + private static nint GetClassLongPtr(HWND hWnd, GET_CLASS_LONG_INDEX nIndex) + { + if (IntPtr.Size == 8) + { + return CommonInterop.GetClassLongPtr64(hWnd, nIndex); + } + else + { + return (nint)PInvoke.GetClassLong(hWnd, nIndex); + } + } + + private static nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint newLong) + { + if (IntPtr.Size == 8) + { + return CommonInterop.SetWindowLongPtr64(hWnd, nIndex, newLong); + } + else + { + return (nint)PInvoke.SetWindowLong(hWnd, nIndex, (int)newLong); + } + } + + private static nint GetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) + { + if (IntPtr.Size == 8) + { + return CommonInterop.GetWindowLongPtr64(hWnd, nIndex); + } + else + { + return PInvoke.GetWindowLong(hWnd, nIndex); + } + } + + // TODO The SnapOffsetHorizontal and SnapThreshold values don't allow for different DPIs. + + // It seems the way rounded corners are implemented means that the window is really 8px + // bigger than it seems, so we'll subtract this when we do sidecar snapping. + private const int SnapOffsetHorizontal = 8; + + // If the target window is moved to within SnapThreshold px of the edge of the screen, we unsnap. + private const int SnapThreshold = 10; + + private static unsafe BOOL EnumProc(HWND hWnd, LPARAM data) + { +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + var enumData = (EnumWindowsData*)data.Value; +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + + // The caller should've set this, but we'll make sure here. + enumData->OutHwnd = HWND.Null; + + // Skip this one if the window doesn't include WS_VISIBLE, or if it's minimized. + if (!PInvoke.IsWindowVisible(hWnd)) + { + return true; + } + + if (PInvoke.IsIconic(hWnd)) + { + return true; + } + + // Skip toolwindows. + var extendedStyle = GetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + var isToolWindow = (extendedStyle & (long)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) + == (long)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW; + if (isToolWindow) + { + return true; + } + + // Skip dialogs. + if (PInvoke.GetAncestor(hWnd, GET_ANCESTOR_FLAGS.GA_ROOTOWNER) != hWnd) + { + return true; + } + + PInvoke.GetWindowRect(hWnd, out var windowRect); + var screenBounds = GetMonitorRectForWindow(hWnd); + var isOnAnyScreen = + windowRect.left < screenBounds.right && windowRect.right > screenBounds.left && + windowRect.top < screenBounds.bottom && windowRect.bottom > screenBounds.top && + windowRect.right - windowRect.left > 1 && windowRect.bottom - windowRect.top > 1; + if (!isOnAnyScreen) + { + return true; + } + + unsafe + { + // Exclude system/shell windows. + var className = stackalloc char[256]; + var classNameLength = PInvoke.GetClassName(hWnd, className, 256); + if (classNameLength == 0) + { + return true; + } + + string classNameString = new(className, 0, classNameLength); + if (classNameString == "Progman" || classNameString == "Shell_TrayWnd" || + classNameString == "WorkerW" || classNameString == "SHELLDLL_DefView" || + classNameString == "IME") + { + return true; + } + + // Exclude cloaked windows. + var cloakedVal = 0; + var hRes = PInvoke.DwmGetWindowAttribute( + hWnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAKED, &cloakedVal, sizeof(int)); + if (hRes != 0) + { + cloakedVal = 0; + } + + if (cloakedVal != 0) + { + return true; + } + + // Skip any windows that are on our exclusion list. + var excludedProcesses = enumData->ExcludedProcesses; + if (excludedProcesses != null && !IsAcceptableWindow(hWnd, excludedProcesses)) + { + return true; + } + } + + // Skip popups, unless they're for UWP apps or for dialog-based MFC apps. + var windowStyle = (WINDOW_STYLE)GetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); + var isPopup = (windowStyle & WINDOW_STYLE.WS_POPUP) == WINDOW_STYLE.WS_POPUP; + + // Dialog-based MFC apps will have WS_POPUP but also WS_OVERLAPPED. + var isOverlapped = (windowStyle & WINDOW_STYLE.WS_OVERLAPPED) == WINDOW_STYLE.WS_OVERLAPPED; + + if (isPopup) + { + var isUwpApp = IsProcessName(hWnd, "applicationframehost"); + if (isUwpApp) + { + // NOTE: We could use SHGetPropertyStoreForWindow + PKEY_AppUserModel_ID + // to get the appid for the app. + // Found a visible UWP window, stop enumerating. + enumData->OutHwnd = hWnd; + return false; + } + else if (isOverlapped) + { + // This is a top-level popup, most likely a dialog-based MFC app. + enumData->OutHwnd = hWnd; + return false; + } + + return true; + } + + // Found a window, stop enumerating. + enumData->OutHwnd = hWnd; + return false; + } + + private static Rectangle GetScreenBounds() + { + Rectangle rectangle = default; + + // Can't use async in EnumProc. + var deviceInfoOp = DeviceInformation.FindAllAsync(DisplayMonitor.GetDeviceSelector()); + deviceInfoOp.AsTask().Wait(); + var displayList = deviceInfoOp.GetResults(); + if (displayList == null || displayList.Count == 0) + { + return rectangle; + } + + var winSize = default(SizeInt32); + var displayOp = DisplayMonitor.FromInterfaceIdAsync(displayList[0].Id); + displayOp.AsTask().Wait(); + var monitorInfo = displayOp.GetResults(); + if (monitorInfo == null) + { + winSize.Width = 800; + winSize.Height = 1200; + } + else + { + winSize.Height = monitorInfo.NativeResolutionInRawPixels.Height; + winSize.Width = monitorInfo.NativeResolutionInRawPixels.Width; + } + + rectangle.Width = winSize.Width; + rectangle.Height = winSize.Height; + + return rectangle; + } + + public enum BinaryType : int + { + Unknown = -1, + X32 = 0, + X64 = 6, + } + + internal static unsafe string GetWindowTitle(HWND hWnd) + { + var length = PInvoke.GetWindowTextLength(hWnd); + var windowText = stackalloc char[length]; + _ = PInvoke.GetWindowText(hWnd, windowText, length); + return new string(windowText); + } + + internal static IntPtr LoadDefaultAppIcon() + { + IntPtr icon = PInvoke.LoadIcon(HINSTANCE.Null, PInvoke.IDI_APPLICATION); + return icon; + } + + internal static Bitmap? GetAppIcon(HWND hWnd) + { + try + { + // Try getting the big icon first. + IntPtr hIcon = default; + hIcon = PInvoke.SendMessage(hWnd, PInvoke.WM_GETICON, PInvoke.ICON_BIG, IntPtr.Zero); + + // If that failed, try getting the small icon (or the system-provided default). + if (hIcon == IntPtr.Zero) + { + hIcon = PInvoke.SendMessage(hWnd, PInvoke.WM_GETICON, PInvoke.ICON_SMALL2, IntPtr.Zero); + if (hIcon == IntPtr.Zero) + { + hIcon = (nint)GetClassLongPtr(hWnd, GET_CLASS_LONG_INDEX.GCL_HICON); + } + } + + if (hIcon != IntPtr.Zero) + { + return new Bitmap(Icon.FromHandle(hIcon).ToBitmap(), 24, 24); + } + else + { + return null; + } + } + catch (Exception) + { + return null; + } + } + + public static async Task GetWinUI3BitmapSourceFromGdiBitmap(System.Drawing.Bitmap bmp) + { + // get pixels as an array of bytes + var data = bmp.LockBits( + new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), + System.Drawing.Imaging.ImageLockMode.ReadOnly, + bmp.PixelFormat); + var bytes = new byte[data.Stride * data.Height]; + Marshal.Copy(data.Scan0, bytes, 0, bytes.Length); + bmp.UnlockBits(data); + + // get WinRT SoftwareBitmap + var softwareBitmap = new Windows.Graphics.Imaging.SoftwareBitmap( + Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8, + bmp.Width, + bmp.Height, + Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied); + softwareBitmap.CopyFromBuffer(bytes.AsBuffer()); + + // build WinUI3 SoftwareBitmapSource + var source = new SoftwareBitmapSource(); + await source.SetBitmapAsync(softwareBitmap); + return source; + } + + internal static unsafe uint GetProcessIdFromWindow(HWND hWnd) + { + uint processID = 0; + _ = PInvoke.GetWindowThreadProcessId(hWnd, &processID); + return processID; + } + + internal static HWINEVENTHOOK WatchWindowPositionEvents(WINEVENTPROC procDelegate, uint processID) + { + var eventHook = PInvoke.SetWinEventHook( + PInvoke.EVENT_OBJECT_DESTROY, + PInvoke.EVENT_OBJECT_LOCATIONCHANGE, + HMODULE.Null, + procDelegate, + processID, + 0, + PInvoke.WINEVENT_OUTOFCONTEXT | PInvoke.WINEVENT_SKIPOWNPROCESS); + return eventHook; + } + + internal static HWINEVENTHOOK WatchWindowForegroundEvents(WINEVENTPROC procDelegate) + { + var eventHook = PInvoke.SetWinEventHook( + PInvoke.EVENT_SYSTEM_FOREGROUND, + PInvoke.EVENT_SYSTEM_FOREGROUND, + HMODULE.Null, + procDelegate, + 0, + 0, + PInvoke.WINEVENT_OUTOFCONTEXT | PInvoke.WINEVENT_SKIPOWNPROCESS); + return eventHook; + } + + internal static HWINEVENTHOOK WatchWindowFocusEvents(WINEVENTPROC procDelegate, uint processID) + { + var eventHook = PInvoke.SetWinEventHook( + PInvoke.EVENT_OBJECT_FOCUS, + PInvoke.EVENT_OBJECT_FOCUS, + HMODULE.Null, + procDelegate, + processID, + 0, + PInvoke.WINEVENT_OUTOFCONTEXT | PInvoke.WINEVENT_SKIPOWNPROCESS); + return eventHook; + } + + internal sealed class EnumWindowsData + { + public HWND OutHwnd { get; set; } + + public StringCollection? ExcludedProcesses { get; set; } + + public EnumWindowsData() + { + OutHwnd = HWND.Null; + } + } + + internal static unsafe HWND FindVisibleForegroundWindow(StringCollection excludedProcesses) + { + EnumWindowsData data = new() { ExcludedProcesses = excludedProcesses }; +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + LPARAM lparamData = new((nint)(&data)); +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + PInvoke.EnumWindows(EnumProc, lparamData); + return data.OutHwnd; + } + + internal static bool IsAcceptableWindow(HWND hWnd, StringCollection excludedProcesses) + { + if (excludedProcesses != null && excludedProcesses.Count > 0) + { + foreach (var processName in excludedProcesses) + { + if (processName != null && IsProcessName(hWnd, processName)) + { + return false; + } + } + } + + return true; + } + + internal static unsafe bool IsProcessName(HWND hWnd, string name) + { + uint processId = 0; + _ = PInvoke.GetWindowThreadProcessId(hWnd, &processId); + var process = Process.GetProcessById((int)processId); + if (string.Equals(process.ProcessName, name, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + + internal static string GetProcessName(uint processId) + { + var process = Process.GetProcessById((int)processId); + return process.ProcessName; + } + + internal static void SetWindowExTransparent(HWND hwnd) + { + var extendedStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + _ = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)(extendedStyle | WINDOW_EX_STYLE.WS_EX_TRANSPARENT)); + } + + internal static void SetWindowExNotTransparent(HWND hwnd) + { + var extendedStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + _ = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)(extendedStyle & ~WINDOW_EX_STYLE.WS_EX_TRANSPARENT)); + } + + internal static T? FindParentControl(DependencyObject child) + where T : DependencyObject + { + var parentObject = VisualTreeHelper.GetParent(child); + if (parentObject == null) + { + return null; + } + + if (parentObject is T parent) + { + return parent; + } + else + { + return FindParentControl(parentObject); + } + } + + internal static void SnapToWindow(IntPtr targetHwnd, IntPtr dbarHwnd, SizeInt32 size) + { + PInvoke.GetWindowRect((HWND)targetHwnd, out var rect); + PInvoke.MoveWindow((HWND)dbarHwnd, rect.right - SnapOffsetHorizontal, rect.top, size.Width, size.Height, true); + } + + internal static bool IsWindowSnapped(HWND hwnd) + { + if (!PInvoke.GetWindowRect(hwnd, out var windowRect)) + { + return false; + } + + var workAreaRect = GetWorkAreaRect(); + + // If the window is within the top, right or bottom (not left) snap threshold, + // consider it snapped to the edge. + var snappedToTop = Math.Abs(windowRect.top - workAreaRect.top) <= SnapThreshold; + var snappedToRight = Math.Abs(windowRect.right - workAreaRect.right) <= SnapThreshold; + var snappedToBottom = Math.Abs(windowRect.bottom - workAreaRect.bottom) <= SnapThreshold; + return snappedToTop || snappedToRight || snappedToBottom; + } + + internal static bool DoWindowsOverlap(HWND hwnd, HWND hwnd2) + { + PInvoke.GetWindowRect(hwnd, out var rect); + PInvoke.GetWindowRect(hwnd2, out var rect2); + + var overlap = rect.left < rect2.right && rect.right > rect2.left && + rect.top < rect2.bottom && rect.bottom > rect2.top; + + return overlap; + } + + internal static bool DoesWindow1CoverTheRightSideOfWindow2(HWND hwnd1, HWND hwnd2) + { + PInvoke.GetWindowRect(hwnd1, out var rect); + PInvoke.GetWindowRect(hwnd2, out var rect2); + + // We'll consider the right side of the window being the far right quarter of the window. Adjust the window's rect to match what we want + rect2.left = rect2.right - ((rect2.right - rect2.left) / 4); + + var overlap = rect.left < rect2.right && rect.right > rect2.left && + rect.top < rect2.bottom && rect.bottom > rect2.top; + + return overlap; + } + + private static RECT GetWorkAreaRect() + { + RECT rect = default; + unsafe + { + PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETWORKAREA, 0, &rect, 0); + } + + return rect; + } + + // TODO Allow for the taskbar when returning screen size. + internal static RECT GetMonitorRectForWindow(HWND hWnd) + { + var monitor = PInvoke.MonitorFromWindow(hWnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); + var monitorInfo = new MONITORINFO { cbSize = (uint)Marshal.SizeOf(typeof(MONITORINFO)) }; + PInvoke.GetMonitorInfo(monitor, ref monitorInfo); + var screenBounds = monitorInfo.rcMonitor; + return screenBounds; + } + + internal static void GetAppInfoUnderMouseCursor(out Process? process, out HWND hwnd) + { + process = null; + + // Grab the window under the cursor and attach to that process + PInvoke.GetCursorPos(out var pt); + hwnd = PInvoke.WindowFromPoint(pt); + + if (hwnd != HWND.Null) + { + var processID = WindowHelper.GetProcessIdFromWindow(hwnd); + + if (processID != 0) + { + process = Process.GetProcessById((int)processID); + } + } + } +} diff --git a/tools/PI/DevHome.PI/Helpers/WindowHooker`1.cs b/tools/PI/DevHome.PI/Helpers/WindowHooker`1.cs new file mode 100644 index 0000000000..e821fdd864 --- /dev/null +++ b/tools/PI/DevHome.PI/Helpers/WindowHooker`1.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading; +using DevHome.PI.Helpers; +using DevHome.PI.Models; +using Serilog; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace DevHome.PI.Helpers; + +internal abstract class WindowHooker +{ + private static nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint newLong) + { + if (IntPtr.Size == 8) + { + return CommonInterop.SetWindowLongPtr64(hWnd, nIndex, newLong); + } + else + { + return (nint)PInvoke.SetWindowLong(hWnd, nIndex, (int)newLong); + } + } + + protected static readonly ILogger Log = Serilog.Log.ForContext("SourceContext", nameof(T)); + + private readonly WNDPROC windowProcHook; + + private HWND listenerHwnd; + + private WNDPROC? originalWndProc; + + protected HWND ListenerHwnd { get => listenerHwnd; set => listenerHwnd = value; } + + internal WindowHooker() + { + windowProcHook = CustomWndProc; + } + + public virtual void Start(HWND listeningWindow) + { + if (ListenerHwnd != HWND.Null) + { + // No-OP if we're already running + Debug.Assert(ListenerHwnd == listeningWindow, "Why are we trying to start with a different hwnd?"); + return; + } + + ArgumentNullException.ThrowIfNull(listeningWindow, nameof(listeningWindow)); + + var wndproc = SetWindowLongPtr(listeningWindow, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(windowProcHook)); + if (wndproc == IntPtr.Zero) + { + Log.Error("SetWindowLongPtr failed: {GetLastError}", Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture)); + return; + } + + originalWndProc = Marshal.GetDelegateForFunctionPointer(wndproc); + ListenerHwnd = listeningWindow; + } + + public virtual void Stop() + { + if (ListenerHwnd != HWND.Null) + { + Debug.Assert(originalWndProc != null, "Where did the original wndproc go?"); + + var result = SetWindowLongPtr(ListenerHwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(originalWndProc)); + if (result == IntPtr.Zero) + { + Log.Error("SetWindowLongPtr failed: {GetLastError}", Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture)); + } + + ListenerHwnd = HWND.Null; + } + } + + protected virtual LRESULT CustomWndProc(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) + { + return PInvoke.CallWindowProc(originalWndProc, hWnd, msg, wParam, lParam); + } +} diff --git a/tools/PI/DevHome.PI/Images/PI.ico b/tools/PI/DevHome.PI/Images/PI.ico new file mode 100644 index 0000000000000000000000000000000000000000..a9b3b3bce940831654f731c42bf18d9073b3c2d3 GIT binary patch literal 140512 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 UcsA!iOD}Cm`6Jg$W7paL2U@&_Gynhq literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/Models/AppRuntimeInfo.cs b/tools/PI/DevHome.PI/Models/AppRuntimeInfo.cs new file mode 100644 index 0000000000..4b1bea3a0e --- /dev/null +++ b/tools/PI/DevHome.PI/Models/AppRuntimeInfo.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.PI.Helpers; +using Microsoft.UI.Xaml; + +namespace DevHome.PI.Models; + +public partial class AppRuntimeInfo : ObservableObject +{ + [ObservableProperty] + private int processId = 0; + + [ObservableProperty] + private int basePriority = 0; + + [ObservableProperty] + private int priorityClass = 0; + + [ObservableProperty] + private string mainModuleFileName = string.Empty; + + [ObservableProperty] + private WindowHelper.BinaryType binaryType = WindowHelper.BinaryType.Unknown; + + [ObservableProperty] + private bool isPackaged = false; + + [ObservableProperty] + private bool usesWpf = false; + + [ObservableProperty] + private bool usesWinForms = false; + + [ObservableProperty] + private bool usesMfc = false; + + [ObservableProperty] + private bool isStoreApp = false; + + [ObservableProperty] + private bool isAvalonia = false; + + [ObservableProperty] + private bool isMaui = false; + + [ObservableProperty] + private bool usesWinAppSdk = false; + + [ObservableProperty] + private bool usesWinUi = false; + + [ObservableProperty] + private bool usesDirectX = false; + + [ObservableProperty] + private bool isRunningAsAdmin = false; + + [ObservableProperty] + private bool isRunningAsSystem = false; + + [ObservableProperty] + private Visibility visibility = Visibility.Visible; +} diff --git a/tools/PI/DevHome.PI/Models/ClipboardContents.cs b/tools/PI/DevHome.PI/Models/ClipboardContents.cs new file mode 100644 index 0000000000..8343513eea --- /dev/null +++ b/tools/PI/DevHome.PI/Models/ClipboardContents.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.PI.Models; + +public class ClipboardContents +{ + public string Raw { get; set; } = string.Empty; + + public string Hex { get; set; } = string.Empty; + + public string Dec { get; set; } = string.Empty; + + public string Code { get; set; } = string.Empty; + + public string Help { get; set; } = string.Empty; + + public void Clear() + { + Raw = string.Empty; + Hex = string.Empty; + Dec = string.Empty; + Code = string.Empty; + Help = string.Empty; + } +} diff --git a/tools/PI/DevHome.PI/Models/ClipboardMonitor.cs b/tools/PI/DevHome.PI/Models/ClipboardMonitor.cs new file mode 100644 index 0000000000..8b4256f09b --- /dev/null +++ b/tools/PI/DevHome.PI/Models/ClipboardMonitor.cs @@ -0,0 +1,201 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading; +using DevHome.PI.Helpers; +using Serilog; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace DevHome.PI.Models; + +internal sealed class ClipboardMonitor : WindowHooker, INotifyPropertyChanged +{ + public static readonly ClipboardMonitor Instance = new(); + + public ClipboardContents Contents { get; private set; } = new(); + + public event PropertyChangedEventHandler? PropertyChanged; + + internal ClipboardMonitor() + { + } + + private void ClipboardChanged() + { + SafeHandle? h = null; + ClipboardContents newContents = new(); + try + { + var clipboardText = string.Empty; + PInvoke.OpenClipboard(ListenerHwnd); + h = PInvoke.GetClipboardData_SafeHandle(13 /* CF_UNICODETEXT */); + if (!h.IsInvalid) + { + unsafe + { + var p = PInvoke.GlobalLock(h); + clipboardText = Marshal.PtrToStringUni((IntPtr)p) ?? string.Empty; + } + + if (clipboardText != string.Empty) + { + newContents = ParseClipboardContents(clipboardText); + } + } + } + finally + { + if (h is not null && !h.IsInvalid) + { + PInvoke.GlobalUnlock(h); + + // You're not suppose to close this handle. + h.SetHandleAsInvalid(); + } + + PInvoke.CloseClipboard(); + + Contents = newContents; + OnPropertyChanged(nameof(Contents)); + } + } + + /* TODO This pattern matches the following: + 100 + 0x100 + 0x80040005 + -2147221499 + -1 + 0xabc + abc + ffffffff + 1de + cab + bee + + ...but sequences like "cab", "bee", "fed" could be false positives. We need + more logic to exclude these. + */ + private static readonly Regex FindNumbersRegex = + new( + pattern: @"(?:0[xX][0-9A-Fa-f]+|-?\b(?:\d+|\d*\.\d+)\b|\b[0-9A-Fa-f]+\b)", + options: RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private ClipboardContents ParseClipboardContents(string text) + { + ClipboardContents newContents = new(); + + // If this text contains a number, show it in different number bases. + var matches = FindNumbersRegex.Matches(text); + var converter = new Int32Converter(); + + foreach (var match in matches.Cast()) + { + var original = match.ToString(); + + // Assume the number is easily identifable as either base 10 or base 16... convert to int. + int? errorAsInt; + + try + { + if (converter.IsValid(original)) + { + // Int32Converter.ConvertFromString() does a pretty good job of parsing numbers, except when given a hex + // number that isn't prefixed with 0x. If it fails, try parsing it using int.Parse(). + errorAsInt = (int?)converter.ConvertFromString(original); + } + else + { + errorAsInt = int.Parse(original, NumberStyles.HexNumber, CultureInfo.CurrentCulture); + } + } + catch + { + // If this ConvertFromString() function fails due to a bad format, update the above regex to ensure + // the bad string isn't fed to this function. + Log.Warning("Failed to parse \" {original} \" to a number", original); + return newContents; + } + + newContents.Raw = original; + newContents.Hex = errorAsInt is not null ? Convert.ToString((int)errorAsInt, 16) : original; + newContents.Dec = errorAsInt is not null ? Convert.ToString((int)errorAsInt, 10) : original; + + // Is there an error code on here? + // if (ErrorLookupHelper.ContainsErrorCode(text, out var hresult)) + if (errorAsInt is not null) + { + var errors = ErrorLookupHelper.LookupError((int)errorAsInt); + if (errors is not null) + { + foreach (var error in errors) + { + // Seperate each error with a space. These errors aren't localized, so we may not need to worry + // about the space being in the wrong place. + if (newContents.Code != string.Empty) + { + newContents.Code += " "; + newContents.Help += " "; + } + + newContents.Code += error.Name; + newContents.Help += error.Help; + } + } + } + + break; + } + + return newContents; + } + + public override void Start(HWND hwndUsedForListening) + { + base.Start(hwndUsedForListening); + + var success = PInvoke.AddClipboardFormatListener(ListenerHwnd); + if (!success) + { + Log.Error("AddClipboardFormatListener failed: {GetLastError}", Marshal.GetLastWin32Error().ToString(CultureInfo.CurrentCulture)); + } + } + + public override void Stop() + { + if (ListenerHwnd != HWND.Null) + { + PInvoke.RemoveClipboardFormatListener(ListenerHwnd); + + base.Stop(); + } + } + + protected override LRESULT CustomWndProc(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam) + { + switch (msg) + { + case PInvoke.WM_CLIPBOARDUPDATE: + { + ThreadPool.QueueUserWorkItem((o) => ClipboardChanged()); + break; + } + } + + return base.CustomWndProc(hWnd, msg, wParam, lParam); + } + + private void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/tools/PI/DevHome.PI/Models/Insight.cs b/tools/PI/DevHome.PI/Models/Insight.cs new file mode 100644 index 0000000000..313f3a6678 --- /dev/null +++ b/tools/PI/DevHome.PI/Models/Insight.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.RegularExpressions; + +namespace DevHome.PI.Models; + +internal enum InsightType +{ + Unknown, + LockedFile, + AccessDeniedFile, + AccessDeniedRegistry, + InvalidPath, + Security, + MemoryViolation, +} + +public sealed class Insight +{ + internal string Title { get; set; } = string.Empty; + + internal string Description { get; set; } = string.Empty; + + internal InsightType InsightType { get; set; } = InsightType.Unknown; +} + +internal sealed class InsightRegex +{ + internal InsightType InsightType { get; set; } + + internal Regex Regex { get; set; } + + internal InsightRegex(InsightType type, Regex regex) + { + Regex = regex; + InsightType = type; + } +} diff --git a/tools/PI/DevHome.PI/Models/NavLink.cs b/tools/PI/DevHome.PI/Models/NavLink.cs new file mode 100644 index 0000000000..d790424bb1 --- /dev/null +++ b/tools/PI/DevHome.PI/Models/NavLink.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace DevHome.PI.Models; + +public class NavLink +{ + public string IconText { get; internal set; } + + public string ContentText { get; internal set; } + + public Type? PageViewModel { get; internal set; } + + public NavLink(string i, string c, Type? pageViewModel) + { + IconText = i; + ContentText = c; + PageViewModel = pageViewModel; + } +} diff --git a/tools/PI/DevHome.PI/Models/PerfCounters.cs b/tools/PI/DevHome.PI/Models/PerfCounters.cs new file mode 100644 index 0000000000..b578c4f167 --- /dev/null +++ b/tools/PI/DevHome.PI/Models/PerfCounters.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using Serilog; + +namespace DevHome.PI.Models; + +public partial class PerfCounters : ObservableObject, IDisposable +{ + private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(PerfCounters)); + + public static readonly PerfCounters Instance = new(); + + private const string ProcessCategory = "Process"; + private const string ProcessorCategory = "Processor"; + private const string MemoryCategory = "Memory"; + private const string DiskCategory = "PhysicalDisk"; + + private const string CpuCounterName = "% Processor Time"; + private const string RamCounterName = "Working Set - Private"; + private const string SystemRamCounterName = "Committed Bytes"; + private const string SystemDiskCounterName = "% Disk Time"; + + private const string ReadCounterName = "IO Read Bytes/sec"; + private const string WriteCounterName = "IO Write Bytes/sec"; + private const string GpuEngineName = "GPU Engine"; + private const string UtilizationPercentageName = "Utilization Percentage"; + + private Process? targetProcess; + private PerformanceCounter? cpuCounter; + private List? gpuCounters; + private PerformanceCounter? ramCounter; + private PerformanceCounter? readCounter; + private PerformanceCounter? writeCounter; + + private PerformanceCounter? systemCpuCounter; + private PerformanceCounter? systemRamCounter; + private PerformanceCounter? systemDiskCounter; + + private Timer? timer; + + [ObservableProperty] + private float cpuUsage; + + [ObservableProperty] + private float gpuUsage; + + [ObservableProperty] + private float ramUsageInMB; + + [ObservableProperty] + private float diskUsage; + + [ObservableProperty] + private float networkUsage; + + [ObservableProperty] + private float systemCpuUsage; + + [ObservableProperty] + private float systemRamUsageInGB; + + [ObservableProperty] + private float systemDiskUsage; + + public PerfCounters() + { + TargetAppData.Instance.PropertyChanged += TargetApp_PropertyChanged; + + ThreadPool.QueueUserWorkItem((o) => + { + systemCpuCounter = new PerformanceCounter(ProcessorCategory, CpuCounterName, "_Total", true); + systemRamCounter = new PerformanceCounter(MemoryCategory, SystemRamCounterName, true); + systemDiskCounter = new PerformanceCounter(DiskCategory, SystemDiskCounterName, "_Total", true); + UpdateTargetProcess(TargetAppData.Instance.TargetProcess); + }); + } + + private void TargetApp_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(TargetAppData.TargetProcess)) + { + ThreadPool.QueueUserWorkItem((o) => UpdateTargetProcess(TargetAppData.Instance.TargetProcess)); + } + } + + private void UpdateTargetProcess(Process? process) + { + if (process == targetProcess) + { + // Already tracking this process. + return; + } + + CloseTargetCounters(); + + targetProcess = process; + if (targetProcess == null) + { + return; + } + + var processName = targetProcess.ProcessName; + cpuCounter = new PerformanceCounter(ProcessCategory, CpuCounterName, processName, true); + ramCounter = new PerformanceCounter(ProcessCategory, RamCounterName, processName, true); + gpuCounters = GetGpuCounters(targetProcess.Id); + readCounter = new PerformanceCounter(ProcessCategory, ReadCounterName, processName, true); + writeCounter = new PerformanceCounter(ProcessCategory, WriteCounterName, processName, true); + } + + public void Start() + { + Stop(); + timer = new Timer(TimerCallback, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + } + + public void Stop() + { + timer?.Dispose(); + timer = null; + } + + private void CloseTargetCounters() + { + cpuCounter?.Close(); + ramCounter?.Close(); + + foreach (var counter in gpuCounters ?? Enumerable.Empty()) + { + counter.Close(); + } + + readCounter?.Close(); + writeCounter?.Close(); + } + + public static List GetGpuCounters(int pid) + { + var category = new PerformanceCounterCategory(GpuEngineName); + var counterNames = category.GetInstanceNames(); + var gpuCounters = counterNames + .Where(counterName => counterName.Contains($"pid_{pid}")) + .SelectMany(category.GetCounters) + .Where(counter => counter.CounterName.Equals(UtilizationPercentageName, StringComparison.Ordinal)) + .ToList(); + return gpuCounters; + } + + private void TimerCallback(object? state) + { + try + { + CpuUsage = cpuCounter?.NextValue() / Environment.ProcessorCount ?? 0; + GpuUsage = GetGpuUsage(gpuCounters); + + // Report app memory usage in MB + RamUsageInMB = ramCounter?.NextValue() / (1024 * 1024) ?? 0; + + var readBytesPerSec = readCounter?.NextValue() ?? 0; + var writeBytesPerSec = writeCounter?.NextValue() ?? 0; + var totalDiskBytesPerSec = readBytesPerSec + writeBytesPerSec; + DiskUsage = totalDiskBytesPerSec / (1024 * 1024); + + SystemCpuUsage = systemCpuCounter?.NextValue() ?? 0; + + // Report system memory usage in GB + SystemRamUsageInGB = systemRamCounter?.NextValue() / (1024 * 1024 * 1024) ?? 0; + SystemDiskUsage = systemDiskCounter?.NextValue() ?? 0; + } + catch (Exception ex) + { + _log.Debug(ex, "Failed to update counters."); + } + } + + public static float GetGpuUsage(List? gpuCounters) + { + float result = 0; + try + { + gpuCounters?.ForEach(x => x.NextValue()); + Thread.Sleep(500); + result = gpuCounters?.Sum(x => x.NextValue()) ?? 0; + } + catch (Exception ex) + { + _log.Debug(ex, "Failed to get Gpu usage."); + } + + return result; + } + + public void Dispose() + { + cpuCounter?.Dispose(); + ramCounter?.Dispose(); + readCounter?.Dispose(); + writeCounter?.Dispose(); + + foreach (var counter in gpuCounters ?? Enumerable.Empty()) + { + counter.Dispose(); + } + + GC.SuppressFinalize(this); + } +} diff --git a/tools/PI/DevHome.PI/Models/RestoreState.cs b/tools/PI/DevHome.PI/Models/RestoreState.cs new file mode 100644 index 0000000000..5544f9b65b --- /dev/null +++ b/tools/PI/DevHome.PI/Models/RestoreState.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.UI.Xaml.Controls; + +namespace DevHome.PI.Models; + +internal sealed class RestoreState +{ + internal double Left { get; set; } + + internal double Top { get; set; } + + internal double Width { get; set; } + + internal double Height { get; set; } + + internal Orientation BarOrientation { get; set; } + + internal bool IsLargePanelVisible { get; set; } +} diff --git a/tools/PI/DevHome.PI/Models/TargetAppData.cs b/tools/PI/DevHome.PI/Models/TargetAppData.cs new file mode 100644 index 0000000000..3d7ac59c60 --- /dev/null +++ b/tools/PI/DevHome.PI/Models/TargetAppData.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Security.Principal; +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.PI.Helpers; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Win32.SafeHandles; +using Windows.Win32; +using Windows.Win32.Foundation; +using static DevHome.PI.Helpers.WindowHelper; + +namespace DevHome.PI.Models; + +public partial class TargetAppData : ObservableObject, INotifyPropertyChanged +{ + public static readonly TargetAppData Instance = new(); + + public int ProcessId => TargetProcess?.Id ?? 0; + + public bool IsRunningAsSystem => TargetProcess?.SessionId == 0; + + public string Title { get; private set; } = string.Empty; + + public bool IsRunningAsAdmin + { + get + { + try + { + SafeFileHandle processToken; + var result = PInvoke.OpenProcessToken(TargetProcess?.SafeHandle, Windows.Win32.Security.TOKEN_ACCESS_MASK.TOKEN_QUERY, out processToken); + if (result != 0) + { + var identity = new WindowsIdentity(processToken.DangerousGetHandle()); + return identity?.Owner?.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) ?? false; + } + + return false; + } + catch (Win32Exception ex) + { + if (ex.NativeErrorCode == (int)WIN32_ERROR.ERROR_ACCESS_DENIED) + { + return true; + } + + return false; + } + } + } + + [ObservableProperty] + private SoftwareBitmapSource? icon; + + [ObservableProperty] + private string appName = string.Empty; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ProcessId))] + private Process? targetProcess; + + internal HWND HWnd { get; private set; } + + [ObservableProperty] + private bool hasExited; + + private async void GetBitmap(Process process, HWND hWnd) + { + try + { + Bitmap? bitmap = null; + + if (hWnd != HWND.Null) + { + // First check if we can get an icon from the HWND + bitmap = GetAppIcon(hWnd); + } + + if (bitmap is null && process.MainWindowHandle != HWND.Null) + { + // If not, try and grab an icon from the process's main window + bitmap = GetAppIcon((HWND)process.MainWindowHandle); + } + + if (bitmap is null && process.MainModule is not null) + { + // Failing that, try and get the icon from the exe + bitmap = System.Drawing.Icon.ExtractAssociatedIcon(process.MainModule.FileName)?.ToBitmap(); + } + + // Failing that, grab the default app icon + bitmap ??= System.Drawing.Icon.FromHandle(LoadDefaultAppIcon()).ToBitmap(); + + if (bitmap is not null) + { + Icon = await WindowHelper.GetWinUI3BitmapSourceFromGdiBitmap(bitmap); + } + else + { + Icon = null; + } + } + catch + { + Icon = null; + } + + return; + } + + private bool IsAppHost(string appName) + { + return string.Equals(appName, "ApplicationFrameHost", StringComparison.OrdinalIgnoreCase); + } + + internal void SetNewAppData(Process process, HWND hWnd) + { + TargetProcess = process; + HWnd = hWnd; + + // Reset hasExited, but don't trigger the property change event. +#pragma warning disable MVVMTK0034 // Direct field reference to [ObservableProperty] backing field + hasExited = false; +#pragma warning restore MVVMTK0034 // Direct field reference to [ObservableProperty] backing field + try + { + // These can throw if we don't have permissions to monitor process state. + TargetProcess.EnableRaisingEvents = true; + TargetProcess.Exited += TargetProcess_Exited; + } + catch + { + } + + Title = GetWindowTitle(hWnd) ?? TargetProcess.MainWindowTitle; + + // Getting the icon will be async + GetBitmap(process, hWnd); + + AppName = IsAppHost(TargetProcess.ProcessName) ? Title : TargetProcess.ProcessName; + + OnPropertyChanged(nameof(AppName)); + OnPropertyChanged(nameof(TargetProcess)); + OnPropertyChanged(nameof(HWnd)); + } + + internal void ClearAppData() + { + Title = string.Empty; + AppName = string.Empty; + Icon = null; + TargetProcess?.Dispose(); + TargetProcess = null; + + OnPropertyChanged(nameof(AppName)); + OnPropertyChanged(nameof(TargetProcess)); + OnPropertyChanged(nameof(HWnd)); + OnPropertyChanged(nameof(Icon)); + } + + private void TargetProcess_Exited(object? sender, EventArgs e) + { + // Change the property, so that we trigger the property change event. + HasExited = true; + } +} diff --git a/tools/PI/DevHome.PI/Models/ThemeName.cs b/tools/PI/DevHome.PI/Models/ThemeName.cs new file mode 100644 index 0000000000..986399780f --- /dev/null +++ b/tools/PI/DevHome.PI/Models/ThemeName.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.UI.Xaml; + +namespace DevHome.PI.Models; + +public class ThemeName +{ + public string Name { get; set; } = string.Empty; + + public ElementTheme Theme { get; set; } + + public ThemeName(string name, ElementTheme theme) => (Name, Theme) = (name, theme); + + public static List Themes { get; private set; } = + [ + new ThemeName("Light", ElementTheme.Light), + new ThemeName("Dark", ElementTheme.Dark), + new ThemeName("Default", ElementTheme.Default) + ]; +} diff --git a/tools/PI/DevHome.PI/Models/WatsonReport.cs b/tools/PI/DevHome.PI/Models/WatsonReport.cs new file mode 100644 index 0000000000..bee4d89c42 --- /dev/null +++ b/tools/PI/DevHome.PI/Models/WatsonReport.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Globalization; + +namespace DevHome.PI.Models; + +public class WatsonReport +{ + private readonly DateTime timeGenerated; + + public string TimeGenerated => timeGenerated.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.CurrentCulture); + + public string Module { get; } + + public string Executable { get; } + + public string EventGuid { get; } + + public string? WatsonLog { get; set; } + + public string? WatsonReportFile { get; set; } + + public WatsonReport(DateTime timeGenerated, string moduleName, string executable, string eventGuid) + { + this.timeGenerated = timeGenerated; + Module = moduleName; + Executable = executable; + EventGuid = eventGuid; + } +} diff --git a/tools/PI/DevHome.PI/Models/WinLogsEntry.cs b/tools/PI/DevHome.PI/Models/WinLogsEntry.cs new file mode 100644 index 0000000000..4ead017e24 --- /dev/null +++ b/tools/PI/DevHome.PI/Models/WinLogsEntry.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using DevHome.PI.Helpers; +using Microsoft.UI; +using Microsoft.UI.Xaml.Media; + +namespace DevHome.PI.Models; + +public class WinLogsEntry +{ + private readonly DateTime timeGenerated; + private readonly WinLogCategory category; + private readonly string errorText = CommonHelper.GetLocalizedString("WinLogCategoryError"); + private readonly string warningText = CommonHelper.GetLocalizedString("WinLogCategoryWarning"); + private readonly string informationText = CommonHelper.GetLocalizedString("WinLogCategoryInformation"); + private readonly string debugText = CommonHelper.GetLocalizedString("WinLogCategoryDebug"); + + public WinLogsEntry(DateTime? time, WinLogCategory category, string message, string toolName) + { + timeGenerated = time ?? DateTime.Now; + this.category = category; + this.Message = message; + this.Tool = toolName; + this.SelectedText = message; + } + + public string TimeGenerated => timeGenerated.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.CurrentCulture); + + public string Tool { get; } + + public string Category => category switch + { + WinLogCategory.Error => errorText, + WinLogCategory.Warning => warningText, + WinLogCategory.Information => informationText, + WinLogCategory.Debug => debugText, + _ => string.Empty, + }; + + public string Message { get; } + + public string SelectedText { get; set; } + + public SolidColorBrush RowColor + { + get + { + switch (category) + { + case WinLogCategory.Error: + return new SolidColorBrush(Colors.Red); + case WinLogCategory.Warning: + return new SolidColorBrush(Colors.Orange); + } + + return new SolidColorBrush(Colors.Black); + } + } +} + +public enum WinLogCategory +{ + Information = 0, + Error, + Warning, + Debug, +} + +public enum WinLogsTool +{ + Unknown = 0, + ETWLogs, + DebugOutput, + EventViewer, + Watson, +} diff --git a/tools/PI/DevHome.PI/NativeMethods.json b/tools/PI/DevHome.PI/NativeMethods.json new file mode 100644 index 0000000000..a6c9ed8b47 --- /dev/null +++ b/tools/PI/DevHome.PI/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "wideCharOnly": true +} \ No newline at end of file diff --git a/tools/PI/DevHome.PI/NativeMethods.txt b/tools/PI/DevHome.PI/NativeMethods.txt new file mode 100644 index 0000000000..3555f0e25a --- /dev/null +++ b/tools/PI/DevHome.PI/NativeMethods.txt @@ -0,0 +1,105 @@ +AddClipboardFormatListener +CallWindowProc +CloseClipboard +CoCreateInstance +CombineRgn +CreateEllipticRgn +CreateRectRgn +CreateRoundRectRgn +DwmGetWindowAttribute +DwmSetWindowAttribute +DwmSetWindowAttribute +EnumThreadWindows +EnumWindows +GetAncestor +GetBinaryType +GetClassLong +GetClassName +GetClipboardData +GetCurrentPackageFullName +GetCursorPos +GetDesktopWindow +GetForegroundWindow +GetMonitorInfo +GetMonitorInfo +GetOpenFileName +GetTopWindow +GetWindowInfo +GetWindowLong +GetWindowRect +GetWindowText +GetWindowText +GetWindowTextLength +GetWindowTextLength +GetWindowThreadProcessId +GlobalAddAtom +GlobalDeleteAtom +GlobalLock +GlobalUnlock +IsIconic +IsImmersiveProcess +IsWindow +IsWindowVisible +IsZoomed +LoadIcon +LoadImage +MessageBox +MonitorFromWindow +MoveWindow +OpenClipboard +OpenProcessToken +RegisterHotKey +RemoveClipboardFormatListener +SendMessage +SetFocus +SetForegroundWindow +SetParent +SetWindowLong +SetWindowLong +SetWindowPos +SetWindowRgn +SetWinEventHook +ShowWindow +SystemParametersInfo +UnhookWinEvent +UnregisterHotKey +WaitForSingleObject +WindowFromPoint +CF_* +DWMWA_* +DWMWINDOWATTRIBUTE +EVENT_* +EVENT_OBJECT_DESTROY +EVENT_OBJECT_LOCATIONCHANGE +EVENT_SYSTEM_FOREGROUND +GET_ANCESTOR_FLAGS +GET_CLASS_LONG_INDEX +HMONITOR +HWND_* +ICON_* +ICON_* +ICON_* +IDI_* +IMAGE_* +INVALID_HANDLE_VALUE +LR_* +MOD_* +// MONITOR_DEFAULTTONEAREST +SECTION_FLAGS +SW_* +VIRTUAL_KEY +VIRTUAL_KEY +WIN32_ERROR +WINEVENT_* +WINEVENT_OUTOFCONTEXT +WINEVENT_SKIPOWNPROCESS +WM_* +WS_* +RmRegisterResources +RmStartSession +RmGetList +RmEndSession +RM_APP_TYPE +RM_UNIQUE_PROCESS +RM_PROCESS_INFO +RM_REBOOT_REASON \ No newline at end of file diff --git a/tools/PI/DevHome.PI/PI.ico b/tools/PI/DevHome.PI/PI.ico new file mode 100644 index 0000000000000000000000000000000000000000..a9b3b3bce940831654f731c42bf18d9073b3c2d3 GIT binary patch literal 140512 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 UcsA!iOD}Cm`6Jg$W7paL2U@&_Gynhq literal 0 HcmV?d00001 diff --git a/tools/PI/DevHome.PI/PIApp.xaml b/tools/PI/DevHome.PI/PIApp.xaml new file mode 100644 index 0000000000..cef6068f6b --- /dev/null +++ b/tools/PI/DevHome.PI/PIApp.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/PIApp.xaml.cs b/tools/PI/DevHome.PI/PIApp.xaml.cs new file mode 100644 index 0000000000..7eda977308 --- /dev/null +++ b/tools/PI/DevHome.PI/PIApp.xaml.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using DevHome.Common.Extensions; +using DevHome.Common.Services; +using DevHome.PI.Controls; +using DevHome.PI.Pages; +using DevHome.PI.Services; +using DevHome.PI.Telemetry; +using DevHome.PI.TelemetryEvents; +using DevHome.PI.ViewModels; +using DevHome.Telemetry; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.UI.Xaml; +using Windows.Storage; + +namespace DevHome.PI; + +public partial class App : Application, IApp +{ + // The .NET Generic Host provides dependency injection, configuration, logging, and other services. + // https://docs.microsoft.com/dotnet/core/extensions/generic-host + // https://docs.microsoft.com/dotnet/core/extensions/dependency-injection + // https://docs.microsoft.com/dotnet/core/extensions/configuration + // https://docs.microsoft.com/dotnet/core/extensions/logging + public IHost Host { get; } + + public T GetService() + where T : class => Host.GetService(); + + public Microsoft.UI.Dispatching.DispatcherQueue? UIDispatcher { get; } + + public App() + { + InitializeComponent(); + + UIDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + + Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() + .ConfigureServices((context, services) => + { + // Services + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + // Window + services.AddSingleton(); + + // Views and ViewModels + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + }).Build(); + } + + internal static bool IsFeatureEnabled() + { + var isEnabled = false; + + ApplicationData.Current.LocalSettings.Values.TryGetValue($"ExperimentalFeature_ProjectIronsidesExperiment", out var isEnabledObj); + if (isEnabledObj is not null && isEnabledObj is string isEnabledValue) + { + isEnabled = isEnabledValue == "true"; + } + else + { +#if DEBUG + // Override on debug builds to be enabled by default + isEnabled = true; +#endif + } + + return isEnabled; + } + + internal static ITelemetry Logger => TelemetryFactory.Get(); + + internal static void LogTimeTaken(string eventName, uint timeTakenMilliseconds, Guid? relatedActivityId = null) => Logger.LogTimeTaken(eventName, timeTakenMilliseconds, relatedActivityId); + + internal static void LogCritical(string eventName, bool isError = false, Guid? relatedActivityId = null) => Logger.LogCritical(eventName, isError, relatedActivityId); + + internal static void Log(string eventName, LogLevel level, T data, Guid? relatedActivityId = null) + where T : EventBase + { + Logger.Log(eventName, level, data, relatedActivityId ?? null); + } + + internal static void LogError(string eventName, LogLevel level, T data, Guid? relatedActivityId = null) + where T : EventBase + { + Logger.LogError(eventName, level, data, relatedActivityId); + } + + internal static void Log(string eventName, LogLevel level, Guid? relatedActivityId = null) => Logger.Log(eventName, level, new UsageEventData(), relatedActivityId); +} diff --git a/tools/PI/DevHome.PI/Pages/AppDetailsPage.xaml b/tools/PI/DevHome.PI/Pages/AppDetailsPage.xaml new file mode 100644 index 0000000000..f276fbb18f --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/AppDetailsPage.xaml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/Pages/ProcessListPage.xaml.cs b/tools/PI/DevHome.PI/Pages/ProcessListPage.xaml.cs new file mode 100644 index 0000000000..883a9b5eef --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/ProcessListPage.xaml.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.Common.Extensions; +using DevHome.PI; +using DevHome.PI.Telemetry; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace DevHome.PI.Pages; + +public sealed partial class ProcessListPage : Page +{ + private ProcessListPageViewModel ViewModel { get; } + + public ProcessListPage() + { + ViewModel = Application.Current.GetService(); + InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + Application.Current.GetService().SwitchTo(Feature.ProcessList); + } +} diff --git a/tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml b/tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml new file mode 100644 index 0000000000..1eac2c4194 --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml.cs b/tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml.cs new file mode 100644 index 0000000000..cbf6294c86 --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/ResourceUsagePage.xaml.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using DevHome.Common.Extensions; +using DevHome.PI; +using DevHome.PI.Telemetry; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace DevHome.PI.Pages; + +public sealed partial class ResourceUsagePage : Page, IDisposable +{ + private ResourceUsagePageViewModel ViewModel { get; } + + public ResourceUsagePage() + { + ViewModel = Application.Current.GetService(); + InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + Application.Current.GetService().SwitchTo(Feature.ResourceUsage); + } + + public void Dispose() + { + ViewModel.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/tools/PI/DevHome.PI/Pages/WatsonsPage.xaml b/tools/PI/DevHome.PI/Pages/WatsonsPage.xaml new file mode 100644 index 0000000000..093456b705 --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/WatsonsPage.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/Pages/WatsonsPage.xaml.cs b/tools/PI/DevHome.PI/Pages/WatsonsPage.xaml.cs new file mode 100644 index 0000000000..c62c41333a --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/WatsonsPage.xaml.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using DevHome.Common.Extensions; +using DevHome.PI; +using DevHome.PI.Telemetry; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace DevHome.PI.Pages; + +public sealed partial class WatsonsPage : Page, IDisposable +{ + private WatsonPageViewModel ViewModel { get; } + + public WatsonsPage() + { + ViewModel = Application.Current.GetService(); + InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + Application.Current.GetService().SwitchTo(Feature.WERReports); + } + + public void Dispose() + { + ViewModel.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/tools/PI/DevHome.PI/Pages/WinLogsPage.xaml b/tools/PI/DevHome.PI/Pages/WinLogsPage.xaml new file mode 100644 index 0000000000..36d499c702 --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/WinLogsPage.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/PI/DevHome.PI/Pages/WinLogsPage.xaml.cs b/tools/PI/DevHome.PI/Pages/WinLogsPage.xaml.cs new file mode 100644 index 0000000000..fbdec195fa --- /dev/null +++ b/tools/PI/DevHome.PI/Pages/WinLogsPage.xaml.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using DevHome.Common.Extensions; +using DevHome.PI; +using DevHome.PI.Telemetry; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace DevHome.PI.Pages; + +public partial class WinLogsPage : Page, IDisposable +{ + private WinLogsPageViewModel ViewModel { get; } + + public WinLogsPage() + { + ViewModel = Application.Current.GetService(); + InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + Application.Current.GetService().SwitchTo(Feature.WinLogs); + } + + public void Dispose() + { + ViewModel.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/tools/PI/DevHome.PI/PrimaryWindow.xaml b/tools/PI/DevHome.PI/PrimaryWindow.xaml new file mode 100644 index 0000000000..c49abe5783 --- /dev/null +++ b/tools/PI/DevHome.PI/PrimaryWindow.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs b/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs new file mode 100644 index 0000000000..9442bc9b1c --- /dev/null +++ b/tools/PI/DevHome.PI/PrimaryWindow.xaml.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using DevHome.PI.Helpers; +using DevHome.PI.Models; +using DevHome.PI.Properties; +using DevHome.Telemetry; +using Microsoft.UI.Xaml; +using Windows.System; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using WinUIEx; +using static DevHome.PI.Helpers.WindowHelper; + +namespace DevHome.PI; + +public sealed partial class PrimaryWindow : WindowEx +{ + private const VirtualKey HotKey = VirtualKey.F12; + + private const HOT_KEY_MODIFIERS KeyModifier = HOT_KEY_MODIFIERS.MOD_WIN; + private HotKeyHelper? hotKeyHelper; + + public BarWindow? DBarWindow { get; private set; } + + public PrimaryWindow() + { + InitializeComponent(); + } + + public void ShowBarWindow() + { + if (DBarWindow == null) + { + DBarWindow = new(); + } + else + { + // Activate is unreliable so use SetForegroundWindow + PInvoke.SetForegroundWindow((HWND)DBarWindow.GetWindowHandle()); + } + } + + public void ClearBarWindow() + { + DBarWindow = null; + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + hotKeyHelper = new(this, HandleHotKey); + hotKeyHelper.RegisterHotKey(HotKey, KeyModifier); + + App.Log("DevHome.PI_MainWindows_Loaded", LogLevel.Measure); + } + + private void WindowEx_Closed(object sender, WindowEventArgs args) + { + DBarWindow?.Close(); + hotKeyHelper?.UnregisterHotKey(); + } + + public void HandleHotKey(int keyId) + { + var hWnd = FindVisibleForegroundWindow(Settings.Default.ExcludedProcesses); + + if (hWnd != IntPtr.Zero) + { + Process? process = null; + + try + { + var processId = GetProcessIdFromWindow(hWnd); + if (processId != 0) + { + process = Process.GetProcessById((int)processId); + } + } + catch + { + } + + if (process == null) + { + // Process must have died before we had a chance to grab it's process object. + return; + } + + if (DBarWindow == null) + { + DBarWindow = new(process, hWnd); + } + else + { + TargetAppData.Instance.SetNewAppData(process, hWnd); + } + } + else + { + // There's no foreground window. Start with the full window, open to the process list. + if (DBarWindow == null) + { + DBarWindow = new(); + } + } + } +} diff --git a/tools/PI/DevHome.PI/Program.cs b/tools/PI/DevHome.PI/Program.cs new file mode 100644 index 0000000000..d4505c07a6 --- /dev/null +++ b/tools/PI/DevHome.PI/Program.cs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using DevHome.Common.Extensions; +using DevHome.Common.Helpers; +using DevHome.PI.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.Windows.AppLifecycle; +using Serilog; +using Windows.ApplicationModel.Activation; +using WinRT; + +namespace DevHome.PI; + +public static class Program +{ + private static App? _app; + private static bool firstActivation = true; + + [global::System.Runtime.InteropServices.DllImport("Microsoft.ui.xaml.dll")] + [global::System.Runtime.InteropServices.DefaultDllImportSearchPaths(global::System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories)] + private static extern void XamlCheckProcessRequirements(); + + private const string MainInstanceKey = "mainInstance"; + + private const string ElevatedInstanceKey = "elevatedInstance"; + + [STAThread] + public static void Main(string[] args) + { + // Set up Logging + Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(Common.Logging.LogFolderRoot, "DevHomePI")); + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings_pi.json") + .Build(); + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .CreateLogger(); + + try + { + XamlCheckProcessRequirements(); + + WinRT.ComWrappersSupport.InitializeComWrappers(); + + var isRedirect = DecideRedirection().GetAwaiter().GetResult(); + + if (!isRedirect) + { + Log.Information("Starting application"); + Application.Start((p) => + { + var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + var context = new DispatcherQueueSynchronizationContext(dispatcherQueue); + SynchronizationContext.SetSynchronizationContext(context); + _app = new App(); + OnActivated(null, AppInstance.GetCurrent().GetActivatedEventArgs()); + }); + } + } + catch (Exception ex) + { + Log.Fatal(ex, "Application start-up failed"); + } + finally + { + Log.CloseAndFlush(); + } + } + + private static async Task DecideRedirection() + { + AppInstance instance; + var activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs(); + if (RuntimeHelper.IsCurrentProcessRunningAsAdmin()) + { + // Wait for unelevated instance to exit + var isUnElevatedInstancePresent = false; + do + { + isUnElevatedInstancePresent = false; + var instanceList = AppInstance.GetInstances(); + foreach (var appInstance in instanceList) + { + if (appInstance.Key.Equals(MainInstanceKey, StringComparison.OrdinalIgnoreCase)) + { + isUnElevatedInstancePresent = true; + } + } + } + while (isUnElevatedInstancePresent); + + // Register the elevated instance key + instance = AppInstance.FindOrRegisterForKey(ElevatedInstanceKey); + } + else + { + instance = AppInstance.FindOrRegisterForKey(MainInstanceKey); + } + + var isRedirect = false; + if (instance.IsCurrent) + { + instance.Activated += OnActivated; + } + else + { + // Redirect the activation (and args) to the registered instance, and exit. + await instance.RedirectActivationToAsync(activatedEventArgs); + isRedirect = true; + } + + return isRedirect; + } + + private static void OnActivated(object? sender, Microsoft.Windows.AppLifecycle.AppActivationArguments e) + { + if (e.Kind == Microsoft.Windows.AppLifecycle.ExtendedActivationKind.Launch) + { + var commandLine = e.Data.As().Arguments; + + // Convert commandLine into a string array. We just can't split based just on spaces, in case there are spaces inclosed in quotes + // i.e. --application "My App" + var commandLineArgs = Regex.Matches(commandLine, @"[\""].+?[\""]|[^ ]+").Select(m => m.Value).ToArray(); + + // TODO: This should be replaced with system.commandline Microsoft.Extensions.Configuration + // is not intended to be a general purpose commandline parser, but rather only supports /key=value or /key value pairs + var builder = new ConfigurationBuilder(); + builder.AddCommandLine(commandLineArgs); + var config = builder.Build(); + + Process? targetProcess = null; + var targetApp = config["application"]; + var targetPid = config["pid"]; + var pageToExpand = config["expandWindow"]; + + try + { + if (targetApp != null) + { + Debug.Assert(targetApp != string.Empty, "Why is appname empty?"); + + Process[] processes = Process.GetProcessesByName(targetApp); + if (processes.Length > 0) + { + targetProcess = processes[0]; + } + } + else if (targetPid != null) + { + var pid = int.Parse(targetPid, CultureInfo.CurrentCulture); + targetProcess = Process.GetProcessById(pid); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to find target process {TargetApp} {TargetPid}", targetApp, targetPid); + } + + if (config["startuptask"] != null) + { + // Start the app in the background to handle the startup task and register the hotkey + if (firstActivation && !App.IsFeatureEnabled()) + { + // Exit the process if PI Expermental feature is not enabled and its the first activation in the process + Log.Information("Experimental feature is not enabled. Exiting the process."); + Process.GetCurrentProcess().Kill(true); + } + } + else + { + Debug.Assert(_app != null, "Why is _app null on a redirection?"); + + // Be sure to set the target app on the UI thread + _app?.UIDispatcher?.TryEnqueue(() => + { + if (targetProcess != null) + { + TargetAppData.Instance.SetNewAppData(targetProcess, Windows.Win32.Foundation.HWND.Null); + } + + // Show the bar window + var primaryWindow = Application.Current.GetService(); + primaryWindow.ShowBarWindow(); + + if (pageToExpand != null) + { + var barWindow = primaryWindow.DBarWindow; + Debug.Assert(barWindow is not null, "We show the bar window, so it cannot be null here"); + + var pageType = Type.GetType($"DevHome.PI.ViewModels.{pageToExpand}"); + if (pageType is not null) + { + barWindow.NavigateTo(pageType); + } + + barWindow.ShouldExpandLargeWindow = true; + } + }); + } + } + + firstActivation = false; + } +} diff --git a/tools/PI/DevHome.PI/Properties/PublishProfiles/win-arm64.pubxml b/tools/PI/DevHome.PI/Properties/PublishProfiles/win-arm64.pubxml new file mode 100644 index 0000000000..227cf87736 --- /dev/null +++ b/tools/PI/DevHome.PI/Properties/PublishProfiles/win-arm64.pubxml @@ -0,0 +1,16 @@ + + + + + FileSystem + arm64 + win-arm64 + true + False + False + True + True + + \ No newline at end of file diff --git a/tools/PI/DevHome.PI/Properties/PublishProfiles/win-x64.pubxml b/tools/PI/DevHome.PI/Properties/PublishProfiles/win-x64.pubxml new file mode 100644 index 0000000000..19ae2a6b9c --- /dev/null +++ b/tools/PI/DevHome.PI/Properties/PublishProfiles/win-x64.pubxml @@ -0,0 +1,16 @@ + + + + + FileSystem + x64 + win-x64 + true + False + False + True + True + + \ No newline at end of file diff --git a/tools/PI/DevHome.PI/Properties/PublishProfiles/win-x86.pubxml b/tools/PI/DevHome.PI/Properties/PublishProfiles/win-x86.pubxml new file mode 100644 index 0000000000..dace1fa912 --- /dev/null +++ b/tools/PI/DevHome.PI/Properties/PublishProfiles/win-x86.pubxml @@ -0,0 +1,16 @@ + + + + + FileSystem + x86 + win-x86 + true + False + False + True + True + + \ No newline at end of file diff --git a/tools/PI/DevHome.PI/Properties/Settings.Designer.cs b/tools/PI/DevHome.PI/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..2bd5c11139 --- /dev/null +++ b/tools/PI/DevHome.PI/Properties/Settings.Designer.cs @@ -0,0 +1,280 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DevHome.PI.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeConHost { + get { + return ((bool)(this["IsProcessFilterIncludeConHost"])); + } + set { + this["IsProcessFilterIncludeConHost"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeDllHost { + get { + return ((bool)(this["IsProcessFilterIncludeDllHost"])); + } + set { + this["IsProcessFilterIncludeDllHost"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeSvcHost { + get { + return ((bool)(this["IsProcessFilterIncludeSvcHost"])); + } + set { + this["IsProcessFilterIncludeSvcHost"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeEdge { + get { + return ((bool)(this["IsProcessFilterIncludeEdge"])); + } + set { + this["IsProcessFilterIncludeEdge"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeWebview { + get { + return ((bool)(this["IsProcessFilterIncludeWebview"])); + } + set { + this["IsProcessFilterIncludeWebview"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeRtb { + get { + return ((bool)(this["IsProcessFilterIncludeRtb"])); + } + set { + this["IsProcessFilterIncludeRtb"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeWmi { + get { + return ((bool)(this["IsProcessFilterIncludeWmi"])); + } + set { + this["IsProcessFilterIncludeWmi"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeWudf { + get { + return ((bool)(this["IsProcessFilterIncludeWudf"])); + } + set { + this["IsProcessFilterIncludeWudf"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsProcessFilterIncludeBgTaskHost { + get { + return ((bool)(this["IsProcessFilterIncludeBgTaskHost"])); + } + set { + this["IsProcessFilterIncludeBgTaskHost"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string status1Source { + get { + return ((string)(this["status1Source"])); + } + set { + this["status1Source"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string status2Source { + get { + return ((string)(this["status2Source"])); + } + set { + this["status2Source"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string status3Source { + get { + return ((string)(this["status3Source"])); + } + set { + this["status3Source"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute(@" + + 50 + 350 + 964 + 680 +")] + public global::System.Collections.Specialized.StringCollection SettingsToolPosition { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["SettingsToolPosition"])); + } + set { + this["SettingsToolPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Default")] + public string CurrentTheme { + get { + return ((string)(this["CurrentTheme"])); + } + set { + this["CurrentTheme"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool IsCpuUsageMonitoringEnabled { + get { + return ((bool)(this["IsCpuUsageMonitoringEnabled"])); + } + set { + this["IsCpuUsageMonitoringEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool IsInsightsOnStartupEnabled { + get { + return ((bool)(this["IsInsightsOnStartupEnabled"])); + } + set { + this["IsInsightsOnStartupEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute(@" + + 70 + 942 + 640 + 222 +")] + public global::System.Collections.Specialized.StringCollection ErrorLookupToolPosition { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["ErrorLookupToolPosition"])); + } + set { + this["ErrorLookupToolPosition"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool IsClipboardMonitoringEnabled { + get { + return ((bool)(this["IsClipboardMonitoringEnabled"])); + } + set { + this["IsClipboardMonitoringEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("\r\n\r\n DevHome.PI\r\n DevEnv\r\n")] + public global::System.Collections.Specialized.StringCollection ExcludedProcesses { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["ExcludedProcesses"])); + } + set { + this["ExcludedProcesses"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0, 0")] + public global::System.Drawing.Size ExpandedLargeSize { + get { + return ((global::System.Drawing.Size)(this["ExpandedLargeSize"])); + } + set { + this["ExpandedLargeSize"] = value; + } + } + } +} diff --git a/tools/PI/DevHome.PI/Properties/Settings.settings b/tools/PI/DevHome.PI/Properties/Settings.settings new file mode 100644 index 0000000000..c382c2da83 --- /dev/null +++ b/tools/PI/DevHome.PI/Properties/Settings.settings @@ -0,0 +1,82 @@ + + + + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + + + + + + + + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <string>50</string> + <string>350</string> + <string>964</string> + <string>680</string> +</ArrayOfString> + + + Default + + + True + + + False + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <string>70</string> + <string>942</string> + <string>640</string> + <string>222</string> +</ArrayOfString> + + + True + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <string>DevHome.PI</string> + <string>DevEnv</string> +</ArrayOfString> + + + 0, 0 + + + \ No newline at end of file diff --git a/tools/PI/DevHome.PI/Properties/launchSettings.json b/tools/PI/DevHome.PI/Properties/launchSettings.json new file mode 100644 index 0000000000..42306ffcb9 --- /dev/null +++ b/tools/PI/DevHome.PI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "DevHome.PI (Unpackaged)": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/tools/PI/DevHome.PI/Services/PINavigationService.cs b/tools/PI/DevHome.PI/Services/PINavigationService.cs new file mode 100644 index 0000000000..35317d02b2 --- /dev/null +++ b/tools/PI/DevHome.PI/Services/PINavigationService.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using DevHome.Common.Extensions; +using DevHome.Common.Services; +using DevHome.PI.Contracts.ViewModels; +using DevHome.PI.Controls; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace DevHome.PI.Services; + +// Similar to DevHome.Services.NavigationService +internal sealed class PINavigationService : INavigationService +{ + private readonly IPageService pageService; + private object? lastParameterUsed; + private Frame? frame; + private string? defaultPage; + + public object? LastParameterUsed => lastParameterUsed; + + public event NavigatedEventHandler? Navigated; + + public Frame? Frame + { + get + { + if (frame == null) + { + var barWindow = Application.Current.GetService().DBarWindow; + frame = barWindow?.GetFrame(); + if (frame is not null) + { + RegisterFrameEvents(); + } + } + + return frame; + } + + set + { + UnregisterFrameEvents(); + frame = value; + RegisterFrameEvents(); + } + } + + public string DefaultPage + { + get => defaultPage ?? typeof(AppDetailsPageViewModel).FullName ?? string.Empty; + set => defaultPage = value; + } + + [MemberNotNullWhen(true, nameof(Frame), nameof(frame))] + public bool CanGoBack => Frame != null && Frame.CanGoBack; + + [MemberNotNullWhen(true, nameof(Frame), nameof(frame))] + public bool CanGoForward => Frame != null && Frame.CanGoForward; + + public PINavigationService(IPageService pageService) + { + this.pageService = pageService; + } + + private void RegisterFrameEvents() + { + if (frame != null) + { + frame.Navigated += OnNavigated; + } + } + + private void UnregisterFrameEvents() + { + if (frame != null) + { + frame.Navigated -= OnNavigated; + } + } + + public bool GoBack() + { + if (CanGoBack) + { + var vmBeforeNavigation = GetPageViewModel(frame); + frame.GoBack(); + if (vmBeforeNavigation is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + + return true; + } + + return false; + } + + public bool GoForward() + { + if (CanGoForward) + { + var vmBeforeNavigation = GetPageViewModel(frame); + frame.GoForward(); + if (vmBeforeNavigation is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + + return true; + } + + return false; + } + + public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false) + { + var pageType = pageService.GetPageType(pageKey); + + if (frame != null && (frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(lastParameterUsed)))) + { + frame.Tag = clearNavigation; + var vmBeforeNavigation = GetPageViewModel(frame); + var navigated = frame.Navigate(pageType, parameter); + if (navigated) + { + lastParameterUsed = parameter; + if (vmBeforeNavigation is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + } + + return navigated; + } + + return false; + } + + private void OnNavigated(object sender, NavigationEventArgs e) + { + if (sender is Frame frame) + { + var clearNavigation = (bool)frame.Tag; + if (clearNavigation) + { + frame.BackStack.Clear(); + } + + if (GetPageViewModel(frame) is INavigationAware navigationAware) + { + navigationAware.OnNavigatedTo(e.Parameter); + } + + Navigated?.Invoke(sender, e); + } + } + + public static object? GetPageViewModel(Frame frame) + { + return frame.Content?.GetType().GetProperty("viewModel")?.GetValue(frame.Content, null); + } +} diff --git a/tools/PI/DevHome.PI/Services/PIPageService.cs b/tools/PI/DevHome.PI/Services/PIPageService.cs new file mode 100644 index 0000000000..fd2a4e56e5 --- /dev/null +++ b/tools/PI/DevHome.PI/Services/PIPageService.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Common.Services; +using DevHome.PI.Pages; +using DevHome.PI.ViewModels; +using Microsoft.UI.Xaml.Controls; + +namespace DevHome.PI.Services; + +// Similar to DevHome.Services.PageService +internal sealed class PIPageService : IPageService +{ + private readonly Dictionary pages = new(); + + public PIPageService() + { + Configure(); + Configure(); + Configure(); + Configure(); + Configure(); + Configure(); + Configure(); + } + + public Type GetPageType(string key) + { + Type? pageType; + lock (pages) + { + if (!pages.TryGetValue(key, out pageType)) + { + throw new ArgumentException($"Page not found: {key}. Did you forget to call PageService.Configure?"); + } + } + + return pageType; + } + + public void Configure() + where T_VM : ObservableObject + where T_V : Page + { + lock (pages) + { + var key = typeof(T_VM).FullName!; + if (pages.ContainsKey(key)) + { + throw new ArgumentException($"The key {key} is already configured in PageService"); + } + + var type = typeof(T_V); + if (pages.Any(p => p.Value == type)) + { + throw new ArgumentException($"This type is already configured with key {pages.First(p => p.Value == type).Key}"); + } + + pages.Add(key, type); + } + } +} diff --git a/tools/PI/DevHome.PI/SettingsUi/AddToolControl.xaml b/tools/PI/DevHome.PI/SettingsUi/AddToolControl.xaml new file mode 100644 index 0000000000..ec22882a7d --- /dev/null +++ b/tools/PI/DevHome.PI/SettingsUi/AddToolControl.xaml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -127,9 +129,9 @@ -