diff --git a/PowerToys.slnx b/PowerToys.slnx index a6bfd3a935..4382ca5934 100644 --- a/PowerToys.slnx +++ b/PowerToys.slnx @@ -4,10 +4,6 @@ - - - - diff --git a/src/common/AllExperiments/AllExperiments.csproj b/src/common/AllExperiments/AllExperiments.csproj deleted file mode 100644 index 2ecd131532..0000000000 --- a/src/common/AllExperiments/AllExperiments.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - enable - enable - PowerToys.AllExperiments - .\Microsoft.VariantAssignment\ - - - - - - - - - - - - - - - - - - diff --git a/src/common/AllExperiments/Experiments.cs b/src/common/AllExperiments/Experiments.cs deleted file mode 100644 index d527c52c81..0000000000 --- a/src/common/AllExperiments/Experiments.cs +++ /dev/null @@ -1,214 +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.Globalization; -using System.Text.Json; - -using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; -using Microsoft.PowerToys.Telemetry; -using Microsoft.VariantAssignment.Client; -using Microsoft.VariantAssignment.Contract; -using Windows.System.Profile; - -namespace AllExperiments -{ - // The dependencies required to build this project are only available in the official build pipeline and are internal to Microsoft. - // However, this project is not required to build a test version of the application. - public class Experiments - { - public enum ExperimentState - { - Enabled, - Disabled, - NotLoaded, - } - -#pragma warning disable SA1401 // Need to use LandingPageExperiment as a static property in OobeShellPage.xaml.cs -#pragma warning disable CA2211 // Non-constant fields should not be visible - public static ExperimentState LandingPageExperiment = ExperimentState.NotLoaded; -#pragma warning restore CA2211 -#pragma warning restore SA1401 - - public async Task EnableLandingPageExperimentAsync() - { - if (Experiments.LandingPageExperiment != ExperimentState.NotLoaded) - { - return Experiments.LandingPageExperiment == ExperimentState.Enabled; - } - - Experiments varServ = new Experiments(); - await varServ.VariantAssignmentProvider_Initialize(); - var landingPageExperiment = varServ.IsExperiment; - - Experiments.LandingPageExperiment = landingPageExperiment ? ExperimentState.Enabled : ExperimentState.Disabled; - - return landingPageExperiment; - } - - private async Task VariantAssignmentProvider_Initialize() - { - IsExperiment = false; - string jsonFilePath = CreateFilePath(); - - var vaSettings = new VariantAssignmentClientSettings - { - Endpoint = new Uri("https://default.exp-tas.com/exptas77/a7a397e7-6fbe-4f21-a4e9-3f542e4b000e-exppowertoys/api/v1/tas"), - EnableCaching = true, - ResponseCacheTime = TimeSpan.FromMinutes(5), - }; - - try - { - var vaClient = vaSettings.GetTreatmentAssignmentServiceClient(); - var vaRequest = GetVariantAssignmentRequest(); - using var variantAssignments = await vaClient.GetVariantAssignmentsAsync(vaRequest).ConfigureAwait(false); - - if (variantAssignments.AssignedVariants.Count != 0) - { - var dataVersion = variantAssignments.DataVersion; - var featureVariables = variantAssignments.GetFeatureVariables(); - var assignmentContext = variantAssignments.GetAssignmentContext(); - var featureFlagValue = featureVariables[0].GetStringValue(); - - var experimentGroup = string.Empty; - string json = File.ReadAllText(jsonFilePath); - var jsonDictionary = JsonSerializer.Deserialize>(json); - - if (jsonDictionary != null) - { - if (!jsonDictionary.TryGetValue("dataversion", out object? value)) - { - value = dataVersion; - jsonDictionary.Add("dataversion", value); - } - - if (!jsonDictionary.ContainsKey("variantassignment")) - { - jsonDictionary.Add("variantassignment", featureFlagValue); - } - else - { - var jsonDataVersion = value.ToString(); - if (jsonDataVersion != null && int.Parse(jsonDataVersion, CultureInfo.InvariantCulture) < dataVersion) - { - jsonDictionary["dataversion"] = dataVersion; - jsonDictionary["variantassignment"] = featureFlagValue; - } - } - - experimentGroup = jsonDictionary["variantassignment"].ToString(); - - string output = JsonSerializer.Serialize(jsonDictionary); - File.WriteAllText(jsonFilePath, output); - } - - if (experimentGroup == "alternate" && AssignmentUnit != string.Empty) - { - IsExperiment = true; - } - - PowerToysTelemetry.Log.WriteEvent(new OobeVariantAssignmentEvent() { AssignmentContext = assignmentContext, ClientID = AssignmentUnit }); - } - } - catch (HttpRequestException ex) - { - string json = File.ReadAllText(jsonFilePath); - var jsonDictionary = JsonSerializer.Deserialize>(json); - - if (jsonDictionary != null) - { - if (jsonDictionary.TryGetValue("variantassignment", out object? value)) - { - if (value.ToString() == "alternate" && AssignmentUnit != string.Empty) - { - IsExperiment = true; - } - } - else - { - jsonDictionary["variantassignment"] = "current"; - } - } - - string output = JsonSerializer.Serialize(jsonDictionary); - File.WriteAllText(jsonFilePath, output); - - Logger.LogError("Error getting to TAS endpoint", ex); - } - catch (Exception ex) - { - Logger.LogError("Error getting variant assignments for experiment", ex); - } - } - - public bool IsExperiment { get; set; } - - private string? AssignmentUnit { get; set; } - - private VariantAssignmentRequest GetVariantAssignmentRequest() - { - var jsonFilePath = CreateFilePath(); - try - { - if (!File.Exists(jsonFilePath)) - { - AssignmentUnit = Guid.NewGuid().ToString(); - var data = new Dictionary() - { - ["clientid"] = AssignmentUnit, - }; - string jsonData = JsonSerializer.Serialize(data); - File.WriteAllText(jsonFilePath, jsonData); - } - else - { - string json = File.ReadAllText(jsonFilePath); - var jsonDictionary = System.Text.Json.JsonSerializer.Deserialize>(json); - if (jsonDictionary != null) - { - AssignmentUnit = jsonDictionary["clientid"]?.ToString(); - } - } - } - catch (Exception ex) - { - Logger.LogError("Error creating/getting AssignmentUnit", ex); - } - - var attrNames = new List { "FlightRing", "c:InstallLanguage" }; - var attrData = AnalyticsInfo.GetSystemPropertiesAsync(attrNames).AsTask().GetAwaiter().GetResult(); - - var flightRing = string.Empty; - var installLanguage = string.Empty; - - if (attrData.ContainsKey("FlightRing")) - { - flightRing = attrData["FlightRing"]; - } - - if (attrData.ContainsKey("InstallLanguage")) - { - installLanguage = attrData["InstallLanguage"]; - } - - return new VariantAssignmentRequest - { - Parameters = - { - { "installLanguage", installLanguage }, - { "flightRing", flightRing }, - { "clientid", AssignmentUnit }, - }, - }; - } - - private string CreateFilePath() - { - var exeDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - var settingsPath = @"Microsoft\PowerToys\experimentation.json"; - var filePath = Path.Combine(exeDir, settingsPath); - return filePath; - } - } -} diff --git a/src/common/AllExperiments/Logger.cs b/src/common/AllExperiments/Logger.cs deleted file mode 100644 index b9e1f20969..0000000000 --- a/src/common/AllExperiments/Logger.cs +++ /dev/null @@ -1,81 +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.Diagnostics; -using System.Globalization; -using System.IO.Abstractions; - -namespace AllExperiments -{ - public static class Logger - { - private static readonly IFileSystem FileSystem = new FileSystem(); - private static readonly IPath Path = FileSystem.Path; - private static readonly IDirectory Directory = FileSystem.Directory; - - private static readonly string ApplicationLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\Settings Logs\\Experimentation"); - - static Logger() - { - if (!Directory.Exists(ApplicationLogPath)) - { - Directory.CreateDirectory(ApplicationLogPath); - } - - // Using InvariantCulture since this is used for a log file name - var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log"); - - Trace.Listeners.Add(new TextWriterTraceListener(logFilePath)); - - Trace.AutoFlush = true; - } - - public static void LogInfo(string message) - { - Log(message, "INFO"); - } - - public static void LogError(string message) - { - Log(message, "ERROR"); -#if DEBUG - Debugger.Break(); -#endif - } - - public static void LogError(string message, Exception e) - { - Log( - message + Environment.NewLine + - e?.Message + Environment.NewLine + - "Inner exception: " + Environment.NewLine + - e?.InnerException?.Message + Environment.NewLine + - "Stack trace: " + Environment.NewLine + - e?.StackTrace, - "ERROR"); -#if DEBUG - Debugger.Break(); -#endif - } - - private static void Log(string message, string type) - { - Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay); - Trace.Indent(); - Trace.WriteLine(GetCallerInfo()); - Trace.WriteLine(message); - Trace.Unindent(); - } - - private static string GetCallerInfo() - { - StackTrace stackTrace = new StackTrace(); - - var methodName = stackTrace.GetFrame(3)?.GetMethod(); - var className = methodName?.DeclaringType?.Name; - return "[Method]: " + methodName?.Name + " [Class]: " + className; - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs deleted file mode 100644 index ee08acd718..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs +++ /dev/null @@ -1,21 +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 Microsoft.VariantAssignment.Contract; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Client -{ -#pragma warning disable SA1200 // Using directives should be placed correctly - using TreatmentAssignmentServiceClient = VariantAssignmentServiceClient; -#pragma warning restore SA1200 // Using directives should be placed correctly - - public static class VariantAssignmentClientExtensionMethods - { - public static IVariantAssignmentProvider GetTreatmentAssignmentServiceClient(this VariantAssignmentClientSettings settings) - { - return new TreatmentAssignmentServiceClient(); - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs deleted file mode 100644 index 373651f83a..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs +++ /dev/null @@ -1,23 +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 Microsoft.VariantAssignment.Contract; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Client -{ - internal sealed partial class VariantAssignmentServiceClient : IVariantAssignmentProvider, IDisposable - where TServerResponse : VariantAssignmentServiceResponse - { - public void Dispose() - { - throw new NotImplementedException(); - } - - public Task GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default) - { - return Task.FromResult(EmptyVariantAssignmentResponse.Instance); - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs deleted file mode 100644 index 0e0cd54094..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs +++ /dev/null @@ -1,54 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public class EmptyVariantAssignmentResponse : IVariantAssignmentResponse - { - /// - /// Singleton instance of . - /// - public static readonly IVariantAssignmentResponse Instance = new EmptyVariantAssignmentResponse(); - - public EmptyVariantAssignmentResponse() - { - } - - public long DataVersion => 0; - - public string Thumbprint => string.Empty; - - /// - public IReadOnlyCollection AssignedVariants => Array.Empty(); - - /// -#pragma warning disable CS8603 // Possible null reference return. - public IFeatureVariable GetFeatureVariable(IReadOnlyList path) => null; -#pragma warning restore CS8603 // Possible null reference return. - - /// - public IReadOnlyList GetFeatureVariables(IReadOnlyList prefix) => Array.Empty(); - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - - string IVariantAssignmentResponse.GetAssignmentContext() - { - throw new NotImplementedException(); - } - - IReadOnlyList IVariantAssignmentResponse.GetFeatureVariables() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs deleted file mode 100644 index 6c9a31e8ce..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs +++ /dev/null @@ -1,11 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IAssignedVariant - { - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs deleted file mode 100644 index fc9193ed0d..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs +++ /dev/null @@ -1,16 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IFeatureVariable - { - /// - /// Gets the variable's value as a string. - /// - /// String value of the variable. - string GetStringValue(); - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs deleted file mode 100644 index dad9d39038..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs +++ /dev/null @@ -1,18 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IVariantAssignmentProvider : IDisposable - { - /// - /// Computes variant assignments based on data. - /// - /// Variant assignment parameters. - /// Propagates notification that operations should be canceled. - /// An awaitable task that returns a . - Task GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default); - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs deleted file mode 100644 index 9639a3a58d..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs +++ /dev/null @@ -1,15 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IVariantAssignmentRequest - { - /// - /// Gets inputs used for evaluating filters, assignment units, etc. - /// - IReadOnlyCollection<(string Key, string Value)> Parameters { get; } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs deleted file mode 100644 index 29ee2209de..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs +++ /dev/null @@ -1,48 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - /// - /// Snapshot of variant assignments. - /// - public interface IVariantAssignmentResponse : IDisposable - { - ///// - ///// Gets the serial number of variant assignment configuration snapshot used when assigning variants. - ///// - long DataVersion { get; } - - ///// - ///// Get a hash of the response suitable for caching. - ///// - // string Thumbprint { get; } - - /// - /// Gets the variants assigned based on request parameters and a variant configuration snapshot. - /// - IReadOnlyCollection AssignedVariants { get; } - - /// - /// Gets feature variables assigned by variants in this response. - /// - /// (Optional) Filter feature variables where contains the . - /// Range of matching feature variables. - IReadOnlyList GetFeatureVariables(IReadOnlyList prefix); - - // this actually part of the interface but gets the job done - IReadOnlyList GetFeatureVariables(); - - // this actually part of the interface but gets the job done - string GetAssignmentContext(); - - /// - /// Gets a single feature variable assigned by variants in this response. - /// - /// Exact feature variable path. - /// Matching feature variable or null. - IFeatureVariable GetFeatureVariable(IReadOnlyList path); - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs deleted file mode 100644 index 6db91f6ffd..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs +++ /dev/null @@ -1,11 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - internal sealed class TreatmentAssignmentServiceResponse : VariantAssignmentServiceResponse - { - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs deleted file mode 100644 index f57986368c..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs +++ /dev/null @@ -1,31 +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.ComponentModel.DataAnnotations; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - /// - /// Configuration for variant assignment service client. - /// - public class VariantAssignmentClientSettings - { - /// - /// Gets or sets the variant assignment service endpoint URL. - /// - [Required] - public Uri? Endpoint { get; set; } - - /// - /// Gets or sets a value indicating whether gets or sets a value whether client side request caching should be enabled. - /// - public bool EnableCaching { get; set; } - - /// - /// Gets or sets the maximum time a cached variant assignment response may be used without re-validating. - /// - public TimeSpan ResponseCacheTime { get; set; } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs deleted file mode 100644 index 976ce53531..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs +++ /dev/null @@ -1,21 +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.Collections.Specialized; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public class VariantAssignmentRequest : IVariantAssignmentRequest - { - private NameValueCollection _parameters = new NameValueCollection(); - - /// - /// Gets or sets mutable . - /// - public NameValueCollection Parameters { get => _parameters; set => _parameters = value; } - - IReadOnlyCollection<(string Key, string Value)> IVariantAssignmentRequest.Parameters => (IReadOnlyCollection<(string Key, string Value)>)_parameters; - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs deleted file mode 100644 index e87425f4d3..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs +++ /dev/null @@ -1,48 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - /// - /// Mutable implementation of for (de)serialization. - /// - internal class VariantAssignmentServiceResponse : IVariantAssignmentResponse, IDisposable - { - /// - public virtual long DataVersion { get; set; } - - public virtual IReadOnlyCollection AssignedVariants { get; set; } = Array.Empty(); - - public IFeatureVariable GetFeatureVariable(IReadOnlyList path) - { - throw new NotImplementedException(); - } - - public IReadOnlyList GetFeatureVariables(IReadOnlyList prefix) - { - throw new NotImplementedException(); - } - - protected virtual void Dispose(bool disposing) - { - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public IReadOnlyList GetFeatureVariables() - { - throw new NotImplementedException(); - } - - public string GetAssignmentContext() - { - return string.Empty; - } - } -} diff --git a/src/common/ManagedCommon/WindowHelpers.cs b/src/common/ManagedCommon/WindowHelpers.cs index c5ee13f69c..442ca34e27 100644 --- a/src/common/ManagedCommon/WindowHelpers.cs +++ b/src/common/ManagedCommon/WindowHelpers.cs @@ -41,6 +41,7 @@ namespace ManagedCommon /// black. Calls DwmExtendFrameIntoClientArea() with a cyTopHeight of 2 to force /// the window's top border to be visible.

/// Is a no-op on versions other than Windows 10. + /// WinUI issue: https://github.com/microsoft/microsoft-ui-xaml/issues/6901 /// public static void ForceTopBorder1PixelInsetOnWindows10(IntPtr handle) { diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/APDialog.dark.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/APDialog.dark.png deleted file mode 100644 index fc4d7a0292..0000000000 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/APDialog.dark.png and /dev/null differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/APDialog.light.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/APDialog.light.png deleted file mode 100644 index 8bc91ca89a..0000000000 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/APDialog.light.png and /dev/null differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal.png index 70512fdbe1..834de00437 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal.png and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Background.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Background.png deleted file mode 100644 index 929a637d34..0000000000 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Background.png and /dev/null differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Hero.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Hero.png deleted file mode 100644 index a8889dcc05..0000000000 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/CmdPal_Hero.png and /dev/null differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdPal.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdPal.png index 17d7a300a8..5bae6296af 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdPal.png and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/CmdPal.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/PTHeroShort.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/PTHeroShort.png deleted file mode 100644 index 085b113599..0000000000 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/PTHeroShort.png and /dev/null differ diff --git a/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs b/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs index 6159b58690..a9aa7f9d15 100644 --- a/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs +++ b/src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs @@ -2,12 +2,73 @@ // 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.ObjectModel; +using System.Linq; +using Microsoft.PowerToys.Settings.UI.OOBE.Enums; + namespace Microsoft.PowerToys.Settings.UI.OOBE.ViewModel { public class OobeShellViewModel { + public ObservableCollection Modules { get; } = new(); + public OobeShellViewModel() { + Modules = new ObservableCollection( + new (PowerToysModules Module, bool IsNew)[] + { + (PowerToysModules.Overview, false), + (PowerToysModules.AdvancedPaste, false), + (PowerToysModules.AlwaysOnTop, false), + (PowerToysModules.Awake, false), + (PowerToysModules.CmdNotFound, false), + (PowerToysModules.CmdPal, false), + (PowerToysModules.ColorPicker, false), + (PowerToysModules.CropAndLock, false), + (PowerToysModules.EnvironmentVariables, false), + (PowerToysModules.FancyZones, false), + (PowerToysModules.FileLocksmith, false), + (PowerToysModules.FileExplorer, false), + (PowerToysModules.ImageResizer, false), + (PowerToysModules.KBM, false), + (PowerToysModules.LightSwitch, false), + (PowerToysModules.MouseUtils, false), + (PowerToysModules.MouseWithoutBorders, false), + (PowerToysModules.Peek, false), + (PowerToysModules.PowerDisplay, true), + (PowerToysModules.PowerRename, false), + (PowerToysModules.Run, false), + (PowerToysModules.QuickAccent, false), + (PowerToysModules.ShortcutGuide, false), + (PowerToysModules.TextExtractor, false), + (PowerToysModules.MeasureTool, false), + (PowerToysModules.Hosts, false), + (PowerToysModules.Workspaces, false), + (PowerToysModules.RegistryPreview, false), + (PowerToysModules.NewPlus, false), + (PowerToysModules.ZoomIt, false), + } + .Select(x => new OobePowerToysModule + { + ModuleName = x.Module.ToString(), + IsNew = x.IsNew, + })); + } + + public OobePowerToysModule GetModule(PowerToysModules module) + { + return Modules.First(m => m.ModuleName == module.ToString()); + } + + public OobePowerToysModule GetModuleFromTag(string tag) + { + if (!Enum.TryParse(tag, ignoreCase: true, out var module)) + { + throw new ArgumentException($"Invalid module tag: {tag}", nameof(tag)); + } + + return GetModule(module); } } } diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index c2b7d81ee4..c1f15281c7 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -28,8 +28,6 @@ - - @@ -115,7 +113,6 @@ - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs index 026b142e7c..071c782901 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs @@ -13,8 +13,11 @@ using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; +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.SettingsXAML.Controls.Dashboard; using Microsoft.PowerToys.Settings.UI.Views; using Microsoft.PowerToys.Telemetry; using Microsoft.UI.Xaml; @@ -25,11 +28,16 @@ using WinUIEx; namespace Microsoft.PowerToys.Settings.UI { - /// - /// Provides application-specific behavior to supplement the default Application class. - /// public partial class App : Application { + public static OobeShellViewModel OobeShellViewModel { get; } = new(); + + private OobeWindow oobeWindow; + + private ScoobeWindow scoobeWindow; + + private ShortcutConflictWindow shortcutConflictWindow; + private enum Arguments { PTPipeName = 1, @@ -231,10 +239,6 @@ namespace Microsoft.PowerToys.Settings.UI // https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground // Need to call SetForegroundWindow to actually gain focus. WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle()); - - // https://github.com/microsoft/microsoft-ui-xaml/issues/8948 - A window's top border incorrectly - // renders as black on Windows 10. - WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); } else { @@ -245,20 +249,11 @@ namespace Microsoft.PowerToys.Settings.UI if (ShowOobe) { - PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent()); - OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview); - oobeWindow.Activate(); - oobeWindow.ExtendsContentIntoTitleBar = true; - WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); - SetOobeWindow(oobeWindow); + OpenOobe(); } else if (ShowScoobe) { - PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent()); - ScoobeWindow newScoobeWindow = new ScoobeWindow(); - newScoobeWindow.Activate(); - WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); - SetScoobeWindow(newScoobeWindow); + OpenScoobe(); } } } @@ -268,7 +263,7 @@ namespace Microsoft.PowerToys.Settings.UI /// will be used such as when the application is launched to open a specific file. /// /// Details about the launch request and process. - protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + protected override void OnLaunched(LaunchActivatedEventArgs args) { var cmdArgs = Environment.GetCommandLineArgs(); @@ -294,8 +289,6 @@ namespace Microsoft.PowerToys.Settings.UI // For debugging purposes // Window is also needed to show MessageDialog settingsWindow = new MainWindow(); - settingsWindow.ExtendsContentIntoTitleBar = true; - WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); settingsWindow.Activate(); settingsWindow.NavigateToSection(StartupPage); @@ -303,7 +296,7 @@ namespace Microsoft.PowerToys.Settings.UI GlobalHotkeyConflictManager.Initialize(message => { // In debug mode, just log or do nothing - System.Diagnostics.Debug.WriteLine($"IPC Message: {message}"); + Debug.WriteLine($"IPC Message: {message}"); return 0; }); #else @@ -335,8 +328,6 @@ namespace Microsoft.PowerToys.Settings.UI public static ThemeService ThemeService => themeService; private static MainWindow settingsWindow; - private static OobeWindow oobeWindow; - private static ScoobeWindow scoobeWindow; public static void ClearSettingsWindow() { @@ -348,34 +339,71 @@ namespace Microsoft.PowerToys.Settings.UI return settingsWindow; } - public static OobeWindow GetOobeWindow() + public static bool IsSecondaryWindowOpen() { - return oobeWindow; + var app = (App)Current; + return app.oobeWindow != null || app.scoobeWindow != null || app.shortcutConflictWindow != null; } - public static void SetOobeWindow(OobeWindow window) + public void OpenScoobe() { - oobeWindow = window; + PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent()); + + if (scoobeWindow == null) + { + scoobeWindow = new ScoobeWindow(); + + scoobeWindow.Closed += (_, _) => + { + scoobeWindow = null; + }; + + scoobeWindow.Activate(); + } + else + { + WindowHelpers.BringToForeground(scoobeWindow.GetWindowHandle()); + } } - public static void ClearOobeWindow() + public void OpenOobe() { - oobeWindow = null; + PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent()); + + if (oobeWindow == null) + { + oobeWindow = new OobeWindow(); + + oobeWindow.Closed += (_, _) => + { + oobeWindow = null; + }; + + oobeWindow.Activate(); + } + else + { + WindowHelpers.BringToForeground(oobeWindow.GetWindowHandle()); + } } - public static ScoobeWindow GetScoobeWindow() + public void OpenShortcutConflictWindow() { - return scoobeWindow; - } + if (shortcutConflictWindow == null) + { + shortcutConflictWindow = new ShortcutConflictWindow(); - public static void SetScoobeWindow(ScoobeWindow window) - { - scoobeWindow = window; - } + shortcutConflictWindow.Closed += (_, _) => + { + shortcutConflictWindow = null; + }; - public static void ClearScoobeWindow() - { - scoobeWindow = null; + shortcutConflictWindow.Activate(); + } + else + { + WindowHelpers.BringToForeground(shortcutConflictWindow.GetWindowHandle()); + } } public static Type GetPage(string settingWindow) @@ -399,6 +427,7 @@ namespace Microsoft.PowerToys.Settings.UI case "MouseWithoutBorders": return typeof(MouseWithoutBordersPage); case "Peek": return typeof(PeekPage); case "PowerAccent": return typeof(PowerAccentPage); + case "PowerDisplay": return typeof(PowerDisplayPage); case "PowerLauncher": return typeof(PowerLauncherPage); case "PowerPreview": return typeof(PowerPreviewPage); case "PowerRename": return typeof(PowerRenamePage); @@ -415,7 +444,6 @@ namespace Microsoft.PowerToys.Settings.UI case "Workspaces": return typeof(WorkspacesPage); case "CmdPal": return typeof(CmdPalPage); case "ZoomIt": return typeof(ZoomItPage); - case "PowerDisplay": return typeof(PowerDisplayPage); default: // Fallback to Dashboard Debug.Assert(false, "Unexpected SettingsWindow argument value"); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs index d7806f17ea..1bb42d8f15 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictControl.xaml.cs @@ -2,10 +2,7 @@ // 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 Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts; using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard; @@ -154,11 +151,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls ConflictCount = this.ConflictCount, }); - // Create and show the new window instead of dialog - var conflictWindow = new ShortcutConflictWindow(); - - // Show the window - conflictWindow.Activate(); + ((App)App.Current)!.OpenShortcutConflictWindow(); } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs index 672b3b51a4..ec6aac76d1 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.cs @@ -2,16 +2,13 @@ // 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 CommunityToolkit.WinUI.Controls; using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Library; -using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts; using Microsoft.PowerToys.Settings.UI.Services; using Microsoft.PowerToys.Settings.UI.ViewModels; using Microsoft.PowerToys.Settings.UI.Views; -using Microsoft.UI; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -26,7 +23,11 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard public ShortcutConflictWindow() { + App.ThemeService.ThemeChanged += OnThemeChanged; + App.ThemeService.ApplyTheme(); + var settingsUtils = SettingsUtils.Default; + ViewModel = new ShortcutConflictViewModel( settingsUtils, SettingsRepository.GetInstance(settingsUtils), @@ -50,6 +51,11 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard ViewModel.OnPageLoaded(); } + private void OnThemeChanged(object sender, ElementTheme theme) + { + WindowHelper.SetTheme(this, theme); + } + private void CenterOnScreen() { var displayArea = DisplayArea.GetFromWindowId(this.AppWindow.Id, DisplayAreaFallback.Nearest); @@ -127,6 +133,14 @@ namespace Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard private void WindowEx_Closed(object sender, WindowEventArgs args) { ViewModel?.Dispose(); + + var mainWindow = App.GetSettingsWindow(); + if (mainWindow != null) + { + mainWindow.CloseHiddenWindow(); + } + + App.ThemeService.ThemeChanged -= OnThemeChanged; } private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs index ba053e1124..488c68a3ae 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutControl.xaml.cs @@ -9,7 +9,6 @@ using System.Linq; using CommunityToolkit.WinUI; 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.Telemetry.Events; using Microsoft.PowerToys.Settings.UI.Services; using Microsoft.PowerToys.Settings.UI.SettingsXAML.Controls.Dashboard; @@ -300,9 +299,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls // Close the current shortcut dialog shortcutDialog.Hide(); - // Create and show the ShortcutConflictWindow - var conflictWindow = new ShortcutConflictWindow(); - conflictWindow.Activate(); + ((App)App.Current)!.OpenShortcutConflictWindow(); } private void UpdateKeyVisualStyles() diff --git a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs index e85633f9e8..ba417ad066 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs @@ -12,6 +12,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Views; using Microsoft.PowerToys.Telemetry; using Microsoft.UI; +using Microsoft.UI.Dispatching; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Windows.Data.Json; @@ -75,7 +76,7 @@ namespace Microsoft.PowerToys.Settings.UI // open main window ShellPage.SetOpenMainWindowCallback(type => { - DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () => + DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () => App.OpenSettingsWindow(type)); }); @@ -106,30 +107,8 @@ namespace Microsoft.PowerToys.Settings.UI return needToUpdate; }); - // open oobe - ShellPage.SetOpenOobeCallback(() => - { - if (App.GetOobeWindow() == null) - { - App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.Overview)); - } - - App.GetOobeWindow().Activate(); - }); - - // open whats new window - ShellPage.SetOpenWhatIsNewCallback(() => - { - if (App.GetScoobeWindow() == null) - { - App.SetScoobeWindow(new ScoobeWindow()); - } - - App.GetScoobeWindow().Activate(); - }); - this.InitializeComponent(); - SetAppTitleBar(); + SetTitleBar(); // receive IPC Message App.IPCMessageReceivedCallback = (string msg) => @@ -156,21 +135,22 @@ namespace Microsoft.PowerToys.Settings.UI PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds }); } - private void SetAppTitleBar() + private void SetTitleBar() { // We need to assign the window here so it can configure the custom title bar area correctly. shellPage.TitleBar.Window = this; + this.ExtendsContentIntoTitleBar = true; WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(this)); } - public void NavigateToSection(System.Type type) + public void NavigateToSection(Type type) { ShellPage.Navigate(type); } public void CloseHiddenWindow() { - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + var hWnd = WindowNative.GetWindowHandle(this); if (!NativeMethods.IsWindowVisible(hWnd)) { Close(); @@ -179,10 +159,10 @@ namespace Microsoft.PowerToys.Settings.UI private void Window_Closed(object sender, WindowEventArgs args) { - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + var hWnd = WindowNative.GetWindowHandle(this); WindowHelper.SerializePlacement(hWnd); - if (App.GetOobeWindow() == null && App.GetScoobeWindow() == null) + if (!App.IsSecondaryWindowOpen()) { App.ClearSettingsWindow(); } @@ -198,10 +178,7 @@ namespace Microsoft.PowerToys.Settings.UI private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) { // Set window icon - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); - WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd); - AppWindow appWindow = AppWindow.GetFromWindowId(windowId); - appWindow.SetIcon("Assets\\Settings\\icon.ico"); + this.SetIcon("Assets\\Settings\\icon.ico"); } private void Window_Activated(object sender, WindowActivatedEventArgs args) @@ -209,7 +186,7 @@ namespace Microsoft.PowerToys.Settings.UI if (args.WindowActivationState != WindowActivationState.Deactivated) { this.Activated -= Window_Activated; - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + var hWnd = WindowNative.GetWindowHandle(this); var placement = WindowHelper.DeserializePlacementOrDefault(hWnd); NativeMethods.SetWindowPlacement(hWnd, ref placement); } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs index 73e25ed856..c0d4a9926c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs @@ -18,15 +18,16 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeAdvancedPaste() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.AdvancedPaste]); + + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.AdvancedPaste); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(AdvancedPastePage)); + OobeWindow.OpenMainWindowCallback(typeof(AdvancedPastePage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs index 08fb0f8a80..8a353a2638 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeAlwaysOnTop() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.AlwaysOnTop]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.AlwaysOnTop); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(AlwaysOnTopPage)); + OobeWindow.OpenMainWindowCallback(typeof(AlwaysOnTopPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs index 624e473523..153b7e5472 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs @@ -17,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeAwake() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Awake]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.Awake); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(AwakePage)); + OobeWindow.OpenMainWindowCallback(typeof(AwakePage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs index 1dea42fa74..65b30533bf 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeCmdNotFound() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.CmdNotFound]); + ViewModel = ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.CmdNotFound); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(CmdNotFoundPage)); + OobeWindow.OpenMainWindowCallback(typeof(CmdNotFoundPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs index ab68213a32..14a87af7b7 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeCmdPal() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.CmdPal]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.CmdPal); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(CmdPalPage)); + OobeWindow.OpenMainWindowCallback(typeof(CmdPalPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs index ce69157269..0a4ff41970 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs @@ -21,15 +21,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeColorPicker() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.ColorPicker]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.ColorPicker); DataContext = ViewModel; } private void Start_ColorPicker_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.ColorPickerSharedEventCallback != null) + if (OobeWindow.ColorPickerSharedEventCallback != null) { - using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, OobeShellPage.ColorPickerSharedEventCallback())) + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, OobeWindow.ColorPickerSharedEventCallback())) { eventHandle.Set(); } @@ -40,9 +40,9 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(ColorPickerPage)); + OobeWindow.OpenMainWindowCallback(typeof(ColorPickerPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs index d4c439927d..1bc2781d03 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeCropAndLock() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.CropAndLock]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.CropAndLock); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(CropAndLockPage)); + OobeWindow.OpenMainWindowCallback(typeof(CropAndLockPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeEnvironmentVariables.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeEnvironmentVariables.xaml.cs index ed13489f78..77da91326a 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeEnvironmentVariables.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeEnvironmentVariables.xaml.cs @@ -22,7 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeEnvironmentVariables() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.EnvironmentVariables]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.EnvironmentVariables); DataContext = ViewModel; } @@ -55,9 +55,9 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views private void Launch_Settings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(EnvironmentVariablesPage)); + OobeWindow.OpenMainWindowCallback(typeof(EnvironmentVariablesPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFancyZones.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFancyZones.xaml.cs index 5308336e16..7461f055d7 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFancyZones.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFancyZones.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeFancyZones() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.FancyZones]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.FancyZones); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(FancyZonesPage)); + OobeWindow.OpenMainWindowCallback(typeof(FancyZonesPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileExplorer.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileExplorer.xaml.cs index e23250f316..3de75f2715 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileExplorer.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileExplorer.xaml.cs @@ -10,9 +10,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeFileExplorer : Page { public OobePowerToysModule ViewModel { get; set; } @@ -20,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeFileExplorer() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.FileExplorer]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.FileExplorer); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(PowerPreviewPage)); + OobeWindow.OpenMainWindowCallback(typeof(PowerPreviewPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileLocksmith.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileLocksmith.xaml.cs index 148dae83c1..79565e0cd8 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileLocksmith.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeFileLocksmith.xaml.cs @@ -10,9 +10,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeFileLocksmith : Page { public OobePowerToysModule ViewModel { get; set; } @@ -20,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeFileLocksmith() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.FileLocksmith]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.FileLocksmith); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(FileLocksmithPage)); + OobeWindow.OpenMainWindowCallback(typeof(FileLocksmithPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeHosts.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeHosts.xaml.cs index 2c0ec62fd8..114c23a9c8 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeHosts.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeHosts.xaml.cs @@ -22,7 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeHosts() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Hosts]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.Hosts); DataContext = ViewModel; } @@ -55,9 +55,9 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views private void Launch_Settings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(HostsPage)); + OobeWindow.OpenMainWindowCallback(typeof(HostsPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeImageResizer.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeImageResizer.xaml.cs index f60f36a37e..b57292f659 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeImageResizer.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeImageResizer.xaml.cs @@ -10,9 +10,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeImageResizer : Page { public OobePowerToysModule ViewModel { get; set; } @@ -20,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeImageResizer() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.ImageResizer]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.ImageResizer); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(ImageResizerPage)); + OobeWindow.OpenMainWindowCallback(typeof(ImageResizerPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeKBM.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeKBM.xaml.cs index a583e351f5..b45a4d7982 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeKBM.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeKBM.xaml.cs @@ -10,9 +10,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeKBM : Page { public OobePowerToysModule ViewModel { get; set; } @@ -20,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeKBM() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.KBM]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.KBM); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(KeyboardManagerPage)); + OobeWindow.OpenMainWindowCallback(typeof(KeyboardManagerPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeLightSwitch.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeLightSwitch.xaml.cs index 32978130f9..d7d1983c57 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeLightSwitch.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeLightSwitch.xaml.cs @@ -17,14 +17,14 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeLightSwitch() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.LightSwitch]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.LightSwitch); } private void SettingsLaunchButton_Click(object sender, RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(LightSwitchPage)); + OobeWindow.OpenMainWindowCallback(typeof(LightSwitchPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMeasureTool.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMeasureTool.xaml.cs index e8a24b9fc5..49bff0daca 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMeasureTool.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMeasureTool.xaml.cs @@ -11,9 +11,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeMeasureTool : Page { public OobePowerToysModule ViewModel { get; set; } @@ -21,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeMeasureTool() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.MeasureTool]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.MeasureTool); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(MeasureToolPage)); + OobeWindow.OpenMainWindowCallback(typeof(MeasureToolPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseUtils.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseUtils.xaml.cs index d62ab2f85f..5d96e3f7ae 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseUtils.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseUtils.xaml.cs @@ -17,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeMouseUtils() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.MouseUtils]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.MouseUtils); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(MouseUtilsPage)); + OobeWindow.OpenMainWindowCallback(typeof(MouseUtilsPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseWithoutBorders.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseWithoutBorders.xaml.cs index 243e871cdd..c4a64db872 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseWithoutBorders.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeMouseWithoutBorders.xaml.cs @@ -10,9 +10,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeMouseWithoutBorders : Page { public OobePowerToysModule ViewModel { get; set; } @@ -20,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeMouseWithoutBorders() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.MouseWithoutBorders]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.MouseWithoutBorders); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(MouseWithoutBordersPage)); + OobeWindow.OpenMainWindowCallback(typeof(MouseWithoutBordersPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs index 381a2b35ff..fe25e57b41 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs @@ -10,9 +10,6 @@ using Microsoft.UI.Xaml.Navigation; namespace Microsoft.PowerToys.Settings.UI.OOBE.Views { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// public sealed partial class OobeNewPlus : Page { public OobePowerToysModule ViewModel { get; set; } @@ -20,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeNewPlus() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.NewPlus]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.NewPlus); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(NewPlusPage)); + OobeWindow.OpenMainWindowCallback(typeof(NewPlusPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeOverview.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeOverview.xaml index 20815cd81c..0570a76d1f 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeOverview.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeOverview.xaml @@ -52,26 +52,8 @@ - - - - - - - - - - - - - - - - - - - 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 deleted file mode 100644 index a18ea16a22..0000000000 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs +++ /dev/null @@ -1,194 +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.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 b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml index f277350fbc..93e197f66e 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml @@ -5,13 +5,183 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="using:CommunityToolkit.WinUI" xmlns:winuiex="using:WinUIEx" + Width="1100" + Height="700" MinWidth="480" MinHeight="480" + Activated="Window_Activated" Closed="Window_Closed" mc:Ignorable="d"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs index 6a63054773..cc98149397 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs @@ -3,125 +3,157 @@ // See the LICENSE file in the project root for more information. using System; - +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; -using Microsoft.PowerToys.Settings.UI.OOBE.Enums; +using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel; using Microsoft.PowerToys.Settings.UI.OOBE.Views; -using Microsoft.UI; -using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; using PowerToys.Interop; -using Windows.Graphics; +using WinRT.Interop; using WinUIEx; -using WinUIEx.Messaging; 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 OobeWindow : WindowEx, IDisposable + public sealed partial class OobeWindow : WindowEx { - private PowerToysModules initialModule; + public OobeShellViewModel ViewModel => App.OobeShellViewModel; - 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 static Func RunSharedEventCallback { get; set; } - public OobeWindow(PowerToysModules initialModule) + public static void SetRunSharedEventCallback(Func implementation) + { + RunSharedEventCallback = implementation; + } + + public static Func ColorPickerSharedEventCallback { get; set; } + + public static void SetColorPickerSharedEventCallback(Func implementation) + { + ColorPickerSharedEventCallback = implementation; + } + + public static Action OpenMainWindowCallback { get; set; } + + public static void SetOpenMainWindowCallback(Action implementation) + { + OpenMainWindowCallback = implementation; + } + + public OobeWindow() { 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; + SetTitleBar(); - var dpi = NativeMethods.GetDpiForWindow(_hWnd); - _currentDPI = dpi; - float scalingFactor = (float)dpi / DefaultDPI; - int width = (int)(ExpectedWidth * scalingFactor); - int height = (int)(ExpectedHeight * scalingFactor); + RootGrid.DataContext = ViewModel; - SizeInt32 size; - size.Width = width; - size.Height = height; - _appWindow.Resize(size); - - this.initialModule = initialModule; - - this.SizeChanged += OobeWindow_SizeChanged; - - var loader = ResourceLoaderInstance.ResourceLoader; - Title = loader.GetString("OobeWindow_Title"); - - if (shellPage != null) - { - shellPage.NavigateToModule(this.initialModule); - } - - OobeShellPage.SetRunSharedEventCallback(() => + SetRunSharedEventCallback(() => { return Constants.PowerLauncherSharedEvent(); }); - OobeShellPage.SetColorPickerSharedEventCallback(() => + SetColorPickerSharedEventCallback(() => { return Constants.ShowColorPickerSharedEvent(); }); - OobeShellPage.SetOpenMainWindowCallback((Type type) => + SetOpenMainWindowCallback((Type type) => { App.OpenSettingsWindow(type); }); } - public void SetAppWindow(PowerToysModules module) + private void SetTitleBar() { - if (shellPage != null) + WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(this)); + this.ExtendsContentIntoTitleBar = true; + this.SetTitleBar(AppTitleBar); + Title = ResourceLoaderInstance.ResourceLoader.GetString("OobeWindow_Title"); + } + + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (navigationView.SelectedItem is NavigationViewItem selectedItem) { - shellPage.NavigateToModule(module); + switch (selectedItem.Tag) + { + case "Overview": NavigationFrame.Navigate(typeof(OobeOverview)); break; + case "AdvancedPaste": NavigationFrame.Navigate(typeof(OobeAdvancedPaste)); break; + case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break; + case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break; + case "CmdNotFound": NavigationFrame.Navigate(typeof(OobeCmdNotFound)); break; + case "CmdPal": NavigationFrame.Navigate(typeof(OobeCmdPal)); break; + case "ColorPicker": NavigationFrame.Navigate(typeof(OobeColorPicker)); break; + case "CropAndLock": NavigationFrame.Navigate(typeof(OobeCropAndLock)); break; + case "EnvironmentVariables": NavigationFrame.Navigate(typeof(OobeEnvironmentVariables)); break; + case "FancyZones": NavigationFrame.Navigate(typeof(OobeFancyZones)); break; + case "FileLocksmith": NavigationFrame.Navigate(typeof(OobeFileLocksmith)); break; + case "Run": NavigationFrame.Navigate(typeof(OobeRun)); break; + case "ImageResizer": NavigationFrame.Navigate(typeof(OobeImageResizer)); break; + case "KBM": NavigationFrame.Navigate(typeof(OobeKBM)); break; + case "LightSwitch": NavigationFrame.Navigate(typeof(OobeLightSwitch)); break; + case "PowerRename": NavigationFrame.Navigate(typeof(OobePowerRename)); break; + case "PowerDisplay": NavigationFrame.Navigate(typeof(OobePowerDisplay)); break; + case "QuickAccent": NavigationFrame.Navigate(typeof(OobePowerAccent)); break; + case "FileExplorer": NavigationFrame.Navigate(typeof(OobeFileExplorer)); break; + case "ShortcutGuide": NavigationFrame.Navigate(typeof(OobeShortcutGuide)); break; + case "TextExtractor": NavigationFrame.Navigate(typeof(OobePowerOCR)); break; + case "MouseUtils": NavigationFrame.Navigate(typeof(OobeMouseUtils)); break; + case "MouseWithoutBorders": NavigationFrame.Navigate(typeof(OobeMouseWithoutBorders)); break; + case "MeasureTool": NavigationFrame.Navigate(typeof(OobeMeasureTool)); break; + case "Hosts": NavigationFrame.Navigate(typeof(OobeHosts)); break; + case "RegistryPreview": NavigationFrame.Navigate(typeof(OobeRegistryPreview)); break; + case "Peek": NavigationFrame.Navigate(typeof(OobePeek)); break; + case "NewPlus": NavigationFrame.Navigate(typeof(OobeNewPlus)); break; + case "Workspaces": NavigationFrame.Navigate(typeof(OobeWorkspaces)); break; + case "ZoomIt": NavigationFrame.Navigate(typeof(OobeZoomIt)); break; + } } } - private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) + 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(Microsoft.UI.Xaml.Controls.TitleBar sender, object args) + { + navigationView.IsPaneOpen = !navigationView.IsPaneOpen; + } + + private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e) + { + ((App)App.Current)!.OpenScoobe(); + } + + private void Window_Activated(object sender, WindowActivatedEventArgs args) { // Set window icon - _appWindow.SetIcon("Assets\\Settings\\icon.ico"); - } - - private void OobeWindow_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); - } + this.SetIcon("Assets\\Settings\\icon.ico"); } private void Window_Closed(object sender, WindowEventArgs args) { - App.ClearOobeWindow(); + if (navigationView.SelectedItem is NavigationViewItem selectedItem && selectedItem.Tag is string tag) + { + App.OobeShellViewModel.GetModuleFromTag(tag).LogClosingModuleEvent(); + } - var mainWindow = App.GetSettingsWindow(); - if (mainWindow != null) + if (App.GetSettingsWindow() is MainWindow mainWindow) { mainWindow.CloseHiddenWindow(); } @@ -134,19 +166,13 @@ namespace Microsoft.PowerToys.Settings.UI WindowHelper.SetTheme(this, theme); } - private void Dispose(bool disposing) + private void NavigationView_Loaded(object sender, RoutedEventArgs e) { - if (!disposedValue) + // Select the first module by default + if (navigationView.MenuItems.Count > 0) { - disposedValue = true; + navigationView.SelectedItem = navigationView.MenuItems[0]; } } - - 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/ScoobeWindow.xaml b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml index 934229cea1..3e6bb53da5 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml @@ -6,12 +6,95 @@ xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:winuiex="using:WinUIEx" + Width="1100" + Height="700" MinWidth="480" MinHeight="480" + Activated="Window_Activated" Closed="Window_Closed" mc:Ignorable="d"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs index 446d0e2d0d..378df70063 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs @@ -3,29 +3,38 @@ // 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.OOBE.Views; -using Microsoft.UI; -using Microsoft.UI.Windowing; +using Microsoft.PowerToys.Settings.UI.SerializationContext; using Microsoft.UI.Xaml; -using PowerToys.Interop; -using Windows.Graphics; +using Microsoft.UI.Xaml.Controls; +using WinRT.Interop; using WinUIEx; -using WinUIEx.Messaging; namespace Microsoft.PowerToys.Settings.UI { - public sealed partial class ScoobeWindow : WindowEx, IDisposable + public sealed partial class ScoobeWindow : WindowEx { - 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 static Action OpenMainWindowCallback { get; set; } + + public static void SetOpenMainWindowCallback(Action implementation) + { + OpenMainWindowCallback = implementation; + } + + /// + /// Gets the list of release groups loaded from GitHub (grouped by major.minor version). + /// + public IList> ReleaseGroups { get; private set; } + + private bool _isLoading; public ScoobeWindow() { @@ -34,63 +43,31 @@ namespace Microsoft.PowerToys.Settings.UI this.InitializeComponent(); - _hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); - _windowId = Win32Interop.GetWindowIdFromWindow(_hWnd); - _appWindow = AppWindow.GetFromWindowId(_windowId); - this.Activated += Window_Activated_SetIcon; - this.ExtendsContentIntoTitleBar = true; + SetTitleBar(); - 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) => + SetOpenMainWindowCallback((Type type) => { App.OpenSettingsWindow(type); }); } - private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) + private void SetTitleBar() { - // Set window icon - _appWindow.SetIcon("Assets\\Settings\\icon.ico"); + WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(this)); + this.ExtendsContentIntoTitleBar = true; + this.SetTitleBar(AppTitleBar); + Title = ResourceLoaderInstance.ResourceLoader.GetString("ScoobeWindow_Title"); } - private void ScoobeWindow_SizeChanged(object sender, WindowSizeChangedEventArgs args) + private void Window_Activated(object sender, WindowActivatedEventArgs 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); - } + // Set window icon + this.SetIcon("Assets\\Settings\\icon.ico"); } private void Window_Closed(object sender, WindowEventArgs args) { - App.ClearScoobeWindow(); - - var mainWindow = App.GetSettingsWindow(); - if (mainWindow != null) + if (App.GetSettingsWindow() is MainWindow mainWindow) { mainWindow.CloseHiddenWindow(); } @@ -103,19 +80,133 @@ namespace Microsoft.PowerToys.Settings.UI WindowHelper.SetTheme(this, theme); } - private void Dispose(bool disposing) + private async void NavigationView_Loaded(object sender, RoutedEventArgs e) { - if (!disposedValue) + await LoadReleasesAsync(); + } + + private async Task LoadReleasesAsync() + { + if (_isLoading) { - disposedValue = true; + 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; } } - public void Dispose() + private static async Task> FetchReleasesFromGitHubAsync() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + 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); + + if (allReleases is null || allReleases.Count == 0) + { + return []; + } + + return allReleases + .OrderByDescending(r => r.PublishedDate) + .ToList(); + } + + private static IList> GroupReleasesByMajorMinor(IList releases) + { + return releases + .GroupBy(GetMajorMinorVersion) + .Select(g => g.OrderByDescending(r => r.PublishedDate).ToList() as IList) + .ToList(); + } + + private static string GetMajorMinorVersion(PowerToysReleaseInfo release) + { + string version = ScoobeReleaseGroupViewModel.GetVersionFromRelease(release); + var parts = version.Split('.'); + if (parts.Length >= 2) + { + return $"{parts[0]}.{parts[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 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(Microsoft.UI.Xaml.Controls.TitleBar sender, object args) + { + navigationView.IsPaneOpen = !navigationView.IsPaneOpen; } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml index bc2c9d2840..95bfab7ad2 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml @@ -21,15 +21,12 @@ - ms-appx:///Assets/Settings/Modules/APDialog.dark.png ms-appx:///Assets/Settings/Icons/Models/OpenAI.dark.svg - ms-appx:///Assets/Settings/Modules/APDialog.light.png ms-appx:///Assets/Settings/Icons/Models/OpenAI.light.svg - ms-appx:///Assets/Settings/Modules/APDialog.light.png ms-appx:///Assets/Settings/Icons/Models/OpenAI.light.svg diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml index b34a516f58..e18927bcb9 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml @@ -28,7 +28,10 @@ - + @@ -40,17 +43,24 @@ - + - + + + + + + + + + + + + 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 ceff1c7918..3e4d122379 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/DashboardPage.xaml.cs @@ -39,6 +39,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views DataContext = ViewModel; Loaded += (s, e) => ViewModel.OnPageLoaded(); + Unloaded += (s, e) => ViewModel?.Dispose(); } public void RefreshEnabledState() @@ -48,12 +49,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views private void WhatsNewButton_Click(object sender, RoutedEventArgs e) { - if (App.GetScoobeWindow() == null) - { - App.SetScoobeWindow(new ScoobeWindow()); - } - - App.GetScoobeWindow().Activate(); + ((App)App.Current)!.OpenScoobe(); } private void SortAlphabetical_Click(object sender, RoutedEventArgs e) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs index c8f3284432..7515cad863 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs @@ -48,16 +48,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views /// public delegate bool UpdatingGeneralSettingsCallback(ModuleType moduleType, bool isEnabled); - /// - /// Declaration for opening oobe window callback function. - /// - public delegate void OobeOpeningCallback(); - - /// - /// Declaration for opening whats new window callback function. - /// - public delegate void WhatIsNewOpeningCallback(); - /// /// Gets or sets a shell handler to be used to update contents of the shell dynamically from page within the frame. /// @@ -88,16 +78,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views /// public static UpdatingGeneralSettingsCallback UpdateGeneralSettingsCallback { get; set; } - /// - /// Gets or sets callback function for opening oobe window - /// - public static OobeOpeningCallback OpenOobeWindowCallback { get; set; } - - /// - /// Gets or sets callback function for opening oobe window - /// - public static WhatIsNewOpeningCallback OpenWhatIsNewWindowCallback { get; set; } - /// /// Gets view model. /// @@ -223,24 +203,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views UpdateGeneralSettingsCallback = implementation; } - /// - /// Set oobe opening callback function - /// - /// delegate function implementation. - public static void SetOpenOobeCallback(OobeOpeningCallback implementation) - { - OpenOobeWindowCallback = implementation; - } - - /// - /// Set whats new opening callback function - /// - /// delegate function implementation. - public static void SetOpenWhatIsNewCallback(WhatIsNewOpeningCallback implementation) - { - OpenWhatIsNewWindowCallback = implementation; - } - public static void SetElevationStatus(bool isElevated) { IsElevated = isElevated; @@ -325,7 +287,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views private void OOBEItem_Tapped(object sender, TappedRoutedEventArgs e) { - OpenOobeWindowCallback(); + ((App)App.Current)!.OpenOobe(); + } + + private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e) + { + ((App)App.Current)!.OpenScoobe(); } private async void FeedbackItem_Tapped(object sender, TappedRoutedEventArgs e) @@ -333,15 +300,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views await Launcher.LaunchUriAsync(new Uri("https://aka.ms/powerToysGiveFeedback")); } - private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e) - { - OpenWhatIsNewWindowCallback(); - } - private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) { - NavigationViewItem selectedItem = args.SelectedItem as NavigationViewItem; - if (selectedItem != null) + if (args.SelectedItem is NavigationViewItem selectedItem) { Type pageType = selectedItem.GetValue(NavHelper.NavigateToProperty) as Type; @@ -409,7 +370,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views navigationView.IsPaneOpen = !navigationView.IsPaneOpen; } - private async void Close_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) + private async void Close_Tapped(object sender, TappedRoutedEventArgs e) { await CloseDialog.ShowAsync(); } diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index 418d6b5964..6301465996 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -55,6 +55,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels // Flag to prevent toggle operations during sorting to avoid race conditions. private bool _isSorting; + private bool _isDisposed; private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData(); @@ -132,8 +133,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private void OnSettingsChanged(GeneralSettings newSettings) { + if (_isDisposed) + { + return; + } + dispatcher.TryEnqueue(() => { + if (_isDisposed) + { + return; + } + generalSettingsConfig = newSettings; // Update local field and notify UI if sort order changed @@ -149,8 +160,18 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e) { + if (_isDisposed) + { + return; + } + dispatcher.TryEnqueue(() => { + if (_isDisposed) + { + return; + } + var allConflictData = e.Conflicts; foreach (var inAppConflict in allConflictData.InAppConflicts) { @@ -363,6 +384,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels /// public void ModuleEnabledChangedOnSettingsPage() { + if (_isDisposed) + { + return; + } + // Ignore if this was triggered by a UI change that we're already handling. if (_isUpdatingFromUI) { @@ -391,6 +417,17 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels /// private void RefreshShortcutModules() { + if (_isDisposed) + { + return; + } + + if (!dispatcher.HasThreadAccess) + { + _ = dispatcher.TryEnqueue(DispatcherQueuePriority.Normal, RefreshShortcutModules); + return; + } + ShortcutModules.Clear(); ActionModules.Clear(); @@ -804,6 +841,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels public override void Dispose() { + if (_isDisposed) + { + return; + } + + _isDisposed = true; base.Dispose(); if (_settingsRepository != null) { diff --git a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs index b925782191..ccadf776ff 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ZoomItViewModel.cs @@ -8,8 +8,8 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.Json; -using AllExperiments; using global::PowerToys.GPOWrapper; +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Helpers;