diff --git a/.github/policies/scheduledSearch.closeNoRecentActivity.yml b/.github/policies/scheduledSearch.closeNoRecentActivity.yml index bceac75ee..ad045df74 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 4de6122bc..bda6f42a6 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 0dc99c78a..0fbbb1baf 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 bcfbb77c0..023fe7c6e 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 7455a9ebe..264238900 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 3e97d3783..01f32e3f7 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 cb1bd9aac..482212233 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 237a336a7..cd5ef3157 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 9ecda6ca8..c5aa4b6d0 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 9876228fa..97082964e 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 6f2d74fb3..e18fe80fe 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 6950a569d..16b10261c 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 3664213b8..234e69b74 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 0562cb61b..04e8360c8 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 7ec08d6bc..de965ed93 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 c32295964..46441eaf6 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 cfae9f31f..11307afe9 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 3969bf9c4..0de373aaf 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 1005d4c65..307440376 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 2892abac7..bf46b8135 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 8ad6742d1..68700f0bf 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 24b9f1268..1ce0b347f 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 c561a13b3..4a5a11ac6 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 09c9057f6..8ae5b289b 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 7f11b7d70..26c2c9766 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 69d835e8b..74fc67f7e 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 0e0e604ec..58247ad83 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 37ced9e21..262e6bf42 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 ed2c0f224..d3139c273 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 d0cc874ab..2ef4c992b 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 b149aa7f4..343557a47 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 fecec6449..955145dbd 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 5fce2221b..fe5883fbe 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 cec394b20..cd12994b4 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 d269e1347..7faaf33eb 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 eba887f23..4802d86c3 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 3d9fe9869..82424f086 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 c05e74bdc..316ba5fb4 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 55a79355f..885164739 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 19b6a3461..a5905cfe2 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 3474c3ba8..84a9f9b89 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 7b0e4b3bc..c00851787 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 a5a3ef145..645869d71 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 9bcf31cda..477b835c8 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 ad93ee163..56978221a 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 8cdde00d9..db071ceba 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 7dcf58847..d0b531839 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 000000000..5b240a86c --- /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 000000000..4b39ece3a 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 000000000..ad6eef1ae 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 000000000..a03e38fc6 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 000000000..69cb35810 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 000000000..89c0b0b38 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 000000000..cb03baaa8 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 000000000..c2aa300ce 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 136cfb14f..cf8f42744 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 1af96b276..831f452f9 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 2ff146750..3c3895198 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 f50e97da4..9da66f1b8 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 7ff3c8b0c..44153a8ea 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 ef55d694c..65a708358 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 60436cd53..bfc44bfbe 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 34d052e80..a71518263 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 798ad88b0..6d36b2ff9 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 90bc5af8d..c763355a7 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 5e5fda489..890675e4c 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 bb9f48455..6ad04cc6c 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 ee1d0c371..749d0bbe0 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 87e026792..b273297a4 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 7534a4fb4..ed3dc79d1 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 9807638ff..9e76b7cd5 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 f49ede7ce..0e4b3517e 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 5a8b1d865..4d3506623 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 de5d2578a..439caf03c 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 000000000..536404e34 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 000000000..61b7bfa36 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 000000000..8e446b907 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 000000000..17a417f18 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 000000000..97d19266b 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 000000000..9f77c6e18 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 000000000..6858c0a06 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 000000000..628ec92c9 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 000000000..a1922d067 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 000000000..377fc28d1 Binary files /dev/null and b/src/Assets/Preview/SSHScreenshotLight.png differ diff --git a/src/DevHome.csproj b/src/DevHome.csproj index f3e47f6c6..4ffdcf19c 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 6128ea35a..5639a65f4 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 7a67a4019..95be03b4a 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 9bcadfa8c..6ef724bbf 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 29b96da8c..100954e88 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 baf47c6a3..cc106d69c 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 46d36e52b..22c053284 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 80465db25..91bdc9e57 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 8eaec4db1..4d17752ed 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 79021fbac..22b271db0 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 6f6f70eae..108bd06c6 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 5abc055b3..08396c0ef 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 95bfb5919..f9739d6db 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 1bdec52d7..ccf2b90e3 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 dc6cb0958..fb9a5f568 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 0b19894d8..26cf6ed7d 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 cb5d8fa0b..a9cec70f5 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 000000000..db73506da --- /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 7c774b1df..6f139add1 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 a338be6dc..4cec6fe43 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 000000000..26d142a89 --- /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 000000000..af08f7bb4 --- /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 8251a3470..cb80fd837 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 581189649..3ab27e2e7 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 32ace0766..1ec702d83 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 c7e05ab11..bdcdaab13 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 80aa5c52a..d66913716 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 10c0975a8..1e605ae57 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 6aa30142b..ae504f411 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 6c45bb15c..d2f2d6dbf 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 b434cfb3a..3a3f245bc 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 1728f021e..268bf441c 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 95c88be29..2524aecf0 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 aeeb78301..43340f6ea 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 6bdf0db08..4ed1b8fac 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 acaef8635..f98826c9f 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 9aab85a39..55845dbb5 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 7560b38a6..b7ad842a2 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 65d855e60..4052adfc2 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 caf8afde6..ed7c8e137 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 092e7cd72..27db9b414 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 bf46c8713..4505a0995 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 0d17f2432..3d299c4de 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 a89117878..464f3193f 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 0c4069e2e..279fb8b43 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 d10180674..5e5786c6c 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 d4bcc73ff..cc25e9207 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 cbff14b99..860ee026f 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 c75e9b5d2..1a2e2c234 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 141ea7ee9..749c72fec 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 2a5c6cc87..bff311b2d 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 fc59c95bf..d2af28834 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 baa40d4c0..3e66c0bba 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 3848cc765..2313d0dc0 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 fe3b3ada8..dca2b3a7b 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 2e4a8d44f..219448f63 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 9fe4c635f..703d38651 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 33688f5ce..110f0b162 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 5775c2f33..79eae11e3 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 02760849c..296089204 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 ee92bef09..db1a2b8f7 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 222380dce..b4b979f4a 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 4cb82a010..ec8274084 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 8ed457f02..a9fe4023b 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 ffe3f1443..29602ce7a 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 54e027a5d..d7dde8374 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 a0d9f825a..260902032 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 9f5d14b9e..ea3631dd1 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 3c033539a..e0192099d 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 c35e63c51..4f85ccaca 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 9aac59e71..9e3952b9f 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 @@ - - + + - +