[Settings]New Landing Page Experimentation (#22365)

Co-authored-by: Sophia Chen <sophia.six.chen@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Sophia Chen <sophchen@microsoft.com>
This commit is contained in:
Sophia Chen 2023-02-14 18:38:53 -08:00 committed by GitHub
parent 44e28886d7
commit df521b4c9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1181 additions and 12 deletions

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -228,6 +228,13 @@ steps:
**\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:

View File

@ -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:

View File

@ -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)

View File

@ -57,4 +57,9 @@
<PackageVersion Include="Vanara.PInvoke.Shell32" Version="3.4.11" />
<PackageVersion Include="WinUIEx" Version="1.8.0" />
</ItemGroup>
<ItemGroup Condition="'$(IsExperimentationLive)'!=''">
<!-- Additional dependencies used by experimentation -->
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>

View File

@ -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}

View File

@ -4,12 +4,15 @@
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define SettingsV2Files=WinUIEx.dll;backup_restore_settings.json;Ijwhost.dll;ColorCode.Core.dll;ColorCode.WinUI.dll;CommunityToolkit.Common.dll;CommunityToolkit.Labs.WinUI.SettingsControls.dll;CommunityToolkit.WinUI.dll;CommunityToolkit.WinUI.UI.Controls.Core.dll;CommunityToolkit.WinUI.UI.Controls.DataGrid.dll;CommunityToolkit.WinUI.UI.Controls.Input.dll;CommunityToolkit.WinUI.UI.Controls.Layout.dll;CommunityToolkit.WinUI.UI.Controls.Markdown.dll;CommunityToolkit.WinUI.UI.Controls.Media.dll;CommunityToolkit.WinUI.UI.Controls.Primitives.dll;CommunityToolkit.WinUI.UI.dll;icon.ico;Microsoft.Graphics.Canvas.Interop.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.Settings.deps.json;PowerToys.Settings.dll;PowerToys.Settings.exe;PowerToys.Settings.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;resources.pri;System.CodeDom.dll;System.IO.Abstractions.dll;WinRT.Runtime.dll;Microsoft.Graphics.Canvas.dll;System.Management.dll;PowerToys.GPOWrapper.dll;System.Text.Json.dll;WindowsBase.dll?>
<?define SettingsV2Files=WinUIEx.dll;backup_restore_settings.json;Ijwhost.dll;ColorCode.Core.dll;ColorCode.WinUI.dll;CommunityToolkit.Common.dll;CommunityToolkit.Labs.WinUI.SettingsControls.dll;CommunityToolkit.WinUI.dll;CommunityToolkit.WinUI.UI.Controls.Core.dll;CommunityToolkit.WinUI.UI.Controls.DataGrid.dll;CommunityToolkit.WinUI.UI.Controls.Input.dll;CommunityToolkit.WinUI.UI.Controls.Layout.dll;CommunityToolkit.WinUI.UI.Controls.Markdown.dll;CommunityToolkit.WinUI.UI.Controls.Media.dll;CommunityToolkit.WinUI.UI.Controls.Primitives.dll;CommunityToolkit.WinUI.UI.dll;icon.ico;Microsoft.Graphics.Canvas.Interop.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.Settings.deps.json;PowerToys.Settings.dll;PowerToys.Settings.exe;PowerToys.Settings.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;resources.pri;System.CodeDom.dll;System.IO.Abstractions.dll;WinRT.Runtime.dll;Microsoft.Graphics.Canvas.dll;System.Management.dll;PowerToys.GPOWrapper.dll;System.Text.Json.dll;WindowsBase.dll;PowerToys.AllExperiments.dll?>
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PowerAccent.png;PowerOCR.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ScreenRuler.png;ShortcutGuide.png;VideoConference.png?>
<?define SettingsV2OOBEAssetsModulesFiles=ColorPicker.gif;AlwaysOnTop.png;HostsFileEditor.png;Awake.png;FancyZones.gif;FileExplorer.png;FileLocksmith.gif;ImageResizer.gif;KBM.gif;MouseUtils.gif;PowerAccent.gif;PowerOCR.gif;PowerRename.gif;Run.gif;ScreenRuler.gif;OOBEShortcutGuide.png;VideoConferenceMute.png;OOBEPTHero.png?>
<?define SettingsV2OOBEAssetsFluentIconsFiles=ColorPicker.png;FancyZones.png;FileLocksmith.png;AlwaysOnTop.png;Awake.png;FileExplorerPreview.png;FindMyMouse.png;Hosts.png;ImageResizer.png;KeyboardManager.png;MouseHighlighter.png;MouseCrosshairs.png;MouseUtils.png;PowerAccent.png;PowerOcr.png;PowerRename.png;PowerToys.png;PowerToysRun.png;ScreenRuler.png;Settings.png;ShortcutGuide.png;VideoConferenceMute.png?>
<?define SettingsV2MicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
<!-- These files are needed for release builds to contain the experimentation DLLs -->
<?define PowerToysExperimentsFiles=Microsoft.VariantAssignment.Client.dll;Microsoft.VariantAssignment.Contract.dll;System.Net.Http.Formatting.dll;Microsoft.Extensions.Configuration.dll;Microsoft.Extensions.Configuration.Abstractions.dll;Microsoft.Extensions.Configuration.Binder.dll;Microsoft.Extensions.DependencyInjection.Abstractions.dll;Microsoft.Extensions.Http.dll;Microsoft.Extensions.Logging.dll;Microsoft.Extensions.Logging.Abstractions.dll;Microsoft.Extensions.Options.dll;Microsoft.Extensions.Primitives.dll;Newtonsoft.Json.dll;Newtonsoft.Json.Bson.dll?>
<Fragment>
<!-- SettingsV2 components -->
<DirectoryRef Id="SettingsV2InstallFolder" FileSource="$(var.BinDir)Settings\">
@ -18,6 +21,13 @@
<File Id="SV2_$(var.File)" Source="$(var.BinDir)Settings\$(var.File)" />
</Component>
<?endforeach?>
<?ifdef env.IsExperimentationLive?>
<?foreach File in $(var.PowerToysExperimentsFiles)?>
<Component Id="SV2CE_$(var.File)" Win64="yes">
<File Id="SV2E_$(var.File)" Source="$(var.BinDir)Settings\$(var.File)" />
</Component>
<?endforeach?>
<?endif?>
</DirectoryRef>
<DirectoryRef Id="SettingsV2AssetsInstallFolder" FileSource="$(var.BinDir)Settings\Assets">
<Component Id="SettingsV2Assets_LogoScale200" Win64="yes">
@ -78,6 +88,11 @@
<?foreach File in $(var.SettingsV2Files)?>
<ComponentRef Id="SV2C_$(var.File)" />
<?endforeach?>
<?ifdef env.IsExperimentationLive?>
<?foreach File in $(var.PowerToysExperimentsFiles)?>
<ComponentRef Id="SV2CE_$(var.File)" />
<?endforeach?>
<?endif?>
<ComponentRef Id="SettingsV2Assets_LogoScale200" />
<ComponentRef Id="SettingsV2Assets_SplashScreen" />
<ComponentRef Id="SettingsV2Assets_StoreLogo_Scale100" />

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Version.props" />
<PropertyGroup>
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TargetName>PowerToys.AllExperiments</TargetName>
<MockDirectory>.\Microsoft.VariantAssignment\</MockDirectory>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<!-- Experimentation is live, forcing inclusion -->
<ItemGroup Condition="'$(IsExperimentationLive)'!=''">
<!-- Newtonsoft.Json is included and a version specified in Directory.Packages.props to avoid a vulnerability from older versions. -->
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Microsoft.VariantAssignment.Client" />
<PackageReference Include="Microsoft.VariantAssignment.Contract" />
<Compile Remove=".\$(MockDirectory)\Client\*.cs" />
<Compile Remove=".\$(MockDirectory)\Contract\*.cs" />
</ItemGroup>
</Project>

