diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5dedc8bfb2be..1e23b1821c70 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -26,7 +26,7 @@
-
+
diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
index fd684168b02a..d14c95fcad65 100644
--- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
+++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs
@@ -32,7 +32,6 @@ public enum PowerToysModules
MeasureTool,
Hosts,
Workspaces,
- WhatsNew,
RegistryPreview,
NewPlus,
ZoomIt,
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
index 417bdeef361c..7f14f1809ed3 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
@@ -9,7 +9,6 @@
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
-
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -227,7 +226,6 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
{
settingsWindow = new MainWindow();
settingsWindow.Activate();
- settingsWindow.ExtendsContentIntoTitleBar = true;
settingsWindow.NavigateToSection(StartupPage);
// https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground
@@ -257,11 +255,10 @@ private void OnLaunchedFromRunner(string[] cmdArgs)
else if (ShowScoobe)
{
PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent());
- OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew);
- scoobeWindow.Activate();
- scoobeWindow.ExtendsContentIntoTitleBar = true;
+ ScoobeWindow newScoobeWindow = new ScoobeWindow();
+ newScoobeWindow.Activate();
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
- SetOobeWindow(scoobeWindow);
+ SetScoobeWindow(newScoobeWindow);
}
}
}
@@ -339,6 +336,7 @@ public static int UpdateUIThemeMethod(string themeName)
private static MainWindow settingsWindow;
private static OobeWindow oobeWindow;
+ private static ScoobeWindow scoobeWindow;
public static void ClearSettingsWindow()
{
@@ -365,6 +363,21 @@ public static void ClearOobeWindow()
oobeWindow = null;
}
+ public static ScoobeWindow GetScoobeWindow()
+ {
+ return scoobeWindow;
+ }
+
+ public static void SetScoobeWindow(ScoobeWindow window)
+ {
+ scoobeWindow = window;
+ }
+
+ public static void ClearScoobeWindow()
+ {
+ scoobeWindow = null;
+ }
+
public static Type GetPage(string settingWindow)
{
switch (settingWindow)
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs
index e67b0efc0a85..e85633f9e8f0 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs
@@ -20,9 +20,6 @@
namespace Microsoft.PowerToys.Settings.UI
{
- ///
- /// An empty window that can be used on its own or navigated to within a Frame.
- ///
public sealed partial class MainWindow : WindowEx
{
public MainWindow(bool createHidden = false)
@@ -35,10 +32,12 @@ public MainWindow(bool createHidden = false)
App.ThemeService.ThemeChanged += OnThemeChanged;
App.ThemeService.ApplyTheme();
+ this.ExtendsContentIntoTitleBar = true;
+
ShellPage.SetElevationStatus(App.IsElevated);
ShellPage.SetIsUserAnAdmin(App.IsUserAnAdmin);
- var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
+ var hWnd = WindowNative.GetWindowHandle(this);
var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
if (createHidden)
{
@@ -121,16 +120,12 @@ public MainWindow(bool createHidden = false)
// open whats new window
ShellPage.SetOpenWhatIsNewCallback(() =>
{
- if (App.GetOobeWindow() == null)
+ if (App.GetScoobeWindow() == null)
{
- App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew));
- }
- else
- {
- App.GetOobeWindow().SetAppWindow(OOBE.Enums.PowerToysModules.WhatsNew);
+ App.SetScoobeWindow(new ScoobeWindow());
}
- App.GetOobeWindow().Activate();
+ App.GetScoobeWindow().Activate();
});
this.InitializeComponent();
@@ -187,7 +182,7 @@ private void Window_Closed(object sender, WindowEventArgs args)
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowHelper.SerializePlacement(hWnd);
- if (App.GetOobeWindow() == null)
+ if (App.GetOobeWindow() == null && App.GetScoobeWindow() == null)
{
App.ClearSettingsWindow();
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
index ed03a477e40c..78fee7fde321 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
@@ -1,60 +1,44 @@
-
-
-
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
+
+
-
+
-
+ Tapped="WhatIsNewItem_Tapped" />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml.cs
index f3e7676a620e..565c7f593807 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml.cs
@@ -5,18 +5,17 @@
using System;
using System.Collections.ObjectModel;
using System.Globalization;
-
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using WinRT.Interop;
+using Microsoft.UI.Xaml.Input;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
- public sealed partial class OobeShellPage : UserControl
+ public sealed partial class OobeShellPage : Page
{
public static Func RunSharedEventCallback { get; set; }
@@ -63,7 +62,6 @@ public OobeShellPage()
// NOTE: Experimentation for OOBE is currently turned off on server side. Keeping this code in a comment to allow future experiments.
// ExperimentationToggleSwitchEnabled = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.EnableExperimentation;
- SetTitleBar();
DataContext = ViewModel;
OobeShellHandler = this;
Modules = new ObservableCollection();
@@ -202,12 +200,6 @@ public OobeShellPage()
IsNew = true,
});
- Modules.Insert((int)PowerToysModules.WhatsNew, new OobePowerToysModule()
- {
- ModuleName = "WhatsNew",
- IsNew = false,
- });
-
Modules.Insert((int)PowerToysModules.RegistryPreview, new OobePowerToysModule()
{
ModuleName = "RegistryPreview",
@@ -229,7 +221,7 @@ public OobeShellPage()
public void OnClosing()
{
- Microsoft.UI.Xaml.Controls.NavigationViewItem selectedItem = this.navigationView.SelectedItem as Microsoft.UI.Xaml.Controls.NavigationViewItem;
+ NavigationViewItem selectedItem = this.navigationView.SelectedItem as NavigationViewItem;
if (selectedItem != null)
{
Modules[(int)(PowerToysModules)Enum.Parse(typeof(PowerToysModules), (string)selectedItem.Tag, true)].LogClosingModuleEvent();
@@ -238,19 +230,22 @@ public void OnClosing()
public void NavigateToModule(PowerToysModules selectedModule)
{
- if (selectedModule == PowerToysModules.WhatsNew)
- {
- navigationView.SelectedItem = navigationView.FooterMenuItems[0];
- }
- else
+ navigationView.SelectedItem = navigationView.MenuItems[(int)selectedModule];
+ }
+
+ private static void OpenScoobeWindow()
+ {
+ if (App.GetScoobeWindow() == null)
{
- navigationView.SelectedItem = navigationView.MenuItems[(int)selectedModule];
+ App.SetScoobeWindow(new ScoobeWindow());
}
+
+ App.GetScoobeWindow().Activate();
}
- private void NavigationView_SelectionChanged(Microsoft.UI.Xaml.Controls.NavigationView sender, Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs args)
+ private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
- Microsoft.UI.Xaml.Controls.NavigationViewItem selectedItem = args.SelectedItem as Microsoft.UI.Xaml.Controls.NavigationViewItem;
+ NavigationViewItem selectedItem = args.SelectedItem as NavigationViewItem;
if (selectedItem != null)
{
@@ -278,7 +273,7 @@ private void NavigationView_SelectionChanged(Microsoft.UI.Xaml.Controls.Navigati
break;
}
*/
- case "WhatsNew": NavigationFrame.Navigate(typeof(OobeWhatsNew)); break;
+
case "AdvancedPaste": NavigationFrame.Navigate(typeof(OobeAdvancedPaste)); break;
case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break;
case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break;
@@ -311,43 +306,37 @@ private void NavigationView_SelectionChanged(Microsoft.UI.Xaml.Controls.Navigati
}
}
- private void SetTitleBar()
- {
- var u = App.GetOobeWindow();
- if (u != null)
- {
- // A custom title bar is required for full window theme and Mica support.
- // https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
- u.ExtendsContentIntoTitleBar = true;
- WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(u));
- u.SetTitleBar(AppTitleBar);
- }
- }
-
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
{
- SetTitleBar();
+ // Select the first module by default
+ if (navigationView.MenuItems.Count > 0)
+ {
+ navigationView.SelectedItem = navigationView.MenuItems[0];
+ }
}
private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
{
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
{
- PaneToggleBtn.Visibility = Visibility.Visible;
- AppTitleBar.Margin = new Thickness(48, 0, 0, 0);
- AppTitleBarText.Margin = new Thickness(12, 0, 0, 0);
+ TitleBarIcon.Margin = new Thickness(0, 0, 8, 0); // Workaround, see XAML comment
+ AppTitleBar.IsPaneToggleButtonVisible = true;
}
else
{
- PaneToggleBtn.Visibility = Visibility.Collapsed;
- AppTitleBar.Margin = new Thickness(16, 0, 0, 0);
- AppTitleBarText.Margin = new Thickness(16, 0, 0, 0);
+ TitleBarIcon.Margin = new Thickness(16, 0, 0, 0); // Workaround, see XAML comment
+ AppTitleBar.IsPaneToggleButtonVisible = false;
}
}
- private void PaneToggleBtn_Click(object sender, RoutedEventArgs e)
+ private void TitleBar_PaneButtonClick(TitleBar sender, object args)
{
navigationView.IsPaneOpen = !navigationView.IsPaneOpen;
}
+
+ private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ OpenScoobeWindow();
+ }
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeWhatsNew.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeWhatsNew.xaml
deleted file mode 100644
index a7db22a7bece..000000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeWhatsNew.xaml
+++ /dev/null
@@ -1,191 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeWhatsNew.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeWhatsNew.xaml.cs
deleted file mode 100644
index 9d7f0e78f337..000000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeWhatsNew.xaml.cs
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using CommunityToolkit.WinUI.Controls;
-using global::PowerToys.GPOWrapper;
-using ManagedCommon;
-using Microsoft.PowerToys.Settings.UI.Helpers;
-using Microsoft.PowerToys.Settings.UI.Library;
-using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
-using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
-using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
-using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
-using Microsoft.PowerToys.Settings.UI.SerializationContext;
-using Microsoft.PowerToys.Settings.UI.Services;
-using Microsoft.PowerToys.Settings.UI.Views;
-using Microsoft.PowerToys.Telemetry;
-using Microsoft.UI.Text;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-
-namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
-{
- public sealed partial class OobeWhatsNew : Page, INotifyPropertyChanged
- {
- public OobePowerToysModule ViewModel { get; set; }
-
- private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData();
-
- public bool ShowDataDiagnosticsInfoBar => GetShowDataDiagnosticsInfoBar();
-
- private int _conflictCount;
-
- public AllHotkeyConflictsData AllHotkeyConflictsData
- {
- get => _allHotkeyConflictsData;
- set
- {
- if (_allHotkeyConflictsData != value)
- {
- _allHotkeyConflictsData = value;
-
- UpdateConflictCount();
-
- OnPropertyChanged(nameof(AllHotkeyConflictsData));
- OnPropertyChanged(nameof(HasConflicts));
- }
- }
- }
-
- public bool HasConflicts => _conflictCount > 0;
-
- private void UpdateConflictCount()
- {
- int count = 0;
- if (AllHotkeyConflictsData == null)
- {
- _conflictCount = count;
- }
-
- if (AllHotkeyConflictsData.InAppConflicts != null)
- {
- foreach (var inAppConflict in AllHotkeyConflictsData.InAppConflicts)
- {
- if (!inAppConflict.ConflictIgnored)
- {
- count++;
- }
- }
- }
-
- if (AllHotkeyConflictsData.SystemConflicts != null)
- {
- foreach (var systemConflict in AllHotkeyConflictsData.SystemConflicts)
- {
- if (!systemConflict.ConflictIgnored)
- {
- count++;
- }
- }
- }
-
- _conflictCount = count;
- }
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public OobeWhatsNew()
- {
- this.InitializeComponent();
- ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.WhatsNew]);
- DataContext = this;
-
- // Subscribe to hotkey conflict updates
- if (GlobalHotkeyConflictManager.Instance != null)
- {
- GlobalHotkeyConflictManager.Instance.ConflictsUpdated += OnConflictsUpdated;
- GlobalHotkeyConflictManager.Instance.RequestAllConflicts();
- }
- }
-
- private void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
- {
- this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
- {
- var allConflictData = e.Conflicts;
- foreach (var inAppConflict in allConflictData.InAppConflicts)
- {
- var hotkey = inAppConflict.Hotkey;
- var hotkeySetting = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
- inAppConflict.ConflictIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySetting);
- }
-
- foreach (var systemConflict in allConflictData.SystemConflicts)
- {
- var hotkey = systemConflict.Hotkey;
- var hotkeySetting = new HotkeySettings(hotkey.Win, hotkey.Ctrl, hotkey.Alt, hotkey.Shift, hotkey.Key);
- systemConflict.ConflictIgnored = HotkeyConflictIgnoreHelper.IsIgnoringConflicts(hotkeySetting);
- }
-
- AllHotkeyConflictsData = e.Conflicts ?? new AllHotkeyConflictsData();
- });
- }
-
- private void OnPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
-
- private bool GetShowDataDiagnosticsInfoBar()
- {
- var isDataDiagnosticsGpoDisallowed = GPOWrapper.GetAllowDataDiagnosticsValue() == GpoRuleConfigured.Disabled;
-
- if (isDataDiagnosticsGpoDisallowed)
- {
- return false;
- }
-
- bool userActed = DataDiagnosticsSettings.GetUserActionValue();
-
- if (userActed)
- {
- return false;
- }
-
- bool registryValue = DataDiagnosticsSettings.GetEnabledValue();
-
- bool isFirstRunAfterUpdate = (App.Current as Microsoft.PowerToys.Settings.UI.App).ShowScoobe;
- if (isFirstRunAfterUpdate && registryValue == false)
- {
- return true;
- }
-
- return false;
- }
-
- ///
- /// Regex to remove installer hash sections from the release notes.
- ///
- private const string RemoveInstallerHashesRegex = @"(\r\n)+## Installer Hashes(\r\n.*)+## Highlights";
- private const string RemoveHotFixInstallerHashesRegex = @"(\r\n)+## Installer Hashes(\r\n.*)+$";
- private const RegexOptions RemoveInstallerHashesRegexOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
- private bool _loadingReleaseNotes;
-
- private static async Task GetReleaseNotesMarkdown()
- {
- string releaseNotesJSON = string.Empty;
-
- // Let's use system proxy
- using var proxyClientHandler = new HttpClientHandler
- {
- DefaultProxyCredentials = CredentialCache.DefaultCredentials,
- Proxy = WebRequest.GetSystemWebProxy(),
- PreAuthenticate = true,
- };
-
- using var getReleaseInfoClient = new HttpClient(proxyClientHandler);
-
- // GitHub APIs require sending an user agent
- // https://docs.github.com/rest/overview/resources-in-the-rest-api#user-agent-required
- getReleaseInfoClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PowerToys");
- releaseNotesJSON = await getReleaseInfoClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases");
- IList releases = JsonSerializer.Deserialize>(releaseNotesJSON, SourceGenerationContextContext.Default.IListPowerToysReleaseInfo);
-
- // Get the latest releases
- var latestReleases = releases.OrderByDescending(release => release.PublishedDate).Take(5);
-
- StringBuilder releaseNotesHtmlBuilder = new StringBuilder(string.Empty);
-
- // Regex to remove installer hash sections from the release notes.
- Regex removeHashRegex = new Regex(RemoveInstallerHashesRegex, RemoveInstallerHashesRegexOptions);
-
- // Regex to remove installer hash sections from the release notes, since there'll be no Highlights section for hotfix releases.
- Regex removeHotfixHashRegex = new Regex(RemoveHotFixInstallerHashesRegex, RemoveInstallerHashesRegexOptions);
- int counter = 0;
- foreach (var release in latestReleases)
- {
- releaseNotesHtmlBuilder.AppendLine("# " + release.Name);
- var notes = removeHashRegex.Replace(release.ReleaseNotes, "\r\n### Highlights");
-
- // Add a unique counter to [github-current-release-work] to distinguish each release,
- // since this variable is used for all latest releases when they are merged.
- notes = notes.Replace("[github-current-release-work]", $"[github-current-release-work{++counter}]");
- notes = removeHotfixHashRegex.Replace(notes, string.Empty);
- releaseNotesHtmlBuilder.AppendLine(notes);
- releaseNotesHtmlBuilder.AppendLine(" ");
- }
-
- return releaseNotesHtmlBuilder.ToString();
- }
-
- private async Task Reload()
- {
- if (_loadingReleaseNotes)
- {
- return;
- }
-
- try
- {
- _loadingReleaseNotes = true;
- ReleaseNotesMarkdown.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
- LoadingProgressRing.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
- string releaseNotesMarkdown = await GetReleaseNotesMarkdown();
- ProxyWarningInfoBar.IsOpen = false;
- ErrorInfoBar.IsOpen = false;
-
- ReleaseNotesMarkdown.Text = releaseNotesMarkdown;
- ReleaseNotesMarkdown.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
- LoadingProgressRing.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
- }
- catch (HttpRequestException httpEx)
- {
- Logger.LogError("Exception when loading the release notes", httpEx);
- if (httpEx.Message.Contains("407", StringComparison.CurrentCulture))
- {
- ProxyWarningInfoBar.IsOpen = true;
- }
- else
- {
- ErrorInfoBar.IsOpen = true;
- }
- }
- catch (Exception ex)
- {
- Logger.LogError("Exception when loading the release notes", ex);
- ErrorInfoBar.IsOpen = true;
- }
- finally
- {
- LoadingProgressRing.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
- _loadingReleaseNotes = false;
- }
- }
-
- private async void Page_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
- {
- await Reload();
- }
-
- ///
- protected override void OnNavigatedTo(NavigationEventArgs e)
- {
- ViewModel.LogOpeningModuleEvent();
- }
-
- ///
- protected override void OnNavigatedFrom(NavigationEventArgs e)
- {
- ViewModel.LogClosingModuleEvent();
-
- // Unsubscribe from conflict updates when leaving the page
- if (GlobalHotkeyConflictManager.Instance != null)
- {
- GlobalHotkeyConflictManager.Instance.ConflictsUpdated -= OnConflictsUpdated;
- }
- }
-
- private void DataDiagnostics_InfoBar_YesNo_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
- {
- string commandArg = string.Empty;
- if (sender is Button senderBtn)
- {
- commandArg = senderBtn.CommandParameter.ToString();
- }
- else if (sender is HyperlinkButton senderLink)
- {
- commandArg = senderLink.CommandParameter.ToString();
- }
-
- if (string.IsNullOrEmpty(commandArg))
- {
- return;
- }
-
- // Update UI
- if (commandArg == "Yes")
- {
- WhatsNewDataDiagnosticsInfoBar.Header = ResourceLoaderInstance.ResourceLoader.GetString("Oobe_WhatsNew_DataDiagnostics_Yes_Click_InfoBar_Title");
- }
- else
- {
- WhatsNewDataDiagnosticsInfoBar.Header = ResourceLoaderInstance.ResourceLoader.GetString("Oobe_WhatsNew_DataDiagnostics_No_Click_InfoBar_Title");
- }
-
- WhatsNewDataDiagnosticsInfoBarDescText.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
- WhatsNewDataDiagnosticsInfoBarDescTextYesClicked.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
- DataDiagnosticsButtonYes.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
- DataDiagnosticsButtonNo.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
-
- // Set Data Diagnostics registry values
- if (commandArg == "Yes")
- {
- DataDiagnosticsSettings.SetEnabledValue(true);
- }
- else
- {
- DataDiagnosticsSettings.SetEnabledValue(false);
- }
-
- DataDiagnosticsSettings.SetUserActionValue(true);
-
- this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () =>
- {
- ShellPage.ShellHandler?.SignalGeneralDataUpdate();
- });
- }
-
- private void DataDiagnostics_InfoBar_Close_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
- {
- WhatsNewDataDiagnosticsInfoBar.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
- }
-
- private void DataDiagnostics_OpenSettings_Click(Microsoft.UI.Xaml.Documents.Hyperlink sender, Microsoft.UI.Xaml.Documents.HyperlinkClickEventArgs args)
- {
- Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Overview);
- }
-
- private async void LoadReleaseNotes_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
- {
- await Reload();
- }
- }
-}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseGroupViewModel.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseGroupViewModel.cs
new file mode 100644
index 000000000000..4689d179b274
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseGroupViewModel.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+using Microsoft.PowerToys.Settings.UI.Helpers;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ ///
+ /// View model for a group of releases (grouped by major.minor version).
+ ///
+ public class ScoobeReleaseGroupViewModel
+ {
+ ///
+ /// Gets the list of releases in this group.
+ ///
+ public IList Releases { get; }
+
+ ///
+ /// Gets the version text to display (e.g., "0.96.0").
+ ///
+ public string VersionText { get; }
+
+ ///
+ /// Gets the date text to display (e.g., "December 2025").
+ ///
+ public string DateText { get; }
+
+ public ScoobeReleaseGroupViewModel(IList releases)
+ {
+ Releases = releases ?? throw new ArgumentNullException(nameof(releases));
+
+ if (releases.Count > 0)
+ {
+ var latestRelease = releases[0];
+ VersionText = GetVersionFromRelease(latestRelease);
+ DateText = latestRelease.PublishedDate.ToString("MMMM yyyy", CultureInfo.CurrentCulture);
+ }
+ else
+ {
+ VersionText = "Unknown";
+ DateText = string.Empty;
+ }
+ }
+
+ private static string GetVersionFromRelease(PowerToysReleaseInfo release)
+ {
+ // TagName is typically like "v0.96.0", Name might be "Release v0.96.0"
+ string version = release.TagName ?? release.Name ?? "Unknown";
+ if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase))
+ {
+ version = version.Substring(1);
+ }
+
+ return version;
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml
new file mode 100644
index 000000000000..5bd267fee570
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs
new file mode 100644
index 000000000000..9371fcbaed30
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeReleaseNotesPage.xaml.cs
@@ -0,0 +1,165 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Text.RegularExpressions;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Helpers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media.Imaging;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ public sealed partial class ScoobeReleaseNotesPage : Page
+ {
+ private IList _currentReleases;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ScoobeReleaseNotesPage()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// Regex to remove installer hash sections from the release notes.
+ ///
+ private const string RemoveInstallerHashesRegex = @"(\r\n)+## Installer Hashes(\r\n.*)+## Highlights";
+ private const string RemoveHotFixInstallerHashesRegex = @"(\r\n)+## Installer Hashes(\r\n.*)+$";
+ private const RegexOptions RemoveInstallerHashesRegexOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;
+
+ ///
+ /// Regex to match markdown images with 'Hero' in the alt text.
+ /// Matches: 
+ ///
+ private static readonly Regex HeroImageRegex = new Regex(
+ @"!\[([^\]]*Hero[^\]]*)\]\(([^)]+)\)",
+ RegexOptions.Compiled | RegexOptions.IgnoreCase);
+
+ ///
+ /// Regex to match GitHub PR/Issue references (e.g., #41029).
+ /// Only matches # followed by digits that are not already part of a markdown link.
+ ///
+ private static readonly Regex GitHubPrReferenceRegex = new Regex(
+ @"(? releases)
+ {
+ if (releases == null || releases.Count == 0)
+ {
+ return (string.Empty, null);
+ }
+
+ StringBuilder releaseNotesHtmlBuilder = new StringBuilder(string.Empty);
+
+ // Regex to remove installer hash sections from the release notes.
+ Regex removeHashRegex = new Regex(RemoveInstallerHashesRegex, RemoveInstallerHashesRegexOptions);
+
+ // Regex to remove installer hash sections from the release notes, since there'll be no Highlights section for hotfix releases.
+ Regex removeHotfixHashRegex = new Regex(RemoveHotFixInstallerHashesRegex, RemoveInstallerHashesRegexOptions);
+
+ string lastHeroImageUrl = null;
+
+ int counter = 0;
+ bool isFirst = true;
+ foreach (var release in releases)
+ {
+ // Add separator between releases
+ if (!isFirst)
+ {
+ releaseNotesHtmlBuilder.AppendLine("---");
+ releaseNotesHtmlBuilder.AppendLine();
+ }
+
+ isFirst = false;
+
+ var releaseUrl = string.Format(CultureInfo.InvariantCulture, GitHubReleaseLinkTemplate, release.TagName);
+ releaseNotesHtmlBuilder.AppendLine(CultureInfo.InvariantCulture, $"# {release.Name}");
+ releaseNotesHtmlBuilder.AppendLine(CultureInfo.InvariantCulture, $"{release.PublishedDate.ToString("MMMM d, yyyy", CultureInfo.CurrentCulture)} � [View on GitHub]({releaseUrl})");
+ releaseNotesHtmlBuilder.AppendLine();
+ releaseNotesHtmlBuilder.AppendLine(" ");
+ releaseNotesHtmlBuilder.AppendLine();
+ var notes = removeHashRegex.Replace(release.ReleaseNotes, "\r\n## Highlights");
+ notes = notes.Replace("[github-current-release-work]", $"[github-current-release-work{++counter}]");
+ notes = removeHotfixHashRegex.Replace(notes, string.Empty);
+
+ // Find all Hero images and keep track of the last one
+ var heroMatches = HeroImageRegex.Matches(notes);
+ foreach (Match match in heroMatches)
+ {
+ lastHeroImageUrl = match.Groups[2].Value;
+ }
+
+ // Remove Hero images from the markdown
+ notes = HeroImageRegex.Replace(notes, string.Empty);
+
+ // Convert GitHub PR/Issue references to hyperlinks
+ notes = GitHubPrReferenceRegex.Replace(notes, match =>
+ string.Format(CultureInfo.InvariantCulture, GitHubPrLinkTemplate, match.Groups[1].Value));
+
+ releaseNotesHtmlBuilder.AppendLine(notes);
+ releaseNotesHtmlBuilder.AppendLine(" ");
+ }
+
+ return (releaseNotesHtmlBuilder.ToString(), lastHeroImageUrl);
+ }
+
+ private void DisplayReleaseNotes()
+ {
+ if (_currentReleases == null || _currentReleases.Count == 0)
+ {
+ ReleaseNotesMarkdown.Visibility = Visibility.Collapsed;
+ return;
+ }
+
+ try
+ {
+ LoadingProgressRing.Visibility = Visibility.Collapsed;
+
+ var (releaseNotesMarkdown, heroImageUrl) = ProcessReleaseNotesMarkdown(_currentReleases);
+
+ // Set the Hero image if found
+ if (!string.IsNullOrEmpty(heroImageUrl))
+ {
+ HeroImageHolder.Source = new BitmapImage(new Uri(heroImageUrl));
+ HeroImageHolder.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ HeroImageHolder.Visibility = Visibility.Collapsed;
+ }
+
+ ReleaseNotesMarkdown.Text = releaseNotesMarkdown;
+ ReleaseNotesMarkdown.Visibility = Visibility.Visible;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Exception when displaying the release notes", ex);
+ }
+ }
+
+ private void Page_Loaded(object sender, RoutedEventArgs e)
+ {
+ DisplayReleaseNotes();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (e.Parameter is IList releases)
+ {
+ _currentReleases = releases;
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml
new file mode 100644
index 000000000000..4ea411a57420
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs
new file mode 100644
index 000000000000..a18ea16a226e
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs
@@ -0,0 +1,194 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Helpers;
+using Microsoft.PowerToys.Settings.UI.SerializationContext;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ public sealed partial class ScoobeShellPage : Page
+ {
+ public static Action OpenMainWindowCallback { get; set; }
+
+ public static void SetOpenMainWindowCallback(Action implementation)
+ {
+ OpenMainWindowCallback = implementation;
+ }
+
+ ///
+ /// Gets or sets a shell handler to be used to update contents of the shell dynamically from page within the frame.
+ ///
+ public static ScoobeShellPage ScoobeShellHandler { get; set; }
+
+ ///
+ /// Gets the list of release groups loaded from GitHub (grouped by major.minor version).
+ ///
+ public IList> ReleaseGroups { get; private set; }
+
+ private bool _isLoading;
+
+ public ScoobeShellPage()
+ {
+ InitializeComponent();
+ ScoobeShellHandler = this;
+ }
+
+ private async void ShellPage_Loaded(object sender, RoutedEventArgs e)
+ {
+ SetTitleBar();
+ await LoadReleasesAsync();
+ }
+
+ private async Task LoadReleasesAsync()
+ {
+ if (_isLoading)
+ {
+ return;
+ }
+
+ _isLoading = true;
+ LoadingProgressRing.Visibility = Visibility.Visible;
+ ErrorInfoBar.IsOpen = false;
+ navigationView.MenuItems.Clear();
+
+ try
+ {
+ var releases = await FetchReleasesFromGitHubAsync();
+ ReleaseGroups = GroupReleasesByMajorMinor(releases);
+ PopulateNavigationItems();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to load releases", ex);
+ ErrorInfoBar.IsOpen = true;
+ }
+ finally
+ {
+ LoadingProgressRing.Visibility = Visibility.Collapsed;
+ _isLoading = false;
+ }
+ }
+
+ private static async Task> FetchReleasesFromGitHubAsync()
+ {
+ using var proxyClientHandler = new HttpClientHandler
+ {
+ DefaultProxyCredentials = CredentialCache.DefaultCredentials,
+ Proxy = WebRequest.GetSystemWebProxy(),
+ PreAuthenticate = true,
+ };
+
+ using var httpClient = new HttpClient(proxyClientHandler);
+ httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PowerToys");
+
+ string json = await httpClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases?per_page=20");
+ var allReleases = JsonSerializer.Deserialize>(json, SourceGenerationContextContext.Default.IListPowerToysReleaseInfo);
+
+ return allReleases
+ .OrderByDescending(r => r.PublishedDate)
+ .ToList();
+ }
+
+ private static IList> GroupReleasesByMajorMinor(IList releases)
+ {
+ return releases
+ .GroupBy(r => GetMajorMinorVersion(r))
+ .Select(g => g.OrderByDescending(r => r.PublishedDate).ToList() as IList)
+ .ToList();
+ }
+
+ private static string GetMajorMinorVersion(PowerToysReleaseInfo release)
+ {
+ string version = GetVersionFromRelease(release);
+ var parts = version.Split('.');
+ if (parts.Length >= 2)
+ {
+ return $"{parts[0]}.{parts[1]}";
+ }
+
+ return version;
+ }
+
+ private static string GetVersionFromRelease(PowerToysReleaseInfo release)
+ {
+ // TagName is typically like "v0.96.0", Name might be "Release v0.96.0"
+ string version = release.TagName ?? release.Name ?? "Unknown";
+ if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase))
+ {
+ version = version.Substring(1);
+ }
+
+ return version;
+ }
+
+ private void PopulateNavigationItems()
+ {
+ if (ReleaseGroups == null || ReleaseGroups.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var releaseGroup in ReleaseGroups)
+ {
+ var viewModel = new ScoobeReleaseGroupViewModel(releaseGroup);
+ navigationView.MenuItems.Add(viewModel);
+ }
+
+ // Select the first item to trigger navigation
+ navigationView.SelectedItem = navigationView.MenuItems[0];
+ }
+
+ private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
+ {
+ if (args.SelectedItem is ScoobeReleaseGroupViewModel viewModel)
+ {
+ NavigationFrame.Navigate(typeof(ScoobeReleaseNotesPage), viewModel.Releases);
+ }
+ }
+
+ private async void RetryButton_Click(object sender, RoutedEventArgs e)
+ {
+ await LoadReleasesAsync();
+ }
+
+ private void SetTitleBar()
+ {
+ var window = App.GetScoobeWindow();
+ if (window != null)
+ {
+ window.ExtendsContentIntoTitleBar = true;
+ window.SetTitleBar(AppTitleBar);
+ }
+ }
+
+ private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args)
+ {
+ if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
+ {
+ TitleBarIcon.Margin = new Thickness(0, 0, 8, 0); // Workaround, see XAML comment
+ AppTitleBar.IsPaneToggleButtonVisible = true;
+ }
+ else
+ {
+ TitleBarIcon.Margin = new Thickness(16, 0, 0, 0); // Workaround, see XAML comment
+ AppTitleBar.IsPaneToggleButtonVisible = false;
+ }
+ }
+
+ private void TitleBar_PaneButtonClick(TitleBar sender, object args)
+ {
+ navigationView.IsPaneOpen = !navigationView.IsPaneOpen;
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs
index 56262fea6aae..6a630547736f 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs
@@ -44,6 +44,7 @@ public OobeWindow(PowerToysModules initialModule)
_windowId = Win32Interop.GetWindowIdFromWindow(_hWnd);
_appWindow = AppWindow.GetFromWindowId(_windowId);
this.Activated += Window_Activated_SetIcon;
+ this.ExtendsContentIntoTitleBar = true;
var dpi = NativeMethods.GetDpiForWindow(_hWnd);
_currentDPI = dpi;
@@ -60,7 +61,7 @@ public OobeWindow(PowerToysModules initialModule)
this.SizeChanged += OobeWindow_SizeChanged;
- var loader = Helpers.ResourceLoaderInstance.ResourceLoader;
+ var loader = ResourceLoaderInstance.ResourceLoader;
Title = loader.GetString("OobeWindow_Title");
if (shellPage != null)
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml
new file mode 100644
index 000000000000..934229cea159
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs
new file mode 100644
index 000000000000..446d0e2d0d48
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+using Microsoft.PowerToys.Settings.UI.Helpers;
+using Microsoft.PowerToys.Settings.UI.OOBE.Views;
+using Microsoft.UI;
+using Microsoft.UI.Windowing;
+using Microsoft.UI.Xaml;
+using PowerToys.Interop;
+using Windows.Graphics;
+using WinUIEx;
+using WinUIEx.Messaging;
+
+namespace Microsoft.PowerToys.Settings.UI
+{
+ public sealed partial class ScoobeWindow : WindowEx, IDisposable
+ {
+ private const int ExpectedWidth = 1100;
+ private const int ExpectedHeight = 700;
+ private const int DefaultDPI = 96;
+ private int _currentDPI;
+ private WindowId _windowId;
+ private IntPtr _hWnd;
+ private AppWindow _appWindow;
+ private bool disposedValue;
+
+ public ScoobeWindow()
+ {
+ App.ThemeService.ThemeChanged += OnThemeChanged;
+ App.ThemeService.ApplyTheme();
+
+ this.InitializeComponent();
+
+ _hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
+ _windowId = Win32Interop.GetWindowIdFromWindow(_hWnd);
+ _appWindow = AppWindow.GetFromWindowId(_windowId);
+ this.Activated += Window_Activated_SetIcon;
+ this.ExtendsContentIntoTitleBar = true;
+
+ var dpi = NativeMethods.GetDpiForWindow(_hWnd);
+ _currentDPI = dpi;
+ float scalingFactor = (float)dpi / DefaultDPI;
+ int width = (int)(ExpectedWidth * scalingFactor);
+ int height = (int)(ExpectedHeight * scalingFactor);
+
+ SizeInt32 size;
+ size.Width = width;
+ size.Height = height;
+ _appWindow.Resize(size);
+
+ this.SizeChanged += ScoobeWindow_SizeChanged;
+
+ var loader = Helpers.ResourceLoaderInstance.ResourceLoader;
+ Title = loader.GetString("ScoobeWindow_Title");
+
+ ScoobeShellPage.SetOpenMainWindowCallback((Type type) =>
+ {
+ App.OpenSettingsWindow(type);
+ });
+ }
+
+ private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args)
+ {
+ // Set window icon
+ _appWindow.SetIcon("Assets\\Settings\\icon.ico");
+ }
+
+ private void ScoobeWindow_SizeChanged(object sender, WindowSizeChangedEventArgs args)
+ {
+ var dpi = NativeMethods.GetDpiForWindow(_hWnd);
+ if (_currentDPI != dpi)
+ {
+ // Reacting to a DPI change. Should not cause a resize -> sizeChanged loop.
+ _currentDPI = dpi;
+ float scalingFactor = (float)dpi / DefaultDPI;
+ int width = (int)(ExpectedWidth * scalingFactor);
+ int height = (int)(ExpectedHeight * scalingFactor);
+ SizeInt32 size;
+ size.Width = width;
+ size.Height = height;
+ _appWindow.Resize(size);
+ }
+ }
+
+ private void Window_Closed(object sender, WindowEventArgs args)
+ {
+ App.ClearScoobeWindow();
+
+ var mainWindow = App.GetSettingsWindow();
+ if (mainWindow != null)
+ {
+ mainWindow.CloseHiddenWindow();
+ }
+
+ App.ThemeService.ThemeChanged -= OnThemeChanged;
+ }
+
+ private void OnThemeChanged(object sender, ElementTheme theme)
+ {
+ WindowHelper.SetTheme(this, theme);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs
index 639eb5f747d6..752cc695edd5 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs
@@ -8,8 +8,6 @@
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
-using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
-using Microsoft.PowerToys.Settings.UI.OOBE.Views;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -50,16 +48,12 @@ public void RefreshEnabledState()
private void WhatsNewButton_Click(object sender, RoutedEventArgs e)
{
- if (App.GetOobeWindow() == null)
+ if (App.GetScoobeWindow() == null)
{
- App.SetOobeWindow(new OobeWindow(PowerToysModules.WhatsNew));
- }
- else
- {
- App.GetOobeWindow().SetAppWindow(PowerToysModules.WhatsNew);
+ App.SetScoobeWindow(new ScoobeWindow());
}
- App.GetOobeWindow().Activate();
+ App.GetScoobeWindow().Activate();
}
private void SortAlphabetical_Click(object sender, RoutedEventArgs e)
diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
index 679910a082e9..9a54159984c4 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -2419,9 +2419,15 @@ From there, simply click on one of the supported files in the File Explorer and
Welcome to PowerToys
-
+
Welcome to PowerToys
+
+ What's new in PowerToys
+
+
+ What's new in PowerToys
+
PowerToys Settings
Title of the settings window when running as user