Skip to content

Commit 4ef5eec

Browse files
committed
Added simple URI activation params
1 parent 41f3e4e commit 4ef5eec

3 files changed

Lines changed: 96 additions & 22 deletions

File tree

src/Platforms/SecureFolderFS.Uno/App.xaml.cs

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Windows.ApplicationModel;
88
using Windows.ApplicationModel.Activation;
99
using Windows.Storage;
10+
using CommunityToolkit.Mvvm.Messaging;
1011
using H.NotifyIcon;
1112
using Microsoft.Extensions.DependencyInjection;
1213
using Microsoft.Extensions.Logging;
@@ -15,7 +16,9 @@
1516
using OwlCore.Storage;
1617
using SecureFolderFS.Sdk.AppModels;
1718
using SecureFolderFS.Sdk.DataModels;
19+
using SecureFolderFS.Sdk.Messages;
1820
using SecureFolderFS.Sdk.Services;
21+
using SecureFolderFS.Sdk.ViewModels;
1922
using SecureFolderFS.Sdk.ViewModels.Views.Host;
2023
using SecureFolderFS.Sdk.ViewModels.Views.Root;
2124
using SecureFolderFS.Shared;
@@ -154,12 +157,13 @@ await SafetyHelpers.NoFailureAsync(async () =>
154157
#if WINDOWS
155158
// Check if the app was launched via file activation (shortcut file)
156159
var isShortcutActivation = IsShortcutFileActivation(Program.InitialActivationArgs);
160+
var isUriActivation = IsUriActivation(Program.InitialActivationArgs);
157161

158162
// Activate MainWindow (required for initialization)
159163
MainWindow.Activate();
160164

161165
// If launched via shortcut file, hide the main window immediately
162-
if (isShortcutActivation)
166+
if (isShortcutActivation || isUriActivation)
163167
MainWindow.Hide(enableEfficiencyMode: false);
164168

165169
// Process initial file activation if the app was launched via file association
@@ -190,24 +194,36 @@ private static bool IsShortcutFileActivation(AppActivationArguments? args)
190194
return file is IStorageFile storageFile &&
191195
storageFile.Path.EndsWith(UI.Constants.FileNames.VAULT_SHORTCUT_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase);
192196
}
197+
198+
private static bool IsUriActivation(AppActivationArguments? args)
199+
{
200+
return args is { Kind: ExtendedActivationKind.Protocol, Data: IProtocolActivatedEventArgs };
201+
}
193202
#endif
194203