View File

@ -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<bool> 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<Dictionary<string, object>>(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<Dictionary<string, object>>(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<string, string>()
{
["clientid"] = AssignmentUnit,
};
string jsonData = JsonSerializer.Serialize(data);
File.WriteAllText(jsonFilePath, jsonData);
}
else
{
string json = File.ReadAllText(jsonFilePath);
var jsonDictionary = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(json);
if (jsonDictionary != null)
{
AssignmentUnit = jsonDictionary["clientid"]?.ToString();
}
}
}
catch (Exception ex)
{
Logger.LogError("Error creating/getting AssignmentUnit", ex);
}
var attrNames = new List<string> { "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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<TreatmentAssignmentServiceResponse>;
#pragma warning restore SA1200 // Using directives should be placed correctly
public static class VariantAssignmentClientExtensionMethods
{
public static IVariantAssignmentProvider GetTreatmentAssignmentServiceClient(this VariantAssignmentClientSettings settings)
{
return new TreatmentAssignmentServiceClient();
}
}
}

View File

@ -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<TServerResponse> : IVariantAssignmentProvider, IDisposable
where TServerResponse : VariantAssignmentServiceResponse
{
public void Dispose()
{
throw new NotImplementedException();
}
public Task<IVariantAssignmentResponse> GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default)
{
return Task.FromResult(EmptyVariantAssignmentResponse.Instance);
}
}
}

