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 }