Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit f40e140

Browse files
authored
Single instancing & protocol activation (#261)
1 parent 9bffd64 commit f40e140

File tree

9 files changed

+156
-21
lines changed

9 files changed

+156
-21
lines changed

common/Services/INavigationService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ bool CanGoBack
2020
get; set;
2121
}
2222

23+
string DefaultPage
24+
{
25+
get; set;
26+
}
27+
2328
bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false);
2429

2530
bool GoBack();

src/App.xaml.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
using DevHome.Views;
1818
using Microsoft.Extensions.DependencyInjection;
1919
using Microsoft.Extensions.Hosting;
20+
using Microsoft.UI.Dispatching;
2021
using Microsoft.UI.Xaml;
22+
using Microsoft.Windows.AppLifecycle;
2123

2224
namespace DevHome;
2325

2426
// To learn more about WinUI 3, see https://docs.microsoft.com/windows/apps/winui/winui3/.
2527
public partial class App : Application, IApp
2628
{
29+
private readonly DispatcherQueue _dispatcherQueue;
30+
2731
// The .NET Generic Host provides dependency injection, configuration, logging, and other services.
2832
// https://docs.microsoft.com/dotnet/core/extensions/generic-host
2933
// https://docs.microsoft.com/dotnet/core/extensions/dependency-injection
@@ -44,16 +48,18 @@ public T GetService<T>()
4448
public App()
4549
{
4650
InitializeComponent();
51+
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
4752

4853
Host = Microsoft.Extensions.Hosting.Host.
4954
CreateDefaultBuilder().
5055
UseContentRoot(AppContext.BaseDirectory).
5156
ConfigureServices((context, services) =>
5257
{
5358
// Default Activation Handler
54-
services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();
59+
services.AddTransient<ActivationHandler<Microsoft.UI.Xaml.LaunchActivatedEventArgs>, DefaultActivationHandler>();
5560

5661
// Other Activation Handlers
62+
services.AddTransient<IActivationHandler, ProtocolActivationHandler>();
5763

5864
// Services
5965
services.AddSingleton<ILocalSettingsService, LocalSettingsService>();
@@ -97,6 +103,7 @@ public App()
97103
Build();
98104

99105
UnhandledException += App_UnhandledException;
106+
AppInstance.GetCurrent().Activated += OnActivated;
100107
}
101108

102109
private async void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
@@ -106,11 +113,19 @@ private async void App_UnhandledException(object sender, Microsoft.UI.Xaml.Unhan
106113
await GetService<IPluginService>().SignalStopPluginsAsync();
107114
}
108115

109-
protected async override void OnLaunched(LaunchActivatedEventArgs args)
116+
protected async override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
110117
{
111118
base.OnLaunched(args);
112119

113-
await GetService<IActivationService>().ActivateAsync(args);
120+
await GetService<IActivationService>().ActivateAsync(AppInstance.GetCurrent().GetActivatedEventArgs().Data);
114121
await GetService<IAccountsService>().InitializeAsync();
115122
}
123+
124+
private void OnActivated(object? sender, AppActivationArguments args)
125+
{
126+
_dispatcherQueue.TryEnqueue(async () =>
127+
{
128+
await GetService<IActivationService>().ActivateAsync(args.Data);
129+
});
130+
}
116131
}

src/DevHome.csproj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,23 @@
7171
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
7272
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
7373
</PropertyGroup>
74+
75+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
76+
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
77+
</PropertyGroup>
78+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
79+
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
80+
</PropertyGroup>
81+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
82+
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
83+
</PropertyGroup>
84+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
85+
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
86+
</PropertyGroup>
87+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|arm64'">
88+
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
89+
</PropertyGroup>
90+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|arm64'">
91+
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
92+
</PropertyGroup>
7493
</Project>

src/Package.appxmanifest

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
<uap3:Name>com.microsoft.devhome</uap3:Name>
2626
</uap3:AppExtensionHost>
2727
</uap3:Extension>
28+
<uap:Extension Category="windows.protocol">
29+
<uap:Protocol Name="ms-devhome">
30+
<uap:DisplayName>Dev Home</uap:DisplayName>
31+
</uap:Protocol>
32+
</uap:Extension>
2833
</Extensions>
2934
</Application>
3035
</Applications>

src/Program.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Corporation and Contributors
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Microsoft.UI.Dispatching;
10+
using Microsoft.Windows.AppLifecycle;
11+
using Windows.ApplicationModel.Activation;
12+
13+
namespace DevHome;
14+
public static class Program
15+
{
16+
[STAThread]
17+
public static void Main(string[] args)
18+
{
19+
WinRT.ComWrappersSupport.InitializeComWrappers();
20+
21+
var isRedirect = DecideRedirection().GetAwaiter().GetResult();
22+
23+
if (!isRedirect)
24+
{
25+
Microsoft.UI.Xaml.Application.Start((p) =>
26+
{
27+
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
28+
var context = new DispatcherQueueSynchronizationContext(dispatcherQueue);
29+
SynchronizationContext.SetSynchronizationContext(context);
30+
var app = new App();
31+
});
32+
}
33+
}
34+
35+
private static async Task<bool> DecideRedirection()
36+
{
37+
var mainInstance = Microsoft.Windows.AppLifecycle.AppInstance.FindOrRegisterForKey("main");
38+
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
39+
40+
if (!mainInstance.IsCurrent)
41+
{
42+
// Redirect the activation (and args) to the "main" instance, and exit.
43+
await mainInstance.RedirectActivationToAsync(activatedEventArgs);
44+
return true;
45+
}
46+
47+
return false;
48+
}
49+
}

src/Services/ActivationService.cs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using DevHome.Contracts.Services;
99
using DevHome.Views;
1010
using Microsoft.UI.Xaml;
11-
using Microsoft.UI.Xaml.Controls;
1211

1312
namespace DevHome.Services;
1413

@@ -19,6 +18,8 @@ public class ActivationService : IActivationService
1918
private readonly IThemeSelectorService _themeSelectorService;
2019
private readonly ILocalSettingsService _localSettingsService;
2120

21+
private bool _isInitialActivation = true;
22+
2223
public ActivationService(
2324
ActivationHandler<LaunchActivatedEventArgs> defaultHandler,
2425
IEnumerable<IActivationHandler> activationHandlers,
@@ -33,25 +34,27 @@ public ActivationService(
3334

3435
public async Task ActivateAsync(object activationArgs)
3536
{
36-
// Execute tasks before activation.
37-
await InitializeAsync();
38-
39-
// Set the MainWindow Content.
40-
if (App.MainWindow.Content == null)
37+
if (_isInitialActivation)
4138
{
39+
_isInitialActivation = false;
40+
41+
// Execute tasks before activation.
42+
await InitializeAsync();
43+
44+
// Set the MainWindow Content.
4245
App.MainWindow.Content = await _localSettingsService.ReadSettingAsync<bool>(WellKnownSettingsKeys.IsNotFirstRun)
4346
? Application.Current.GetService<ShellPage>()
4447
: Application.Current.GetService<InitializationPage>();
48+
49+
// Activate the MainWindow.
50+
App.MainWindow.Activate();
51+
52+
// Execute tasks after activation.
53+
await StartupAsync();
4554
}
4655

4756
// Handle activation via ActivationHandlers.
4857
await HandleActivationAsync(activationArgs);
49-
50-
// Activate the MainWindow.
51-
App.MainWindow.Activate();
52-
53-
// Execute tasks after activation.
54-
await StartupAsync();
5558
}
5659

5760
private async Task HandleActivationAsync(object activationArgs)
@@ -62,11 +65,6 @@ private async Task HandleActivationAsync(object activationArgs)
6265
{
6366
await activationHandler.HandleAsync(activationArgs);
6467
}
65-
66-
if (_defaultHandler.CanHandle(activationArgs))
67-
{
68-
await _defaultHandler.HandleAsync(activationArgs);
69-
}
7068
}
7169

7270
private async Task InitializeAsync()

src/Services/NavigationService.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using DevHome.Common.Services;
66
using DevHome.Contracts.Services;
77
using DevHome.Contracts.ViewModels;
8+
using DevHome.Dashboard.ViewModels;
89
using DevHome.Helpers;
910
using Microsoft.UI.Xaml.Controls;
1011
using Microsoft.UI.Xaml.Navigation;
@@ -18,6 +19,7 @@ public class NavigationService : INavigationService
1819
private readonly IPageService _pageService;
1920
private object? _lastParameterUsed;
2021
private Frame? _frame;
22+
private string? _defaultPage;
2123

2224
public event NavigatedEventHandler? Navigated;
2325

@@ -42,6 +44,12 @@ public Frame? Frame
4244
}
4345
}
4446

47+
public string DefaultPage
48+
{
49+
get => _defaultPage ?? typeof(DashboardViewModel).FullName ?? string.Empty;
50+
set => _defaultPage = value;
51+
}
52+
4553
[MemberNotNullWhen(true, nameof(Frame), nameof(_frame))]
4654
public bool CanGoBack => Frame != null && Frame.CanGoBack;
4755

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Corporation and Contributors
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using DevHome.Activation;
10+
using DevHome.Common.Services;
11+
using DevHome.Settings.ViewModels;
12+
using Windows.ApplicationModel.Activation;
13+
14+
namespace DevHome.Services;
15+
public class ProtocolActivationHandler : ActivationHandler<ProtocolActivatedEventArgs>
16+
{
17+
private const string SettingsAccountsUri = "settings/accounts";
18+
19+
private readonly INavigationService _navigationService;
20+
21+
public ProtocolActivationHandler(INavigationService navigationService)
22+
{
23+
this._navigationService = navigationService;
24+
}
25+
26+
protected override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
27+
{
28+
if (args.Uri.AbsolutePath == SettingsAccountsUri)
29+
{
30+
_navigationService.DefaultPage = typeof(AccountsViewModel).FullName!;
31+
_navigationService.NavigateTo(_navigationService.DefaultPage);
32+
}
33+
34+
return Task.CompletedTask;
35+
}
36+
}

src/ViewModels/ShellViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public async Task OnLoaded()
5252
{
5353
if (await _localSettingsService.ReadSettingAsync<bool>(WellKnownSettingsKeys.IsNotFirstRun))
5454
{
55-
NavigationService.NavigateTo(typeof(DashboardViewModel).FullName!);
55+
NavigationService.NavigateTo(NavigationService.DefaultPage);
5656
}
5757
else
5858
{

0 commit comments

Comments
 (0)