View File

@ -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
{
/// <summary>
/// Singleton instance of <see cref="EmptyVariantAssignmentResponse"/>.
/// </summary>
public static readonly IVariantAssignmentResponse Instance = new EmptyVariantAssignmentResponse();
public EmptyVariantAssignmentResponse()
{
}
public long DataVersion => 0;
public string Thumbprint => string.Empty;
/// <inheritdoc/>
public IReadOnlyCollection<IAssignedVariant> AssignedVariants => Array.Empty<IAssignedVariant>();
/// <inheritdoc/>
#pragma warning disable CS8603 // Possible null reference return.
public IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path) => null;
#pragma warning restore CS8603 // Possible null reference return.
/// <inheritdoc/>
public IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix) => Array.Empty<IFeatureVariable>();
void IDisposable.Dispose()
{
}
string IVariantAssignmentResponse.GetAssignmentContext()
{
throw new NotImplementedException();
}
IReadOnlyList<IFeatureVariable> IVariantAssignmentResponse.GetFeatureVariables()
{
throw new NotImplementedException();
}
}
}

View File

@ -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
{
}
}

View File

@ -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
{
/// <summary>
/// Gets the variable's value as a string.
/// </summary>
/// <returns>String value of the variable.</returns>
string GetStringValue();
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
namespace Microsoft.VariantAssignment.Contract
{
public interface IVariantAssignmentProvider : IDisposable
{
/// <summary>
/// Computes variant assignments based on <paramref name="request"/> data.
/// </summary>
/// <param name="request">Variant assignment parameters.</param>
/// <param name="ct">Propagates notification that operations should be canceled.</param>
/// <returns>An awaitable task that returns a <see cref="IVariantAssignmentResponse"/>.</returns>
Task<IVariantAssignmentResponse> GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default);
}
}

View File

@ -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
{
/// <summary>
/// Gets inputs used for evaluating filters, assignment units, etc.
/// </summary>
IReadOnlyCollection<(string Key, string Value)> Parameters { get; }
}
}

View File

@ -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
{
/// <summary>
/// Snapshot of variant assignments.
/// </summary>
public interface IVariantAssignmentResponse : IDisposable
{
///// <summary>
///// Gets the serial number of variant assignment configuration snapshot used when assigning variants.
///// </summary>
long DataVersion { get; }
///// <summary>
///// Get a hash of the response suitable for caching.
///// </summary>
// string Thumbprint { get; }
/// <summary>
/// Gets the variants assigned based on request parameters and a variant configuration snapshot.
/// </summary>
IReadOnlyCollection<IAssignedVariant> AssignedVariants { get; }
/// <summary>
/// Gets feature variables assigned by variants in this response.
/// </summary>
/// <param name="prefix">(Optional) Filter feature variables where <see cref="IFeatureVariable.KeySegments"/> contains the <paramref name="prefix"/>.</param>
/// <returns>Range of matching feature variables.</returns>
IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix);
// this actually part of the interface but gets the job done
IReadOnlyList<IFeatureVariable> GetFeatureVariables();
// this actually part of the interface but gets the job done
string GetAssignmentContext();
/// <summary>
/// Gets a single feature variable assigned by variants in this response.
/// </summary>
/// <param name="path">Exact feature variable path.</param>
/// <returns>Matching feature variable or null.</returns>
IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path);
}
}

