diff --git a/.github/policies/scheduledSearch.closeNoRecentActivity.yml b/.github/policies/scheduledSearch.closeNoRecentActivity.yml index bceac75ee6..ad045df745 100644 --- a/.github/policies/scheduledSearch.closeNoRecentActivity.yml +++ b/.github/policies/scheduledSearch.closeNoRecentActivity.yml @@ -13,7 +13,6 @@ configuration: * Issue is Open * Issue has the label Needs-Author-Feedback * Issue has the label Status-No recent activity - * Issue does not have the label Issue-Feature * Has not had activity in the last 5 days Then - * Close the Issue @@ -27,8 +26,6 @@ configuration: label: Needs-Author-Feedback - hasLabel: label: Status-No recent activity - - isNotLabeledWith: - label: Issue-Feature - noActivitySince: days: 5 actions: diff --git a/.github/policies/scheduledSearch.markNoRecentActivity.yml b/.github/policies/scheduledSearch.markNoRecentActivity.yml index 4de6122bc0..bda6f42a63 100644 --- a/.github/policies/scheduledSearch.markNoRecentActivity.yml +++ b/.github/policies/scheduledSearch.markNoRecentActivity.yml @@ -13,7 +13,6 @@ configuration: * Issue is Open * Issue has the label Needs-Author-Feedback * Issue does not have the label Status-No recent activity - * Issue does not have the label Issue-Feature * Has not had activity in the last 5 days Then - * Mark the issue as Status-No recent activity @@ -29,8 +28,6 @@ configuration: days: 5 - isNotLabeledWith: label: Status-No recent activity - - isNotLabeledWith: - label: Issue-Feature actions: - addLabel: label: Status-No recent activity diff --git a/.github/workflows/DevHome-CI.yml b/.github/workflows/DevHome-CI.yml index 0dc99c78a4..0fbbb1baf5 100644 --- a/.github/workflows/DevHome-CI.yml +++ b/.github/workflows/DevHome-CI.yml @@ -15,7 +15,7 @@ jobs: configuration: [Release, Debug] platform: [x64, x86, arm64] os: [windows-latest] - dotnet-version: ['6.0.x'] + dotnet-version: ['8.0.x'] exclude: - configuration: Debug platform: x64 @@ -77,11 +77,11 @@ jobs: - name: DevHome UnitTests if: ${{ matrix.platform != 'arm64' }} - run: cmd /c "$env:VSDevTestCmd" /Platform:${{ matrix.platform }} test\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net6.0-windows10.0.22000.0\\DevHome.Test.dll + run: cmd /c "$env:VSDevTestCmd" /Platform:${{ matrix.platform }} test\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net8.0-windows10.0.22000.0\\DevHome.Test.dll - name: Tools UnitTests if: ${{ matrix.platform != 'arm64' }} run: | - foreach ($UnitTestPath in (Get-ChildItem "tools\\*\\*UnitTest\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net6.0-windows10.0.22000.0\\*.UnitTest.dll")) { + foreach ($UnitTestPath in (Get-ChildItem "tools\\*\\*UnitTest\\bin\\${{ matrix.platform }}\\${{ matrix.configuration }}\\net8.0-windows10.0.22000.0\\*.UnitTest.dll")) { cmd /c "$env:VSDevTestCmd" /Platform:${{ matrix.platform }} $UnitTestPath.FullName } diff --git a/CoreWidgetProvider/CoreWidgetProvider.csproj b/CoreWidgetProvider/CoreWidgetProvider.csproj index bcfbb77c0f..023fe7c6e1 100644 --- a/CoreWidgetProvider/CoreWidgetProvider.csproj +++ b/CoreWidgetProvider/CoreWidgetProvider.csproj @@ -17,11 +17,11 @@ - - + + all - + diff --git a/CoreWidgetProvider/Helpers/CPUStats.cs b/CoreWidgetProvider/Helpers/CPUStats.cs index 7455a9ebe0..2642389006 100644 --- a/CoreWidgetProvider/Helpers/CPUStats.cs +++ b/CoreWidgetProvider/Helpers/CPUStats.cs @@ -5,7 +5,7 @@ namespace CoreWidgetProvider.Helpers; -internal class CPUStats : IDisposable +internal sealed class CPUStats : IDisposable { // CPU counters private readonly PerformanceCounter procPerf = new ("Processor Information", "% Processor Utility", "_Total"); @@ -13,7 +13,7 @@ internal class CPUStats : IDisposable private readonly PerformanceCounter procFrequency = new ("Processor Information", "Processor Frequency", "_Total"); private readonly Dictionary cpuCounters = new (); - internal class ProcessStats + internal sealed class ProcessStats { public Process? Process { get; set; } @@ -31,7 +31,12 @@ internal class ProcessStats public CPUStats() { CpuUsage = 0; - ProcessCPUStats = new ProcessStats[3] { new ProcessStats(), new ProcessStats(), new ProcessStats() }; + ProcessCPUStats = + [ + new ProcessStats(), + new ProcessStats(), + new ProcessStats() + ]; InitCPUPerfCounters(); } diff --git a/CoreWidgetProvider/Helpers/ChartHelper.cs b/CoreWidgetProvider/Helpers/ChartHelper.cs index 3e97d37834..01f32e3f73 100644 --- a/CoreWidgetProvider/Helpers/ChartHelper.cs +++ b/CoreWidgetProvider/Helpers/ChartHelper.cs @@ -7,7 +7,7 @@ namespace CoreWidgetProvider.Helpers; -internal class ChartHelper +internal sealed class ChartHelper { public enum ChartType { diff --git a/CoreWidgetProvider/Helpers/DataManager.cs b/CoreWidgetProvider/Helpers/DataManager.cs index cb1bd9aac8..482212233d 100644 --- a/CoreWidgetProvider/Helpers/DataManager.cs +++ b/CoreWidgetProvider/Helpers/DataManager.cs @@ -5,7 +5,7 @@ namespace CoreWidgetProvider.Helpers; -internal class DataManager : IDisposable +internal sealed class DataManager : IDisposable { private readonly SystemData systemData; private readonly DataType dataType; diff --git a/CoreWidgetProvider/Helpers/GPUStats.cs b/CoreWidgetProvider/Helpers/GPUStats.cs index 237a336a7f..cd5ef3157a 100644 --- a/CoreWidgetProvider/Helpers/GPUStats.cs +++ b/CoreWidgetProvider/Helpers/GPUStats.cs @@ -7,14 +7,14 @@ namespace CoreWidgetProvider.Helpers; -internal class GPUStats : IDisposable +internal sealed class GPUStats : IDisposable { // GPU counters private readonly Dictionary> gpuCounters = new (); private readonly List stats = new (); - public class Data + public sealed class Data { public string? Name { get; set; } @@ -79,12 +79,13 @@ public void GetGPUPerfCounters() continue; } - if (!gpuCounters.ContainsKey(phys)) + if (!gpuCounters.TryGetValue(phys, out var value)) { - gpuCounters.Add(phys, new ()); + value = new (); + gpuCounters.Add(phys, value); } - gpuCounters[phys].Add(counter); + value.Add(counter); } } } diff --git a/CoreWidgetProvider/Helpers/IconLoader.cs b/CoreWidgetProvider/Helpers/IconLoader.cs index 9ecda6ca8e..c5aa4b6d08 100644 --- a/CoreWidgetProvider/Helpers/IconLoader.cs +++ b/CoreWidgetProvider/Helpers/IconLoader.cs @@ -10,13 +10,14 @@ public class IconLoader public static string GetIconAsBase64(string filename) { Log.Logger()?.ReportDebug(nameof(IconLoader), $"Asking for icon: {filename}"); - if (!Base64ImageRegistry.ContainsKey(filename)) + if (!Base64ImageRegistry.TryGetValue(filename, out var value)) { - Base64ImageRegistry.Add(filename, ConvertIconToDataString(filename)); + value = ConvertIconToDataString(filename); + Base64ImageRegistry.Add(filename, value); Log.Logger()?.ReportDebug(nameof(IconLoader), $"The icon {filename} was converted and is now stored."); } - return Base64ImageRegistry[filename]; + return value; } private static string ConvertIconToDataString(string fileName) diff --git a/CoreWidgetProvider/Helpers/MemoryStats.cs b/CoreWidgetProvider/Helpers/MemoryStats.cs index 9876228fa6..97082964e5 100644 --- a/CoreWidgetProvider/Helpers/MemoryStats.cs +++ b/CoreWidgetProvider/Helpers/MemoryStats.cs @@ -7,7 +7,7 @@ namespace CoreWidgetProvider.Helpers; -internal class MemoryStats : IDisposable +internal sealed class MemoryStats : IDisposable { private readonly PerformanceCounter memCommitted = new ("Memory", "Committed Bytes", string.Empty); private readonly PerformanceCounter memCached = new ("Memory", "Cache Bytes", string.Empty); diff --git a/CoreWidgetProvider/Helpers/NetworkStats.cs b/CoreWidgetProvider/Helpers/NetworkStats.cs index 6f2d74fb3a..e18fe80fe9 100644 --- a/CoreWidgetProvider/Helpers/NetworkStats.cs +++ b/CoreWidgetProvider/Helpers/NetworkStats.cs @@ -5,7 +5,7 @@ namespace CoreWidgetProvider.Helpers; -internal class NetworkStats : IDisposable +internal sealed class NetworkStats : IDisposable { private readonly Dictionary> networkCounters = new (); @@ -13,7 +13,7 @@ internal class NetworkStats : IDisposable private Dictionary> NetChartValues { get; set; } = new Dictionary>(); - public class Data + public sealed class Data { public float Usage { @@ -114,12 +114,12 @@ public Data GetNetworkUsage(int networkIndex) } var currNetworkName = NetChartValues.ElementAt(networkIndex).Key; - if (!NetworkUsages.ContainsKey(currNetworkName)) + if (!NetworkUsages.TryGetValue(currNetworkName, out var value)) { return new Data(); } - return NetworkUsages[currNetworkName]; + return value; } public int GetPrevNetworkIndex(int networkIndex) diff --git a/CoreWidgetProvider/Helpers/Resources.cs b/CoreWidgetProvider/Helpers/Resources.cs index 6950a569d7..16b10261c5 100644 --- a/CoreWidgetProvider/Helpers/Resources.cs +++ b/CoreWidgetProvider/Helpers/Resources.cs @@ -51,8 +51,8 @@ public static string ReplaceIdentifers(string str, string[] resourceIdentifiers, // These are all the string identifiers that appear in widgets. public static string[] GetWidgetResourceIdentifiers() { - return new string[] - { + return + [ "Widget_Template/Loading", "Widget_Template_Tooltip/Submit", "SSH_Widget_Template/Name", @@ -89,6 +89,6 @@ public static string[] GetWidgetResourceIdentifiers() "CPUUsage_Widget_Template/End_Process", "Widget_Template_Button/Save", "Widget_Template_Button/Cancel", - }; + ]; } } diff --git a/CoreWidgetProvider/Helpers/SystemData.cs b/CoreWidgetProvider/Helpers/SystemData.cs index 3664213b8d..234e69b743 100644 --- a/CoreWidgetProvider/Helpers/SystemData.cs +++ b/CoreWidgetProvider/Helpers/SystemData.cs @@ -3,7 +3,7 @@ namespace CoreWidgetProvider.Helpers; -internal class SystemData : IDisposable +internal sealed class SystemData : IDisposable { public static MemoryStats MemStats { get; set; } = new MemoryStats(); diff --git a/CoreWidgetProvider/Widgets/CoreWidget.cs b/CoreWidgetProvider/Widgets/CoreWidget.cs index 0562cb61b5..04e8360c80 100644 --- a/CoreWidgetProvider/Widgets/CoreWidget.cs +++ b/CoreWidgetProvider/Widgets/CoreWidget.cs @@ -125,10 +125,10 @@ public virtual string GetData(WidgetPageState page) protected string GetTemplateForPage(WidgetPageState page) { - if (Template.ContainsKey(page)) + if (Template.TryGetValue(page, out var value)) { Log.Logger()?.ReportDebug(Name, ShortId, $"Using cached template for {page}"); - return Template[page]; + return value; } try diff --git a/CoreWidgetProvider/Widgets/SSHWalletWidget.cs b/CoreWidgetProvider/Widgets/SSHWalletWidget.cs index 7ec08d6bc2..de965ed939 100644 --- a/CoreWidgetProvider/Widgets/SSHWalletWidget.cs +++ b/CoreWidgetProvider/Widgets/SSHWalletWidget.cs @@ -13,17 +13,17 @@ namespace CoreWidgetProvider.Widgets; -internal class SSHWalletWidget : CoreWidget +internal sealed class SSHWalletWidget : CoreWidget { - protected static readonly string DefaultConfigFile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\.ssh\\config"; + private static readonly string DefaultConfigFile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\.ssh\\config"; private static readonly Regex HostRegex = new (@"^Host\s+(\S*)\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); private FileSystemWatcher? FileWatcher { get; set; } - protected static readonly new string Name = nameof(SSHWalletWidget); + private static readonly new string Name = nameof(SSHWalletWidget); - protected string ConfigFile + private string ConfigFile { get => State(); @@ -48,7 +48,7 @@ public override void LoadContentData() // Widget will remain in configuring state, waiting for config file path input. if (string.IsNullOrWhiteSpace(ConfigFile)) { - ContentData = new JsonObject { { "configuring", true } }.ToJsonString(); + ContentData = EmptyJson; DataState = WidgetDataState.Okay; return; } @@ -86,6 +86,11 @@ public override void LoadContentData() catch (Exception e) { Log.Logger()?.ReportError(Name, ShortId, "Error retrieving data.", e); + var content = new JsonObject + { + { "errorMessage", e.Message }, + }; + ContentData = content.ToJsonString(); DataState = WidgetDataState.Failed; return; } @@ -246,7 +251,7 @@ private void OnConfigFileRenamed(object sender, FileSystemEventArgs e) UpdateWidget(); } - private JsonObject FillConfigurationData(bool hasConfiguration, string configFile, int numOfEntries = 0, bool configuring = true, string errorMessage = "") + private JsonObject FillConfigurationData(bool hasConfiguration, string configFile, int numOfEntries = 0, string errorMessage = "") { var configurationData = new JsonObject(); @@ -265,7 +270,6 @@ private JsonObject FillConfigurationData(bool hasConfiguration, string configFil { "numOfEntries", numOfEntries.ToString(CultureInfo.InvariantCulture) }, }; - configurationData.Add("configuring", configuring); configurationData.Add("hasConfiguration", hasConfiguration); configurationData.Add("configuration", sshConfigData); configurationData.Add("savedConfigFile", _savedConfigFile); @@ -298,18 +302,18 @@ public override string GetConfiguration(string data) var numberOfEntries = GetNumberOfHostEntries(); - configurationData = FillConfigurationData(true, ConfigFile, numberOfEntries, false); + configurationData = FillConfigurationData(true, ConfigFile, numberOfEntries); } else { - configurationData = FillConfigurationData(false, data, 0, true, Resources.GetResource(@"SSH_Widget_Template/ConfigFileNotFound", Logger())); + configurationData = FillConfigurationData(false, data, 0, Resources.GetResource(@"SSH_Widget_Template/ConfigFileNotFound", Logger())); } } catch (Exception ex) { Log.Logger()?.ReportError(Name, ShortId, $"Failed getting configuration information for input config file path: {data}", ex); - configurationData = FillConfigurationData(false, data, 0, true, Resources.GetResource(@"SSH_Widget_Template/ErrorProcessingConfigFile", Logger())); + configurationData = FillConfigurationData(false, data, 0, Resources.GetResource(@"SSH_Widget_Template/ErrorProcessingConfigFile", Logger())); return configurationData.ToString(); } @@ -365,7 +369,7 @@ public override string GetData(WidgetPageState page) { WidgetPageState.Configure => GetConfiguration(ConfigFile), WidgetPageState.Content => ContentData, - WidgetPageState.Loading => new JsonObject { { "configuring", true } }.ToJsonString(), + WidgetPageState.Loading => EmptyJson, // In case of unknown state default to empty data _ => EmptyJson, @@ -401,7 +405,7 @@ private void SetConfigure() } } -internal class DataPayload +internal sealed class DataPayload { public string? ConfigFile { @@ -411,6 +415,6 @@ public string? ConfigFile [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(DataPayload))] -internal partial class SourceGenerationContext : JsonSerializerContext +internal sealed partial class SourceGenerationContext : JsonSerializerContext { } diff --git a/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs b/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs index c32295964a..46441eaf6a 100644 --- a/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs +++ b/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs @@ -8,11 +8,11 @@ using Microsoft.Windows.Widgets.Providers; namespace CoreWidgetProvider.Widgets; -internal class SystemCPUUsageWidget : CoreWidget, IDisposable +internal sealed class SystemCPUUsageWidget : CoreWidget, IDisposable { private static Dictionary Templates { get; set; } = new (); - protected static readonly new string Name = nameof(SystemCPUUsageWidget); + private static readonly new string Name = nameof(SystemCPUUsageWidget); private readonly DataManager dataManager; @@ -58,6 +58,11 @@ public override void LoadContentData() catch (Exception e) { Log.Logger()?.ReportError(Name, ShortId, "Error retrieving stats.", e); + var content = new JsonObject + { + { "errorMessage", e.Message }, + }; + ContentData = content.ToJsonString(); DataState = WidgetDataState.Failed; return; } diff --git a/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs b/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs index cfae9f31f2..11307afe92 100644 --- a/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs +++ b/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs @@ -9,11 +9,11 @@ using Microsoft.Windows.Widgets.Providers; namespace CoreWidgetProvider.Widgets; -internal class SystemGPUUsageWidget : CoreWidget, IDisposable +internal sealed class SystemGPUUsageWidget : CoreWidget, IDisposable { private static Dictionary Templates { get; set; } = new (); - protected static readonly new string Name = nameof(SystemGPUUsageWidget); + private static readonly new string Name = nameof(SystemGPUUsageWidget); private readonly DataManager dataManager; @@ -61,6 +61,11 @@ public override void LoadContentData() catch (Exception e) { Log.Logger()?.ReportError(Name, ShortId, "Error retrieving data.", e); + var content = new JsonObject + { + { "errorMessage", e.Message }, + }; + ContentData = content.ToJsonString(); DataState = WidgetDataState.Failed; return; } diff --git a/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs b/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs index 3969bf9c40..0de373aaff 100644 --- a/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs +++ b/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs @@ -8,11 +8,11 @@ namespace CoreWidgetProvider.Widgets; -internal class SystemMemoryWidget : CoreWidget, IDisposable +internal sealed class SystemMemoryWidget : CoreWidget, IDisposable { private static Dictionary Templates { get; set; } = new (); - protected static readonly new string Name = nameof(SystemMemoryWidget); + private static readonly new string Name = nameof(SystemMemoryWidget); private readonly DataManager dataManager; @@ -78,6 +78,11 @@ public override void LoadContentData() catch (Exception e) { Log.Logger()?.ReportError(Name, ShortId, "Error retrieving data.", e); + var content = new JsonObject + { + { "errorMessage", e.Message }, + }; + ContentData = content.ToJsonString(); DataState = WidgetDataState.Failed; return; } diff --git a/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs b/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs index 1005d4c650..3074403768 100644 --- a/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs +++ b/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs @@ -8,13 +8,13 @@ using Microsoft.Windows.Widgets.Providers; namespace CoreWidgetProvider.Widgets; -internal class SystemNetworkUsageWidget : CoreWidget, IDisposable +internal sealed class SystemNetworkUsageWidget : CoreWidget, IDisposable { private static Dictionary Templates { get; set; } = new (); private int networkIndex; - protected static readonly new string Name = nameof(SystemNetworkUsageWidget); + private static readonly new string Name = nameof(SystemNetworkUsageWidget); private readonly DataManager dataManager; @@ -78,6 +78,11 @@ public override void LoadContentData() catch (Exception e) { Log.Logger()?.ReportError(Name, ShortId, "Error retrieving data.", e); + var content = new JsonObject + { + { "errorMessage", e.Message }, + }; + ContentData = content.ToJsonString(); DataState = WidgetDataState.Failed; return; } diff --git a/CoreWidgetProvider/Widgets/Templates/SSHWalletConfigurationTemplate.json b/CoreWidgetProvider/Widgets/Templates/SSHWalletConfigurationTemplate.json index 2892abac72..bf46b81350 100644 --- a/CoreWidgetProvider/Widgets/Templates/SSHWalletConfigurationTemplate.json +++ b/CoreWidgetProvider/Widgets/Templates/SSHWalletConfigurationTemplate.json @@ -75,7 +75,6 @@ { "type": "ColumnSet", "spacing": "ExtraLarge", - "$when": "${$root.savedConfigFile != \"\"}", "columns": [ { "type": "Column", @@ -100,7 +99,8 @@ { "type": "Action.Execute", "title": "%Widget_Template_Button/Cancel%", - "verb": "Cancel" + "verb": "Cancel", + "isEnabled": "${$root.savedConfigFile != \"\"}" } ] } diff --git a/CoreWidgetProvider/Widgets/Templates/SSHWalletTemplate.json b/CoreWidgetProvider/Widgets/Templates/SSHWalletTemplate.json index 8ad6742d18..68700f0bf7 100644 --- a/CoreWidgetProvider/Widgets/Templates/SSHWalletTemplate.json +++ b/CoreWidgetProvider/Widgets/Templates/SSHWalletTemplate.json @@ -3,53 +3,72 @@ "body": [ { "type": "Container", - "$when": "${(count(hosts) == 0)}", + "$when": "${errorMessage != null}", "items": [ { "type": "TextBlock", - "text": "%SSH_Widget_Template/EmptyHosts%", + "text": "${errorMessage}", "wrap": true, - "weight": "Bolder", - "horizontalAlignment": "Center" + "size": "small" } ], - "spacing": "Medium", - "verticalContentAlignment": "Center" + "style": "warning" }, { - "$data": "${hosts}", - "type": "ColumnSet", - "style": "emphasis", - "selectAction": { - "type": "Action.Execute", - "verb": "Connect", - "data": "${host}" - }, - "columns": [ + "type": "Container", + "$when": "${errorMessage == null}", + "items": [ { - "type": "Column", - "verticalContentAlignment": "Center", - "width": "auto", + "type": "Container", + "$when": "${(count(hosts) == 0)}", "items": [ { - "type": "Image", - "url": "data:image/png;base64,${icon}", - "size": "small", - "horizontalAlignment": "left" + "type": "TextBlock", + "text": "%SSH_Widget_Template/EmptyHosts%", + "wrap": true, + "weight": "Bolder", + "horizontalAlignment": "Center" } - ] + ], + "spacing": "Medium", + "verticalContentAlignment": "Center" }, { - "type": "Column", - "width": "stretch", - "verticalContentAlignment": "Center", - "items": [ + "$data": "${hosts}", + "type": "ColumnSet", + "style": "emphasis", + "selectAction": { + "type": "Action.Execute", + "verb": "Connect", + "data": "${host}" + }, + "columns": [ { - "type": "TextBlock", - "text": "${host}", - "size": "medium", - "wrap": true, - "horizontalAlignment": "left" + "type": "Column", + "verticalContentAlignment": "Center", + "width": "auto", + "items": [ + { + "type": "Image", + "url": "data:image/png;base64,${icon}", + "size": "small", + "horizontalAlignment": "left" + } + ] + }, + { + "type": "Column", + "width": "stretch", + "verticalContentAlignment": "Center", + "items": [ + { + "type": "TextBlock", + "text": "${host}", + "size": "medium", + "wrap": true, + "horizontalAlignment": "left" + } + ] } ] } diff --git a/CoreWidgetProvider/Widgets/Templates/SystemCPUUsageTemplate.json b/CoreWidgetProvider/Widgets/Templates/SystemCPUUsageTemplate.json index 24b9f1268f..1ce0b347f5 100644 --- a/CoreWidgetProvider/Widgets/Templates/SystemCPUUsageTemplate.json +++ b/CoreWidgetProvider/Widgets/Templates/SystemCPUUsageTemplate.json @@ -2,70 +2,89 @@ "type": "AdaptiveCard", "body": [ { - "type": "Image", - "url": "${cpuGraphUrl}", - "height": "${chartHeight}", - "width": "${chartWidth}", - "horizontalAlignment": "center" + "type": "Container", + "$when": "${errorMessage != null}", + "items": [ + { + "type": "TextBlock", + "text": "${errorMessage}", + "wrap": true, + "size": "small" + } + ], + "style": "warning" }, { - "type": "ColumnSet", - "columns": [ + "type": "Container", + "$when": "${errorMessage == null}", + "items": [ { - "type": "Column", - "items": [ - { - "type": "TextBlock", - "isSubtle": true, - "text": "%CPUUsage_Widget_Template/CPU_Usage%" - }, - { - "type": "TextBlock", - "size": "large", - "weight": "bolder", - "text": "${cpuUsage}" - } - ] + "type": "Image", + "url": "${cpuGraphUrl}", + "height": "${chartHeight}", + "width": "${chartWidth}", + "horizontalAlignment": "center" }, { - "type": "Column", - "items": [ + "type": "ColumnSet", + "columns": [ { - "type": "TextBlock", - "isSubtle": true, - "horizontalAlignment": "right", - "text": "%CPUUsage_Widget_Template/CPU_Speed%" + "type": "Column", + "items": [ + { + "type": "TextBlock", + "isSubtle": true, + "text": "%CPUUsage_Widget_Template/CPU_Usage%" + }, + { + "type": "TextBlock", + "size": "large", + "weight": "bolder", + "text": "${cpuUsage}" + } + ] }, { - "type": "TextBlock", - "size": "large", - "horizontalAlignment": "right", - "text": "${cpuSpeed}" + "type": "Column", + "items": [ + { + "type": "TextBlock", + "isSubtle": true, + "horizontalAlignment": "right", + "text": "%CPUUsage_Widget_Template/CPU_Speed%" + }, + { + "type": "TextBlock", + "size": "large", + "horizontalAlignment": "right", + "text": "${cpuSpeed}" + } + ] } ] + }, + { + "type": "TextBlock", + "isSubtle": true, + "text": "%CPUUsage_Widget_Template/Processes%", + "wrap": true + }, + { + "type": "TextBlock", + "size": "medium", + "text": "${cpuProc1}" + }, + { + "type": "TextBlock", + "size": "medium", + "text": "${cpuProc2}" + }, + { + "type": "TextBlock", + "size": "medium", + "text": "${cpuProc3}" } ] - }, - { - "type": "TextBlock", - "isSubtle": true, - "text": "%CPUUsage_Widget_Template/Processes%", - "wrap": true - }, - { - "type": "TextBlock", - "size": "medium", - "text": "${cpuProc1}" - }, - { - "type": "TextBlock", - "size": "medium", - "text": "${cpuProc2}" - }, - { - "type": "TextBlock", - "size": "medium", - "text": "${cpuProc3}" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", diff --git a/CoreWidgetProvider/Widgets/Templates/SystemGPUUsageTemplate.json b/CoreWidgetProvider/Widgets/Templates/SystemGPUUsageTemplate.json index c561a13b39..4a5a11ac6f 100644 --- a/CoreWidgetProvider/Widgets/Templates/SystemGPUUsageTemplate.json +++ b/CoreWidgetProvider/Widgets/Templates/SystemGPUUsageTemplate.json @@ -2,70 +2,89 @@ "type": "AdaptiveCard", "body": [ { - "type": "Image", - "url": "${gpuGraphUrl}", - "height": "${chartHeight}", - "width": "${chartWidth}", - "horizontalAlignment": "center" + "type": "Container", + "$when": "${errorMessage != null}", + "items": [ + { + "type": "TextBlock", + "text": "${errorMessage}", + "wrap": true, + "size": "small" + } + ], + "style": "warning" }, { - "type": "ColumnSet", - "columns": [ + "type": "Container", + "$when": "${errorMessage == null}", + "items": [ { - "type": "Column", - "items": [ - { - "text": "%GPUUsage_Widget_Template/GPU_Usage%", - "type": "TextBlock", - "size": "small", - "isSubtle": true - }, - { - "text": "${gpuUsage}", - "type": "TextBlock", - "size": "large", - "weight": "bolder" - } - ] + "type": "Image", + "url": "${gpuGraphUrl}", + "height": "${chartHeight}", + "width": "${chartWidth}", + "horizontalAlignment": "center" }, { - "type": "Column", - "items": [ + "type": "ColumnSet", + "columns": [ { - "text": "%GPUUsage_Widget_Template/GPU_Temperature%", - "type": "TextBlock", - "size": "small", - "isSubtle": true, - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%GPUUsage_Widget_Template/GPU_Usage%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${gpuUsage}", + "type": "TextBlock", + "size": "large", + "weight": "bolder" + } + ] }, { - "text": "${gpuTemp}", - "type": "TextBlock", - "size": "large", - "weight": "bolder", - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%GPUUsage_Widget_Template/GPU_Temperature%", + "type": "TextBlock", + "size": "small", + "isSubtle": true, + "horizontalAlignment": "right" + }, + { + "text": "${gpuTemp}", + "type": "TextBlock", + "size": "large", + "weight": "bolder", + "horizontalAlignment": "right" + } + ] } ] + }, + { + "text": "%GPUUsage_Widget_Template/GPU_Name%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${gpuName}", + "type": "TextBlock", + "size": "medium" + } + ], + "actions": [ + { + "type": "Action.Execute", + "title": "%GPUUsage_Widget_Template/Next_GPU%", + "verb": "NextItem" } ] - }, - { - "text": "%GPUUsage_Widget_Template/GPU_Name%", - "type": "TextBlock", - "size": "small", - "isSubtle": true - }, - { - "text": "${gpuName}", - "type": "TextBlock", - "size": "medium" - } - ], - "actions": [ - { - "type": "Action.Execute", - "title": "%GPUUsage_Widget_Template/Next_GPU%", - "verb": "NextItem" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", diff --git a/CoreWidgetProvider/Widgets/Templates/SystemMemoryTemplate.json b/CoreWidgetProvider/Widgets/Templates/SystemMemoryTemplate.json index 09c9057f63..8ae5b289b9 100644 --- a/CoreWidgetProvider/Widgets/Templates/SystemMemoryTemplate.json +++ b/CoreWidgetProvider/Widgets/Templates/SystemMemoryTemplate.json @@ -2,126 +2,145 @@ "type": "AdaptiveCard", "body": [ { - "type": "Image", - "url": "${memGraphUrl}", - "height": "${chartHeight}", - "width": "${chartWidth}", - "horizontalAlignment": "center" - }, - { - "type": "ColumnSet", - "columns": [ + "type": "Container", + "$when": "${errorMessage != null}", + "items": [ { - "type": "Column", - "items": [ - { - "text": "%Memory_Widget_Template/UsedMemory%", - "type": "TextBlock", - "size": "small", - "isSubtle": true - }, - { - "text": "${usedMem}", - "type": "TextBlock", - "size": "large", - "weight": "bolder" - } - ] - }, - { - "type": "Column", - "items": [ - { - "text": "%Memory_Widget_Template/AllMemory%", - "type": "TextBlock", - "size": "small", - "isSubtle": true, - "horizontalAlignment": "right" - }, - { - "text": "${allMem}", - "type": "TextBlock", - "size": "large", - "weight": "bolder", - "horizontalAlignment": "right" - } - ] + "type": "TextBlock", + "text": "${errorMessage}", + "wrap": true, + "size": "small" } - ] + ], + "style": "warning" }, { - "type": "ColumnSet", - "columns": [ + "type": "Container", + "$when": "${errorMessage == null}", + "items": [ { - "type": "Column", - "items": [ - { - "text": "%Memory_Widget_Template/Committed%", - "type": "TextBlock", - "size": "small", - "isSubtle": true - }, - { - "text": "${committedMem}/${committedLimitMem}", - "type": "TextBlock", - "size": "medium" - } - ] + "type": "Image", + "url": "${memGraphUrl}", + "height": "${chartHeight}", + "width": "${chartWidth}", + "horizontalAlignment": "center" }, { - "type": "Column", - "items": [ + "type": "ColumnSet", + "columns": [ { - "text": "%Memory_Widget_Template/Cached%", - "type": "TextBlock", - "size": "small", - "isSubtle": true, - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%Memory_Widget_Template/UsedMemory%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${usedMem}", + "type": "TextBlock", + "size": "large", + "weight": "bolder" + } + ] }, { - "text": "${cachedMem}", - "type": "TextBlock", - "size": "medium", - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%Memory_Widget_Template/AllMemory%", + "type": "TextBlock", + "size": "small", + "isSubtle": true, + "horizontalAlignment": "right" + }, + { + "text": "${allMem}", + "type": "TextBlock", + "size": "large", + "weight": "bolder", + "horizontalAlignment": "right" + } + ] } ] - } - ] - }, - { - "type": "ColumnSet", - "columns": [ + }, { - "type": "Column", - "items": [ + "type": "ColumnSet", + "columns": [ { - "text": "%Memory_Widget_Template/PagedPool%", - "type": "TextBlock", - "size": "small", - "isSubtle": true + "type": "Column", + "items": [ + { + "text": "%Memory_Widget_Template/Committed%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${committedMem}/${committedLimitMem}", + "type": "TextBlock", + "size": "medium" + } + ] }, { - "text": "${pagedPoolMem}", - "type": "TextBlock", - "size": "medium" + "type": "Column", + "items": [ + { + "text": "%Memory_Widget_Template/Cached%", + "type": "TextBlock", + "size": "small", + "isSubtle": true, + "horizontalAlignment": "right" + }, + { + "text": "${cachedMem}", + "type": "TextBlock", + "size": "medium", + "horizontalAlignment": "right" + } + ] } ] }, { - "type": "Column", - "items": [ + "type": "ColumnSet", + "columns": [ { - "text": "%Memory_Widget_Template/NonPagedPool%", - "type": "TextBlock", - "size": "small", - "isSubtle": true, - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%Memory_Widget_Template/PagedPool%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${pagedPoolMem}", + "type": "TextBlock", + "size": "medium" + } + ] }, { - "text": "${nonPagedPoolMem}", - "type": "TextBlock", - "size": "medium", - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%Memory_Widget_Template/NonPagedPool%", + "type": "TextBlock", + "size": "small", + "isSubtle": true, + "horizontalAlignment": "right" + }, + { + "text": "${nonPagedPoolMem}", + "type": "TextBlock", + "size": "medium", + "horizontalAlignment": "right" + } + ] } ] } diff --git a/CoreWidgetProvider/Widgets/Templates/SystemNetworkUsageTemplate.json b/CoreWidgetProvider/Widgets/Templates/SystemNetworkUsageTemplate.json index 7f11b7d706..26c2c97661 100644 --- a/CoreWidgetProvider/Widgets/Templates/SystemNetworkUsageTemplate.json +++ b/CoreWidgetProvider/Widgets/Templates/SystemNetworkUsageTemplate.json @@ -2,72 +2,91 @@ "type": "AdaptiveCard", "body": [ { - "type": "Image", - "url": "${netGraphUrl}", - "height": "${chartHeight}", - "width": "${chartWidth}", - "horizontalAlignment": "center" + "type": "Container", + "$when": "${errorMessage != null}", + "items": [ + { + "type": "TextBlock", + "text": "${errorMessage}", + "wrap": true, + "size": "small" + } + ], + "style": "warning" }, { - "type": "ColumnSet", - "columns": [ + "type": "Container", + "$when": "${errorMessage == null}", + "items": [ { - "type": "Column", - "items": [ - { - "text": "%NetworkUsage_Widget_Template/Sent%", - "type": "TextBlock", - "spacing": "none", - "size": "small", - "isSubtle": true - }, - { - "text": "${netSent}", - "type": "TextBlock", - "size": "large", - "weight": "bolder" - } - ] + "type": "Image", + "url": "${netGraphUrl}", + "height": "${chartHeight}", + "width": "${chartWidth}", + "horizontalAlignment": "center" }, { - "type": "Column", - "items": [ + "type": "ColumnSet", + "columns": [ { - "text": "%NetworkUsage_Widget_Template/Received%", - "type": "TextBlock", - "spacing": "none", - "size": "small", - "isSubtle": true, - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%NetworkUsage_Widget_Template/Sent%", + "type": "TextBlock", + "spacing": "none", + "size": "small", + "isSubtle": true + }, + { + "text": "${netSent}", + "type": "TextBlock", + "size": "large", + "weight": "bolder" + } + ] }, { - "text": "${netReceived}", - "type": "TextBlock", - "size": "large", - "weight": "bolder", - "horizontalAlignment": "right" + "type": "Column", + "items": [ + { + "text": "%NetworkUsage_Widget_Template/Received%", + "type": "TextBlock", + "spacing": "none", + "size": "small", + "isSubtle": true, + "horizontalAlignment": "right" + }, + { + "text": "${netReceived}", + "type": "TextBlock", + "size": "large", + "weight": "bolder", + "horizontalAlignment": "right" + } + ] } ] + }, + { + "text": "%NetworkUsage_Widget_Template/Network_Name%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${networkName}", + "type": "TextBlock", + "size": "medium" + } + ], + "actions": [ + { + "type": "Action.Execute", + "title": "%NetworkUsage_Widget_Template/Next_Network%", + "verb": "NextItem" } ] - }, - { - "text": "%NetworkUsage_Widget_Template/Network_Name%", - "type": "TextBlock", - "size": "small", - "isSubtle": true - }, - { - "text": "${networkName}", - "type": "TextBlock", - "size": "medium" - } - ], - "actions": [ - { - "type": "Action.Execute", - "title": "%NetworkUsage_Widget_Template/Next_Network%", - "verb": "NextItem" } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", diff --git a/CoreWidgetProvider/Widgets/WidgetImplFactory.cs b/CoreWidgetProvider/Widgets/WidgetImplFactory.cs index 69d835e8b0..74fc67f7ed 100644 --- a/CoreWidgetProvider/Widgets/WidgetImplFactory.cs +++ b/CoreWidgetProvider/Widgets/WidgetImplFactory.cs @@ -5,7 +5,7 @@ using Microsoft.Windows.Widgets.Providers; namespace CoreWidgetProvider.Widgets; -internal class WidgetImplFactory : IWidgetImplFactory +internal sealed class WidgetImplFactory : IWidgetImplFactory where T : WidgetImpl, new() { public WidgetImpl Create(WidgetContext widgetContext, string state) diff --git a/CoreWidgetProvider/Widgets/WidgetProvider.cs b/CoreWidgetProvider/Widgets/WidgetProvider.cs index 0e0e604ec2..58247ad83a 100644 --- a/CoreWidgetProvider/Widgets/WidgetProvider.cs +++ b/CoreWidgetProvider/Widgets/WidgetProvider.cs @@ -10,7 +10,7 @@ namespace CoreWidgetProvider.Widgets; [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] [Guid("F8B2DBB9-3687-4C6E-99B2-B92C82905937")] -internal class WidgetProvider : IWidgetProvider, IWidgetProvider2 +internal sealed class WidgetProvider : IWidgetProvider, IWidgetProvider2 { private readonly Dictionary widgetDefinitionRegistry = new (); private readonly Dictionary runningWidgets = new (); @@ -37,7 +37,7 @@ private void InitializeWidget(WidgetContext widgetContext, string state) var widgetDefinitionId = widgetContext.DefinitionId; Log.Logger()?.ReportDebug($"Calling Initialize for Widget Id: {widgetId} - {widgetDefinitionId}"); - if (!widgetDefinitionRegistry.ContainsKey(widgetDefinitionId)) + if (!widgetDefinitionRegistry.TryGetValue(widgetDefinitionId, out var value)) { Log.Logger()?.ReportError($"Unknown widget DefinitionId: {widgetDefinitionId}"); return; @@ -49,7 +49,7 @@ private void InitializeWidget(WidgetContext widgetContext, string state) return; } - var factory = widgetDefinitionRegistry[widgetDefinitionId]; + var factory = value; var widgetImpl = factory.Create(widgetContext, state); runningWidgets.Add(widgetId, widgetImpl); } @@ -94,18 +94,18 @@ public void Activate(WidgetContext widgetContext) { Log.Logger()?.ReportDebug($"Activate id: {widgetContext.Id} definitionId: {widgetContext.DefinitionId}"); var widgetId = widgetContext.Id; - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var runningWidget)) { - runningWidgets[widgetId].Activate(widgetContext); + runningWidget.Activate(widgetContext); } else { // Called to activate a widget that we don't know about, which is unexpected. Try to recover by creating it. Log.Logger()?.ReportWarn($"Found WidgetId that was not known: {widgetContext.Id}, attempting to recover by creating it."); CreateWidget(widgetContext); - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var recoveredWidget)) { - runningWidgets[widgetId].Activate(widgetContext); + recoveredWidget.Activate(widgetContext); } } } @@ -113,18 +113,18 @@ public void Activate(WidgetContext widgetContext) public void Deactivate(string widgetId) { Log.Logger()?.ReportDebug($"Deactivate id: {widgetId}"); - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var value)) { - runningWidgets[widgetId].Deactivate(widgetId); + value.Deactivate(widgetId); } } public void DeleteWidget(string widgetId, string customState) { Log.Logger()?.ReportInfo($"DeleteWidget id: {widgetId}"); - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var value)) { - runningWidgets[widgetId].DeleteWidget(widgetId, customState); + value.DeleteWidget(widgetId, customState); runningWidgets.Remove(widgetId); } } @@ -134,9 +134,9 @@ public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs) Log.Logger()?.ReportDebug($"OnActionInvoked id: {actionInvokedArgs.WidgetContext.Id} definitionId: {actionInvokedArgs.WidgetContext.DefinitionId}"); var widgetContext = actionInvokedArgs.WidgetContext; var widgetId = widgetContext.Id; - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var value)) { - runningWidgets[widgetId].OnActionInvoked(actionInvokedArgs); + value.OnActionInvoked(actionInvokedArgs); } } @@ -145,9 +145,9 @@ public void OnCustomizationRequested(WidgetCustomizationRequestedArgs customizat Log.Logger()?.ReportDebug($"OnCustomizationRequested id: {customizationRequestedArgs.WidgetContext.Id} definitionId: {customizationRequestedArgs.WidgetContext.DefinitionId}"); var widgetContext = customizationRequestedArgs.WidgetContext; var widgetId = widgetContext.Id; - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var value)) { - runningWidgets[widgetId].OnCustomizationRequested(customizationRequestedArgs); + value.OnCustomizationRequested(customizationRequestedArgs); } } @@ -156,9 +156,9 @@ public void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs) Log.Logger()?.ReportDebug($"OnWidgetContextChanged id: {contextChangedArgs.WidgetContext.Id} definitionId: {contextChangedArgs.WidgetContext.DefinitionId}"); var widgetContext = contextChangedArgs.WidgetContext; var widgetId = widgetContext.Id; - if (runningWidgets.ContainsKey(widgetId)) + if (runningWidgets.TryGetValue(widgetId, out var value)) { - runningWidgets[widgetId].OnWidgetContextChanged(contextChangedArgs); + value.OnWidgetContextChanged(contextChangedArgs); } } } diff --git a/CoreWidgetProvider/Widgets/WidgetServer.cs b/CoreWidgetProvider/Widgets/WidgetServer.cs index 37ced9e21e..262e6bf424 100644 --- a/CoreWidgetProvider/Widgets/WidgetServer.cs +++ b/CoreWidgetProvider/Widgets/WidgetServer.cs @@ -69,7 +69,7 @@ public void Dispose() } } - private class Ole32 + private sealed class Ole32 { #pragma warning disable SA1310 // Field names should not contain underscore // https://docs.microsoft.com/windows/win32/api/combaseapi/ne-combaseapi-regcls diff --git a/Directory.Build.props b/Directory.Build.props index ed2c0f224e..d3139c273b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -37,5 +37,12 @@ - + + + + true + + + + \ No newline at end of file diff --git a/SampleExtension/SampleExtension.csproj b/SampleExtension/SampleExtension.csproj index d0cc874abc..2ef4c992b9 100644 --- a/SampleExtension/SampleExtension.csproj +++ b/SampleExtension/SampleExtension.csproj @@ -1,7 +1,7 @@  Exe - net6.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0 10.0.17763.0 SampleExtension x86;x64;arm64 @@ -25,9 +25,9 @@ - + - + diff --git a/Test.ps1 b/Test.ps1 index b149aa7f41..343557a471 100644 --- a/Test.ps1 +++ b/Test.ps1 @@ -103,12 +103,12 @@ Try { $vstestArgs = @( ("/Platform:$platform"), ("/Logger:trx;LogFileName=DevHome.Test-$platform-$configuration.trx"), - ("test\bin\$platform\$configuration\net6.0-windows10.0.22000.0\DevHome.Test.dll") + ("test\bin\$platform\$configuration\net8.0-windows10.0.22000.0\DevHome.Test.dll") ) $winAppTestArgs = @( ("/Platform:$platform"), ("/Logger:trx;LogFileName=DevHome.UITest-$platform-$configuration.trx"), - ("uitest\bin\$platform\$configuration\net6.0-windows10.0.22000.0\DevHome.UITest.dll") + ("uitest\bin\$platform\$configuration\net8.0-windows10.0.22000.0\DevHome.UITest.dll") ) & $vstestPath $vstestArgs @@ -122,13 +122,13 @@ Try { $vstestArgs = @( ("/Platform:$platform"), ("/Logger:trx;LogFileName=$tool.Test-$platform-$configuration.trx"), - ("tools\$tool\*UnitTest\bin\$platform\$configuration\net6.0-windows10.0.22000.0\*.UnitTest.dll") + ("tools\$tool\*UnitTest\bin\$platform\$configuration\net8.0-windows10.0.22000.0\*.UnitTest.dll") ) $winAppTestArgs = @( ("/Platform:$platform"), ("/Logger:trx;LogFileName=$tool.UITest-$platform-$configuration.trx"), - ("tools\$tool\*UITest\bin\$platform\$configuration\net6.0-windows10.0.22000.0\*.UITest.dll") + ("tools\$tool\*UITest\bin\$platform\$configuration\net8.0-windows10.0.22000.0\*.UITest.dll") ) & $vstestPath $vstestArgs diff --git a/ToolingVersions.props b/ToolingVersions.props index fecec6449a..955145dbd2 100644 --- a/ToolingVersions.props +++ b/ToolingVersions.props @@ -2,8 +2,8 @@ - net6.0-windows10.0.22000.0 - 10.0.19045.0 - 10.0.19045.0 + net8.0-windows10.0.22000.0 + 10.0.19041.0 + 10.0.19041.0 - \ No newline at end of file + diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 5fce2221bd..fe5883fbec 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -21,7 +21,7 @@ parameters: variables: # MSIXVersion's second part should always be odd to account for stub app's version - MSIXVersion: '0.901' + MSIXVersion: '0.1001' VersionOfSDK: '0.100' solution: '**/DevHome.sln' appxPackageDir: 'AppxPackages' @@ -65,7 +65,7 @@ extends: steps: - task: NuGetToolInstaller@1 - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 inputs: nuGetServiceConnections: 'DevHomeInternal' @@ -132,7 +132,7 @@ extends: steps: - task: NuGetToolInstaller@1 - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 inputs: nuGetServiceConnections: 'DevHomeInternal' @@ -251,22 +251,23 @@ extends: MaxConcurrency: '50' MaxRetryAttempts: '5' - - task: AzureKeyVault@1 - inputs: - azureSubscription: 'DevHomeAzureServiceConnection' - KeyVaultName: 'DevHomeKeyVault' - SecretsFilter: 'ApiScanConnectionString' - RunAsPreJob: false - - - task: APIScan@2 - inputs: - softwareFolder: '$(Build.StagingDirectory)' - softwareName: 'Dev Home' - softwareVersionNum: '1.0' - softwareBuildNum: '$(Build.BuildId)' - symbolsFolder: 'SRV*http://symweb' - env: - AzureServicesAuthConnectionString: $(ApiScanConnectionString) + # Commented out until our implementation is fixed + # - task: AzureKeyVault@1 + # inputs: + # azureSubscription: 'DevHomeAzureServiceConnection' + # KeyVaultName: 'DevHomeKeyVault' + # SecretsFilter: 'ApiScanConnectionString' + # RunAsPreJob: false + + # - task: APIScan@2 + # inputs: + # softwareFolder: '$(Build.StagingDirectory)' + # softwareName: 'Dev Home' + # softwareVersionNum: '1.0' + # softwareBuildNum: '$(Build.BuildId)' + # symbolsFolder: 'SRV*http://symweb' + # env: + # AzureServicesAuthConnectionString: $(ApiScanConnectionString) - task: Windows Application Driver@0 condition: and(always(), ne('${{ platform}}', 'arm64')) diff --git a/build/scripts/CreateBuildInfo.ps1 b/build/scripts/CreateBuildInfo.ps1 index cec394b20a..cd12994b4b 100644 --- a/build/scripts/CreateBuildInfo.ps1 +++ b/build/scripts/CreateBuildInfo.ps1 @@ -6,7 +6,7 @@ Param( ) $Major = "0" -$Minor = "9" +$Minor = "10" $Patch = "99" # default to 99 for local builds $versionSplit = $Version.Split("."); diff --git a/codeAnalysis/GlobalSuppressions.cs b/codeAnalysis/GlobalSuppressions.cs index d269e13471..7faaf33ebd 100644 --- a/codeAnalysis/GlobalSuppressions.cs +++ b/codeAnalysis/GlobalSuppressions.cs @@ -34,6 +34,7 @@ [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Static methods may improve performance but decrease maintainability")] [assembly: SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Renaming everything would be a lot of work. It does not do any harm if an EventHandler delegate ends with the suffix EventHandler. Besides this, the Rule causes some false positives.")] [assembly: SuppressMessage("Performance", "CA1838:Avoid 'StringBuilder' parameters for P/Invokes", Justification = "We are not concerned about the performance impact of marshaling a StringBuilder")] +[assembly: SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "Constant arrays are required for DataRow", Scope = "member", Target = "~M:DevHome.Tests.UITest.WidgetTest.AddWidgetsTest(System.String[])")] // Threading suppressions [assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.Controls.Notification.OnClose")] diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index eba887f23d..4802d86c33 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -17,21 +17,23 @@ - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/common/Extensions/WindowExExtensions.cs b/common/Extensions/WindowExExtensions.cs index 3d9fe98698..82424f0862 100644 --- a/common/Extensions/WindowExExtensions.cs +++ b/common/Extensions/WindowExExtensions.cs @@ -153,7 +153,7 @@ public static void SetRequestedTheme(this WindowEx window, ElementTheme theme) // "It is the responsibility of the caller to free the string pointed to by ppszName // when it is no longer needed. Call CoTaskMemFree on *ppszName to free the memory." PWSTR pFileName; - ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &pFileName); + ppsi.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out pFileName); fileName = new string(pFileName); Marshal.FreeCoTaskMem((IntPtr)pFileName.Value); } diff --git a/common/Helpers/Deployment.cs b/common/Helpers/Deployment.cs index c05e74bdc9..316ba5fb49 100644 --- a/common/Helpers/Deployment.cs +++ b/common/Helpers/Deployment.cs @@ -23,9 +23,9 @@ public static Guid Identifier try { var localSettings = ApplicationData.Current.LocalSettings; - if (localSettings.Values.ContainsKey(DeploymentIdentifierKeyName)) + if (localSettings.Values.TryGetValue(DeploymentIdentifierKeyName, out var value)) { - return (Guid)localSettings.Values[DeploymentIdentifierKeyName]; + return (Guid)value; } var newGuid = Guid.NewGuid(); diff --git a/common/Helpers/Json.cs b/common/Helpers/Json.cs index 55a79355f1..8851647391 100644 --- a/common/Helpers/Json.cs +++ b/common/Helpers/Json.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. +using System.IO; +using System.Text; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; namespace DevHome.Common.Helpers; @@ -15,10 +17,8 @@ public static async Task ToObjectAsync(string value) return (T)(object)bool.Parse(value); } - return await Task.Run(() => - { - return JsonConvert.DeserializeObject(value)!; - }); + await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(value)); + return (await JsonSerializer.DeserializeAsync(stream))!; } public static async Task StringifyAsync(T value) @@ -28,9 +28,8 @@ public static async Task StringifyAsync(T value) return value!.ToString()!.ToLowerInvariant(); } - return await Task.Run(() => - { - return JsonConvert.SerializeObject(value); - }); + await using var stream = new MemoryStream(); + await JsonSerializer.SerializeAsync(stream, value); + return Encoding.UTF8.GetString(stream.ToArray()); } } diff --git a/common/Models/ExtensionAdaptiveCard.cs b/common/Models/ExtensionAdaptiveCard.cs index 19b6a34617..a5905cfe2c 100644 --- a/common/Models/ExtensionAdaptiveCard.cs +++ b/common/Models/ExtensionAdaptiveCard.cs @@ -7,8 +7,6 @@ using AdaptiveCards.Templating; using DevHome.Logging; using Microsoft.Windows.DevHome.SDK; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace DevHome.Common.Models; public class ExtensionAdaptiveCard : IExtensionAdaptiveCard @@ -31,7 +29,12 @@ public ExtensionAdaptiveCard() public ProviderOperationResult Update(string templateJson, string dataJson, string state) { var template = new AdaptiveCardTemplate(templateJson ?? TemplateJson); - var adaptiveCardString = template.Expand(JsonConvert.DeserializeObject(dataJson ?? DataJson)); + + // Need to use Newtonsoft.Json here because System.Text.Json is missing a set of wrapping brackets + // which causes AdaptiveCardTemplate.Expand to fail. System.Text.Json also does not support parsing + // an empty string. + var adaptiveCardString = template.Expand(Newtonsoft.Json.JsonConvert.DeserializeObject(dataJson ?? DataJson)); + var parseResult = AdaptiveCard.FromJsonString(adaptiveCardString); if (parseResult.AdaptiveCard is null) diff --git a/common/Services/AppInstallManagerService.cs b/common/Services/AppInstallManagerService.cs index 3474c3ba87..84a9f9b892 100644 --- a/common/Services/AppInstallManagerService.cs +++ b/common/Services/AppInstallManagerService.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Threading.Tasks; +using DevHome.Common.Helpers; using Windows.ApplicationModel.Store.Preview.InstallControl; using Windows.Foundation; @@ -16,6 +17,8 @@ public class AppInstallManagerService : IAppInstallManagerService { private readonly AppInstallManager _appInstallManager; + private static readonly TimeSpan StoreInstallTimeout = new (0, 0, 60); + public event TypedEventHandler ItemCompleted { add => _appInstallManager.ItemCompleted += value; @@ -68,4 +71,72 @@ private async Task SearchForUpdateAsync(string productId, AppUpdateOptions // Check if update is available return appInstallItem != null; } + + public async Task TryInstallPackageAsync(string packageId) + { + try + { + var installTask = InstallPackageAsync(packageId); + + // Wait for a maximum of StoreInstallTimeout (60 seconds). + var completedTask = await Task.WhenAny(installTask, Task.Delay(StoreInstallTimeout)); + + if (completedTask.Exception != null) + { + throw completedTask.Exception; + } + + if (completedTask != installTask) + { + throw new TimeoutException("Store Install task did not finish in time."); + } + + return true; + } + catch (Exception ex) + { + Log.Logger()?.ReportError("Package installation Failed", ex); + } + + return false; + } + + private async Task InstallPackageAsync(string packageId) + { + await Task.Run(() => + { + var tcs = new TaskCompletionSource(); + AppInstallItem installItem; + try + { + Log.Logger()?.ReportInfo("WidgetHostingService", $"Starting {packageId} install"); + installItem = _appInstallManager.StartAppInstallAsync(packageId, null, true, false).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Log.Logger()?.ReportInfo("WidgetHostingService", $"{packageId} install failure"); + tcs.SetException(ex); + return tcs.Task; + } + + installItem.Completed += (sender, args) => + { + tcs.SetResult(true); + }; + + installItem.StatusChanged += (sender, args) => + { + if (installItem.GetCurrentStatus().InstallState == AppInstallState.Canceled + || installItem.GetCurrentStatus().InstallState == AppInstallState.Error) + { + tcs.TrySetException(new System.Management.Automation.JobFailedException(installItem.GetCurrentStatus().ErrorCode.ToString())); + } + else if (installItem.GetCurrentStatus().InstallState == AppInstallState.Completed) + { + tcs.SetResult(true); + } + }; + return tcs.Task; + }); + } } diff --git a/common/Services/FileService.cs b/common/Services/FileService.cs index 7b0e4b3bcd..c00851787e 100644 --- a/common/Services/FileService.cs +++ b/common/Services/FileService.cs @@ -3,8 +3,8 @@ using System.IO; using System.Text; +using System.Text.Json; using DevHome.Common.Contracts; -using Newtonsoft.Json; namespace DevHome.Common.Services; @@ -16,8 +16,8 @@ public T Read(string folderPath, string fileName) var path = Path.Combine(folderPath, fileName); if (File.Exists(path)) { - var json = File.ReadAllText(path); - return JsonConvert.DeserializeObject(json); + using var fileStream = File.OpenText(path); + return JsonSerializer.Deserialize(fileStream.BaseStream); } return default; @@ -31,7 +31,7 @@ public void Save(string folderPath, string fileName, T content) Directory.CreateDirectory(folderPath); } - var fileContent = JsonConvert.SerializeObject(content); + var fileContent = JsonSerializer.Serialize(content); File.WriteAllText(Path.Combine(folderPath, fileName), fileContent, Encoding.UTF8); } diff --git a/common/Services/IAppInstallManagerService.cs b/common/Services/IAppInstallManagerService.cs index a5a3ef1452..645869d715 100644 --- a/common/Services/IAppInstallManagerService.cs +++ b/common/Services/IAppInstallManagerService.cs @@ -31,4 +31,11 @@ public interface IAppInstallManagerService /// True if an app update was triggered, false otherwise /// Throws exception if operation failed (e.g. product id was not found) public Task StartAppUpdateAsync(string productId); + + /// + /// Install a package from the store. + /// + /// Target package id + /// True if package was installed, false otherwise + public Task TryInstallPackageAsync(string packageId); } diff --git a/common/Services/LocalSettingsService.cs b/common/Services/LocalSettingsService.cs index 9bcf31cdad..477b835c8a 100644 --- a/common/Services/LocalSettingsService.cs +++ b/common/Services/LocalSettingsService.cs @@ -26,7 +26,7 @@ public class LocalSettingsService : ILocalSettingsService private readonly string _applicationDataFolder; private readonly string _localSettingsFile; - private IDictionary _settings; + private Dictionary _settings; private bool _isInitialized; @@ -45,7 +45,7 @@ private async Task InitializeAsync() { if (!_isInitialized) { - _settings = await Task.Run(() => _fileService.Read>(_applicationDataFolder, _localSettingsFile)) ?? new Dictionary(); + _settings = await Task.Run(() => _fileService.Read>(_applicationDataFolder, _localSettingsFile)) ?? new Dictionary(); _isInitialized = true; } diff --git a/common/TelemetryEvents/DeveloperId/DeveloperIdHelper.cs b/common/TelemetryEvents/DeveloperId/DeveloperIdHelper.cs index ad93ee163d..56978221a6 100644 --- a/common/TelemetryEvents/DeveloperId/DeveloperIdHelper.cs +++ b/common/TelemetryEvents/DeveloperId/DeveloperIdHelper.cs @@ -14,9 +14,8 @@ public static string GetHashedDeveloperId(string providerName, IDeveloperId devI { // TODO: Instead of LoginId, hash a globally unique id of DeveloperId (like url) // https://github.com/microsoft/devhome/issues/611 - using var hasher = SHA256.Create(); var loginIdBytes = Encoding.ASCII.GetBytes(devId.LoginId); - var hashedLoginId = hasher.ComputeHash(loginIdBytes); + var hashedLoginId = SHA256.HashData(loginIdBytes); if (BitConverter.IsLittleEndian) { Array.Reverse(hashedLoginId); @@ -32,7 +31,7 @@ public static string GetHashedDeveloperId(string providerName, IDeveloperId devI { loginIdBytes = Encoding.ASCII.GetBytes(loginSessionId); - hashedLoginId = hasher.ComputeHash(loginIdBytes); + hashedLoginId = SHA256.HashData(loginIdBytes); if (BitConverter.IsLittleEndian) { Array.Reverse(hashedLoginId); diff --git a/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs b/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs index 8cdde00d93..db071cebad 100644 --- a/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs +++ b/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs @@ -29,13 +29,13 @@ public enum EntryPoint } private readonly string[] entryPointNames = - { + [ string.Empty, "Settings", "SetupFlow", "WhatsNewPage", "Widget", - }; + ]; public EntryPointEvent(EntryPoint entryPoint) { diff --git a/common/Views/ExtensionAdaptiveCardPanel.cs b/common/Views/ExtensionAdaptiveCardPanel.cs index 7dcf588479..d0b531839d 100644 --- a/common/Views/ExtensionAdaptiveCardPanel.cs +++ b/common/Views/ExtensionAdaptiveCardPanel.cs @@ -10,7 +10,6 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Windows.DevHome.SDK; -using Newtonsoft.Json; namespace DevHome.Common.Views; diff --git a/docs/specs/#2072 - Add some PowerToys utilities/spec.md b/docs/specs/#2072 - Add some PowerToys utilities/spec.md new file mode 100644 index 0000000000..5b240a86c1 --- /dev/null +++ b/docs/specs/#2072 - Add some PowerToys utilities/spec.md @@ -0,0 +1,165 @@ +--- +author: Kayla Cinnamon @cinnamon-msft +created on: 2023-12-22 +last updated: 2024-01-04 +issue id: #2072 +--- + +# Add some PowerToys utilities + +## 1. Overview + +### 1.1 Establish the Problem + +Dev Home is intended to be a centralized location for developers to help them stay productive on Windows. One feature we had been thinking of is adding helpful utilities into Dev Home for quick and easy access. + +### 1.2 Introduce the Solution + +[PowerToys](https://github.com/microsoft/powertoys) has created a few utilities with the original intent of migrating them into Dev Home. These utilities include Registry Preview, Hosts File Editor, and Environment Variables. We can add these utilities into Dev Home as the "stable" versions of these tools, while maintaining their "preview" versions in PowerToys. By adding these utilities to Dev Home, we can provide developers with more value out of the box in Windows while also provide more awareness to PowerToys. + +### 1.3 Rough-in Designs for initial release + +![V1 of PowerToys in Dev Home](./v1-pt-in-dev-home.png) + +## 2. Goals & User Cans + +### 2.1 Goals + +1. Add additional functionality to Dev Home +2. Provide helpful inbox utilities to the user +3. Bring awareness to PowerToys +4. Provide stable versions of PowerToys utilities +5. Provide a surface within Dev Home for additional utilities outside of PowerToys + +### 2.2 Non-Goals + +1. This feature is not intended to replace PowerToys +2. This feature is not intended to deprecate existing PowerToys + +### 2.3 User Cans Summary Table + +| No. | User Can | Pri | +| --- | -------- | --- | +| 1 | User can launch Registry Preview in a separate window. | 0 | +| 2 | User can launch Hosts File Editor in a separate window. | 0 | +| 3 | User can launch Environment Variables in a separate window. | 0 | +| 4 | User can modify Registry Preview's settings from Dev Home. | 0 | +| 5 | User can modify Hosts File Editor's settings from Dev Home. | 0 | +| 6 | User can modify Environment Variable's settings from Dev Home. | 0 | +| 7 | User can launch Registry Preview as an embedded page in Dev Home | 1 | +| 8 | User can launch Hosts File Editor as an embedded page in Dev Home | 2 | +| 9 | User can launch Environment Variables as an embedded page in Dev Home | 2 | + +## 3. User Stories + +### 3.1 User story - Launching Registry Preview in a separate window + +#### Job-to-be-done + +Pam wants to launch Registry Preview from Dev Home in order to look at her registry files. + +#### User experience + +1. Pam opens Dev Home +2. Pam navigates to the Utilities page +3. Pam clicks Launch on the Registry Preview card +4. Registry Preview is launched in a separate window on Pam's desktop + +#### Golden paths (with images to guide) + +#### Edge cases + +1. Pam also has PowerToys installed. This shouldn't cause a conflict on the machine because the utilities should be registered separately. + +### 3.2 User story - Launching Hosts File Editor or Environment Variables in a separate window + +#### Job-to-be-done + +Sean wants to launch Hosts File Editor from Dev Home in order to edit his Hosts file. This user experience is the same for Environment Variables. + +#### User experience + +1. Sean opens Dev Home +2. Sean navigates to the Utilities page +3. Sean clicks Launch on the Hosts File Editor card +4. A UAC prompt appears on Sean's screen +5. Sean clicks Yes +6. Hosts File Editor is launched as Administrator in a separate window on Sean's desktop + +#### Golden paths (with images to guide) + +#### Edge cases + +1. Sean doesn't have admin rights. This would prevent Hosts File Editor and/or Environment Variables from launching. +2. Sean also has PowerToys installed. This shouldn't cause a conflict on the machine because the utilities should be registered separately. + +### 3.3 User story - Using Registry Preview from within Dev Home + +#### Job-to-be-done + +Tina wants to launch use Registry Preview from inside Dev Home in order to look at her registry files. + +#### User experience + +1. Tina opens Dev Home +2. Tina expands the navigation item for Utilities +3. Tina navigates to the Registry Preview page +4. Tina sees the Registry Preview tool embedded within a page in Dev Home + +#### Golden paths (with images to guide) + +#### Edge cases + +1. Tina also has PowerToys installed. This shouldn't cause a conflict on the machine because the utilities should be registered separately. + +## 4. Requirements + +### 4.1 Functional Requirements + +#### Summary + +This feature will deliver stable versions of Registry Preview, Hosts File Editor, and Environment Variables from PowerToys with Dev Home. In V1, the user will be able to launch these utilities in separate windows from the new Utilities page. These utilities will all have settings within Dev Home as well. V2 will provide an embedded experience for Registry Preview inside Dev Home, so users can use this utility without leaving Dev Home. V3 will have pages for all three of these utilities if possible - Hosts File Editor and Environment Variables require Administrator, which may pose a technical challenge for having them as embedded experiences. + +#### Detailed Experience Walkthrough + +##### V1 of each utility being launchable as separate windows from the Utilities page + +![V1 of PowerToys in Dev Home](./v1-pt-in-dev-home.png) + +##### V1 settings page + +![V1 of settings page](./v1-settings-page.png) + +##### V1 utilities settings page + +![V1 of utilities settings page](./v1-utilities-settings-page.png) + +##### V1 Registry Preview settings page + +![V1 of Registry Preview settings page](./v1-registry-preview-settings.png) + +##### V1 Hosts Files Editor settings page + +![V1 of Hosts File Editor settings page](./v1-hosts-file-editor-settings.png) + +##### V1 Environment Variables settings page + +![V1 of Environment Variables settings page](./v1-environment-variables-settings.png) + +##### V2 of Registry Preview being embedded as a page inside Dev Home + +![V2 of PowerToys in Dev Home](./v2-pt-in-dev-home.png) + +#### Detailed Functional Requirements + +[comment]: # Priority definitions: P0 = must have for WIP (minimum initial experiment), P1 = must have for GA, P2 = nice to have for GA, P3 = GA+ + +| No. | Requirement | Pri | +| --- | ----------- | --- | +| 1 | Registry Preview (stable) is delivered with Dev Home | 0 | +| 2 | Environment Variables (stable) is delivered with Dev Home | 0 | +| 3 | Hosts File Editor (stable) is delivered with Dev Home | 0 | +| 4 | A Utilities page is created with links to launch Registry Preview, Environment Variables, and Hosts File Editor | 0 | +| 5 | Registry Preview has an embedded page in Dev Home | 1 | +| 6 | Registry Preview has an embedded page in Dev Home | 2 | +| 7 | Registry Preview has an embedded page in Dev Home | 2 | diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v1-environment-variables-settings.png b/docs/specs/#2072 - Add some PowerToys utilities/v1-environment-variables-settings.png new file mode 100644 index 0000000000..4b39ece3a1 Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v1-environment-variables-settings.png differ diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v1-hosts-file-editor-settings.png b/docs/specs/#2072 - Add some PowerToys utilities/v1-hosts-file-editor-settings.png new file mode 100644 index 0000000000..ad6eef1ae7 Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v1-hosts-file-editor-settings.png differ diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v1-pt-in-dev-home.png b/docs/specs/#2072 - Add some PowerToys utilities/v1-pt-in-dev-home.png new file mode 100644 index 0000000000..a03e38fc61 Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v1-pt-in-dev-home.png differ diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v1-registry-preview-settings.png b/docs/specs/#2072 - Add some PowerToys utilities/v1-registry-preview-settings.png new file mode 100644 index 0000000000..69cb35810a Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v1-registry-preview-settings.png differ diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v1-settings-page.png b/docs/specs/#2072 - Add some PowerToys utilities/v1-settings-page.png new file mode 100644 index 0000000000..89c0b0b380 Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v1-settings-page.png differ diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v1-utilities-settings-page.png b/docs/specs/#2072 - Add some PowerToys utilities/v1-utilities-settings-page.png new file mode 100644 index 0000000000..cb03baaa85 Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v1-utilities-settings-page.png differ diff --git a/docs/specs/#2072 - Add some PowerToys utilities/v2-pt-in-dev-home.png b/docs/specs/#2072 - Add some PowerToys utilities/v2-pt-in-dev-home.png new file mode 100644 index 0000000000..c2aa300ce8 Binary files /dev/null and b/docs/specs/#2072 - Add some PowerToys utilities/v2-pt-in-dev-home.png differ diff --git a/docs/specs/spec-template.md b/docs/specs/spec-template.md index 136cfb14f6..cf8f42744d 100644 --- a/docs/specs/spec-template.md +++ b/docs/specs/spec-template.md @@ -7,52 +7,76 @@ issue id: # Spec Title -## Abstract +## 1. Overview -[comment]: # Outline what this spec describes +### 1.1 Establish the Problem -## Inspiration +[comment]: # What is the problem? Consider an interesting hook; think of this like the start of an elevator pitch. -[comment]: # What were the drivers/inspiration behind the creation of this spec. +### 1.2 Introduce the Solution -## Solution Design +[comment]: # How does this idea help fix the problem above? What is the benefit/value of doing this? Which customer(s) is your feature focused on specifically? -[comment]: # Outline the design of the solution. Feel free to include ASCII-art diagrams, etc. +### 1.3 Rough-in Designs -## UI/UX Design +[comment]: # Show, don’t just tell. It’s fine for these to be “low fidelity” – but help us understand the flow. -[comment]: # What will this fix/feature look like? How will it affect the end user? +## 2. Goals & User Cans -## Capabilities +### 2.1 Goals -[comment]: # Discuss how the proposed fixes/features impact the following key considerations: +[comment]: # What are the main goals of this feature (usually between 2-7 goals)? This section can be used for scoping and should help the reader get a sense of why we are building the feature. There is no need to list obvious goals, such as meeting compliance goals. -### Accessibility +### 2.2 Non-Goals -[comment]: # How will the proposed change impact accessibility for users of screen readers, assistive input devices, etc. +[comment]: # Are there any explicit non-goals for this feature? This section can be helpful for scoping and to help readers get an understanding of what will not be covered by this feature. -### Security +### 2.3 User Cans Summary Table -[comment]: # How will the proposed change impact security? +[comment]: # What are the high-level User Cans needed to complete this feature? A User Can is written as something a user can do. For example: A user can turn the feature on in settings. The focus of this section is to ensure that the dev team has enough information to do high level swag costing. -### Reliability +## 3. User Stories -[comment]: # Will the proposed change improve reliability? If not, why make the change? +[comment]: # What are the user stories for their jobs-to-be-done? What is the user experience within this feature for the user to complete their job? What are the golden paths for getting the user to complete their job? What are the edge cases for this scenario? -### Compatibility +### 3.1 User story - XXX -[comment]: # Will the proposed change break existing code/behaviors? If so, how, and is the breaking change "worth it"? +#### Job-to-be-done -### Performance, Power, and Efficiency +#### User experience -## Potential Issues +#### Golden paths (with images to guide) -[comment]: # What are some of the things that might cause problems with the fixes/features proposed? Consider how the user might be negatively impacted. +#### Edge cases -## Future considerations +### 3.2 User story - XXX -[comment]: # What are some of the things that the fixes/features might unlock in the future? Does the implementation of this spec enable scenarios? +#### Job-to-be-done -## Resources +#### User experience + +#### Golden paths (with images to guide) + +#### Edge cases + +## 4. Requirements + +### 4.1 Functional Requirements + +#### Summary + +[comment]: # Write a paragraph that can be copy/pasted into an email explaining the use case & core behavior covered by this spec. Assume that your audience understands the customer and business value of your feature (which is outlined in the Overview section of this doc for reference). Focus on describing the functionality. Limit yourself to a max of 4-5 sentences. + +#### Detailed Experience Walkthrough + +[comment]: # Include images pulled from the Figma doc and place them directly into this document. + +#### Detailed Functional Requirements + +[comment]: # Priority definitions: P0 = must have for WIP (minimum initial experiment), P1 = must have for GA, P2 = nice to have for GA, P3 = GA+ + +| No. | Requirement | Pri | +| --- | ----------- | --- | +| 1 | | | +| 2 | | | -[comment]: # Be sure to add links to references, resources, footnotes, etc. \ No newline at end of file diff --git a/extensionsdk/Microsoft.Windows.DevHome.SDK.Lib/Microsoft.Windows.DevHome.SDK.Lib.csproj b/extensionsdk/Microsoft.Windows.DevHome.SDK.Lib/Microsoft.Windows.DevHome.SDK.Lib.csproj index 1af96b2760..831f452f9c 100644 --- a/extensionsdk/Microsoft.Windows.DevHome.SDK.Lib/Microsoft.Windows.DevHome.SDK.Lib.csproj +++ b/extensionsdk/Microsoft.Windows.DevHome.SDK.Lib/Microsoft.Windows.DevHome.SDK.Lib.csproj @@ -1,9 +1,10 @@  - net6.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0 10.0.17763.0 x86;x64;arm64;AnyCPU + win-x86;win-x64;win-arm64 None enable enable @@ -26,7 +27,7 @@ all runtime; build; native; contentfiles; analyzers - + diff --git a/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.vcxproj b/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.vcxproj index 2ff146750c..3c38951980 100644 --- a/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.vcxproj +++ b/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.vcxproj @@ -1,7 +1,7 @@ - - + + true @@ -239,8 +239,8 @@ - - + + @@ -248,9 +248,9 @@ - - - - + + + + \ No newline at end of file diff --git a/extensionsdk/Microsoft.Windows.DevHome.SDK/packages.config b/extensionsdk/Microsoft.Windows.DevHome.SDK/packages.config index f50e97da40..9da66f1b88 100644 --- a/extensionsdk/Microsoft.Windows.DevHome.SDK/packages.config +++ b/extensionsdk/Microsoft.Windows.DevHome.SDK/packages.config @@ -1,6 +1,6 @@  - - + + \ No newline at end of file diff --git a/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec b/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec index 7ff3c8b0c7..44153a8ea2 100644 --- a/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec +++ b/extensionsdk/nuget/Microsoft.Windows.DevHome.SDK.nuspec @@ -14,7 +14,7 @@ https://github.com/microsoft/devhome - + @@ -24,12 +24,12 @@ - - + + - - - + + + diff --git a/logging/helpers/DictionaryExtensions.cs b/logging/helpers/DictionaryExtensions.cs index ef55d694cc..65a708358c 100644 --- a/logging/helpers/DictionaryExtensions.cs +++ b/logging/helpers/DictionaryExtensions.cs @@ -7,10 +7,7 @@ public static class DictionaryExtensions { public static void DisposeAll(this IDictionary dictionary) { - if (dictionary is null) - { - throw new ArgumentNullException(nameof(dictionary)); - } + ArgumentNullException.ThrowIfNull(dictionary); foreach (var kv in dictionary) { diff --git a/settings/DevHome.Settings.UITest/DevHome.Dashboard.UITest.csproj b/settings/DevHome.Settings.UITest/DevHome.Dashboard.UITest.csproj index 60436cd53d..bfc44bfbea 100644 --- a/settings/DevHome.Settings.UITest/DevHome.Dashboard.UITest.csproj +++ b/settings/DevHome.Settings.UITest/DevHome.Dashboard.UITest.csproj @@ -1,8 +1,9 @@ - net6.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0 Dashboard.UITest x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable true diff --git a/settings/DevHome.Settings.UnitTest/DevHome.SetupFlow.UnitTest.csproj b/settings/DevHome.Settings.UnitTest/DevHome.SetupFlow.UnitTest.csproj index 34d052e80a..a71518263b 100644 --- a/settings/DevHome.Settings.UnitTest/DevHome.SetupFlow.UnitTest.csproj +++ b/settings/DevHome.Settings.UnitTest/DevHome.SetupFlow.UnitTest.csproj @@ -1,8 +1,9 @@  - net6.0-windows10.0.19041.0 + net8.0-windows10.0.19041.0 Dashboard.Test x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable enable @@ -15,7 +16,7 @@ - + diff --git a/settings/DevHome.Settings/DevHome.Settings.csproj b/settings/DevHome.Settings/DevHome.Settings.csproj index 798ad88b02..6d36b2ff92 100644 --- a/settings/DevHome.Settings/DevHome.Settings.csproj +++ b/settings/DevHome.Settings/DevHome.Settings.csproj @@ -1,5 +1,5 @@  - + DevHome.Settings x86;x64;arm64 @@ -27,13 +27,12 @@ - - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs b/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs index 90bc5af8d4..c763355a78 100644 --- a/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs +++ b/settings/DevHome.Settings/ViewModels/ExperimentalFeaturesViewModel.cs @@ -18,7 +18,7 @@ namespace DevHome.Settings.ViewModels; public class ExperimentalFeaturesViewModel : ObservableObject { - private ILocalSettingsService _localSettingsService; + private readonly ILocalSettingsService _localSettingsService; public ObservableCollection Features { get; } = new (); diff --git a/settings/DevHome.Settings/Views/AboutPage.xaml.cs b/settings/DevHome.Settings/Views/AboutPage.xaml.cs index 5e5fda489c..890675e4c0 100644 --- a/settings/DevHome.Settings/Views/AboutPage.xaml.cs +++ b/settings/DevHome.Settings/Views/AboutPage.xaml.cs @@ -31,8 +31,8 @@ public AboutPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), - new Breadcrumb(stringResource.GetLocalized("Settings_About_Header"), typeof(AboutViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_About_Header"), typeof(AboutViewModel).FullName!), }; } diff --git a/settings/DevHome.Settings/Views/AccountsPage.xaml.cs b/settings/DevHome.Settings/Views/AccountsPage.xaml.cs index bb9f484559..6ad04cc6c3 100644 --- a/settings/DevHome.Settings/Views/AccountsPage.xaml.cs +++ b/settings/DevHome.Settings/Views/AccountsPage.xaml.cs @@ -46,8 +46,8 @@ public AccountsPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), - new Breadcrumb(stringResource.GetLocalized("Settings_Accounts_Header"), typeof(AccountsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Accounts_Header"), typeof(AccountsViewModel).FullName!), }; } diff --git a/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs b/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs index ee1d0c3712..749d0bbe0f 100644 --- a/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs +++ b/settings/DevHome.Settings/Views/ExperimentalFeaturesPage.xaml.cs @@ -46,8 +46,8 @@ public ExperimentalFeaturesPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), - new Breadcrumb(stringResource.GetLocalized("Settings_ExperimentalFeatures_Header"), typeof(ExperimentalFeaturesViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_ExperimentalFeatures_Header"), typeof(ExperimentalFeaturesViewModel).FullName!), }; } diff --git a/settings/DevHome.Settings/Views/ExtensionsPage.xaml.cs b/settings/DevHome.Settings/Views/ExtensionsPage.xaml.cs index 87e0267920..b273297a4f 100644 --- a/settings/DevHome.Settings/Views/ExtensionsPage.xaml.cs +++ b/settings/DevHome.Settings/Views/ExtensionsPage.xaml.cs @@ -29,8 +29,8 @@ public ExtensionsPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), - new Breadcrumb(stringResource.GetLocalized("Settings_Extensions_Header"), typeof(ExtensionsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Extensions_Header"), typeof(ExtensionsViewModel).FullName!), }; } diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml b/settings/DevHome.Settings/Views/FeedbackPage.xaml index 7534a4fb49..ed3dc79d1c 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml @@ -43,9 +43,9 @@ - - - + + + @@ -55,8 +55,8 @@ - - + + @@ -96,7 +96,7 @@ - + diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs index 9807638ffb..9e76b7cd5f 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs @@ -49,8 +49,8 @@ public FeedbackPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), - new Breadcrumb(stringResource.GetLocalized("Settings_Feedback_Header"), typeof(ExtensionsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Feedback_Header"), typeof(ExtensionsViewModel).FullName!), }; } diff --git a/settings/DevHome.Settings/Views/PreferencesPage.xaml.cs b/settings/DevHome.Settings/Views/PreferencesPage.xaml.cs index f49ede7cec..0e4b3517e2 100644 --- a/settings/DevHome.Settings/Views/PreferencesPage.xaml.cs +++ b/settings/DevHome.Settings/Views/PreferencesPage.xaml.cs @@ -31,8 +31,8 @@ public PreferencesPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), - new Breadcrumb(stringResource.GetLocalized("Settings_Preferences_Header"), typeof(PreferencesViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Preferences_Header"), typeof(PreferencesViewModel).FullName!), }; } diff --git a/settings/DevHome.Settings/Views/SettingsPage.xaml.cs b/settings/DevHome.Settings/Views/SettingsPage.xaml.cs index 5a8b1d865a..4d35066236 100644 --- a/settings/DevHome.Settings/Views/SettingsPage.xaml.cs +++ b/settings/DevHome.Settings/Views/SettingsPage.xaml.cs @@ -29,7 +29,7 @@ public SettingsPage() var stringResource = new StringResource("DevHome.Settings/Resources"); Breadcrumbs = new ObservableCollection { - new Breadcrumb(stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), + new (stringResource.GetLocalized("Settings_Header"), typeof(SettingsViewModel).FullName!), }; } diff --git a/src/App.xaml.cs b/src/App.xaml.cs index de5d2578a9..439caf03cc 100644 --- a/src/App.xaml.cs +++ b/src/App.xaml.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. -using System.Web.Services.Description; using DevHome.Activation; using DevHome.Common.Contracts; using DevHome.Common.Contracts.Services; diff --git a/src/Assets/Preview/CPUScreenshotDark.png b/src/Assets/Preview/CPUScreenshotDark.png new file mode 100644 index 0000000000..536404e346 Binary files /dev/null and b/src/Assets/Preview/CPUScreenshotDark.png differ diff --git a/src/Assets/Preview/CPUScreenshotLight.png b/src/Assets/Preview/CPUScreenshotLight.png new file mode 100644 index 0000000000..61b7bfa36c Binary files /dev/null and b/src/Assets/Preview/CPUScreenshotLight.png differ diff --git a/src/Assets/Preview/GPUScreenshotDark.png b/src/Assets/Preview/GPUScreenshotDark.png new file mode 100644 index 0000000000..8e446b907c Binary files /dev/null and b/src/Assets/Preview/GPUScreenshotDark.png differ diff --git a/src/Assets/Preview/GPUScreenshotLight.png b/src/Assets/Preview/GPUScreenshotLight.png new file mode 100644 index 0000000000..17a417f186 Binary files /dev/null and b/src/Assets/Preview/GPUScreenshotLight.png differ diff --git a/src/Assets/Preview/MemoryScreenshotDark.png b/src/Assets/Preview/MemoryScreenshotDark.png new file mode 100644 index 0000000000..97d19266bd Binary files /dev/null and b/src/Assets/Preview/MemoryScreenshotDark.png differ diff --git a/src/Assets/Preview/MemoryScreenshotLight.png b/src/Assets/Preview/MemoryScreenshotLight.png new file mode 100644 index 0000000000..9f77c6e18d Binary files /dev/null and b/src/Assets/Preview/MemoryScreenshotLight.png differ diff --git a/src/Assets/Preview/NetworkScreenshotDark.png b/src/Assets/Preview/NetworkScreenshotDark.png new file mode 100644 index 0000000000..6858c0a06f Binary files /dev/null and b/src/Assets/Preview/NetworkScreenshotDark.png differ diff --git a/src/Assets/Preview/NetworkScreenshotLight.png b/src/Assets/Preview/NetworkScreenshotLight.png new file mode 100644 index 0000000000..628ec92c9e Binary files /dev/null and b/src/Assets/Preview/NetworkScreenshotLight.png differ diff --git a/src/Assets/Preview/SSHScreenshotDark.png b/src/Assets/Preview/SSHScreenshotDark.png new file mode 100644 index 0000000000..a1922d0675 Binary files /dev/null and b/src/Assets/Preview/SSHScreenshotDark.png differ diff --git a/src/Assets/Preview/SSHScreenshotLight.png b/src/Assets/Preview/SSHScreenshotLight.png new file mode 100644 index 0000000000..377fc28d13 Binary files /dev/null and b/src/Assets/Preview/SSHScreenshotLight.png differ diff --git a/src/DevHome.csproj b/src/DevHome.csproj index f3e47f6c6d..4ffdcf19c2 100644 --- a/src/DevHome.csproj +++ b/src/DevHome.csproj @@ -20,6 +20,16 @@ $(DefineConstants);DISABLE_XAML_GENERATED_MAIN + + + + + + + + + + @@ -32,13 +42,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers - + contentFiles diff --git a/src/Helpers/NavConfigHelper.cs b/src/Helpers/NavConfigHelper.cs index 6128ea35ac..5639a65f43 100644 --- a/src/Helpers/NavConfigHelper.cs +++ b/src/Helpers/NavConfigHelper.cs @@ -7,7 +7,7 @@ namespace DevHome.Helpers; -internal class NavConfig +internal sealed class NavConfig { [JsonPropertyName("navMenu")] public NavMenu NavMenu { get; set; } @@ -16,13 +16,13 @@ internal class NavConfig public string[] ExperimentIds { get; set; } } -internal class NavMenu +internal sealed class NavMenu { [JsonPropertyName("groups")] public Group[] Groups { get; set; } } -internal class Group +internal sealed class Group { [JsonPropertyName("identity")] public string Identity { get; set; } @@ -31,7 +31,7 @@ internal class Group public Tool[] Tools { get; set; } } -internal class Tool +internal sealed class Tool { [JsonPropertyName("identity")] public string Identity { get; set; } @@ -55,7 +55,7 @@ internal class Tool // Uses .NET's JSON source generator support for serializing / deserializing NavConfig to get some perf gains at startup. [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(NavConfig))] -internal partial class SourceGenerationContext : JsonSerializerContext +internal sealed partial class SourceGenerationContext : JsonSerializerContext { } diff --git a/src/Helpers/SettingsStorageExtensions.cs b/src/Helpers/SettingsStorageExtensions.cs index 7a67a40198..95be03b4ac 100644 --- a/src/Helpers/SettingsStorageExtensions.cs +++ b/src/Helpers/SettingsStorageExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. -using DevHome.Common.Helpers; using Windows.Storage; using Windows.Storage.Streams; @@ -21,7 +20,7 @@ public static bool IsRoamingStorageAvailable(this ApplicationData appData) public static async Task SaveAsync(this StorageFolder folder, string name, T content) { var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting); - var fileContent = await Json.StringifyAsync(content!); + var fileContent = await Common.Helpers.Json.StringifyAsync(content!); await FileIO.WriteTextAsync(file, fileContent); } @@ -36,12 +35,12 @@ public static async Task SaveAsync(this StorageFolder folder, string name, T var file = await folder.GetFileAsync($"{name}.json"); var fileContent = await FileIO.ReadTextAsync(file); - return await Json.ToObjectAsync(fileContent); + return await Common.Helpers.Json.ToObjectAsync(fileContent); } public static async Task SaveAsync(this ApplicationDataContainer settings, string key, T value) { - settings.SaveString(key, await Json.StringifyAsync(value!)); + settings.SaveString(key, await Common.Helpers.Json.StringifyAsync(value!)); } public static void SaveString(this ApplicationDataContainer settings, string key, string value) @@ -55,7 +54,7 @@ public static void SaveString(this ApplicationDataContainer settings, string key if (settings.Values.TryGetValue(key, out obj)) { - return await Json.ToObjectAsync((string)obj); + return await Common.Helpers.Json.ToObjectAsync((string)obj); } return default; @@ -63,10 +62,7 @@ public static void SaveString(this ApplicationDataContainer settings, string key public static async Task SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } + ArgumentNullException.ThrowIfNull(content); if (string.IsNullOrEmpty(fileName)) { diff --git a/src/Models/WhatsNewCard.cs b/src/Models/WhatsNewCard.cs index 9bcadfa8c1..6ef724bbf7 100644 --- a/src/Models/WhatsNewCard.cs +++ b/src/Models/WhatsNewCard.cs @@ -59,6 +59,11 @@ public string? Link public bool ShouldShowLink { get; set; } = true; + public bool? ShouldShowIcon + { + get; set; + } + public bool? IsBig { get; set; diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest index 29b96da8c0..100954e882 100644 --- a/src/Package.appxmanifest +++ b/src/Package.appxmanifest @@ -18,7 +18,7 @@ disabled - + @@ -102,12 +102,15 @@ - + + + + @@ -129,12 +132,15 @@ - + + + + @@ -156,12 +162,15 @@ - + + + + @@ -183,12 +192,15 @@ - + + + + @@ -210,12 +222,15 @@ - + + + + diff --git a/src/Services/GitWatcher.cs b/src/Services/GitWatcher.cs index baf47c6a3f..cc106d69cf 100644 --- a/src/Services/GitWatcher.cs +++ b/src/Services/GitWatcher.cs @@ -255,10 +255,9 @@ private void CreateWatcher(string filePattern, string repository) lock (modificationLock) { var key = repository.ToLower(CultureInfo.InvariantCulture); - if (!watchers.ContainsKey(key)) + if (watchers.TryAdd(key, watcher)) { watcher.EnableRaisingEvents = true; - watchers.Add(key, watcher); } } } diff --git a/src/Services/InfoBarService.cs b/src/Services/InfoBarService.cs index 46d36e52ba..22c0532847 100644 --- a/src/Services/InfoBarService.cs +++ b/src/Services/InfoBarService.cs @@ -6,7 +6,7 @@ using Microsoft.UI.Xaml.Controls; namespace DevHome.Services; -internal class InfoBarService : IInfoBarService +internal sealed class InfoBarService : IInfoBarService { private readonly InfoBarModel _shellInfoBarModel; diff --git a/src/ViewModels/InitializationViewModel.cs b/src/ViewModels/InitializationViewModel.cs index 80465db25b..91bdc9e575 100644 --- a/src/ViewModels/InitializationViewModel.cs +++ b/src/ViewModels/InitializationViewModel.cs @@ -6,6 +6,7 @@ using DevHome.Contracts.Services; using DevHome.Dashboard.Services; using DevHome.Logging; +using DevHome.Services; using DevHome.Views; using Microsoft.UI.Xaml; @@ -15,11 +16,21 @@ public class InitializationViewModel : ObservableObject { private readonly IThemeSelectorService _themeSelector; private readonly IWidgetHostingService _widgetHostingService; + private readonly IAppInstallManagerService _appInstallManagerService; - public InitializationViewModel(IThemeSelectorService themeSelector, IWidgetHostingService widgetHostingService) +#if CANARY_BUILD + private const string GitHubExtensionStorePackageId = "9N806ZKPW85R"; +#elif STABLE_BUILD + private const string GitHubExtensionStorePackageId = "9NZCC27PR6N6"; +#else + private const string GitHubExtensionStorePackageId = ""; +#endif + + public InitializationViewModel(IThemeSelectorService themeSelector, IWidgetHostingService widgetHostingService, IAppInstallManagerService appInstallManagerService) { _themeSelector = themeSelector; _widgetHostingService = widgetHostingService; + _appInstallManagerService = appInstallManagerService; } public async void OnPageLoaded() @@ -33,6 +44,23 @@ public async void OnPageLoaded() GlobalLog.Logger?.ReportInfo("InitializationViewModel", "Installing WidgetService failed: ", ex); } + if (string.IsNullOrEmpty(GitHubExtensionStorePackageId)) + { + GlobalLog.Logger?.ReportInfo("InitializationViewModel", "Skipping installing DevHomeGitHubExtension."); + } + else + { + try + { + GlobalLog.Logger?.ReportInfo("InitializationViewModel", "Installing DevHomeGitHubExtension..."); + await _appInstallManagerService.TryInstallPackageAsync(GitHubExtensionStorePackageId); + } + catch (Exception ex) + { + GlobalLog.Logger?.ReportInfo("InitializationViewModel", "Installing DevHomeGitHubExtension failed: ", ex); + } + } + App.MainWindow.Content = Application.Current.GetService(); _themeSelector.SetRequestedTheme(); diff --git a/src/Views/WhatsNewPage.xaml b/src/Views/WhatsNewPage.xaml index 8eaec4db15..4d17752ed8 100644 --- a/src/Views/WhatsNewPage.xaml +++ b/src/Views/WhatsNewPage.xaml @@ -291,15 +291,24 @@ - diff --git a/src/Views/WhatsNewPage.xaml.cs b/src/Views/WhatsNewPage.xaml.cs index 79021fbace..22b271db0c 100644 --- a/src/Views/WhatsNewPage.xaml.cs +++ b/src/Views/WhatsNewPage.xaml.cs @@ -60,6 +60,10 @@ private async void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) card.ShouldShowLink = false; } } + else + { + card.ShouldShowIcon = false; + } ViewModel.AddCard(card); } diff --git a/telemetry/DevHome.Telemetry/Telemetry.cs b/telemetry/DevHome.Telemetry/Telemetry.cs index 6f6f70eae7..108bd06c6a 100644 --- a/telemetry/DevHome.Telemetry/Telemetry.cs +++ b/telemetry/DevHome.Telemetry/Telemetry.cs @@ -10,7 +10,7 @@ namespace DevHome.Telemetry; -internal class Telemetry : ITelemetry +internal sealed class Telemetry : ITelemetry { private const string ProviderName = "Microsoft.Windows.DevHome"; // Generated provider GUID: {2e74ff65-bbda-5e80-4c0a-bd8320d4223b} diff --git a/test/DevHome.Test.csproj b/test/DevHome.Test.csproj index 5abc055b33..08396c0ef9 100644 --- a/test/DevHome.Test.csproj +++ b/test/DevHome.Test.csproj @@ -3,6 +3,7 @@ DevHome.Test x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable enable diff --git a/test/GitWatcherTests.cs b/test/GitWatcherTests.cs index 95bfb5919f..f9739d6db7 100644 --- a/test/GitWatcherTests.cs +++ b/test/GitWatcherTests.cs @@ -173,7 +173,7 @@ public void SyntheticRepo_CreateModifyDeleteFile_Success() // Test file modification testFile2 = File.OpenWrite(pathToTargetFile2); - testFile2.Write(new byte[4] { 1, 2, 3, 4 }); + testFile2.Write([1, 2, 3, 4]); testFile2.Close(); Assert.IsTrue(eventFired.WaitOne(1000)); eventFired.Reset(); @@ -205,7 +205,7 @@ public void SyntheticRepo_CreateModifyDeleteFile_Success() // Test file modification testFile3 = File.OpenWrite(pathToTargetFile3); - testFile3.Write(new byte[4] { 1, 2, 3, 4 }); + testFile3.Write([1, 2, 3, 4]); testFile3.Close(); Assert.IsTrue(eventFired.WaitOne(1000)); eventFired.Reset(); diff --git a/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj b/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj index 1bdec52d74..ccf2b90e3a 100644 --- a/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj +++ b/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj @@ -3,6 +3,7 @@ Dashboard.Test x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable enable diff --git a/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj b/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj index dc6cb09588..fb9a5f5681 100644 --- a/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj +++ b/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj @@ -17,10 +17,8 @@ - - - + diff --git a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs index 0b19894d87..26cf6ed7de 100644 --- a/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs +++ b/tools/Dashboard/DevHome.Dashboard/Extensions/ServiceExtensions.cs @@ -15,10 +15,12 @@ public static IServiceCollection AddDashboard(this IServiceCollection services, // View-models services.AddSingleton(); services.AddTransient(); + services.AddTransient(); // Services services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); return services; diff --git a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs index cb5d8fa0b7..a9cec70f5f 100644 --- a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs +++ b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs @@ -12,7 +12,7 @@ using Microsoft.Windows.Widgets.Hosts; namespace DevHome.Dashboard.Helpers; -internal class WidgetHelpers +internal sealed class WidgetHelpers { public const string DevHomeHostName = "DevHome"; diff --git a/tools/Dashboard/DevHome.Dashboard/Services/IWidgetScreenshotService.cs b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetScreenshotService.cs new file mode 100644 index 0000000000..db73506da3 --- /dev/null +++ b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetScreenshotService.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Windows.Widgets.Hosts; + +namespace DevHome.Dashboard.Services; + +public interface IWidgetScreenshotService +{ + public Task GetScreenshotFromCache(WidgetDefinition widgetDefinition, ElementTheme actualTheme); +} diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs index 7c774b1df5..6f139add1e 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using DevHome.Common.Helpers; using DevHome.Common.Services; +using DevHome.Services; using Microsoft.Windows.Widgets.Hosts; using Windows.ApplicationModel.Store.Preview.InstallControl; using Log = DevHome.Dashboard.Helpers.Log; @@ -16,6 +17,8 @@ public class WidgetHostingService : IWidgetHostingService { private readonly IPackageDeploymentService _packageDeploymentService; + private readonly IAppInstallManagerService _appInstallManagerService; + private static readonly string WidgetServiceStorePackageId = "9N3RK8ZV2ZR8"; private static readonly TimeSpan StoreInstallTimeout = new (0, 0, 60); @@ -35,9 +38,10 @@ public enum WidgetServiceStates Unknown, } - public WidgetHostingService(IPackageDeploymentService packageDeploymentService) + public WidgetHostingService(IPackageDeploymentService packageDeploymentService, IAppInstallManagerService appInstallManagerService) { _packageDeploymentService = packageDeploymentService; + _appInstallManagerService = appInstallManagerService; } public async Task EnsureWidgetServiceAsync() @@ -79,7 +83,7 @@ public async Task EnsureWidgetServiceAsync() { // Try to install and report the outcome. Log.Logger()?.ReportInfo("WidgetHostingService", "On Windows 10, TryInstallWidgetServicePackageAsync..."); - var installedSuccessfully = await TryInstallWidgetServicePackageAsync(); + var installedSuccessfully = await _appInstallManagerService.TryInstallPackageAsync(WidgetServiceStorePackageId); _widgetServiceState = installedSuccessfully ? WidgetServiceStates.HasStoreWidgetServiceGoodVersion : WidgetServiceStates.HasStoreWidgetServiceNoOrBadVersion; Log.Logger()?.ReportInfo("WidgetHostingService", $"On Windows 10, ...{_widgetServiceState}"); return installedSuccessfully; @@ -111,74 +115,6 @@ private bool HasValidWidgetServicePackage() return packages.Any(); } - private async Task TryInstallWidgetServicePackageAsync() - { - try - { - var installTask = InstallWidgetServicePackageAsync(WidgetServiceStorePackageId); - - // Wait for a maximum of StoreInstallTimeout (60 seconds). - var completedTask = await Task.WhenAny(installTask, Task.Delay(StoreInstallTimeout)); - - if (completedTask.Exception != null) - { - throw completedTask.Exception; - } - - if (completedTask != installTask) - { - throw new TimeoutException("Store Install task did not finish in time."); - } - - return true; - } - catch (Exception ex) - { - Log.Logger()?.ReportError("WidgetService installation Failed", ex); - } - - return false; - } - - private async Task InstallWidgetServicePackageAsync(string packageId) - { - await Task.Run(() => - { - var tcs = new TaskCompletionSource(); - AppInstallItem installItem; - try - { - Log.Logger()?.ReportInfo("WidgetHostingService", "Starting WidgetService install"); - installItem = new AppInstallManager().StartAppInstallAsync(packageId, null, true, false).GetAwaiter().GetResult(); - } - catch (Exception ex) - { - Log.Logger()?.ReportInfo("WidgetHostingService", "WidgetService install failure"); - tcs.SetException(ex); - return tcs.Task; - } - - installItem.Completed += (sender, args) => - { - tcs.SetResult(true); - }; - - installItem.StatusChanged += (sender, args) => - { - if (installItem.GetCurrentStatus().InstallState == AppInstallState.Canceled - || installItem.GetCurrentStatus().InstallState == AppInstallState.Error) - { - tcs.TrySetException(new System.Management.Automation.JobFailedException(installItem.GetCurrentStatus().ErrorCode.ToString())); - } - else if (installItem.GetCurrentStatus().InstallState == AppInstallState.Completed) - { - tcs.SetResult(true); - } - }; - return tcs.Task; - }); - } - public async Task GetWidgetHostAsync() { if (_widgetHost == null) diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs index a338be6dc1..4cec6fe433 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetIconService.cs @@ -66,15 +66,8 @@ public async Task AddIconsToCacheAsync(WidgetDefinition widgetDef) // There is a widget bug where Definition update events are being raised as added events. // If we already have an icon for this key, just remove and add again in case the icons changed. - if (_widgetLightIconCache.ContainsKey(widgetDefId)) - { - _widgetLightIconCache.Remove(widgetDefId); - } - - if (_widgetDarkIconCache.ContainsKey(widgetDefId)) - { - _widgetDarkIconCache.Remove(widgetDefId); - } + _widgetLightIconCache.Remove(widgetDefId); + _widgetDarkIconCache.Remove(widgetDefId); _widgetLightIconCache.Add(widgetDefId, itemLightImage); _widgetDarkIconCache.Add(widgetDefId, itemDarkImage); diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetScreenshotService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetScreenshotService.cs new file mode 100644 index 0000000000..26d142a894 --- /dev/null +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetScreenshotService.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Windows.Widgets.Hosts; +using Windows.Storage.Streams; +using WinUIEx; + +namespace DevHome.Dashboard.Services; + +public class WidgetScreenshotService : IWidgetScreenshotService +{ + private readonly WindowEx _windowEx; + + private readonly Dictionary _widgetLightScreenshotCache; + private readonly Dictionary _widgetDarkScreenshotCache; + + public WidgetScreenshotService(WindowEx windowEx) + { + _windowEx = windowEx; + + _widgetLightScreenshotCache = new Dictionary(); + _widgetDarkScreenshotCache = new Dictionary(); + } + + public async Task GetScreenshotFromCache(WidgetDefinition widgetDefinition, ElementTheme actualTheme) + { + var widgetDefinitionId = widgetDefinition.Id; + BitmapImage bitmapImage; + + // First, check the cache to see if the screenshot is already there. + if (actualTheme == ElementTheme.Dark) + { + _widgetDarkScreenshotCache.TryGetValue(widgetDefinitionId, out bitmapImage); + } + else + { + _widgetLightScreenshotCache.TryGetValue(widgetDefinitionId, out bitmapImage); + } + + if (bitmapImage != null) + { + return bitmapImage; + } + + // If the screenshot wasn't already in the cache, get it from the widget definition and add it to the cache before returning. + if (actualTheme == ElementTheme.Dark) + { + bitmapImage = await WidgetScreenshotToBitmapImageAsync(widgetDefinition.GetThemeResource(WidgetTheme.Dark).GetScreenshots().FirstOrDefault().Image); + _widgetDarkScreenshotCache.TryAdd(widgetDefinitionId, bitmapImage); + } + else + { + bitmapImage = await WidgetScreenshotToBitmapImageAsync(widgetDefinition.GetThemeResource(WidgetTheme.Light).GetScreenshots().FirstOrDefault().Image); + _widgetLightScreenshotCache.TryAdd(widgetDefinitionId, bitmapImage); + } + + return bitmapImage; + } + + public void RemoveScreenshotsFromCache(string definitionId) + { + _widgetLightScreenshotCache.Remove(definitionId); + _widgetDarkScreenshotCache.Remove(definitionId); + } + + private async Task WidgetScreenshotToBitmapImageAsync(IRandomAccessStreamReference iconStreamRef) + { + // Return the bitmap image via TaskCompletionSource. Using WCT's EnqueueAsync does not suffice here, since if + // we're already on the thread of the DispatcherQueue then it just directly calls the function, with no async involved. + var completionSource = new TaskCompletionSource(); + _windowEx.DispatcherQueue.TryEnqueue(async () => + { + using var bitmapStream = await iconStreamRef.OpenReadAsync(); + var itemImage = new BitmapImage(); + await itemImage.SetSourceAsync(bitmapStream); + completionSource.TrySetResult(itemImage); + }); + + var bitmapImage = await completionSource.Task; + + return bitmapImage; + } +} diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/AddWidgetViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/AddWidgetViewModel.cs new file mode 100644 index 0000000000..af08f7bb47 --- /dev/null +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/AddWidgetViewModel.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation and Contributors +// Licensed under the MIT license. + +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Dashboard.Services; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.Windows.Widgets.Hosts; + +namespace DevHome.Dashboard.ViewModels; + +public partial class AddWidgetViewModel : ObservableObject +{ + private readonly IWidgetScreenshotService _widgetScreenshotService; + + [ObservableProperty] + private string _widgetDisplayTitle; + + [ObservableProperty] + private string _widgetProviderDisplayTitle; + + [ObservableProperty] + private Brush _widgetScreenshot; + + [ObservableProperty] + private bool _pinButtonVisibility; + + public AddWidgetViewModel(IWidgetScreenshotService widgetScreenshotService) + { + _widgetScreenshotService = widgetScreenshotService; + } + + public async Task SetWidgetDefinition(WidgetDefinition selectedWidgetDefinition, ElementTheme actualTheme) + { + var bitmap = await _widgetScreenshotService.GetScreenshotFromCache(selectedWidgetDefinition, actualTheme); + + WidgetDisplayTitle = selectedWidgetDefinition.DisplayTitle; + WidgetProviderDisplayTitle = selectedWidgetDefinition.ProviderDefinition.DisplayName; + WidgetScreenshot = new ImageBrush + { + ImageSource = bitmap, + }; + PinButtonVisibility = true; + } + + public void Clear() + { + WidgetDisplayTitle = string.Empty; + WidgetProviderDisplayTitle = string.Empty; + WidgetScreenshot = null; + PinButtonVisibility = false; + } +} diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardBannerViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardBannerViewModel.cs index 8251a34707..cb80fd8378 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardBannerViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardBannerViewModel.cs @@ -10,7 +10,7 @@ namespace DevHome.Dashboard.ViewModels; -internal partial class DashboardBannerViewModel : ObservableObject +internal sealed partial class DashboardBannerViewModel : ObservableObject { private const string _hideDashboardBannerKey = "HideDashboardBanner"; diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs index 5811896498..3ab27e2e71 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs @@ -54,9 +54,6 @@ public partial class WidgetViewModel : ObservableObject [ObservableProperty] private bool _isInEditMode; - [ObservableProperty] - private bool _configuring; - partial void OnWidgetChanging(Widget value) { if (Widget != null) @@ -131,13 +128,6 @@ await Task.Run(async () => Log.Logger()?.ReportDebug("WidgetViewModel", $"cardTemplate = {cardTemplate}"); Log.Logger()?.ReportDebug("WidgetViewModel", $"cardData = {cardData}"); - // If we're in the Add or Edit dialog, check the cardData to see if the card is in a configuration state - // or if it is able to be pinned yet. If still configuring, the Pin button will be disabled. - if (IsInAddMode || IsInEditMode) - { - GetConfiguring(cardData); - } - // Use the data to fill in the template. AdaptiveCardParseResult card; try @@ -206,20 +196,6 @@ await Task.Run(async () => }); } - // Check if the card data indicates a configuration state. Configuring is bound to the Pin button and will disable it if true. - private void GetConfiguring(string cardData) - { - var jsonObj = JsonObject.Parse(cardData); - if (jsonObj != null) - { - var isConfiguring = jsonObj.GetNamedBoolean("configuring", false); - _dispatcher.TryEnqueue(() => - { - Configuring = isConfiguring; - }); - } - } - private async Task IsWidgetContentAvailable() { return await Task.Run(async () => @@ -271,7 +247,7 @@ public void ShowErrorCard(string error, string subError = null) }); } - private FrameworkElement GetErrorCard(string error, string subError = null) + private Grid GetErrorCard(string error, string subError = null) { var resourceLoader = new Microsoft.Windows.ApplicationModel.Resources.ResourceLoader("DevHome.Dashboard.pri", "DevHome.Dashboard/Resources"); diff --git a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml index 32ace0766c..1ec702d837 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml +++ b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml @@ -8,7 +8,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:commonviews="using:DevHome.Common.Views" - xmlns:converters="using:CommunityToolkit.WinUI.Converters" xmlns:i="using:Microsoft.Xaml.Interactivity" xmlns:ic="using:Microsoft.Xaml.Interactions.Core" mc:Ignorable="d" @@ -24,18 +23,21 @@ 652 652 - 684 + 590 0,0,0,0 0,0,0,0 0,0,0,0 - + 0,20 + 0,42 + 0,20,0,0 + 0,42,0,0 - + @@ -48,13 +50,13 @@ IsPaneToggleButtonVisible="False" IsTitleBarAutoPaddingEnabled="False" OpenPaneLength="218" - MaxHeight="650" + MaxHeight="560" SelectionChanged="AddWidgetNavigationView_SelectionChanged"> - - + + @@ -62,10 +64,11 @@ - - - + + + + + + Command="{x:Bind PinButtonClickCommand}" + Margin="{StaticResource LargePinButtonMargin}"> diff --git a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs index c7e05ab11c..bdcdaab135 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/AddWidgetDialog.xaml.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using AdaptiveCards.Rendering.WinUI3; using CommunityToolkit.Mvvm.Input; using DevHome.Common.Extensions; using DevHome.Dashboard.Helpers; @@ -18,18 +17,17 @@ using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Shapes; using Microsoft.Windows.Widgets.Hosts; -using WinUIEx; namespace DevHome.Dashboard.Views; public sealed partial class AddWidgetDialog : ContentDialog { - private Widget _currentWidget; + private WidgetDefinition _selectedWidget; private static DispatcherQueue _dispatcher; - public Widget AddedWidget { get; set; } + public WidgetDefinition AddedWidget { get; private set; } - public WidgetViewModel ViewModel { get; set; } + public AddWidgetViewModel ViewModel { get; set; } private readonly IWidgetHostingService _hostingService; private readonly IWidgetIconService _widgetIconService; @@ -38,7 +36,7 @@ public AddWidgetDialog( DispatcherQueue dispatcher, ElementTheme theme) { - ViewModel = new WidgetViewModel(null, Microsoft.Windows.Widgets.WidgetSize.Large, null, dispatcher); + ViewModel = Application.Current.GetService(); _hostingService = Application.Current.GetService(); _widgetIconService = Application.Current.GetService(); @@ -49,9 +47,6 @@ public AddWidgetDialog( // Strange behavior: just setting the requested theme when we new-up the dialog results in // the wrong theme's resources being used. Setting RequestedTheme here fixes the problem. RequestedTheme = theme; - - // Get the application root window so we know when it has closed. - Application.Current.GetService().Closed += OnMainWindowClosed; } [RelayCommand] @@ -125,10 +120,11 @@ private async Task FillAvailableWidgetsAsync() } } - // If there were no available widgets, show a message. + // If there were no available widgets, log an error. + // This should never happen since Dev Home's core widgets are always available. if (!AddWidgetNavigationView.MenuItems.Any()) { - ViewModel.ShowErrorCard("WidgetErrorCardNoWidgetsText"); + Log.Logger()?.ReportError("AddWidgetDialog", $"FillAvailableWidgetsAsync found no available widgets."); } } @@ -208,128 +204,76 @@ private async void AddWidgetNavigationView_SelectionChanged( NavigationView sender, NavigationViewSelectionChangedEventArgs args) { - // Delete previously shown configuration widget. - // Clearing the UI here results in a flash, so don't bother. It will update soon. - Log.Logger()?.ReportDebug("AddWidgetDialog", $"Widget selection changed, delete widget if one exists"); - var clearWidgetTask = ClearCurrentWidget(); - - // Selected item could be null if list of widgets became empty. + // Selected item could be null if list of widgets became empty, but list should never be empty + // since core widgets are always available. if (sender.SelectedItem is null) { + ViewModel.Clear(); return; } - // Load selected widget configuration. + // Get selected widget definition. var selectedTag = (sender.SelectedItem as NavigationViewItem).Tag; if (selectedTag is null) { Log.Logger()?.ReportError("AddWidgetDialog", $"Selected widget description did not have a tag"); + ViewModel.Clear(); return; } - // If the user has selected a widget, show configuration UI. If they selected a provider, leave space blank. + // If the user has selected a widget, show preview. If they selected a provider, leave space blank. if (selectedTag as WidgetDefinition is WidgetDefinition selectedWidgetDefinition) { - var size = WidgetHelpers.GetLargestCapabilitySize(selectedWidgetDefinition.GetWidgetCapabilities()); - - // Create the widget for configuration. We will need to delete it if the user closes the dialog - // without pinning, or selects a different widget. - Widget widget = null; - try - { - var widgetHost = await _hostingService.GetWidgetHostAsync(); - widget = await Task.Run(async () => await widgetHost?.CreateWidgetAsync(selectedWidgetDefinition.Id, size)); - } - catch (Exception ex) - { - Log.Logger()?.ReportWarn("AddWidgetDialog", $"CreateWidgetAsync failed: ", ex); - } - - if (widget is not null) - { - Log.Logger()?.ReportInfo("AddWidgetDialog", $"Created Widget {widget.Id}"); - - ViewModel.Widget = widget; - ViewModel.IsInAddMode = true; - PinButton.Visibility = Visibility.Visible; - - var widgetCatalog = await _hostingService.GetWidgetCatalogAsync(); - ViewModel.WidgetDefinition = await Task.Run(() => widgetCatalog?.GetWidgetDefinition(widget.DefinitionId)); - - clearWidgetTask.Wait(); - } - else - { - Log.Logger()?.ReportWarn("AddWidgetDialog", $"Widget creation failed."); - ViewModel.ShowErrorCard("WidgetErrorCardCreate1Text", "WidgetErrorCardCreate2Text"); - } - - _currentWidget = widget; + _selectedWidget = selectedWidgetDefinition; + await ViewModel.SetWidgetDefinition(selectedWidgetDefinition, ActualTheme); } else if (selectedTag as WidgetProviderDefinition is not null) { - ConfigurationContentFrame.Content = null; - PinButton.Visibility = Visibility.Collapsed; + ViewModel.Clear(); } } - private void PinButton_Click(object sender, RoutedEventArgs e) + [RelayCommand] + private void PinButtonClick() { - AddedWidget = _currentWidget; + Log.Logger()?.ReportDebug("AddWidgetDialog", $"Pin selected"); + AddedWidget = _selectedWidget; HideDialogAsync(); } - private async void CancelButton_Click(object sender, RoutedEventArgs e) + [RelayCommand] + private void CancelButtonClick() { - // Delete previously shown configuration card. - Log.Logger()?.ReportDebug("AddWidgetDialog", $"Canceled dialog, delete widget"); - await ClearCurrentWidget(); + Log.Logger()?.ReportDebug("AddWidgetDialog", $"Canceled dialog"); + AddedWidget = null; HideDialogAsync(); } private async void HideDialogAsync() { - _currentWidget = null; + _selectedWidget = null; ViewModel = null; - Application.Current.GetService().Closed -= OnMainWindowClosed; var widgetCatalog = await _hostingService.GetWidgetCatalogAsync(); widgetCatalog!.WidgetDefinitionDeleted -= WidgetCatalog_WidgetDefinitionDeleted; this.Hide(); } - private async void OnMainWindowClosed(object sender, WindowEventArgs args) - { - Log.Logger()?.ReportInfo("AddWidgetDialog", $"Window Closed, delete partially created widget"); - await ClearCurrentWidget(); - } - - private async Task ClearCurrentWidget() - { - if (_currentWidget != null) - { - var widgetIdToDelete = _currentWidget.Id; - await _currentWidget.DeleteAsync(); - Log.Logger()?.ReportInfo("AddWidgetDialog", $"Deleted Widget {widgetIdToDelete}"); - _currentWidget = null; - } - } - private void WidgetCatalog_WidgetDefinitionDeleted(WidgetCatalog sender, WidgetDefinitionDeletedEventArgs args) { var deletedDefinitionId = args.DefinitionId; _dispatcher.TryEnqueue(() => { - // If we currently have the deleted widget open, show an error message instead. - if (_currentWidget is not null && - _currentWidget.DefinitionId.Equals(deletedDefinitionId, StringComparison.Ordinal)) + // If we currently have the deleted widget open, un-select it. + if (_selectedWidget is not null && + _selectedWidget.Id.Equals(deletedDefinitionId, StringComparison.Ordinal)) { - Log.Logger()?.ReportInfo("AddWidgetDialog", $"Widget definition deleted while creating that widget."); - ViewModel.ShowErrorCard("WidgetErrorCardCreate1Text", "WidgetErrorCardCreate2Text"); + Log.Logger()?.ReportInfo("AddWidgetDialog", $"Widget definition deleted while selected."); + ViewModel.Clear(); AddWidgetNavigationView.SelectedItem = null; } @@ -350,10 +294,11 @@ private void WidgetCatalog_WidgetDefinitionDeleted(WidgetCatalog sender, WidgetD { menuItems.Remove(providerItem); - // If we've removed all providers from the list, show a message. + // If we've removed all providers from the list, log an error. + // This should never happen since Dev Home's core widgets are always available. if (!menuItems.Any()) { - ViewModel.ShowErrorCard("WidgetErrorCardNoWidgetsText"); + Log.Logger()?.ReportError("AddWidgetDialog", $"WidgetCatalog_WidgetDefinitionDeleted found no available widgets."); } } } @@ -365,11 +310,34 @@ private void WidgetCatalog_WidgetDefinitionDeleted(WidgetCatalog sender, WidgetD private void ContentDialog_SizeChanged(object sender, SizeChangedEventArgs e) { - const int ContentDialogMaxHeight = 684; + var contentDialogMaxHeight = (double)Resources["ContentDialogMaxHeight"]; + const int SmallThreshold = 324; + const int MediumThreshold = 360; - AddWidgetNavigationView.Height = Math.Min(this.ActualHeight, ContentDialogMaxHeight) - AddWidgetTitleBar.ActualHeight; + var smallPinButtonMargin = (Thickness)Resources["SmallPinButtonMargin"]; + var largePinButtonMargin = (Thickness)Resources["LargePinButtonMargin"]; + var smallWidgetPreviewTopMargin = (Thickness)Resources["SmallWidgetPreviewTopMargin"]; + var largeWidgetPreviewTopMargin = (Thickness)Resources["LargeWidgetPreviewTopMargin"]; - // Subtract 45 for the margin around ConfigurationContentFrame. - ConfigurationContentViewer.Height = AddWidgetNavigationView.Height - PinRow.ActualHeight - 45; + AddWidgetNavigationView.Height = Math.Min(this.ActualHeight, contentDialogMaxHeight) - AddWidgetTitleBar.ActualHeight; + + var previewHeightAvailable = AddWidgetNavigationView.Height - TitleRow.ActualHeight - PinRow.ActualHeight; + + // Adjust margins when the height gets too small to show everything. + if (previewHeightAvailable < SmallThreshold) + { + PreviewRow.Padding = smallWidgetPreviewTopMargin; + PinButton.Margin = smallPinButtonMargin; + } + else if (previewHeightAvailable < MediumThreshold) + { + PreviewRow.Padding = smallWidgetPreviewTopMargin; + PinButton.Margin = largePinButtonMargin; + } + else + { + PreviewRow.Padding = largeWidgetPreviewTopMargin; + PinButton.Margin = largePinButtonMargin; + } } } diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index 80aa5c52a8..d669137165 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -266,37 +266,32 @@ public async Task AddWidgetClickAsync() RequestedTheme = this.ActualTheme, }; - // If the dialog was closed in a way we don't already handle (for example, pressing Esc), - // delete the partially created widget. - dialog.Closed += async (sender, args) => - { - if (dialog.AddedWidget == null && dialog.ViewModel != null && dialog.ViewModel.Widget != null) - { - await dialog.ViewModel.Widget.DeleteAsync(); - } - }; - _ = await dialog.ShowAsync(); - var newWidget = dialog.AddedWidget; + var newWidgetDefinition = dialog.AddedWidget; - if (newWidget != null) + if (newWidgetDefinition != null) { - // Set custom state on new widget. - var position = PinnedWidgets.Count; - var newCustomState = WidgetHelpers.CreateWidgetCustomState(position); - Log.Logger()?.ReportDebug("DashboardView", $"SetCustomState: {newCustomState}"); - await newWidget.SetCustomStateAsync(newCustomState); - - // Put new widget on the Dashboard. - var widgetCatalog = await ViewModel.WidgetHostingService.GetWidgetCatalogAsync(); - var widgetDefinition = await Task.Run(() => widgetCatalog?.GetWidgetDefinition(newWidget.DefinitionId)); - if (widgetDefinition is not null) + Widget newWidget; + try { - var size = WidgetHelpers.GetDefaultWidgetSize(widgetDefinition.GetWidgetCapabilities()); - await newWidget.SetSizeAsync(size); + var size = WidgetHelpers.GetDefaultWidgetSize(newWidgetDefinition.GetWidgetCapabilities()); + var widgetHost = await ViewModel.WidgetHostingService.GetWidgetHostAsync(); + newWidget = await Task.Run(async () => await widgetHost?.CreateWidgetAsync(newWidgetDefinition.Id, size)); + + // Set custom state on new widget. + var position = PinnedWidgets.Count; + var newCustomState = WidgetHelpers.CreateWidgetCustomState(position); + Log.Logger()?.ReportDebug("DashboardView", $"SetCustomState: {newCustomState}"); + await newWidget.SetCustomStateAsync(newCustomState); + + // Put new widget on the Dashboard. await InsertWidgetInPinnedWidgetsAsync(newWidget, size, position); } + catch (Exception ex) + { + Log.Logger()?.ReportWarn("AddWidgetDialog", $"Creating widget failed: ", ex); + } } } diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj index 10c0975a8d..1e605ae57c 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj @@ -13,7 +13,7 @@ - + diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryBannerViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryBannerViewModel.cs index 6aa30142b7..ae504f411a 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryBannerViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryBannerViewModel.cs @@ -10,7 +10,7 @@ namespace DevHome.ExtensionLibrary.ViewModels; -internal partial class ExtensionLibraryBannerViewModel : ObservableObject +internal sealed partial class ExtensionLibraryBannerViewModel : ObservableObject { private const string _hideExtensionsBannerKey = "HideExtensionsBanner"; diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml index 6c45bb15c9..d2f2d6dbfd 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml @@ -69,7 +69,7 @@ Description="{x:Bind GeneratePackageDetails(Version,Publisher , InstalledDate), Mode=OneWay}" Margin="{ThemeResource SettingsCardMargin}" ItemsSource="{x:Bind InstalledExtensionsList}" - IsExpanded="True"> + IsExpanded="False"> diff --git a/tools/SampleTool/unittest/SampleTool.UnitTest.csproj b/tools/SampleTool/unittest/SampleTool.UnitTest.csproj index b434cfb3a2..3a3f245bcf 100644 --- a/tools/SampleTool/unittest/SampleTool.UnitTest.csproj +++ b/tools/SampleTool/unittest/SampleTool.UnitTest.csproj @@ -3,6 +3,7 @@ SampleTool.Test x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable enable diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs index 1728f021e8..268bf441c1 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs @@ -151,7 +151,7 @@ private void LogConfigurationDiagnostics(DiagnosticInformation diagnosticInforma /// /// Target string /// Input stream - private IInputStream StringToStream(string str) + private InMemoryRandomAccessStream StringToStream(string str) { InMemoryRandomAccessStream result = new (); using (DataWriter writer = new (result)) diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj b/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj index 95c88be292..2524aecf00 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj @@ -25,10 +25,10 @@ - + - + all runtime; build; native; contentfiles; analyzers diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs index aeeb783010..43340f6ea4 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs @@ -11,6 +11,7 @@ using DevHome.SetupFlow.Common.Contracts; using DevHome.SetupFlow.Common.Helpers; using Windows.Win32; +using Windows.Win32.Foundation; using Windows.Win32.System.Com; using WinRT; @@ -248,7 +249,7 @@ public static (RemoteObject, Process) CreateOutOfProcessObjectAndGetProcess( unsafe { // Write the object into a stream from which will be copied to the shared memory - Marshal.ThrowExceptionForHR(PInvoke.CreateStreamOnHGlobal(0, fDeleteOnRelease: true, out var stream)); + Marshal.ThrowExceptionForHR(PInvoke.CreateStreamOnHGlobal((HGLOBAL)(nint)0, fDeleteOnRelease: true, out var stream)); var marshaler = MarshalInterface.CreateMarshaler(value); var marshalerAbi = MarshalInterface.GetAbi(marshaler); diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs index 6bdf0db083..4ed1b8fac3 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs @@ -11,7 +11,7 @@ namespace DevHome.SetupFlow.Common.TelemetryEvents; [EventData] -internal class ConfigurationSetResultEvent : EventBase +internal sealed class ConfigurationSetResultEvent : EventBase { private readonly ConfigurationSet _configSet; diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs index acaef8635f..f98826c9fd 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs @@ -11,7 +11,7 @@ namespace DevHome.SetupFlow.Common.TelemetryEvents; [EventData] -internal class ConfigurationUnitResultEvent : EventBase +internal sealed class ConfigurationUnitResultEvent : EventBase { private readonly ApplyConfigurationUnitResult _unitResult; diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs index 9aab85a39f..55845dbb5c 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs @@ -6,7 +6,7 @@ namespace DevHome.SetupFlow.Common.WindowsPackageManager; -internal class ClassModel +internal sealed class ClassModel { /// /// Gets the interface for the projected class type generated by CsWinRT diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj index 7560b38a67..b7ad842a2d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj index 65d855e60f..4052adfc2e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs index caf8afde6f..ed7c8e137e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs @@ -252,7 +252,7 @@ private void EndOperation(ITaskArguments taskArguments, bool isSuccessful) /// /// Class for tracking the state of an operation. /// - private class OperationState + private sealed class OperationState { /// /// Gets or sets the number of remaining attempts to execute this operation. diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/NativeMethods.txt b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/NativeMethods.txt index 092e7cd725..27db9b4147 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/NativeMethods.txt +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/NativeMethods.txt @@ -30,4 +30,5 @@ FSCTL_QUERY_PERSISTENT_VOLUME_STATE StrFormatByteSizeEx S_OK DetachVirtualDisk -OpenVirtualDisk \ No newline at end of file +OpenVirtualDisk +FILE_ACCESS_RIGHTS \ No newline at end of file diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs index bf46c87134..4505a0995f 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs @@ -212,7 +212,7 @@ private HRESULT CreatePartition(string virtDiskPhysicalPath, out uint diskNumber Log.Logger?.ReportInfo(Log.Component.DevDrive, nameof(CreatePartition), $"Starting CreateFile from physical path"); var diskHandle = PInvoke.CreateFile( virtDiskPhysicalPath, - FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE, + (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, diff --git a/tools/SetupFlow/DevHome.SetupFlow.UnitTest/DevHome.SetupFlow.UnitTest.csproj b/tools/SetupFlow/DevHome.SetupFlow.UnitTest/DevHome.SetupFlow.UnitTest.csproj index 0d17f24327..3d299c4de6 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.UnitTest/DevHome.SetupFlow.UnitTest.csproj +++ b/tools/SetupFlow/DevHome.SetupFlow.UnitTest/DevHome.SetupFlow.UnitTest.csproj @@ -3,6 +3,7 @@ DevHome.SetupFlow.UnitTest x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable enable diff --git a/tools/SetupFlow/DevHome.SetupFlow/Controls/PackageDetailsTooltip.xaml b/tools/SetupFlow/DevHome.SetupFlow/Controls/PackageDetailsTooltip.xaml index a891178780..464f3193f1 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Controls/PackageDetailsTooltip.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Controls/PackageDetailsTooltip.xaml @@ -12,6 +12,7 @@ diff --git a/tools/SetupFlow/DevHome.SetupFlow/DevHome.SetupFlow.csproj b/tools/SetupFlow/DevHome.SetupFlow/DevHome.SetupFlow.csproj index 0c4069e2e6..279fb8b435 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/DevHome.SetupFlow.csproj +++ b/tools/SetupFlow/DevHome.SetupFlow/DevHome.SetupFlow.csproj @@ -1,5 +1,5 @@  - + DevHome.SetupFlow x86;x64;arm64 @@ -9,17 +9,17 @@ - - + + - - + + contentFiles - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Common.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Common.cs index d101806747..5e5786c6c2 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/Common.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Common.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. namespace DevHome.SetupFlow.Models; -internal class Common +internal sealed class Common { /// /// Used to keep track of what page the user is on. diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs index d4bcc73ffb..cc25e92077 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs @@ -23,7 +23,7 @@ namespace DevHome.SetupFlow.Models; -internal class CreateDevDriveTask : ISetupTask +internal sealed class CreateDevDriveTask : ISetupTask { private readonly TaskMessages _taskMessages; private readonly ActionCenterMessages _actionCenterMessages = new (); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs index cbff14b997..860ee026f1 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs @@ -7,11 +7,9 @@ using LibGit2Sharp; using Microsoft.Windows.DevHome.SDK; using Windows.Foundation; -using Windows.Win32.Storage.FileSystem; -using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext; namespace DevHome.SetupFlow.Models; -internal class GenericRepository : Microsoft.Windows.DevHome.SDK.IRepository +internal sealed class GenericRepository : Microsoft.Windows.DevHome.SDK.IRepository { private readonly string _displayName; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs index c75e9b5d22..1a2e2c234e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs @@ -29,7 +29,7 @@ namespace DevHome.SetupFlow.Models; /// Object that holds a reference to the providers in a extension. /// This needs to be changed to handle multiple accounts per provider. /// -internal class RepositoryProvider +internal sealed class RepositoryProvider { /// /// Wrapper for the extension that is providing a repository and developer id. diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs index 141ea7ee92..749c72fec1 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs @@ -21,7 +21,7 @@ namespace DevHome.SetupFlow.Models; /// /// This class only uses providers that implement IDeveloperIdProvider and IRepositoryProvider. /// -internal class RepositoryProviders +internal sealed class RepositoryProviders { /// /// Hold all providers and organize by their names. @@ -53,9 +53,9 @@ public void StartAllExtensions() public void StartIfNotRunning(string providerName) { Log.Logger?.ReportInfo(Log.Component.RepoConfig, $"Starting RepositoryProvider {providerName}"); - if (_providers.ContainsKey(providerName)) + if (_providers.TryGetValue(providerName, out var value)) { - _providers[providerName].StartIfNotRunning(); + value.StartIfNotRunning(); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/TaskInformation.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/TaskInformation.cs index 2a5c6cc87c..bff311b2d9 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/TaskInformation.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/TaskInformation.cs @@ -20,7 +20,7 @@ public ISetupTask TaskToExecute } /// - /// The message to display in the loading screen. + /// Gets or sets the message to display in the loading screen. /// public string MessageToShow { diff --git a/tools/SetupFlow/DevHome.SetupFlow/NativeMethods.txt b/tools/SetupFlow/DevHome.SetupFlow/NativeMethods.txt index fc59c95bf0..d2af288340 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/NativeMethods.txt +++ b/tools/SetupFlow/DevHome.SetupFlow/NativeMethods.txt @@ -9,4 +9,5 @@ StrFormatByteSizeEx LocalFree FormatMessage S_OK -IsApiSetImplemented \ No newline at end of file +IsApiSetImplemented +FILE_ACCESS_RIGHTS \ No newline at end of file diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs index baa40d4c05..3e66c0bbae 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs @@ -10,6 +10,7 @@ using DevHome.Common.Models; using DevHome.Common.Services; using DevHome.SetupFlow.Common.Helpers; +using DevHome.SetupFlow.Models; using DevHome.SetupFlow.TaskGroups; using DevHome.SetupFlow.Utilities; using DevHome.SetupFlow.ViewModels; @@ -145,7 +146,7 @@ public IDevDrive GetNewDevDrive() { // Currently only one Dev Drive can be created at a time. If one was // produced before reuse it. - if (_devDrives.Any()) + if (_devDrives.Count != 0) { Log.Logger?.ReportInfo(Log.Component.DevDrive, "Reusing existing Dev Drive"); _devDrives.First().State = DevDriveState.New; @@ -192,7 +193,7 @@ public IEnumerable GetAllDevDrivesThatExistOnSystem() SafeFileHandle volumeFileHandle = PInvoke.CreateFile( volumePath, - FILE_ACCESS_FLAGS.FILE_READ_ATTRIBUTES | FILE_ACCESS_FLAGS.FILE_WRITE_ATTRIBUTES, + (uint)(FILE_ACCESS_RIGHTS.FILE_READ_ATTRIBUTES | FILE_ACCESS_RIGHTS.FILE_WRITE_ATTRIBUTES), FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, @@ -253,7 +254,7 @@ public IEnumerable GetAllDevDrivesThatExistOnSystem() /// /// Gets prepopulated data and updates the passed in dev drive object with it. /// - private IDevDrive GetDevDriveWithDefaultInfo() + private DevDrive GetDevDriveWithDefaultInfo() { Log.Logger?.ReportInfo(Log.Component.DevDrive, "Setting default Dev Drive info"); var root = Path.GetPathRoot(Environment.SystemDirectory); @@ -470,7 +471,7 @@ public void CancelChangesToDevDrive() /// public void ConfirmChangesToDevDrive() { - if (_devDrives.Any()) + if (_devDrives.Count != 0) { PreviousDevDrive = _devDrives.First(); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/PackageProvider.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/PackageProvider.cs index 3848cc7650..2313d0dc0b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/PackageProvider.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/PackageProvider.cs @@ -25,7 +25,7 @@ namespace DevHome.SetupFlow.Services; /// public class PackageProvider { - private class PackageCache + private sealed class PackageCache { /// /// Gets or sets the cached package view model diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowStringResource.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowStringResource.cs index fe3b3ada8e..dca2b3a7b0 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowStringResource.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowStringResource.cs @@ -45,7 +45,7 @@ public string GetLocalizedErrorMsg(int errorCode, string logComponent) } finally { - PInvoke.LocalFree((IntPtr)formattedMessage.Value); + PInvoke.LocalFree((HLOCAL)(IntPtr)formattedMessage.Value); } } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs index 2e4a8d44f2..219448f63a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs @@ -19,7 +19,7 @@ namespace DevHome.SetupFlow.Services; public class WinGetFeaturedApplicationsDataSource : WinGetPackageDataSource { private readonly IExtensionService _extensionService; - private readonly IList _groups; + private readonly List _groups; public WinGetFeaturedApplicationsDataSource(IWindowsPackageManager wpm, IExtensionService extensionService) : base(wpm) @@ -115,7 +115,7 @@ public async override Task> LoadCatalogsAsync() /// /// List of package URI strings /// List of package URIs - private IList ParseURIs(IReadOnlyList uriStrings) + private List ParseURIs(IReadOnlyList uriStrings) { var result = new List(); foreach (var app in uriStrings) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageJsonDataSource.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageJsonDataSource.cs index 9fe4c635f3..703d38651d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageJsonDataSource.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageJsonDataSource.cs @@ -23,7 +23,7 @@ public class WinGetPackageJsonDataSource : WinGetPackageDataSource /// /// Class for deserializing a JSON winget package /// - private class JsonWinGetPackage + private sealed class JsonWinGetPackage { public Uri Uri { get; set; } @@ -34,7 +34,7 @@ private class JsonWinGetPackage /// Class for deserializing a JSON package catalog with package ids from /// winget /// - private class JsonWinGetPackageCatalog + private sealed class JsonWinGetPackageCatalog { public string NameResourceKey { get; set; } @@ -45,6 +45,7 @@ private class JsonWinGetPackageCatalog private readonly ISetupFlowStringResource _stringResource; private readonly string _fileName; + private readonly JsonSerializerOptions jsonSerializerOptions = new () { ReadCommentHandling = JsonCommentHandling.Skip }; private IList _jsonCatalogs = new List(); public override int CatalogCount => _jsonCatalogs.Count; @@ -64,8 +65,8 @@ public async override Task InitializeAsync() // Open and deserialize JSON file Log.Logger?.ReportInfo(Log.Component.AppManagement, $"Reading package list from JSON file {_fileName}"); using var fileStream = File.OpenRead(_fileName); - var options = new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip }; - _jsonCatalogs = await JsonSerializer.DeserializeAsync>(fileStream, options); + + _jsonCatalogs = await JsonSerializer.DeserializeAsync>(fileStream, jsonSerializerOptions); } public async override Task> LoadCatalogsAsync() diff --git a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw index 33688f5ce7..110f0b162f 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw +++ b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw @@ -549,6 +549,10 @@ Check the spelling or try new keywords. Text displayed when no search results were found + + Remove all + Label for removing all items from selection + Remove Text announced when screen readers focus on the 'Remove' button. The 'Remove' button allows users to remove an application from their cart diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs index 5775c2f33a..79eae11e35 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/DevDriveTaskGroup.cs @@ -39,7 +39,7 @@ public DevDriveTaskGroup(IHost host, ISetupFlowStringResource stringResource) /// public void AddDevDriveTask(IDevDrive devDrive) { - if (_devDriveTasks.Any()) + if (_devDriveTasks.Count != 0) { Log.Logger?.ReportInfo(Log.Component.DevDrive, $"Overwriting existing dev drive task"); _devDriveTasks[0].DevDrive = devDrive; @@ -61,7 +61,9 @@ public void RemoveDevDriveTasks() _devDriveTasks.Clear(); } - private readonly IList _devDriveTasks = new List(); + private readonly List _devDriveTasks = + [ + ]; public IEnumerable SetupTasks { diff --git a/tools/SetupFlow/DevHome.SetupFlow/TelemetryEvents/DevDriveTriggeredEvent.cs b/tools/SetupFlow/DevHome.SetupFlow/TelemetryEvents/DevDriveTriggeredEvent.cs index 02760849c3..2960892043 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/TelemetryEvents/DevDriveTriggeredEvent.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/TelemetryEvents/DevDriveTriggeredEvent.cs @@ -11,7 +11,7 @@ namespace DevHome.SetupFlow.Common.TelemetryEvents; [EventData] -internal class DevDriveTriggeredEvent : EventBase +internal sealed class DevDriveTriggeredEvent : EventBase { public DevDriveTriggeredEvent(IDevDrive devDrive, long duration, int hr) { diff --git a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs index ee92bef09b..db1a2b8f71 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs @@ -215,7 +215,7 @@ public static bool IsInvalidFileNameOrPath(InvalidCharactersKind type, string fi /// /// The type of invalid characters to get. Either for a path or filename /// Set of invalid characters based on the type passed in - private static ISet GetInvalidCharacters(InvalidCharactersKind type) + private static HashSet GetInvalidCharacters(InvalidCharactersKind type) { List invalidFileChars; if (type == InvalidCharactersKind.Path) diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AppManagementViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AppManagementViewModel.cs index 222380dce1..b4b979f4a1 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AppManagementViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AppManagementViewModel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; @@ -41,6 +42,8 @@ public partial class AppManagementViewModel : SetupPageViewModelBase StringResource.GetLocalized(StringResourceKey.ApplicationsAddedSingular) : StringResource.GetLocalized(StringResourceKey.ApplicationsAddedPlural, SelectedPackages.Count); + public bool EnableRemoveAll => SelectedPackages.Count > 0; + public AppManagementViewModel( ISetupFlowStringResource stringResource, SetupFlowOrchestrator orchestrator, @@ -57,6 +60,7 @@ public AppManagementViewModel( _screenReaderService = host.GetService(); _packageProvider.PackageSelectionChanged += (_, _) => OnPropertyChanged(nameof(ApplicationsAddedText)); + _packageProvider.PackageSelectionChanged += (_, _) => OnPropertyChanged(nameof(EnableRemoveAll)); PageTitle = StringResource.GetLocalized(StringResourceKey.ApplicationsPageTitle); @@ -128,4 +132,14 @@ private async Task SearchTextChangedAsync(string text, CancellationToken cancell break; } } + + [RelayCommand] + private void RemoveAllPackages() + { + Log.Logger?.ReportInfo(Log.Component.AppManagement, $"Removing all packages from selected applications for installation"); + foreach (var package in SelectedPackages.ToList()) + { + package.IsSelected = false; + } + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs index 4cb82a0106..ec82740845 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs @@ -623,7 +623,7 @@ private void ValidateDriveLetter() { DriveLetterError = null; - if (!DriveLetters.Any()) + if (DriveLetters.Count == 0) { DriveLetterError = DevDriveValidationResult.NoDriveLettersAvailable; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs index 8ed457f02c..a9fe4023bb 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs @@ -66,7 +66,7 @@ public partial class LoadingViewModel : SetupPageViewModelBase /// /// Keep track of all failed tasks so they can be re-ran if the user wishes. /// - private readonly IList _failedTasks; + private readonly List _failedTasks; public IList FailedTasks => _failedTasks; @@ -394,7 +394,7 @@ await Parallel.ForEachAsync(tasksToRunSecond, options, async (taskInformation, t }); // All the tasks are done. Re-try logic follows. - if (!_failedTasks.Any()) + if (_failedTasks.Count == 0) { Log.Logger?.ReportInfo(Log.Component.Loading, "All tasks succeeded. Moving to next page"); ExecutionFinished.Invoke(null, null); @@ -415,7 +415,7 @@ await Parallel.ForEachAsync(tasksToRunSecond, options, async (taskInformation, t IsNavigationBarVisible = true; } - if (_failedTasks.Any()) + if (_failedTasks.Count != 0) { TelemetryFactory.Get().Log("Loading_FailedTasks_Event", LogLevel.Critical, new LoadingRetryEvent(_failedTasks.Count), _activityId); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs index ffe3f14431..29602ce7a0 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs @@ -110,7 +110,7 @@ public SearchViewModel(IWindowsPackageManager wpm, ISetupFlowStringResource stri ResultPackages = await Task.Run(() => matches.Select(m => _packageProvider.CreateOrGet(m)).ToList()); // Announce the results. - if (ResultPackages.Any()) + if (ResultPackages.Count != 0) { _screenReaderService.Announce(SearchCountText); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs index 54e027a5d9..d7dde83740 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs @@ -262,7 +262,7 @@ await Task.Run(async () => /// configuration file task. /// /// List of configuration unit result - private IList GetConfigurationUnitResults() + private List GetConfigurationUnitResults() { List unitResults = new (); var configTaskGroup = _orchestrator.GetTaskGroup(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs index a0d9f825a4..2609020322 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs @@ -24,7 +24,7 @@ namespace DevHome.SetupFlow.Views; /// /// Dialog to allow users to select repositories they want to clone. /// -internal partial class AddRepoDialog : ContentDialog +internal sealed partial class AddRepoDialog : ContentDialog { private readonly string _defaultClonePath; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml index 9f5d14b9ea..ea3631dd1a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml @@ -108,11 +108,22 @@ - - - - - + + + + + + + + + + + + + - + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs index 3c033539a0..e0192099da 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs @@ -103,12 +103,12 @@ private async Task AddRepoAsync() } // Handle the case the user de-selected all repos. - if (result == ContentDialogResult.Primary && !everythingToClone.Any()) + if (result == ContentDialogResult.Primary && everythingToClone.Count == 0) { ViewModel.SaveSetupTaskInformation(everythingToClone); } - if (result == ContentDialogResult.Primary && everythingToClone.Any()) + if (result == ContentDialogResult.Primary && everythingToClone.Count != 0) { // Currently clone path supports either a local path or a new Dev Drive. Only one can be selected // during the add repo dialog flow. If multiple repositories are selected and the user chose to clone them to a Dev Drive @@ -165,7 +165,7 @@ private async Task AddRepoAsync() // Only 1 provider can be selected per repo dialog session. // Okay to use EverythingToClone[0].ProviderName here. - var providerName = _addRepoDialog.AddRepoViewModel.EverythingToClone.Any() ? _addRepoDialog.AddRepoViewModel.EverythingToClone[0].ProviderName : string.Empty; + var providerName = _addRepoDialog.AddRepoViewModel.EverythingToClone.Count != 0 ? _addRepoDialog.AddRepoViewModel.EverythingToClone[0].ProviderName : string.Empty; // If needs be, this can run inside a foreach loop to capture details on each repo. if (cloneLocationKind == CloneLocationKind.DevDrive) diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/ShimmerPackageCatalogView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/ShimmerPackageCatalogView.xaml index c35e63c51b..4f85ccaca0 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/ShimmerPackageCatalogView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/ShimmerPackageCatalogView.xaml @@ -16,16 +16,14 @@ - + diff --git a/uitest/DevHome.UITest.csproj b/uitest/DevHome.UITest.csproj index 9aac59e710..9e3952b9f1 100644 --- a/uitest/DevHome.UITest.csproj +++ b/uitest/DevHome.UITest.csproj @@ -3,6 +3,7 @@ DevHome.UITest x86;x64;arm64 + win10-x86;win10-x64;win10-arm64 false enable true @@ -12,12 +13,12 @@ - - + + - +