diff --git a/ToolingVersions.props b/ToolingVersions.props index bc7b7f2cf..fecec6449 100644 --- a/ToolingVersions.props +++ b/ToolingVersions.props @@ -3,6 +3,7 @@ net6.0-windows10.0.22000.0 - 10.0.17763.0 + 10.0.19045.0 + 10.0.19045.0 \ No newline at end of file diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index 91feb7f84..af29e034f 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -27,7 +27,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/common/Helpers/RuntimeHelper.cs b/common/Helpers/RuntimeHelper.cs index 1fef17b88..b2e6d94c3 100644 --- a/common/Helpers/RuntimeHelper.cs +++ b/common/Helpers/RuntimeHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. +using System; using Windows.Win32; using Windows.Win32.Foundation; @@ -17,4 +18,13 @@ public static bool IsMSIX return PInvoke.GetCurrentPackageFullName(ref length, null) != WIN32_ERROR.APPMODEL_ERROR_NO_PACKAGE; } } + + public static bool IsOnWindows11 + { + get + { + var version = Environment.OSVersion.Version; + return version.Major >= 10 && version.Build >= 22000; + } + } } diff --git a/common/Services/IPackageDeploymentService.cs b/common/Services/IPackageDeploymentService.cs index de248c1ab..5bdd754cd 100644 --- a/common/Services/IPackageDeploymentService.cs +++ b/common/Services/IPackageDeploymentService.cs @@ -26,6 +26,7 @@ public interface IPackageDeploymentService /// /// Find packages for the current user. If maxVersion is specified, package versions must be /// between minVersion and maxVersion. If maxVersion is null, packages must be above minVersion. + /// If no minVersion is specified, returns packages of any version. /// /// An IEnumerable containing the installed packages that meet the version criteria. public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version? maxVersion)[] ranges); diff --git a/common/Services/PackageDeploymentService.cs b/common/Services/PackageDeploymentService.cs index 74d1bb698..4b75a8a01 100644 --- a/common/Services/PackageDeploymentService.cs +++ b/common/Services/PackageDeploymentService.cs @@ -47,11 +47,13 @@ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, var version = package.Id.Version; var major = version.Major; var minor = version.Minor; + var build = version.Build; + var revision = version.Revision; Log.Logger()?.ReportInfo("PackageDeploymentService", $"Found package {package.Id.FullName}"); // Create System.Version type from PackageVersion to test. System.Version supports CompareTo() for easy comparisons. - if (IsVersionSupported(new (major, minor), ranges)) + if (IsVersionSupported(new (major, minor, build, revision), ranges)) { versionedPackages.Add(package); } @@ -79,6 +81,12 @@ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, private bool IsVersionSupported(Version target, params (Version minVersion, Version? maxVersion)[] ranges) { + // If a min version wasn't specified, any version is fine. + if (ranges.Length == 0) + { + return true; + } + foreach (var (minVersion, maxVersion) in ranges) { if (maxVersion == null) diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw index dfb89df9f..84d4a6825 100644 --- a/settings/DevHome.Settings/Strings/en-us/Resources.resw +++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw @@ -268,8 +268,8 @@ Opt into including experimentation information into your issue template - Include installed Dev Home extensions - Opt into including information on extensions into your issue template + Include installed Dev Home extensions and related packages + Opt into including information on extensions and related packages into your issue template Include my system information @@ -452,6 +452,10 @@ Extensions Label for displaying device's installed extensions + + Widget Service + Label for displaying device's installed widget service + Cancel diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml b/settings/DevHome.Settings/Views/FeedbackPage.xaml index 2e5ba8463..172aa951e 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml @@ -56,6 +56,7 @@ + diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs index 845e2ed5e..9807638ff 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs @@ -136,7 +136,7 @@ private async void DisplayReportBugDialog(object sender, RoutedEventArgs e) var extensionsInfo = string.Empty; if (ReportBugIncludeExtensions.IsChecked.GetValueOrDefault()) { - extensionsInfo = HttpUtility.UrlEncode(GetExtensions()); + extensionsInfo = HttpUtility.UrlEncode(GetExtensions() + "\n" + GetWidgetService()); } var otherSoftwareText = "OS Build Version: " + GetOSVersion() + "\n.NET Version: " + GetDotNetVersion(); @@ -196,6 +196,7 @@ private void ShowSysInfoExpander_Expanding(Expander sender, ExpanderExpandingEve private void ShowExtensionsInfoExpander_Expanding(Expander sender, ExpanderExpandingEventArgs args) { ReportBugIncludeExtensionsList.Text = GetExtensions(); + WidgetServiceInfo.Text = GetWidgetService(); } private async void Reload() @@ -328,6 +329,30 @@ private string GetExtensions() return extensionsStr; } + private string GetWidgetService() + { + var stringResource = new StringResource("DevHome.Settings/Resources"); + var widgetServiceString = stringResource.GetLocalized("Settings_Feedback_WidgetService") + ": \n"; + var packageDeploymentService = Application.Current.GetService(); + + // Only one package is expected in total from these two queries, but print anything just in case. + const string webExperienceFamilyName = "MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy"; + var webPackages = packageDeploymentService.FindPackagesForCurrentUser(webExperienceFamilyName); + foreach (var package in webPackages) + { + widgetServiceString += package.Id.FullName + "\n"; + } + + const string widgetServiceFamilyName = "Microsoft.WidgetsPlatformRuntime_8wekyb3d8bbwe"; + var widgetPackages = packageDeploymentService.FindPackagesForCurrentUser(widgetServiceFamilyName); + foreach (var package in widgetPackages) + { + widgetServiceString += package.Id.FullName + "\n"; + } + + return widgetServiceString; + } + private async void BuildExtensionButtonClicked(object sender, RoutedEventArgs e) { await Launcher.LaunchUriAsync(new ("https://go.microsoft.com/fwlink/?linkid=2234795")); diff --git a/src/DevHome.csproj b/src/DevHome.csproj index 7d14e7e04..f3e47f6c6 100644 --- a/src/DevHome.csproj +++ b/src/DevHome.csproj @@ -5,11 +5,6 @@ WinExe - - - 10.0.22000.0 - 10.0.22000.0 DevHome Assets/DevHome.ico app.manifest diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest index 68a866e3b..29b96da8c 100644 --- a/src/Package.appxmanifest +++ b/src/Package.appxmanifest @@ -18,7 +18,7 @@ disabled - + diff --git a/src/Services/ActivationService.cs b/src/Services/ActivationService.cs index 4b565d15b..5fdedce40 100644 --- a/src/Services/ActivationService.cs +++ b/src/Services/ActivationService.cs @@ -13,7 +13,6 @@ namespace DevHome.Services; public class ActivationService : IActivationService { - private readonly ActivationHandler _defaultHandler; private readonly IEnumerable _activationHandlers; private readonly IThemeSelectorService _themeSelectorService; private readonly ILocalSettingsService _localSettingsService; @@ -21,12 +20,10 @@ public class ActivationService : IActivationService private bool _isInitialActivation = true; public ActivationService( - ActivationHandler defaultHandler, IEnumerable activationHandlers, IThemeSelectorService themeSelectorService, ILocalSettingsService localSettingsService) { - _defaultHandler = defaultHandler; _activationHandlers = activationHandlers; _themeSelectorService = themeSelectorService; _localSettingsService = localSettingsService; @@ -41,8 +38,13 @@ public async Task ActivateAsync(object activationArgs) // Execute tasks before activation. await InitializeAsync(); + // We can skip the initialization page if it's not our first run and we're on Windows 11. + // If we're on Windows 10, we need to go to the initialization page to install the WidgetService if we don't have it already. + var skipInitialization = await _localSettingsService.ReadSettingAsync(WellKnownSettingsKeys.IsNotFirstRun) + && RuntimeHelper.IsOnWindows11; + // Set the MainWindow Content. - App.MainWindow.Content = await _localSettingsService.ReadSettingAsync(WellKnownSettingsKeys.IsNotFirstRun) + App.MainWindow.Content = skipInitialization ? Application.Current.GetService() : Application.Current.GetService(); diff --git a/src/ViewModels/InitializationViewModel.cs b/src/ViewModels/InitializationViewModel.cs index bff8764c0..80465db25 100644 --- a/src/ViewModels/InitializationViewModel.cs +++ b/src/ViewModels/InitializationViewModel.cs @@ -3,25 +3,36 @@ using CommunityToolkit.Mvvm.ComponentModel; using DevHome.Common.Extensions; -using DevHome.Common.Services; using DevHome.Contracts.Services; +using DevHome.Dashboard.Services; +using DevHome.Logging; using DevHome.Views; using Microsoft.UI.Xaml; namespace DevHome.ViewModels; + public class InitializationViewModel : ObservableObject { private readonly IThemeSelectorService _themeSelector; - private readonly IExtensionService _extensionService; + private readonly IWidgetHostingService _widgetHostingService; - public InitializationViewModel(IThemeSelectorService themeSelector, IExtensionService extensionService) + public InitializationViewModel(IThemeSelectorService themeSelector, IWidgetHostingService widgetHostingService) { _themeSelector = themeSelector; - _extensionService = extensionService; + _widgetHostingService = widgetHostingService; } - public void OnPageLoaded() + public async void OnPageLoaded() { + try + { + await _widgetHostingService.EnsureWidgetServiceAsync(); + } + catch (Exception ex) + { + GlobalLog.Logger?.ReportInfo("InitializationViewModel", "Installing WidgetService failed: ", ex); + } + App.MainWindow.Content = Application.Current.GetService(); _themeSelector.SetRequestedTheme(); diff --git a/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj b/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj index 53a13a0e3..dc6cb0958 100644 --- a/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj +++ b/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj @@ -20,6 +20,7 @@ + diff --git a/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs index cdbed78a7..20e066af8 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/IWidgetHostingService.cs @@ -3,11 +3,16 @@ using System.Threading.Tasks; using Microsoft.Windows.Widgets.Hosts; +using static DevHome.Dashboard.Services.WidgetHostingService; namespace DevHome.Dashboard.Services; public interface IWidgetHostingService { + public Task EnsureWidgetServiceAsync(); + + public WidgetServiceStates GetWidgetServiceState(); + public Task GetWidgetHostAsync(); public Task GetWidgetCatalogAsync(); diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs index 9946f65f2..7c774b1df 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs @@ -2,19 +2,181 @@ // Licensed under the MIT license. using System; +using System.Linq; using System.Threading.Tasks; -using DevHome.Dashboard.Helpers; +using DevHome.Common.Helpers; +using DevHome.Common.Services; using Microsoft.Windows.Widgets.Hosts; +using Windows.ApplicationModel.Store.Preview.InstallControl; +using Log = DevHome.Dashboard.Helpers.Log; namespace DevHome.Dashboard.Services; public class WidgetHostingService : IWidgetHostingService { + private readonly IPackageDeploymentService _packageDeploymentService; + + private static readonly string WidgetServiceStorePackageId = "9N3RK8ZV2ZR8"; + private static readonly TimeSpan StoreInstallTimeout = new (0, 0, 60); + private WidgetHost _widgetHost; private WidgetCatalog _widgetCatalog; - public WidgetHostingService() + private WidgetServiceStates _widgetServiceState = WidgetServiceStates.Unknown; + + public WidgetServiceStates GetWidgetServiceState() => _widgetServiceState; + + public enum WidgetServiceStates + { + HasWebExperienceGoodVersion, + HasWebExperienceNoOrBadVersion, + HasStoreWidgetServiceGoodVersion, + HasStoreWidgetServiceNoOrBadVersion, + Unknown, + } + + public WidgetHostingService(IPackageDeploymentService packageDeploymentService) + { + _packageDeploymentService = packageDeploymentService; + } + + public async Task EnsureWidgetServiceAsync() + { + // If we're on Windows 11, check if we have the right WebExperiencePack version of the WidgetService. + if (RuntimeHelper.IsOnWindows11) + { + if (HasValidWebExperiencePack()) + { + Log.Logger()?.ReportInfo("WidgetHostingService", "On Windows 11, HasWebExperienceGoodVersion"); + _widgetServiceState = WidgetServiceStates.HasWebExperienceGoodVersion; + return true; + } + else + { + Log.Logger()?.ReportInfo("WidgetHostingService", "On Windows 11, HasWebExperienceNoOrBadVersion"); + _widgetServiceState = WidgetServiceStates.HasWebExperienceNoOrBadVersion; + return false; + } + } + else + { + // If we're on Windows 10, check if we have the store version installed. Check against what's really + // installed instead of the enum, just in case something changed between startup and now. + if (HasValidWidgetServicePackage()) + { + Log.Logger()?.ReportInfo("WidgetHostingService", "On Windows 10, HasStoreWidgetServiceGoodVersion"); + _widgetServiceState = WidgetServiceStates.HasStoreWidgetServiceGoodVersion; + return true; + } + else if (_widgetServiceState == WidgetServiceStates.HasStoreWidgetServiceNoOrBadVersion) + { + // If it's not there and we already knew that, it means we tried to install during setup and it failed. + // Don't try again when we get to the Dashboard, it takes too long. + Log.Logger()?.ReportInfo("WidgetHostingService", "On Windows 10, already HasStoreWidgetServiceNoOrBadVersion"); + return false; + } + else + { + // Try to install and report the outcome. + Log.Logger()?.ReportInfo("WidgetHostingService", "On Windows 10, TryInstallWidgetServicePackageAsync..."); + var installedSuccessfully = await TryInstallWidgetServicePackageAsync(); + _widgetServiceState = installedSuccessfully ? WidgetServiceStates.HasStoreWidgetServiceGoodVersion : WidgetServiceStates.HasStoreWidgetServiceNoOrBadVersion; + Log.Logger()?.ReportInfo("WidgetHostingService", $"On Windows 10, ...{_widgetServiceState}"); + return installedSuccessfully; + } + } + } + + private bool HasValidWebExperiencePack() + { + var minSupportedVersion400 = new Version(423, 3800); + var minSupportedVersion500 = new Version(523, 3300); + var version500 = new Version(500, 0); + + // Ensure the application is installed, and the version is high enough. + const string packageFamilyName = "MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy"; + var packages = _packageDeploymentService.FindPackagesForCurrentUser( + packageFamilyName, + (minSupportedVersion400, version500), + (minSupportedVersion500, null)); + return packages.Any(); + } + + private bool HasValidWidgetServicePackage() { + var minSupportedVersion = new Version(1, 0, 0, 0); + + const string packageFamilyName = "Microsoft.WidgetsPlatformRuntime_8wekyb3d8bbwe"; + var packages = _packageDeploymentService.FindPackagesForCurrentUser(packageFamilyName, (minSupportedVersion, null)); + 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() diff --git a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw index f0375eb23..7319078d3 100644 --- a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw +++ b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw @@ -130,6 +130,18 @@ + Add new widget The hyperlink to bring the user to the Add Widget dialog. Shows if the user hasn't added any widgets. + + We're having trouble displaying widgets. Restarting Dev Home may help. + Message shown when there's no widget service and the user should restart Dev Home. + + + You do not have the required version of the Windows Web Experience Pack to display widgets. Please ensure you have the latest version installed and then restart Dev Home. + Message shown when no widgets have been added to the Dashboard + + + Get in Store app + Button text to go to store listing + This widget could not be displayed Message shown in the widget if the widget was given bad data and can't be rendered. diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs index c822e41c7..bb0d8e700 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation and Contributors // Licensed under the MIT license. -using System; -using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; -using DevHome.Common.Services; using DevHome.Dashboard.Services; using Microsoft.UI.Xaml; @@ -16,48 +13,23 @@ public partial class DashboardViewModel : ObservableObject public IWidgetIconService WidgetIconService { get; } - private readonly IPackageDeploymentService _packageDeploymentService; - - private bool _validatedWebExpPack; - [ObservableProperty] private bool _isLoading; + [ObservableProperty] + private bool _hasWidgetService; + public DashboardViewModel( - IPackageDeploymentService packageDeploymentService, IWidgetHostingService widgetHostingService, IWidgetIconService widgetIconService) { - _packageDeploymentService = packageDeploymentService; WidgetIconService = widgetIconService; WidgetHostingService = widgetHostingService; } - public bool EnsureWebExperiencePack() - { - // If already validated there's a good version, don't check again. - if (_validatedWebExpPack) - { - return true; - } - - var minSupportedVersion400 = new Version(423, 3800); - var minSupportedVersion500 = new Version(523, 3300); - var version500 = new Version(500, 0); - - // Ensure the application is installed, and the version is high enough. - const string packageFamilyName = "MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy"; - var packages = _packageDeploymentService.FindPackagesForCurrentUser( - packageFamilyName, - (minSupportedVersion400, version500), - (minSupportedVersion500, null)); - _validatedWebExpPack = packages.Any(); - return _validatedWebExpPack; - } - public Visibility GetNoWidgetMessageVisibility(int widgetCount, bool isLoading) { - if (widgetCount == 0 && !isLoading) + if (widgetCount == 0 && !isLoading && HasWidgetService) { return Visibility.Visible; } diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml index 19e5ab89d..cdc5617fe 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml @@ -13,8 +13,6 @@ xmlns:views="using:DevHome.Dashboard.Views" xmlns:controls="using:DevHome.Dashboard.Controls" xmlns:behaviors="using:DevHome.Common.Behaviors" - xmlns:i="using:Microsoft.Xaml.Interactivity" - xmlns:ic="using:Microsoft.Xaml.Interactions.Core" xmlns:converters="using:CommunityToolkit.WinUI.Converters" behaviors:NavigationViewHeaderBehavior.HeaderMode="Never" mc:Ignorable="d"> @@ -45,13 +43,11 @@ - +