View File

@ -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
{
}
}

View File

@ -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
{
/// <summary>
/// Configuration for variant assignment service client.
/// </summary>
public class VariantAssignmentClientSettings
{
/// <summary>
/// Gets or sets the variant assignment service endpoint URL.
/// </summary>
[Required]
public Uri? Endpoint { get; set; }
/// <summary>
/// Gets or sets a value indicating whether gets or sets a value whether client side request caching should be enabled.
/// </summary>
public bool EnableCaching { get; set; }
/// <summary>
/// Gets or sets the the maximum time a cached variant assignment response may be used without re-validating.
/// </summary>
public TimeSpan ResponseCacheTime { get; set; }
}
}

View File

@ -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();
/// <summary>
/// Gets or sets mutable <see cref="IVariantAssignmentRequest.Parameters"/>.
/// </summary>
public NameValueCollection Parameters { get => _parameters; set => _parameters = value; }
IReadOnlyCollection<(string Key, string Value)> IVariantAssignmentRequest.Parameters => (IReadOnlyCollection<(string Key, string Value)>)_parameters;
}
}

View File

@ -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
{
/// <summary>
/// Mutable implementation of <see cref="IVariantAssignmentResponse"/> for (de)serialization.
/// </summary>
internal class VariantAssignmentServiceResponse : IVariantAssignmentResponse, IDisposable
{
/// <inheritdoc />
public virtual long DataVersion { get; set; }
public virtual IReadOnlyCollection<IAssignedVariant> AssignedVariants { get; set; } = Array.Empty<IAssignedVariant>();
public IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path)
{
throw new NotImplementedException();
}
public IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix)
{
throw new NotImplementedException();
}
protected virtual void Dispose(bool disposing)
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public IReadOnlyList<IFeatureVariable> GetFeatureVariables()
{
throw new NotImplementedException();
}
public string GetAssignmentContext()
{
return string.Empty;
}
}
}

View File

@ -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");

View File

@ -11,6 +11,7 @@ struct GeneralSettings
bool isRunElevated;
bool isAdmin;
bool downloadUpdatesAutomatically;
bool enableExperimentation;
std::wstring theme;
std::wstring systemTheme;
std::wstring powerToysVersion;

View File

@ -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"),

View File

@ -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

View File

@ -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;
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
<IsPackable>false</IsPackable>
<RuntimeIdentifiers>win10-x64;win10-arm64</RuntimeIdentifiers>
<Version>$(Version).0</Version>
</PropertyGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

View File

@ -10,12 +10,13 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="280" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image
x:Name="HeaderImage"
Height="{x:Bind HeroImageHeight}"
Source="{x:Bind HeroImage}"
Stretch="UniformToFill" />
@ -23,9 +24,7 @@
Grid.Row="1"
Padding="32,24,32,24"
VerticalScrollBarVisibility="Auto">
<StackPanel
VerticalAlignment="Top"
Orientation="Vertical">
<StackPanel VerticalAlignment="Top" Orientation="Vertical">
<TextBlock
x:Name="TitleTxt"

View File

@ -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));
}
}

View File

