[OOBE]Add What's New page - SCOOBE (#16478)

* [Oobe]Add a What's New page

* Show What's New when version changes

* Add link to GitHub

* Use generic icon for now

* Get only the latest 5 releases

* fix spellchecker

* rename last_version_run.json

* Remove UserControl_Loaded

* Remove installer hash from the release notes

* constexpr some strings

* Add check your internet connection message
This commit is contained in:
Jaime Bernardo
2022-02-22 11:02:08 +00:00
committed by GitHub
parent e9dccf82ab
commit 87bb89ab15
18 changed files with 322 additions and 25 deletions

View File

@@ -1780,7 +1780,7 @@ scancode
scanled
schedtasks
scm
SCOOBE
scoobe
SCOPEID
screenshot
scrollable

View File

@@ -5,6 +5,9 @@ namespace PTSettingsHelper
{
constexpr inline const wchar_t* settings_filename = L"\\settings.json";
constexpr inline const wchar_t* oobe_filename = L"oobe_settings.json";
constexpr inline const wchar_t* last_version_run_filename = L"last_version_run.json";
constexpr inline const wchar_t* opened_at_first_launch_json_field_name = L"openedAtFirstLaunch";
constexpr inline const wchar_t* last_version_json_field_name = L"last_version";
std::wstring get_root_save_folder_location()
{
@@ -90,7 +93,7 @@ namespace PTSettingsHelper
return false;
}
bool opened = saved_settings->GetNamedBoolean(L"openedAtFirstLaunch", false);
bool opened = saved_settings->GetNamedBoolean(opened_at_first_launch_json_field_name, false);
return opened;
}
@@ -103,8 +106,39 @@ namespace PTSettingsHelper
oobePath = oobePath.append(oobe_filename);
json::JsonObject obj;
obj.SetNamedValue(L"openedAtFirstLaunch", json::value(true));
obj.SetNamedValue(opened_at_first_launch_json_field_name, json::value(true));
json::to_file(oobePath.c_str(), obj);
}
std::wstring get_last_version_run()
{
std::filesystem::path lastVersionRunPath(PTSettingsHelper::get_root_save_folder_location());
lastVersionRunPath = lastVersionRunPath.append(last_version_run_filename);
if (std::filesystem::exists(lastVersionRunPath))
{
auto saved_settings = json::from_file(lastVersionRunPath.c_str());
if (!saved_settings.has_value())
{
return L"";
}
std::wstring last_version = saved_settings->GetNamedString(last_version_json_field_name, L"").c_str();
return last_version;
}
return L"";
}
void save_last_version_run(const std::wstring& version)
{
std::filesystem::path lastVersionRunPath(PTSettingsHelper::get_root_save_folder_location());
lastVersionRunPath = lastVersionRunPath.append(last_version_run_filename);
json::JsonObject obj;
obj.SetNamedValue(last_version_json_field_name, json::value(version));
json::to_file(lastVersionRunPath.c_str(), obj);
}
}

View File

@@ -21,4 +21,6 @@ namespace PTSettingsHelper
bool get_oobe_opened_state();
void save_oobe_opened_state();
std::wstring get_last_version_run();
void save_last_version_run(const std::wstring& version);
}

View File

