Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activate application from .winget files #2379

Merged
merged 12 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using CommunityToolkit.WinUI;
using DevHome.Activation;
using DevHome.Common.Contracts;
using DevHome.Common.Contracts.Services;
Expand Down Expand Up @@ -84,10 +85,11 @@ public App()
ConfigureServices((context, services) =>
{
// Default Activation Handler
services.AddTransient<ActivationHandler<Microsoft.UI.Xaml.LaunchActivatedEventArgs>, DefaultActivationHandler>();
services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();

// Other Activation Handlers
services.AddTransient<IActivationHandler, ProtocolActivationHandler>();
services.AddTransient<IActivationHandler, DSCFileActivationHandler>();

// Services
services.AddSingleton<ILocalSettingsService, LocalSettingsService>();
Expand Down Expand Up @@ -185,17 +187,19 @@ await Task.WhenAll(
GetService<IAppManagementInitializer>().InitializeAsync());
}

private void OnActivated(object? sender, AppActivationArguments args)
private async void OnActivated(object? sender, AppActivationArguments args)
{
if (args.Kind == ExtendedActivationKind.ToastNotification)
{
GetService<ToastNotificationService>().HandlerNotificationActions(args);
return;
}

_dispatcherQueue.TryEnqueue(async () =>
{
await GetService<IActivationService>().ActivateAsync(args.Data);
});
// Note: Keep the reference to 'args.Data' object, as 'args' may be
// disposed before the async operation completes (RpcCallFailed: 0x800706be)
var localArgsDataReference = args.Data;

// Activate the app and ensure the appropriate handlers are called.
await _dispatcherQueue.EnqueueAsync(async () => await GetService<IActivationService>().ActivateAsync(localArgsDataReference));
}
}
7 changes: 7 additions & 0 deletions src/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="winget">
<uap:SupportedFileTypes>
<uap:FileType>.winget</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions>
</Application>
</Applications>
Expand Down
2 changes: 1 addition & 1 deletion src/Properties/launchsettings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"profiles": {
"DevHome (Package)": {
"commandName": "MsixPackage"
Expand Down
8 changes: 8 additions & 0 deletions src/Services/ActivationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ namespace DevHome.Services;

public class ActivationService : IActivationService
{
private readonly ActivationHandler<LaunchActivatedEventArgs> _defaultHandler;
private readonly IEnumerable<IActivationHandler> _activationHandlers;
private readonly IThemeSelectorService _themeSelectorService;
private readonly ILocalSettingsService _localSettingsService;

private bool _isInitialActivation = true;

public ActivationService(
ActivationHandler<LaunchActivatedEventArgs> defaultHandler,
IEnumerable<IActivationHandler> activationHandlers,
IThemeSelectorService themeSelectorService,
ILocalSettingsService localSettingsService)
{
_defaultHandler = defaultHandler;
_activationHandlers = activationHandlers;
_themeSelectorService = themeSelectorService;
_localSettingsService = localSettingsService;
Expand Down Expand Up @@ -64,6 +67,11 @@ private async Task HandleActivationAsync(object activationArgs)
{
await activationHandler.HandleAsync(activationArgs);
}

AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
if (_defaultHandler.CanHandle(activationArgs))
{
await _defaultHandler.HandleAsync(activationArgs);
}
}

private async Task InitializeAsync()
Expand Down
103 changes: 103 additions & 0 deletions src/Services/DSCFileActivationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics;
using DevHome.Activation;
using DevHome.Common.Extensions;
using DevHome.Common.Services;
using DevHome.SetupFlow.Common.Helpers;
using DevHome.SetupFlow.Services;
using DevHome.SetupFlow.ViewModels;
using Microsoft.UI.Xaml;
using Windows.ApplicationModel.Activation;
using Windows.Storage;

namespace DevHome.Services;

/// <summary>
/// Class that handles the activation of the application when a DSC file (*.winget) is opened
/// </summary>
public class DSCFileActivationHandler : ActivationHandler<FileActivatedEventArgs>
{
private const string WinGetFileExtension = ".winget";
private readonly ISetupFlowStringResource _setupFlowStringResource;
private readonly INavigationService _navigationService;
private readonly SetupFlowViewModel _setupFlowViewModel;
private readonly SetupFlowOrchestrator _setupFlowOrchestrator;
private readonly WindowEx _mainWindow;

public DSCFileActivationHandler(
ISetupFlowStringResource setupFlowStringResource,
INavigationService navigationService,
SetupFlowOrchestrator setupFlowOrchestrator,
SetupFlowViewModel setupFlowViewModel,
WindowEx mainWindow)
{
_setupFlowStringResource = setupFlowStringResource;
_setupFlowViewModel = setupFlowViewModel;
_setupFlowOrchestrator = setupFlowOrchestrator;
_navigationService = navigationService;
_mainWindow = mainWindow;
}

protected override bool CanHandleInternal(FileActivatedEventArgs args)
{
return args.Files.Count > 0 && args.Files[0] is StorageFile file && file.FileType == WinGetFileExtension;
}

protected async override Task HandleInternalAsync(FileActivatedEventArgs args)
{
Debug.Assert(_navigationService.Frame != null, "Main window content is expected to be set before activation handlers are executed");
var file = (StorageFile)args.Files[0];
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
async void DSCActivationFlowHandlerAsync(object sender, RoutedEventArgs e)
{
// Only execute once
_navigationService.Frame!.Loaded -= DSCActivationFlowHandlerAsync;
await DSCActivationFlowAsync(file);
}

// If the application was activated from a file, the XamlRoot here is null
if (_navigationService.Frame.XamlRoot == null)
{
// Wait until the frame is loaded before starting the flow
Log.Logger?.ReportInfo("DSC flow activated from a file but the application is not yet ready. Activation will start once the page is loaded.");
_navigationService.Frame!.Loaded += DSCActivationFlowHandlerAsync;
}
else
{
// If the application was already running, start the flow immediately
await DSCActivationFlowAsync(file);
}
}

/// <summary>
/// Navigates to the setup flow and starts the DSC activation flow
/// </summary>
/// <param name="file">The DSC file to activate</param>
private async Task DSCActivationFlowAsync(StorageFile file)
{
try
{
// Don't interrupt the user if the machine configuration is in progress
if (_setupFlowOrchestrator.IsMachineConfigurationInProgress)
{
Log.Logger?.ReportWarn("Cannot activate the DSC flow because the machine configuration is in progress");
await _mainWindow.ShowErrorMessageDialogAsync(
_setupFlowStringResource.GetLocalized(StringResourceKey.ConfigurationViewTitle, file.Name),
_setupFlowStringResource.GetLocalized(StringResourceKey.ConfigurationActivationFailedBusy),
_setupFlowStringResource.GetLocalized(StringResourceKey.Close));
}
else
{
// Start the setup flow with the DSC file
Log.Logger?.ReportInfo("Starting DSC file activation");
_navigationService.NavigateTo(typeof(SetupFlowViewModel).FullName!);
await _setupFlowViewModel.StartFileActivationFlowAsync(file);
}
}
catch (Exception ex)
{
Log.Logger?.ReportError("Error executing the DSC activation flow", ex);
}
}
}
16 changes: 10 additions & 6 deletions src/ViewModels/ShellViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using DevHome.Common.Services;
using DevHome.Contracts.Services;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Windows.AppLifecycle;

namespace DevHome.ViewModels;

Expand Down Expand Up @@ -51,13 +52,16 @@ public ShellViewModel(

public async Task OnLoaded()
{
if (await _localSettingsService.ReadSettingAsync<bool>(WellKnownSettingsKeys.IsNotFirstRun))
switch (AppInstance.GetCurrent().GetActivatedEventArgs().Kind)
{
NavigationService.NavigateTo(NavigationService.DefaultPage);
}
else
{
NavigationService.NavigateTo(typeof(WhatsNewViewModel).FullName!);
case ExtendedActivationKind.File:
Copy link
Contributor Author

@AmelBawa-msft AmelBawa-msft Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create an issue to consider moving this methods logic to an activation handler instead

// Allow the file activation handler to navigate to the appropriate page.
break;
case ExtendedActivationKind.Launch:
default:
var isNotFirstRun = await _localSettingsService.ReadSettingAsync<bool>(WellKnownSettingsKeys.IsNotFirstRun);
NavigationService.NavigateTo(isNotFirstRun ? NavigationService.DefaultPage : typeof(WhatsNewViewModel).FullName!);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ public IReadOnlyList<SetupPageViewModelBase> FlowPages

public bool HasPreviousPage => _currentPageIndex > 0;

public bool IsMachineConfigurationInProgress => FlowPages.Count > 1;

/// <summary>
/// Gets or sets a value indicating whether the done button should be shown. When false, the cancel
/// hyperlink button will be shown in the UI.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public static class StringResourceKey
public static readonly string ConfigurationUnitSummaryNoId = nameof(ConfigurationUnitSummaryNoId);
public static readonly string ConfigurationUnitStats = nameof(ConfigurationUnitStats);
public static readonly string ConfigurationViewTitle = nameof(ConfigurationViewTitle);
public static readonly string ConfigurationActivationFailedDisabled = nameof(ConfigurationActivationFailedDisabled);
public static readonly string ConfigurationActivationFailedBusy = nameof(ConfigurationActivationFailedBusy);
public static readonly string DevDriveReviewTitle = nameof(DevDriveReviewTitle);
public static readonly string DevDriveDefaultFileName = nameof(DevDriveDefaultFileName);
public static readonly string DevDriveDefaultFolderName = nameof(DevDriveDefaultFolderName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@
<value>View {0}</value>
<comment>{Locked="{0}"}Title for the page showing the contents of a configuration file. {0} is replaced by the configuration file name.</comment>
</data>
<data name="ConfigurationActivationFailedDisabled" xml:space="preserve">
<value>Winget configuration is currently disabled on your machine. We are initiating the enablement process, which may take a few minutes. Please try again soon.</value>
<comment>Message displayed to the user when they attempt to activate Dev Home from a file but the Winget configuration feature is disabled.</comment>
</data>
<data name="ConfigurationActivationFailedBusy" xml:space="preserve">
<value>The DSC flow cannot be activated while machine configuration is in progress. Please complete your current configuration tasks and then try again.</value>
<comment>Message displayed to the user when they attempt to activate Dev Home from a file but the machine configuration flow is currently in progress.</comment>
</data>
<data name="ConfigurationUnitSuccess" xml:space="preserve">
<value>Configuration successfully applied</value>
<comment>Message displayed when applying a configuration unit succeeds.</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using DevHome.SetupFlow.Models;
using DevHome.SetupFlow.ViewModels;
using Windows.Storage;

namespace DevHome.SetupFlow.TaskGroups;

Expand All @@ -19,6 +20,8 @@ public ConfigurationFileTaskGroup(ConfigurationFileViewModel configurationFileVi
}

public async Task<bool> PickConfigurationFileAsync() => await _viewModel.PickConfigurationFileAsync();

public async Task<bool> LoadFromLocalFileAsync(StorageFile file) => await _viewModel.LoadFileAsync(file);

public IEnumerable<ISetupTask> SetupTasks => _viewModel.TaskList;

Expand Down
Loading
Loading