@ -0,0 +1,193 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeOverviewAlternate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
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"
mc:Ignorable="d">
<controls:OOBEPageControl
x:Uid="Oobe_Overview"
HeroImage="ms-appx:///Assets/Modules/OOBE/OOBEPTHero.png"
HeroImageHeight="120">
<controls:OOBEPageControl.PageContent>
<StackPanel Orientation="Vertical">
<TextBlock
x:Uid="Alternate_OOBE_Description"
Margin="0,24,0,12"
FontWeight="SemiBold" />
<GridView Margin="-8,0,0,0" SelectionMode="None">
<GridViewItem>
<Grid
Width="280"
Margin="8"
Padding="16,16,16,10"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Width="36"
HorizontalAlignment="Left"
Source="ms-appx:///Assets/FluentIcons/FluentIconsFancyZones.png" />
<TextBlock
x:Uid="Alternate_OOBE_FancyZones_Title"
Grid.Row="1"
Margin="0,12,0,6"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<TextBlock
x:Uid="Alternate_OOBE_FancyZones_Description"
Grid.Row="2"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<controls:ShortcutWithTextLabelControl
x:Name="FancyZonesHotkeyControl"
Grid.Row="3"
Margin="0,8,0,0" />
</Grid>
</GridViewItem>
<GridViewItem>
<Grid
Width="280"
Padding="16,16,16,10"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Width="36"
HorizontalAlignment="Left"
Source="ms-appx:///Assets/FluentIcons/FluentIconsPowerToysRun.png" />
<TextBlock
x:Uid="Alternate_OOBE_Run_Title"
Grid.Row="1"
Margin="0,12,0,6"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<TextBlock
x:Uid="Alternate_OOBE_Run_Description"
Grid.Row="2"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<controls:ShortcutWithTextLabelControl
x:Name="RunHotkeyControl"
Grid.Row="3"
Margin="0,8,0,0" />
</Grid>
</GridViewItem>
<GridViewItem>
<Grid
Width="280"
Padding="16,16,16,10"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Width="36"
HorizontalAlignment="Left"
Source="ms-appx:///Assets/FluentIcons/FluentIconsColorPicker.png" />
<TextBlock
x:Uid="Alternate_OOBE_ColorPicker_Title"
Grid.Row="1"
Margin="0,12,0,6"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<TextBlock
x:Uid="Alternate_OOBE_ColorPicker_Description"
Grid.Row="2"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="To pick a color:"
TextWrapping="Wrap" />
<controls:ShortcutWithTextLabelControl
x:Name="ColorPickerHotkeyControl"
Grid.Row="3"
Margin="0,8,0,0" />
</Grid>
</GridViewItem>
<GridViewItem>
<Grid
Width="280"
Padding="16,16,16,10"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Width="36"
HorizontalAlignment="Left"
Source="ms-appx:///Assets/FluentIcons/FluentIconsAlwaysOnTop.png" />
<TextBlock
x:Uid="Alternate_OOBE_AlwaysOnTop_Title"
Grid.Row="1"
Margin="0,12,0,6"
HorizontalAlignment="Left"
FontSize="16"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<TextBlock
x:Uid="Alternate_OOBE_AlwaysOnTop_Description"
Grid.Row="2"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<controls:ShortcutWithTextLabelControl
x:Name="AlwaysOnTopHotkeyControl"
Grid.Row="3"
Margin="0,8,0,0" />
</Grid>
</GridViewItem>
</GridView>
</StackPanel>
</controls:OOBEPageControl.PageContent>
</controls:OOBEPageControl>
</Page>

View File

@ -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<FancyZonesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.GetKeysList();
RunHotkeyControl.Keys = SettingsRepository<PowerLauncherSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.OpenPowerLauncher.GetKeysList();
ColorPickerHotkeyControl.Keys = SettingsRepository<ColorPickerSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
AlwaysOnTopHotkeyControl.Keys = SettingsRepository<AlwaysOnTopSettings>.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();
}
}
}

View File

@ -0,0 +1,23 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
<!-- Licensed under the MIT License. See LICENSE in the project root for license information. -->
<Page
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeOverviewPlaceholder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Loaded="Page_Loaded"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="0,24,0,0">
<ProgressRing
x:Name="LoadingProgressRing"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsIndeterminate="True"
Visibility="Visible" />
</Grid>
</Page>