@@ -105,7 +105,7 @@ void debug_verify_launcher_assets()
}
}
int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow, bool openOobe)
int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow, bool openOobe, bool openScoobe)
{
Logger::info("Runner is starting. Elevated={}", isProcessElevated);
DPIAware::EnableDPIAwarenessForThisProcess();
@@ -179,8 +179,9 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
}
// Start initial powertoys
start_enabled_powertoys();
Trace::EventLaunch(get_product_version(), isProcessElevated);
std::wstring product_version = get_product_version();
Trace::EventLaunch(product_version, isProcessElevated);
PTSettingsHelper::save_last_version_run(product_version);
if (openSettings)
{
@@ -196,6 +197,10 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
{
open_oobe_window();
}
else if (openScoobe)
{
open_scoobe_window();
}
settings_telemetry::init();
result = run_message_loop();
@@ -373,6 +378,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
Logger::error("Failed to get or save OOBE state with an exception: {}", e.what());
}
bool openScoobe = false;
try
{
std::wstring last_version_run = PTSettingsHelper::get_last_version_run();
openScoobe = last_version_run != get_product_version();
}
catch (const std::exception& e)
{
Logger::error("Failed to get last version with an exception: {}", e.what());
}
int result = 0;
try
{
@@ -398,7 +414,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
else if (elevated || !run_elevated_setting || with_dont_elevate_arg)
{
result = runner(elevated, open_settings, settings_window, openOobe);
result = runner(elevated, open_settings, settings_window, openOobe, openScoobe);
// Save settings on closing
auto general_settings = get_general_settings();

View File

@@ -260,7 +260,7 @@ BOOL run_settings_non_elevated(LPCWSTR executable_path, LPWSTR executable_args,
DWORD g_settings_process_id = 0;
void run_settings_window(bool show_oobe_window, std::optional<std::wstring> settings_window)
void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional<std::wstring> settings_window)
{
g_isLaunchInProgress = true;
@@ -327,6 +327,9 @@ void run_settings_window(bool show_oobe_window, std::optional<std::wstring> sett
// Arg 8: should oobe window be shown
std::wstring settings_showOobe = show_oobe_window ? L"true" : L"false";
// Arg 9: should scoobe window be shown
std::wstring settings_showScoobe = show_scoobe_window ? L"true" : L"false";
// create general settings file to initialize the settings file with installation configurations like :
// 1. Run on start up.
PTSettingsHelper::save_general_settings(save_settings.to_json());
@@ -347,6 +350,8 @@ void run_settings_window(bool show_oobe_window, std::optional<std::wstring> sett
executable_args.append(settings_isUserAnAdmin);
executable_args.append(L" ");
executable_args.append(settings_showOobe);
executable_args.append(L" ");
executable_args.append(settings_showScoobe);
if (settings_window.has_value())
{
@@ -490,7 +495,7 @@ void open_settings_window(std::optional<std::wstring> settings_window)
if (!g_isLaunchInProgress)
{
std::thread([settings_window]() {
run_settings_window(false, settings_window);
run_settings_window(false, false, settings_window);
}).detach();
}
}
@@ -511,7 +516,14 @@ void close_settings_window()
void open_oobe_window()
{
std::thread([]() {
run_settings_window(true, std::nullopt);
run_settings_window(true, false, std::nullopt);
}).detach();
}
void open_scoobe_window()
{
std::thread([]() {
run_settings_window(false, true, std::nullopt);
}).detach();
}

View File

@@ -24,4 +24,5 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value);
void open_settings_window(std::optional<std::wstring> settings_window);
void close_settings_window();
void open_oobe_window();
void open_oobe_window();
void open_scoobe_window();

View File

@@ -18,6 +18,8 @@ namespace PowerToys.Settings
public bool ShowOobe { get; set; }
public bool ShowScoobe { get; set; }
public Type StartupPage { get; set; } = typeof(Microsoft.PowerToys.Settings.UI.Views.GeneralPage);
public void OpenSettingsWindow(Type type)
@@ -45,7 +47,7 @@ namespace PowerToys.Settings
private void Application_Startup(object sender, StartupEventArgs e)
{
if (!ShowOobe)
if (!ShowOobe && !ShowScoobe)
{
settingsWindow = new MainWindow();
settingsWindow.Show();
@@ -53,15 +55,22 @@ namespace PowerToys.Settings
}
else
{
PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent());
// Create the Settings window so that it's fully initialized and
// it will be ready to receive the notification if the user opens
// the Settings from the tray icon.
InitHiddenSettingsWindow();
OobeWindow oobeWindow = new OobeWindow();
oobeWindow.Show();
if (ShowOobe)
{
PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent());
OobeWindow oobeWindow = new OobeWindow((int)Microsoft.PowerToys.Settings.UI.OOBE.Enums.PowerToysModulesEnum.Overview);
oobeWindow.Show();
}
else if (ShowScoobe)
{
PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent());
OobeWindow scoobeWindow = new OobeWindow((int)Microsoft.PowerToys.Settings.UI.OOBE.Enums.PowerToysModulesEnum.WhatsNew);
scoobeWindow.Show();
}
}
}
}

View File

@@ -125,7 +125,7 @@ namespace PowerToys.Settings
// open oobe
ShellPage.SetOpenOobeCallback(() =>
{
var oobe = new OobeWindow();
var oobe = new OobeWindow((int)Microsoft.PowerToys.Settings.UI.OOBE.Enums.PowerToysModulesEnum.Overview);
oobe.Show();
});

View File

