diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f11f76c..48d2b8e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -163,4 +163,12 @@ jobs:
with:
tag_name: ${{ needs.prepare.outputs.tag }}
files: release-assets/*
- generate_release_notes: true
\ No newline at end of file
+ generate_release_notes: true
+ token: ${{ secrets.PAT_TOKEN }}
+
+ - name: Trigger deployment
+ uses: peter-evans/repository-dispatch@v4
+ with:
+ event-type: deploy
+ token: ${{ secrets.PAT_TOKEN }}
+ client_payload: '{"tag": "${{ needs.prepare.outputs.tag }}"}'
\ No newline at end of file
diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml
index 5e8ca16..7bd6fa0 100644
--- a/.github/workflows/deploy-release.yml
+++ b/.github/workflows/deploy-release.yml
@@ -1,8 +1,8 @@
name: Deploy release to consumers
on:
- release:
- types: [published]
+ repository_dispatch:
+ types: [deploy]
workflow_dispatch:
inputs:
tag:
@@ -27,7 +27,7 @@ jobs:
run: |
set -euo pipefail
- TAG="${{ github.event.release.tag_name || inputs.tag }}"
+ TAG="${{ github.event.client_payload.tag || inputs.tag }}"
if [[ -z "$TAG" ]]; then
echo "::error::No tag available — triggered via workflow_dispatch without tag input"
diff --git a/Directory.Build.props b/Directory.Build.props
index fb23463..b7cb11c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,7 +5,7 @@
enable
default
https://github.com/frequency403/OpenSSH-GUI
- 3.2.2
+ 3.2.3
true
diff --git a/OpenSSH_GUI.Core/MVVM/ViewModelBase.cs b/OpenSSH_GUI.Core/MVVM/ViewModelBase.cs
index 120d3c7..ae63173 100644
--- a/OpenSSH_GUI.Core/MVVM/ViewModelBase.cs
+++ b/OpenSSH_GUI.Core/MVVM/ViewModelBase.cs
@@ -34,6 +34,13 @@ public virtual ValueTask InitializeAsync(
public abstract partial class ViewModelBase : ReactiveObject, IDisposable, IAsyncDisposable, IActivatableViewModel,
IInitializableViewModel
{
+ ///
+ /// The TopLevel this view is hosted in. Set by the view once attached to the visual tree,
+ /// since clipboard access must go through the actual hosting window on Windows (OLE clipboard
+ /// is bound to the calling HWND, not a globally cached one).
+ ///
+ public TopLevel? OwnerTopLevel { get; set; }
+
protected readonly CompositeDisposable Disposables;
///
diff --git a/OpenSSH_GUI.Core/Resources/Wrapper/WindowBase.cs b/OpenSSH_GUI.Core/Resources/Wrapper/WindowBase.cs
index 7b62e46..711adb7 100644
--- a/OpenSSH_GUI.Core/Resources/Wrapper/WindowBase.cs
+++ b/OpenSSH_GUI.Core/Resources/Wrapper/WindowBase.cs
@@ -1,6 +1,7 @@
using System.Reactive.Disposables;
using System.Reactive.Disposables.Fluent;
using System.Reactive.Linq;
+using Avalonia;
using Avalonia.Controls;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -30,7 +31,7 @@ public abstract class WindowBase : ReactiveWindow, IDisp
public required ILogger> Logger { get; set; }
public required IServiceProvider Services { get; set; }
public required AppIconStore AppIconStore { get; set; }
-
+
public void Dispose() { Disposables.Dispose(); }
protected void WindowInitialize(WindowStartupLocation startupLocation = WindowStartupLocation.CenterScreen)
@@ -45,6 +46,7 @@ protected void WindowInitialize(WindowStartupLocation startupLocation = WindowSt
try
{
ViewModel = Services.GetRequiredKeyedService(typeof(TViewModel).Name);
+ ViewModel.OwnerTopLevel = GetTopLevel(this);
}
catch (Exception e)
{
diff --git a/OpenSSH_GUI/Extensions/DependencyInjectionExtensions.cs b/OpenSSH_GUI/Extensions/DependencyInjectionExtensions.cs
index 61e462d..caebb19 100644
--- a/OpenSSH_GUI/Extensions/DependencyInjectionExtensions.cs
+++ b/OpenSSH_GUI/Extensions/DependencyInjectionExtensions.cs
@@ -44,10 +44,7 @@ internal IHostBuilder RegisterOpenSshGuiServices()
services.AddSingleton();
services.AddSingleton(sp => sp.GetRequiredKeyedService(nameof(MainWindow)));
- services.AddSingleton(sp => sp.GetRequiredKeyedService(nameof(MainWindow)));
- services.AddSingleton(sp => sp.GetRequiredKeyedService(nameof(MainWindow)).Clipboard!);
- services.AddSingleton(sp => sp.GetRequiredKeyedService(nameof(MainWindow)).StorageProvider);
- services.AddSingleton(sp => sp.GetRequiredKeyedService(nameof(MainWindow)).Launcher);
+ services.AddTransient(sp => sp.GetRequiredKeyedService(nameof(MainWindow)));
services.RegisterViewWithViewModel(ServiceLifetime.Singleton);
services.RegisterViewWithViewModel();
diff --git a/OpenSSH_GUI/OpenSSH_GUI.csproj b/OpenSSH_GUI/OpenSSH_GUI.csproj
index 3c0d78e..6269eee 100644
--- a/OpenSSH_GUI/OpenSSH_GUI.csproj
+++ b/OpenSSH_GUI/OpenSSH_GUI.csproj
@@ -1,7 +1,7 @@
- Exe
+ WinExe
true
app.manifest
true
diff --git a/OpenSSH_GUI/ViewModels/ApplicationSettingsViewModel.cs b/OpenSSH_GUI/ViewModels/ApplicationSettingsViewModel.cs
index 79f4d8d..eccba23 100644
--- a/OpenSSH_GUI/ViewModels/ApplicationSettingsViewModel.cs
+++ b/OpenSSH_GUI/ViewModels/ApplicationSettingsViewModel.cs
@@ -28,12 +28,10 @@ namespace OpenSSH_GUI.ViewModels;
public partial class ApplicationSettingsViewModel : ViewModelBase
{
private readonly Application _application;
- private readonly ILauncher _launcher;
private readonly LoggingLevelSwitch _levelSwitch;
private readonly ILogger _logger;
private readonly IMessageBoxProvider _messageBoxProvider;
private readonly IMutableConfiguration _mutableConfiguration;
- private readonly IStorageProvider _storageProvider;
[ObservableAsProperty(ReadOnly = true)]
private ApplicationConfiguration _applicationConfiguration = ApplicationConfiguration.Default;
@@ -50,16 +48,12 @@ public partial class ApplicationSettingsViewModel : ViewModelBase
public ApplicationSettingsViewModel(ILogger logger,
IMutableConfiguration mutableConfiguration,
- ILauncher launcher,
- IStorageProvider storageProvider,
IMessageBoxProvider messageBoxProvider,
Application application,
LoggingLevelSwitch levelSwitch)
{
_logger = logger;
_mutableConfiguration = mutableConfiguration;
- _launcher = launcher;
- _storageProvider = storageProvider;
_messageBoxProvider = messageBoxProvider;
_levelSwitch = levelSwitch;
_application = application;
@@ -161,7 +155,7 @@ private Task DeleteLookupPathAsync(string path, CancellationToken cancellationTo
[ReactiveCommand]
private async Task AddLookupPathAsync(CancellationToken cancellationToken = default)
{
- if (await _storageProvider.OpenFolderPickerAsync(
+ if (OwnerTopLevel is { StorageProvider: { } storageProvider} && await storageProvider.OpenFolderPickerAsync(
new FolderPickerOpenOptions
{
AllowMultiple = false
@@ -282,11 +276,12 @@ private void DeleteOldLogFiles()
}
[ReactiveCommand]
- private Task OpenCacheFolder(CancellationToken token = default) => _launcher.LaunchDirectoryInfoAsync(
- new DirectoryInfo(
- Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- AppDomain.CurrentDomain.FriendlyName)));
+ private async Task OpenCacheFolder(CancellationToken token = default) => OwnerTopLevel is { Launcher: { } launcher }
+ && await launcher.LaunchDirectoryInfoAsync(
+ new DirectoryInfo(
+ Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ AppDomain.CurrentDomain.FriendlyName)));
private async void OnNextTheme(ThemeVariant variant)
{
diff --git a/OpenSSH_GUI/ViewModels/ExportWindowViewModel.cs b/OpenSSH_GUI/ViewModels/ExportWindowViewModel.cs
index 8f5ad30..f961bb1 100644
--- a/OpenSSH_GUI/ViewModels/ExportWindowViewModel.cs
+++ b/OpenSSH_GUI/ViewModels/ExportWindowViewModel.cs
@@ -7,7 +7,7 @@
namespace OpenSSH_GUI.ViewModels;
[UsedImplicitly]
-public partial class ExportWindowViewModel(ILogger logger, IClipboard clipboard)
+public partial class ExportWindowViewModel(ILogger logger)
: ViewModelBase<(string WindowTitle, string Export)>
{
[Reactive] private string _export = string.Empty;
@@ -27,18 +27,15 @@ protected override async Task BooleanSubmitAsync(bool inputParameter,
{
try
{
- if (inputParameter)
+ if (inputParameter && OwnerTopLevel is { Clipboard: { } clipboard })
+ {
await clipboard.SetTextAsync(Export);
+ await clipboard.FlushAsync();
+ }
}
catch (Exception e)
{
logger.LogError(e, "Error submitting export to clipboard");
}
}
-}
-
-public record ExportWindowViewModelInitializerParameters
-{
- public string WindowTitle { get; init; } = string.Empty;
- public string Export { get; init; } = string.Empty;
}
\ No newline at end of file
diff --git a/OpenSSH_GUI/ViewModels/FileInfoWindowViewModel.cs b/OpenSSH_GUI/ViewModels/FileInfoWindowViewModel.cs
index 0f5296c..2228c6f 100644
--- a/OpenSSH_GUI/ViewModels/FileInfoWindowViewModel.cs
+++ b/OpenSSH_GUI/ViewModels/FileInfoWindowViewModel.cs
@@ -24,7 +24,6 @@ namespace OpenSSH_GUI.ViewModels;
[UsedImplicitly]
public partial class FileInfoWindowViewModel : ViewModelBase
{
- private readonly IClipboard _clipboard;
private readonly SshKeyManager _keyManager;
private readonly ILogger _logger;
private readonly IMessageBoxProvider _messageBoxProvider;
@@ -49,12 +48,11 @@ public partial class FileInfoWindowViewModel : ViewModelBase
private string _windowTitle = "Key info";
public FileInfoWindowViewModel(ILogger logger, IMessageBoxProvider messageBoxProvider,
- IServiceProvider serviceProvider, IClipboard clipboard, SshKeyManager keyManager)
+ IServiceProvider serviceProvider, SshKeyManager keyManager)
{
_logger = logger;
_messageBoxProvider = messageBoxProvider;
_serviceProvider = serviceProvider;
- _clipboard = clipboard;
_keyManager = keyManager;
_keyFile = _serviceProvider.GetRequiredService();
@@ -240,8 +238,12 @@ private async Task CopyPasswordIntoClipboardAsync(SshKeyFilePassword password, C
{
try
{
- await _clipboard.SetTextAsync(password.GetPasswordString());
- await _clipboard.FlushAsync();
+ if (OwnerTopLevel is { Clipboard: { } clipboard })
+ {
+ await clipboard.SetTextAsync(password.GetPasswordString());
+ await clipboard.FlushAsync();
+ }
+
await _messageBoxProvider.ShowMessageBoxAsync(
StringsAndTexts.FileInfoWindowPasswordCopied,
StringsAndTexts.FileInfoWindowPasswordCopied, MessageBoxButtons.Ok,
diff --git a/OpenSSH_GUI/ViewModels/MainWindowViewModel.cs b/OpenSSH_GUI/ViewModels/MainWindowViewModel.cs
index b7e5af8..2aaadc0 100644
--- a/OpenSSH_GUI/ViewModels/MainWindowViewModel.cs
+++ b/OpenSSH_GUI/ViewModels/MainWindowViewModel.cs
@@ -34,7 +34,6 @@ public partial class MainWindowViewModel : ViewModelBase
.FirstOrDefault(a => a.Key == "ProjectUrl")?.Value;
private readonly IDialogHost _dialogHost;
- private readonly ILauncher _launcher;
private readonly ILogger _logger;
private readonly IMessageBoxProvider _messageBoxProvider;
private readonly IServiceProvider _serviceProvider;
@@ -58,7 +57,6 @@ public MainWindowViewModel(
IServiceProvider serviceProvider,
IConfiguration configuration,
IMessageBoxProvider messageBoxProvider,
- ILauncher launcher,
IDialogHost dialogHost)
{
SshKeyManager = sshKeyManager;
@@ -66,7 +64,6 @@ public MainWindowViewModel(
_logger = logger;
_serviceProvider = serviceProvider;
_messageBoxProvider = messageBoxProvider;
- _launcher = launcher;
_dialogHost = dialogHost;
Version = configuration[Program.VersionEnvVar] ?? "VERSION ERROR";
@@ -232,7 +229,8 @@ private async Task OpenBrowserAsync(int commandTypeParameter, CancellationToken
break;
}
- await _launcher.LaunchUriAsync(uriBuilder.Uri);
+ if(OwnerTopLevel is { Launcher: { } launcher})
+ await launcher.LaunchUriAsync(uriBuilder.Uri);
}
[ReactiveCommand]
diff --git a/global.json b/global.json
index 9a523dc..397bd94 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "10.0.0",
+ "version": "10.0.100",
"rollForward": "latestMajor",
"allowPrerelease": false
}