View File

@ -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<bool> 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();
}
}
}

View File

@ -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<OobePowerToysModule> Modules { get; }
private static ISettingsUtils settingsUtils = new SettingsUtils();
private bool ExperimentationToggleSwitchEnabled { get; set; } = true;
public OobeShellPage()
{
InitializeComponent();
ExperimentationToggleSwitchEnabled = SettingsRepository<GeneralSettings>.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;

View File

@ -55,6 +55,9 @@
<PropertyGroup>
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>
<ItemGroup>
<None Remove="OOBE\Views\OobeOverviewPlaceholder.xaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Controls\ColorFormatEditor.xaml" />
</ItemGroup>
@ -91,15 +94,19 @@
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored -->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\AllExperiments\AllExperiments.csproj" />
<ProjectReference Include="..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
<ItemGroup>
<Page Update="FlyoutWindow.xaml">
<Generator>MSBuild:Compile</Generator>
@ -122,10 +129,17 @@
<None Update="icon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Page Update="OOBE\Views\OobeOverviewPlaceholder.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="OOBE\Views\OobeHosts.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="OOBE\Views\OobeOverviewAlternate.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="OOBE\Views\OobePowerOCR.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>

View File

@ -2830,6 +2830,10 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Launch Host File Editor</value>
<comment>"Host File Editor" is a product name</comment>
</data>
<data name="Hosts_LaunchButton_Accessible.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Launch Host File Editor</value>
<comment>"Host File Editor" is a product name</comment>
</data>
<data name="Hosts_AdditionalLinesPosition.Header" xml:space="preserve">
<value>Position of additional content</value>
</data>
@ -2891,6 +2895,39 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="TextExtractor_Languages.Header" xml:space="preserve">
<value>Preferred language</value>
</data>
<data name="Alternate_OOBE_AlwaysOnTop_Description.Text" xml:space="preserve">
<value>Pin a window so that:</value>
</data>
<data name="Alternate_OOBE_AlwaysOnTop_Title.Text" xml:space="preserve">
<value>Always On Top</value>
</data>
<data name="Alternate_OOBE_ColorPicker_Description.Text" xml:space="preserve">
<value>To pick a color:</value>
</data>
<data name="Alternate_OOBE_ColorPicker_Title.Text" xml:space="preserve">
<value>Color Picker</value>
</data>
<data name="Alternate_OOBE_Description.Text" xml:space="preserve">
<value>Here are a few shortcuts to get you started:</value>
</data>
<data name="Alternate_OOBE_FancyZones_Description.Text" xml:space="preserve">
<value>To open the FancyZones editor, press:</value>
</data>
<data name="Alternate_OOBE_FancyZones_Title.Text" xml:space="preserve">
<value>FancyZones</value>
</data>
<data name="Alternate_OOBE_Run_Description.Text" xml:space="preserve">
<value>Get access to your files and more:</value>
</data>
<data name="Alternate_OOBE_Run_Title.Text" xml:space="preserve">
<value>PowerToys Run</value>
</data>
<data name="GeneralPage_EnableExperimentation.Description" xml:space="preserve">
<value>Only affects Windows Insider builds</value>
</data>
<data name="GeneralPage_EnableExperimentation.Header" xml:space="preserve">
<value>Enable experimentation</value>
</data>
<data name="AllAppsTxt.Text" xml:space="preserve">
<value>All apps</value>
</data>

View File

@ -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

View File

@ -369,8 +369,13 @@
</labs:SettingsCard>
</labs:SettingsExpander.Items>
</labs:SettingsExpander>
<labs:SettingsCard x:Uid="GeneralPage_EnableExperimentation"
HeaderIcon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsExperimentation.png}">
<ToggleSwitch
x:Uid="ToggleSwitch"
IsOn="{Binding Mode=TwoWay, Path=EnableExperimentation}" />
</labs:SettingsCard>
</controls:SettingsGroup>
<InfoBar
x:Uid="General_SettingsBackupMessageResults"
Title="{Binding SettingsBackupMessage}"