diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 4b8e0ce0a0..49012460e6 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -163,6 +163,7 @@ bricelam
BRIGHTGREEN
Browsable
bsd
+Bson
bstr
bthprops
bti
@@ -221,6 +222,7 @@ CLASSNOTAVAILABLE
clickable
clickonce
CLIENTEDGE
+clientid
clientside
CLIPCHILDREN
Clipperton
@@ -347,6 +349,7 @@ DARKYELLOW
datareader
datatemplate
Datavalue
+dataversion
DATAW
davidegiacometti
Dayof
@@ -1859,6 +1862,7 @@ TRAYMOUSEMESSAGE
triaging
TRK
trl
+TServer
Tshuapa
TStr
Tuva
@@ -1911,6 +1915,7 @@ unmute
UNORM
unregistering
unremapped
+Unstub
unsubscribe
unvirtualized
Updatelayout
@@ -1936,6 +1941,7 @@ UYVY
vabdq
validmodulename
Vanara
+variantassignment
vcamp
vccorlib
vcdl
diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt
index 774349778a..42ceb68d7f 100644
--- a/.github/actions/spell-check/patterns.txt
+++ b/.github/actions/spell-check/patterns.txt
@@ -62,6 +62,9 @@ https?://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]*
link\.medium\.com/[a-zA-Z0-9]+
\bmedium\.com/\@[^/]+/[-\w]+
+# experimentation urls
+https?://default\.exp-tas\.com/[-_a-zA-Z0-9/]*
+
publicKeyToken=(['"]|)[0-9a-f]+\g{-1}
\@sha256:[0-9a-f]{64}\b
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index af84380a24..ce467a6317 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -23,6 +23,7 @@
"PowerToys.Settings.UI.Lib.dll",
"PowerToys.GPOWrapper.dll",
"PowerToys.GPOWrapperProjection.dll",
+ "PowerToys.AllExperiments.dll",
"modules\\AlwaysOnTop\\PowerToys.AlwaysOnTop.exe",
"modules\\AlwaysOnTop\\PowerToys.AlwaysOnTopModuleInterface.dll",
@@ -202,6 +203,8 @@
"Mono.Cecil.Mdb.dll",
"Mono.Cecil.Pdb.dll",
"Mono.Cecil.Rocks.dll",
+ "Newtonsoft.Json.dll",
+ "Newtonsoft.Json.Bson.dll",
"NLog.dll",
"HtmlAgilityPack.dll",
"Markdig.Signed.dll",
diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml
index 26754babe8..703d520f10 100644
--- a/.pipelines/ci/templates/build-powertoys-steps.yml
+++ b/.pipelines/ci/templates/build-powertoys-steps.yml
@@ -69,7 +69,7 @@ steps:
configPath: NuGet.config
restoreSolution: PowerToys.sln
restoreDirectory: '$(Build.SourcesDirectory)\packages'
-
+
- task: VSBuild@1
displayName: 'Build PowerToys.sln'
inputs:
@@ -227,10 +227,17 @@ steps:
**\powerpreviewTest.dll
**\UnitTests-FancyZones.dll
!**\obj\**
-
+
+- task: PowerShell@2
+ displayName: Trigger dotnet welcome message so that it does not cause errors on other scripts
+ inputs:
+ targetType: 'inline'
+ script: |
+ dotnet list $(build.sourcesdirectory)\src\common\Common.UI\Common.UI.csproj package
+
- task: PowerShell@2
displayName: Verifying Notice.md and Nuget packages match
inputs:
filePath: '$(build.sourcesdirectory)\.pipelines\verifyNoticeMdAgainstNugetPackages.ps1'
arguments: -path '$(build.sourcesdirectory)\'
- pwsh: true
\ No newline at end of file
+ pwsh: true
diff --git a/.pipelines/release.yml b/.pipelines/release.yml
index 01665c74c1..81da06f416 100644
--- a/.pipelines/release.yml
+++ b/.pipelines/release.yml
@@ -23,6 +23,7 @@ parameters:
variables:
IsPipeline: 1 # The installer uses this to detect whether it should pick up localizations
SkipCppCodeAnalysis: 1 # Skip the code analysis to speed up release CI. It runs on PR CI, anyway
+ IsExperimentationLive: 1 # The build and installer use this to turn on experimentation
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
resources:
diff --git a/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1 b/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1
index affff4c782..2ee58da745 100644
--- a/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1
+++ b/.pipelines/verifyNoticeMdAgainstNugetPackages.ps1
@@ -38,6 +38,8 @@ $totalList = $projFiles | ForEach-Object -Parallel {
if($nugetTemp -is [array] -and $nugetTemp.count -gt 3)
{
+ # Need to debug this script? Uncomment this line.
+ # Write-Host $csproj "`r`n" $nugetTemp "`r`n"
$temp = New-Object System.Collections.ArrayList
$temp.AddRange($nugetTemp)
$temp.RemoveRange(0, 3)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 493af35f66..7b11df8764 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -57,4 +57,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/PowerToys.sln b/PowerToys.sln
index 63b54af475..df3b5bf6e7 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -487,6 +487,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StlThumbnailProviderCpp", "
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SvgThumbnailProviderCpp", "src\modules\previewpane\SvgThumbnailProviderCpp\SvgThumbnailProviderCpp.vcxproj", "{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllExperiments", "src\common\AllExperiments\AllExperiments.csproj", "{9CE59ED5-7087-4353-88EB-788038A73CEC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2022,6 +2024,18 @@ Global
{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x64.Build.0 = Release|x64
{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x86.ActiveCfg = Release|x64
{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x86.Build.0 = Release|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|ARM64.Build.0 = Debug|ARM64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x64.ActiveCfg = Debug|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x64.Build.0 = Debug|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x86.ActiveCfg = Debug|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x86.Build.0 = Debug|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|ARM64.ActiveCfg = Release|ARM64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|ARM64.Build.0 = Release|ARM64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x64.ActiveCfg = Release|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x64.Build.0 = Release|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x86.ActiveCfg = Release|x64
+ {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2192,6 +2206,7 @@ Global
{CA5518ED-0458-4B09-8F53-4122B9888655} = {2F305555-C296-497E-AC20-5FA1B237996A}
{D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D} = {2F305555-C296-497E-AC20-5FA1B237996A}
{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA} = {2F305555-C296-497E-AC20-5FA1B237996A}
+ {9CE59ED5-7087-4353-88EB-788038A73CEC} = {1AFB6476-670D-4E80-A464-657E01DFF482}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/installer/PowerToysSetup/Settings.wxs b/installer/PowerToysSetup/Settings.wxs
index a1072b59f6..a162135bd8 100644
--- a/installer/PowerToysSetup/Settings.wxs
+++ b/installer/PowerToysSetup/Settings.wxs
@@ -4,12 +4,15 @@
-
+
+
+
+
@@ -18,6 +21,13 @@
+
+
+
+
+
+
+
@@ -78,6 +88,11 @@
+
+
+
+
+
diff --git a/src/common/AllExperiments/AllExperiments.csproj b/src/common/AllExperiments/AllExperiments.csproj
new file mode 100644
index 0000000000..44b823cbbc
--- /dev/null
+++ b/src/common/AllExperiments/AllExperiments.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net7.0-windows10.0.19041.0
+ enable
+ enable
+ PowerToys.AllExperiments
+ .\Microsoft.VariantAssignment\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/common/AllExperiments/Experiments.cs b/src/common/AllExperiments/Experiments.cs
new file mode 100644
index 0000000000..9663c28b96
--- /dev/null
+++ b/src/common/AllExperiments/Experiments.cs
@@ -0,0 +1,211 @@
+// 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.Numerics;
+using System.Runtime.InteropServices;
+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
+ public static ExperimentState LandingPageExperiment = ExperimentState.NotLoaded;
+#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.ContainsKey("dataversion"))
+ {
+ jsonDictionary.Add("dataversion", dataVersion);
+ }
+
+ if (!jsonDictionary.ContainsKey("variantassignment"))
+ {
+ jsonDictionary.Add("variantassignment", featureFlagValue);
+ }
+ else
+ {
+ var jsonDataVersion = jsonDictionary["dataversion"].ToString();
+ if (jsonDataVersion != null && int.Parse(jsonDataVersion) < 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.ContainsKey("variantassignment"))
+ {
+ if (jsonDictionary["variantassignment"].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 IVariantAssignmentRequest 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
new file mode 100644
index 0000000000..7604618bdf
--- /dev/null
+++ b/src/common/AllExperiments/Logger.cs
@@ -0,0 +1,81 @@
+// 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) + ".txt");
+
+ 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
new file mode 100644
index 0000000000..ee08acd718
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs
@@ -0,0 +1,21 @@
+// 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
new file mode 100644
index 0000000000..f90952859a
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs
@@ -0,0 +1,23 @@
+// 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 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
new file mode 100644
index 0000000000..90c797155e
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs
@@ -0,0 +1,48 @@
+// 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();
+
+ void IDisposable.Dispose()
+ {
+ }
+
+ 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
new file mode 100644
index 0000000000..6c9a31e8ce
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs
@@ -0,0 +1,11 @@
+// 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
new file mode 100644
index 0000000000..fc9193ed0d
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs
@@ -0,0 +1,16 @@
+// 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
new file mode 100644
index 0000000000..dad9d39038
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// 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
new file mode 100644
index 0000000000..9639a3a58d
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs
@@ -0,0 +1,15 @@
+// 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
new file mode 100644
index 0000000000..29ee2209de
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs
@@ -0,0 +1,48 @@
+// 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
new file mode 100644
index 0000000000..3b082549f7
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs
@@ -0,0 +1,11 @@
+// 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 class TreatmentAssignmentServiceResponse : VariantAssignmentServiceResponse
+ {
+ }
+}
diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs
new file mode 100644
index 0000000000..1ea295bfda
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs
@@ -0,0 +1,31 @@
+// 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 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
new file mode 100644
index 0000000000..976ce53531
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs
@@ -0,0 +1,21 @@
+// 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
new file mode 100644
index 0000000000..e87425f4d3
--- /dev/null
+++ b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs
@@ -0,0 +1,48 @@
+// 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/runner/general_settings.cpp b/src/runner/general_settings.cpp
index 60f69eccdf..de72d9aa2d 100644
--- a/src/runner/general_settings.cpp
+++ b/src/runner/general_settings.cpp
@@ -16,6 +16,7 @@
static std::wstring settings_theme = L"system";
static bool run_as_elevated = false;
static bool download_updates_automatically = true;
+static bool enable_experimentation = true;
json::JsonObject GeneralSettings::to_json()
{
@@ -37,6 +38,7 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"is_elevated", json::value(isElevated));
result.SetNamedValue(L"run_elevated", json::value(isRunElevated));
result.SetNamedValue(L"download_updates_automatically", json::value(downloadUpdatesAutomatically));
+ result.SetNamedValue(L"enable_experimentation", json::value(enableExperimentation));
result.SetNamedValue(L"is_admin", json::value(isAdmin));
result.SetNamedValue(L"theme", json::value(theme));
result.SetNamedValue(L"system_theme", json::value(systemTheme));
@@ -55,6 +57,7 @@ json::JsonObject load_general_settings()
}
run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false);
download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true) && check_user_is_admin();
+ enable_experimentation = loaded.GetNamedBoolean(L"enable_experimentation",true);
return loaded;
}
@@ -67,6 +70,7 @@ GeneralSettings get_general_settings()
.isRunElevated = run_as_elevated,
.isAdmin = is_user_admin,
.downloadUpdatesAutomatically = download_updates_automatically && is_user_admin,
+ .enableExperimentation = enable_experimentation,
.theme = settings_theme,
.systemTheme = WindowsColors::is_dark_mode() ? L"dark" : L"light",
.powerToysVersion = get_product_version()
@@ -89,6 +93,8 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
+ enable_experimentation = general_configs.GetNamedBoolean(L"enable_experimentation", true);
+
if (json::has(general_configs, L"startup", json::JsonValueType::Boolean))
{
const bool startup = general_configs.GetNamedBoolean(L"startup");
diff --git a/src/runner/general_settings.h b/src/runner/general_settings.h
index bc7f59cae6..4f87ffd884 100644
--- a/src/runner/general_settings.h
+++ b/src/runner/general_settings.h
@@ -11,6 +11,7 @@ struct GeneralSettings
bool isRunElevated;
bool isAdmin;
bool downloadUpdatesAutomatically;
+ bool enableExperimentation;
std::wstring theme;
std::wstring systemTheme;
std::wstring powerToysVersion;
diff --git a/src/runner/trace.cpp b/src/runner/trace.cpp
index 5e5bdcbf38..82522bd0f4 100644
--- a/src/runner/trace.cpp
+++ b/src/runner/trace.cpp
@@ -56,6 +56,7 @@ void Trace::SettingsChanged(const GeneralSettings& settings)
TraceLoggingWideString(enabledModules.c_str(), "ModulesEnabled"),
TraceLoggingBoolean(settings.isRunElevated, "AlwaysRunElevated"),
TraceLoggingBoolean(settings.downloadUpdatesAutomatically, "DownloadUpdatesAutomatically"),
+ TraceLoggingBoolean(settings.enableExperimentation, "EnableExperimentation"),
TraceLoggingWideString(settings.theme.c_str(), "Theme"),
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
diff --git a/src/settings-ui/Settings.UI.Library/GeneralSettings.cs b/src/settings-ui/Settings.UI.Library/GeneralSettings.cs
index 25bd1b2a07..e59e685b76 100644
--- a/src/settings-ui/Settings.UI.Library/GeneralSettings.cs
+++ b/src/settings-ui/Settings.UI.Library/GeneralSettings.cs
@@ -49,12 +49,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("download_updates_automatically")]
public bool AutoDownloadUpdates { get; set; }
+ [JsonPropertyName("enable_experimentation")]
+ public bool EnableExperimentation { get; set; }
+
public GeneralSettings()
{
Startup = false;
IsAdmin = false;
IsElevated = false;
AutoDownloadUpdates = false;
+ EnableExperimentation = true;
Theme = "system";
SystemTheme = "light";
try
diff --git a/src/settings-ui/Settings.UI.Library/Telemetry/Events/OobeVariantAssignmentEvent.cs b/src/settings-ui/Settings.UI.Library/Telemetry/Events/OobeVariantAssignmentEvent.cs
new file mode 100644
index 0000000000..8f17b58dc3
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Library/Telemetry/Events/OobeVariantAssignmentEvent.cs
@@ -0,0 +1,21 @@
+// 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.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events
+{
+ [EventData]
+ public class OobeVariantAssignmentEvent : EventBase, IEvent
+ {
+ public string AssignmentContext { get; set; }
+
+ public string ClientID { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/settings-ui/Settings.UI.UnitTests/Settings.UI.UnitTests.csproj b/src/settings-ui/Settings.UI.UnitTests/Settings.UI.UnitTests.csproj
index b083e176cc..495d233dec 100644
--- a/src/settings-ui/Settings.UI.UnitTests/Settings.UI.UnitTests.csproj
+++ b/src/settings-ui/Settings.UI.UnitTests/Settings.UI.UnitTests.csproj
@@ -3,7 +3,7 @@
net7.0-windows10.0.19041.0
false
-
+ win10-x64;win10-arm64
$(Version).0
diff --git a/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsExperimentation.png b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsExperimentation.png
new file mode 100644
index 0000000000..dc5e0c3169
Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsExperimentation.png differ
diff --git a/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml b/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml
index 391026cee4..98877bd01c 100644
--- a/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml
+++ b/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml
@@ -10,12 +10,13 @@
-
+
@@ -23,9 +24,7 @@
Grid.Row="1"
Padding="32,24,32,24"
VerticalScrollBarVisibility="Auto">
-
+
-
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml.cs b/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml.cs
index abc73ee3e2..72abab90d6 100644
--- a/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml.cs
+++ b/src/settings-ui/Settings.UI/Controls/OOBEPageControl/OOBEPageControl.xaml.cs
@@ -32,6 +32,12 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(HeroImageProperty, value);
}
+ public double HeroImageHeight
+ {
+ get { return (double)GetValue(HeroImageHeightProperty); }
+ set { SetValue(HeroImageHeightProperty, value); }
+ }
+
public object PageContent
{
get { return (object)GetValue(PageContentProperty); }
@@ -42,5 +48,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(SettingsPageControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty HeroImageProperty = DependencyProperty.Register("HeroImage", typeof(string), typeof(SettingsPageControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty PageContentProperty = DependencyProperty.Register("PageContent", typeof(object), typeof(SettingsPageControl), new PropertyMetadata(new Grid()));
+ public static readonly DependencyProperty HeroImageHeightProperty = DependencyProperty.Register("HeroImageHeight", typeof(double), typeof(SettingsPageControl), new PropertyMetadata(280.0));
}
}
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewAlternate.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewAlternate.xaml
new file mode 100644
index 0000000000..ab32d44c5b
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewAlternate.xaml
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewAlternate.xaml.cs b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewAlternate.xaml.cs
new file mode 100644
index 0000000000..c5171f4159
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewAlternate.xaml.cs
@@ -0,0 +1,50 @@
+// 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.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
+using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ public sealed partial class OobeOverviewAlternate : Page
+ {
+ public OobePowerToysModule ViewModel { get; set; }
+
+ public OobeOverviewAlternate()
+ {
+ this.InitializeComponent();
+ ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Overview]);
+ DataContext = ViewModel;
+
+ FancyZonesHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.GetKeysList();
+ RunHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
+ ColorPickerHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
+ AlwaysOnTopHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.GetKeysList();
+ }
+
+ private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ if (OobeShellPage.OpenMainWindowCallback != null)
+ {
+ OobeShellPage.OpenMainWindowCallback(typeof(GeneralPage));
+ }
+
+ ViewModel.LogOpeningSettingsEvent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ ViewModel.LogOpeningModuleEvent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ViewModel.LogClosingModuleEvent();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewPlaceholder.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewPlaceholder.xaml
new file mode 100644
index 0000000000..3c2a5abb45
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewPlaceholder.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewPlaceholder.xaml.cs b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewPlaceholder.xaml.cs
new file mode 100644
index 0000000000..40dcb56aa2
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeOverviewPlaceholder.xaml.cs
@@ -0,0 +1,73 @@
+// 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.Threading.Tasks;
+using AllExperiments;
+using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
+using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
+using Microsoft.PowerToys.Settings.UI.Services;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ public sealed partial class OobeOverviewPlaceholder : Page
+ {
+ public OobePowerToysModule ViewModel { get; set; }
+
+ public OobeOverviewPlaceholder()
+ {
+ this.InitializeComponent();
+ ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Overview]);
+ DataContext = ViewModel;
+ }
+
+ private static async Task GetIsExperiment()
+ {
+ Experiments landingPageExp = new Experiments();
+ var experimentEnabled = await landingPageExp.EnableLandingPageExperimentAsync();
+ return experimentEnabled;
+ }
+
+ private async void Reload()
+ {
+ var isExperiment = await GetIsExperiment();
+
+ if (isExperiment)
+ {
+ this.Frame.Navigate(typeof(OobeOverviewAlternate));
+ }
+ else
+ {
+ this.Frame.Navigate(typeof(OobeOverview));
+ }
+ }
+
+ private void Page_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ Reload();
+ }
+
+ private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ if (OobeShellPage.OpenMainWindowCallback != null)
+ {
+ OobeShellPage.OpenMainWindowCallback(typeof(GeneralPage));
+ }
+
+ ViewModel.LogOpeningSettingsEvent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ ViewModel.LogOpeningModuleEvent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ViewModel.LogClosingModuleEvent();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml.cs b/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml.cs
index dcd099285c..05498bd208 100644
--- a/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml.cs
@@ -4,6 +4,8 @@
using System;
using System.Collections.ObjectModel;
+using System.Globalization;
+using AllExperiments;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
@@ -47,10 +49,16 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
public ObservableCollection Modules { get; }
+ private static ISettingsUtils settingsUtils = new SettingsUtils();
+
+ private bool ExperimentationToggleSwitchEnabled { get; set; } = true;
+
public OobeShellPage()
{
InitializeComponent();
+ ExperimentationToggleSwitchEnabled = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.EnableExperimentation;
+
DataContext = ViewModel;
OobeShellHandler = this;
UpdateUITheme();
@@ -186,7 +194,27 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
switch (selectedItem.Tag)
{
- case "Overview": NavigationFrame.Navigate(typeof(OobeOverview)); break;
+ case "Overview":
+ if (ExperimentationToggleSwitchEnabled)
+ {
+ switch (AllExperiments.Experiments.LandingPageExperiment)
+ {
+ case Experiments.ExperimentState.Enabled:
+ NavigationFrame.Navigate(typeof(OobeOverviewAlternate)); break;
+ case Experiments.ExperimentState.Disabled:
+ NavigationFrame.Navigate(typeof(OobeOverview)); break;
+ case Experiments.ExperimentState.NotLoaded:
+ NavigationFrame.Navigate(typeof(OobeOverviewPlaceholder)); break;
+ }
+
+ break;
+ }
+ else
+ {
+ NavigationFrame.Navigate(typeof(OobeOverview));
+ break;
+ }
+
case "WhatsNew": NavigationFrame.Navigate(typeof(OobeWhatsNew)); break;
case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break;
case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break;
diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
index 90b499c799..7a5f19c1c2 100644
--- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
+++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj
@@ -55,6 +55,9 @@
https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json
+
+
+
@@ -91,15 +94,19 @@
+
+
+
+
MSBuild:Compile
@@ -122,10 +129,17 @@
Always
+
+ MSBuild:Compile
+
MSBuild:Compile
+
+ $(DefaultXamlRuntime)
+
+
$(DefaultXamlRuntime)
diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
index a08afd5429..c397cbcbd8 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -2830,6 +2830,10 @@ Activate by holding the key for the character you want to add an accent to, then
Launch Host File Editor
"Host File Editor" is a product name
+
+ Launch Host File Editor
+ "Host File Editor" is a product name
+
Position of additional content
@@ -2891,6 +2895,39 @@ Activate by holding the key for the character you want to add an accent to, then
Preferred language
+
+ Pin a window so that:
+
+
+ Always On Top
+
+
+ To pick a color:
+
+
+ Color Picker
+
+
+ Here are a few shortcuts to get you started:
+
+
+ To open the FancyZones editor, press:
+
+
+ FancyZones
+
+
+ Get access to your files and more:
+
+
+ PowerToys Run
+
+
+ Only affects Windows Insider builds
+
+
+ Enable experimentation
+
All apps
diff --git a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs
index e7447ed1c9..babf4e0158 100644
--- a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs
@@ -125,6 +125,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_startup = GeneralSettingsConfig.Startup;
_autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates;
+ _enableExperimentation = GeneralSettingsConfig.EnableExperimentation;
_isElevated = isElevated;
_runElevated = GeneralSettingsConfig.RunElevated;
@@ -152,6 +153,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private int _themeIndex;
private bool _autoDownloadUpdates;
+ private bool _enableExperimentation;
private UpdatingSettings.UpdatingState _updatingState = UpdatingSettings.UpdatingState.UpToDate;
private string _newAvailableVersion = string.Empty;
@@ -284,6 +286,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
+ public bool EnableExperimentation
+ {
+ get
+ {
+ return _enableExperimentation;
+ }
+
+ set
+ {
+ if (_enableExperimentation != value)
+ {
+ _enableExperimentation = value;
+ GeneralSettingsConfig.EnableExperimentation = value;
+ NotifyPropertyChanged();
+ }
+ }
+ }
+
public static bool AutoUpdatesEnabled
{
get
diff --git a/src/settings-ui/Settings.UI/Views/GeneralPage.xaml b/src/settings-ui/Settings.UI/Views/GeneralPage.xaml
index fdb0bcaefd..7ac95c8537 100644
--- a/src/settings-ui/Settings.UI/Views/GeneralPage.xaml
+++ b/src/settings-ui/Settings.UI/Views/GeneralPage.xaml
@@ -369,8 +369,13 @@
+
+
+
-