@@ -20,6 +20,7 @@ namespace PowerToys.Settings
{
private static Window inst;
private OobeShellPage shellPage;
private int initialModule;
public static bool IsOpened
{
@@ -29,10 +30,11 @@ namespace PowerToys.Settings
}
}
public OobeWindow()
public OobeWindow(int initialModule)
{
InitializeComponent();
Utils.FitToScreen(this);
this.initialModule = initialModule;
ResourceLoader loader = ResourceLoader.GetForViewIndependentUse();
Title = loader.GetString("OobeWindow_Title");
@@ -69,6 +71,11 @@ namespace PowerToys.Settings
WindowsXamlHost windowsXamlHost = sender as WindowsXamlHost;
shellPage = windowsXamlHost.GetUwpInternalObject() as OobeShellPage;
if (shellPage != null)
{
shellPage.NavigateToModule(initialModule);
}
OobeShellPage.SetRunSharedEventCallback(() =>
{
return Constants.PowerLauncherSharedEvent();

View File

@@ -21,12 +21,13 @@ namespace PowerToys.Settings
ElevatedStatus,
IsUserAdmin,
ShowOobeWindow,
ShowScoobeWindow,
SettingsWindow,
}
// Quantity of arguments
private const int RequiredArgumentsQty = 7;
private const int RequiredAndOptionalArgumentsQty = 8;
private const int RequiredArgumentsQty = 8;
private const int RequiredAndOptionalArgumentsQty = 9;
// Create an instance of the IPC wrapper.
private static TwoWayPipeMessageIPCManaged ipcmanager;
@@ -55,6 +56,7 @@ namespace PowerToys.Settings
IsElevated = args[(int)Arguments.ElevatedStatus] == "true";
IsUserAnAdmin = args[(int)Arguments.IsUserAdmin] == "true";
app.ShowOobe = args[(int)Arguments.ShowOobeWindow] == "true";
app.ShowScoobe = args[(int)Arguments.ShowScoobeWindow] == "true";
if (args.Length == RequiredAndOptionalArgumentsQty)
{

View File

@@ -0,0 +1,18 @@
// 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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events
{
[EventData]
public class ScoobeStartedEvent : EventBase, IEvent
{
public bool ScoobeStarted { get; set; } = true;
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -7,6 +7,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
public enum PowerToysModulesEnum
{
Overview = 0,
WhatsNew,
AlwaysOnTop,
Awake,
ColorPicker,

View File

@@ -9,8 +9,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
winui:BackdropMaterial.ApplyToRootOrPageBackground="True"
mc:Ignorable="d"
HighContrastAdjustment="None"
Loaded="UserControl_Loaded">
HighContrastAdjustment="None">
<UserControl.Resources>
<DataTemplate x:Key="NavigationViewMenuItem" x:DataType="localModels:OobePowerToysModule">

View File

@@ -71,6 +71,14 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
DescriptionLink = "https://aka.ms/PowerToysOverview",
Link = "https://github.com/microsoft/PowerToys/releases/",
});
Modules.Insert((int)PowerToysModulesEnum.WhatsNew, new OobePowerToysModule()
{
ModuleName = loader.GetString("Oobe_WhatsNew"),
Tag = "WhatsNew",
IsNew = false,
Icon = "\uEF3C",
FluentIcon = "ms-appx:///Assets/FluentIcons/FluentIconsSettings.png",
});
Modules.Insert((int)PowerToysModulesEnum.AlwaysOnTop, new OobePowerToysModule()
{
ModuleName = loader.GetString("Oobe_AlwaysOnTop"),
@@ -226,11 +234,11 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
}
}
private void UserControl_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
public void NavigateToModule(int moduleIndex)
{
if (Modules.Count > 0)
{
NavigationView.SelectedItem = Modules[(int)PowerToysModulesEnum.Overview];
NavigationView.SelectedItem = Modules[moduleIndex];
}
}
@@ -242,6 +250,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
switch (selectedItem.Tag)
{
case "Overview": NavigationFrame.Navigate(typeof(OobeOverview)); break;
case "WhatsNew": NavigationFrame.Navigate(typeof(OobeWhatsNew)); break;
case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break;
case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break;
case "ColorPicker": NavigationFrame.Navigate(typeof(OobeColorPicker)); break;

View File

@@ -0,0 +1,52 @@
<Page x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeWhatsNew"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:toolkitcontrols="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d"
Loaded="Page_Loaded">
<ScrollViewer
VerticalScrollBarVisibility="Auto"
Padding="32,24,32,24">
<StackPanel
Orientation="Vertical"
VerticalAlignment="Top">
<TextBlock
x:Name="TitleTxt"
Text=""
AutomationProperties.HeadingLevel="Level1"
Style="{StaticResource TitleTextBlockStyle}" />
<HyperlinkButton
NavigateUri="https://github.com/microsoft/PowerToys/releases"
Margin="0,0,0,16"
Style="{StaticResource TextButtonStyle}">
<TextBlock
x:Uid="Oobe_WhatsNew_DetailedReleaseNotesLink"
TextWrapping="Wrap" />
</HyperlinkButton>
<muxc:ProgressRing
x:Name="LoadingProgressRing"
IsIndeterminate="True"
Visibility="Visible"/>
<muxc:InfoBar
Severity="Error"
x:Name="ErrorInfoBar"
x:Uid="Oobe_WhatsNew_LoadingError"
Visibility="Collapsed"
IsClosable="False"
IsOpen="True"
IsTabStop="True" />
<toolkitcontrols:MarkdownTextBlock
x:Name="ReleaseNotesMarkdown"
Visibility="Collapsed"
Background="Transparent"
LinkClicked="ReleaseNotesMarkdown_LinkClicked"/>
</StackPanel>
</ScrollViewer>
</Page>

View File

@@ -0,0 +1,118 @@
// 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.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.ApplicationModel.Resources;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
public sealed partial class OobeWhatsNew : Page
{
// Contains information for a release. Used to deserialize release JSON info from GitHub.
private class PowerToysReleaseInfo
{
[JsonPropertyName("published_at")]
public DateTimeOffset PublishedDate { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("tag_name")]
public string TagName { get; set; }
[JsonPropertyName("body")]
public string ReleaseNotes { get; set; }
}
public OobePowerToysModule ViewModel { get; set; }
public OobeWhatsNew()
{
this.InitializeComponent();
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModulesEnum.WhatsNew]);
DataContext = ViewModel;
}
private async Task<string> GetReleaseNotesMarkdown()
{
string releaseNotesJSON = string.Empty;
using (HttpClient getReleaseInfoClient = new HttpClient())
{
// GitHub APIs require sending an user agent
// https://docs.github.com/en/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<PowerToysReleaseInfo> releases = JsonSerializer.Deserialize<IList<PowerToysReleaseInfo>>(releaseNotesJSON);
// 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(@"(\r\n)+#+ installer( SHA256)? hash(\r\n)+[0-9A-F]{64}", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
foreach (var release in latestReleases)
{
releaseNotesHtmlBuilder.AppendLine("# " + release.Name);
releaseNotesHtmlBuilder.AppendLine(removeHashRegex.Replace(release.ReleaseNotes, string.Empty));
}
return releaseNotesHtmlBuilder.ToString();
}
private async void Page_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
ResourceLoader loader = ResourceLoader.GetForViewIndependentUse();
TitleTxt.Text = loader.GetString("Oobe_WhatsNew");
try
{
ReleaseNotesMarkdown.Text = await GetReleaseNotesMarkdown();
ReleaseNotesMarkdown.Visibility = Windows.UI.Xaml.Visibility.Visible;
LoadingProgressRing.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
catch (Exception ex)
{
Logger.LogError("Exception when loading the release notes", ex);
LoadingProgressRing.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
ErrorInfoBar.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
ViewModel.LogClosingModuleEvent();
}
private void ReleaseNotesMarkdown_LinkClicked(object sender, Toolkit.Uwp.UI.Controls.LinkClickedEventArgs e)
{
if (Uri.TryCreate(e.Link, UriKind.Absolute, out Uri link))
{
Process.Start(new ProcessStartInfo(link.ToString()) { UseShellExecute = true });
}
}
}
}

View File

@@ -167,6 +167,9 @@
<Compile Include="OOBE\Views\OobeMouseUtils.xaml.cs">
<DependentUpon>OobeMouseUtils.xaml</DependentUpon>
</Compile>
<Compile Include="OOBE\Views\OobeWhatsNew.xaml.cs">
<DependentUpon>OobeWhatsNew.xaml</DependentUpon>
</Compile>
<Compile Include="OOBE\Views\OobeOverview.xaml.cs">
<DependentUpon>OobeOverview.xaml</DependentUpon>
</Compile>
@@ -409,6 +412,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="OOBE\Views\OobeWhatsNew.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="OOBE\Views\OobeOverview.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@@ -1438,6 +1438,16 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
<data name="Oobe_Welcome" xml:space="preserve">
<value>Welcome</value>
</data>
<data name="Oobe_WhatsNew" xml:space="preserve">
<value>What's new?</value>
</data>
<data name="Oobe_WhatsNew_LoadingError.Title" xml:space="preserve">
<value>Couldn't load the release notes. Please check your internet connection.</value>
</data>
<data name="Oobe_WhatsNew_DetailedReleaseNotesLink.Text" xml:space="preserve">
<value>See more detailed release notes on the GitHub repository</value>
<comment>Don't loc "GitHub", it's the name of a product</comment>
</data>
<data name="OOBE_Settings.Content" xml:space="preserve">
<value>Open Settings</value>
</data>