195204
/// <summary>
196205
/// Invoked when the application is activated by opening a file.
197206
/// </summary>
198207
public async Task OnActivatedAsync(AppActivationArguments args)
199208
{
200-
if (args.Kind != ExtendedActivationKind.File)
201-
return;
209+
if (args.Kind == ExtendedActivationKind.File)
210+
{
211+
if (args.Data is not IFileActivatedEventArgs fileArgs)
212+
return;
202213

203-
if (args.Data is not IFileActivatedEventArgs fileArgs)
204-
return;
214+
var file = fileArgs.Files.Count > 0 ? fileArgs.Files[0] : null;
215+
if (file is not IStorageFile storageFile || !storageFile.Path.EndsWith(Constants.FileNames.VAULT_SHORTCUT_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
216+
return;
205217

206-
var file = fileArgs.Files.Count > 0 ? fileArgs.Files[0] : null;
207-
if (file is not IStorageFile storageFile || !storageFile.Path.EndsWith(Constants.FileNames.VAULT_SHORTCUT_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
208-
return;
218+
await HandleVaultShortcutActivationAsync(storageFile.Path);
219+
}
220+
else if (args.Kind == ExtendedActivationKind.Protocol)
221+
{
222+
if (args.Data is not IProtocolActivatedEventArgs protocolArgs)
223+
return;
209224

210-
await HandleVaultShortcutActivationAsync(storageFile.Path);
225+
await HandleUriActivationAsync(protocolArgs.Uri);
226+
}
211227
}
212228

213229
/// <summary>
@@ -220,14 +236,21 @@ public async Task HandleVaultShortcutActivationAsync(string filePath)
220236
await using var shortcutStream = await shortcutFile.OpenReadAsync(default);
221237

222238
var shortcutData = await SerializationExtensions.DeserializeAsync<Stream, VaultShortcutDataModel>(StreamSerializer.Instance, shortcutStream);
223-
if (shortcutData?.PersistableId is null || MainViewModel is null)
239+
if (shortcutData?.PersistableId is null)
240+
return;
241+
242+
await HandleVaultPreviewActivationAsync(shortcutData.PersistableId);
243+
}
244+
245+
private async Task HandleVaultPreviewActivationAsync(string persistableId)
246+
{
247+
if (MainViewModel is null)
224248
return;
225249

226-
// Wait for the main window to finish initializing (vault collection loaded, navigation set up)
227250
await MainWindowInitialized.Task;
228251

229-
// Find the vault in the collection by PersistableId
230-
var listItemViewModel = MainViewModel.VaultListViewModel.Items.FirstOrDefault(x => x.VaultViewModel.VaultModel.DataModel.PersistableId == shortcutData.PersistableId);
252+
var listItemViewModel = MainViewModel.VaultListViewModel.Items.FirstOrDefault(x =>
253+
x.VaultViewModel.VaultModel.DataModel.PersistableId == persistableId);
231254
if (listItemViewModel is null)
232255
return;
233256

@@ -237,11 +260,9 @@ await MainWindowSynchronizationContext.PostOrExecuteAsync(async () =>
237260
if (MainViewModel.RootNavigationService.CurrentView is not MainHostViewModel mainHostViewModel)
238261
return;
239262

240-
// Create the preview window
241263
var window = new Window();
242264
window.Closed += PreviewWindow_Closed;
243265

244-
// Initialize preview view model
245266
var title = $"{nameof(SecureFolderFS)} - {listItemViewModel.VaultViewModel.Title}";
246267
var vaultPreviewViewModel = !vaultViewModel.IsUnlocked
247268
? new VaultPreviewViewModel(vaultViewModel, mainHostViewModel.NavigationService)
@@ -254,18 +275,13 @@ await MainWindowSynchronizationContext.PostOrExecuteAsync(async () =>
254275
EnsureEarlyWindow(window, title);
255276

256277
#if WINDOWS
257-
// Get BoundsManager
258278
var boundsManager = Platforms.Windows.Helpers.WindowsBoundsManager.AddOrGet(window);
259-
260-
// Set minimum window size
261279
boundsManager.MinWidth = 464;
262280
boundsManager.MinHeight = 640;
263281
window.AppWindow.MoveAndResize(new(100, 100, 464, 640));
264282
#endif
265283

266-
// Initialize the login view model
267284
await vaultPreviewViewModel.InitAsync();
268-
269285
window.Activate();
270286
});
271287

@@ -279,6 +295,54 @@ static void PreviewWindow_Closed(object sender, WindowEventArgs args)
279295
}
280296
}
281297

298+
private async Task HandleVaultLockActivationAsync(string persistableId)
299+
{
300+
if (MainViewModel is null)
301+
return;
302+
303+
await MainWindowInitialized.Task;
304+
305+
var listItemViewModel = MainViewModel.VaultListViewModel.Items.FirstOrDefault(x =>
306+
x.VaultViewModel.VaultModel.DataModel.PersistableId == persistableId);
307+
if (listItemViewModel is null)
308+
return;
309+
310+
var vaultViewModel = listItemViewModel.VaultViewModel;
311+
if (!vaultViewModel.IsUnlocked)
312+
return;
313+
314+
await MainWindowSynchronizationContext.PostOrExecuteAsync(async () =>
315+
{
316+
WeakReferenceMessenger.Default.Send(new VaultLockRequestedMessage(vaultViewModel.VaultModel));
317+
});
318+
}
319+
320+
/// <summary>
321+
/// Handles URI protocol activation (e.g. sffs://vault/preview?id=...).
322+
/// </summary>
323+
public async Task HandleUriActivationAsync(Uri uri)
324+
{
325+
if (!uri.Host.Equals("vault", StringComparison.OrdinalIgnoreCase))
326+
return;
327+
328+
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
329+
var persistableId = query["id"];
330+
if (persistableId is null)
331+
return;
332+
333+
var action = uri.AbsolutePath.Trim('/');
334+
switch (action)
335+
{
336+
case "preview":
337+
await HandleVaultPreviewActivationAsync(persistableId);
338+
break;
339+
340+
case "lock":
341+
await HandleVaultLockActivationAsync(persistableId);
342+
break;
343+
}
344+
}
345+
282346
#region Window Configuration
283347

284348
private static void EnsureEarlyWindow(Window window, string title)

src/Platforms/SecureFolderFS.Uno/Package.appxmanifest

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
<uap:InfoTip>SecureFolderFS Vault</uap:InfoTip>
7171
<uap:EditFlags OpenIsSafe="true"/>
7272
</uap:FileTypeAssociation>
73+
<uap:Protocol Name="sffs">
74+
<uap:DisplayName>SecureFolderFS</uap:DisplayName>
75+
<uap:Logo>Assets\AppIcon\AppIcon.png</uap:Logo>
76+
</uap:Protocol>
7377
</uap:Extension>
7478
</Extensions>
7579
</Application>

src/Platforms/SecureFolderFS.Uno/Program.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#if WINDOWS
22
using System;
33
using System.Threading;
4+
using System.Threading.Tasks;
45
using Microsoft.UI.Dispatching;
56
using Microsoft.UI.Xaml;
67
using Microsoft.Windows.AppLifecycle;
@@ -55,8 +56,13 @@ private static void Main(string[] args)
5556
private static void RedirectActivationTo(AppInstance targetInstance, AppActivationArguments args)
5657
{
5758
// Redirect on a background thread to avoid blocking
58-
var redirectTask = targetInstance.RedirectActivationToAsync(args).AsTask();
59-
redirectTask.Wait();
59+
var redirectSemaphore = new SemaphoreSlim(0, 1);
60+
Task.Run(async () =>
61+
{
62+
await targetInstance.RedirectActivationToAsync(args);
63+
redirectSemaphore.Release();
64+
});
65+
redirectSemaphore.Wait();
6066
}
6167

6268
private static async void OnActivated(object? sender, AppActivationArguments args)

0 commit comments

Comments
 (0)