diff --git a/src/Files.App/Helpers/NativePreviewSupport.cs b/src/Files.App/Helpers/NativePreviewSupport.cs new file mode 100644 index 000000000000..60d2c4f9df96 --- /dev/null +++ b/src/Files.App/Helpers/NativePreviewSupport.cs @@ -0,0 +1,13 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Uno.UI.NativeElementHosting; + +namespace Files.App.Helpers +{ + internal static class NativePreviewSupport + { + // Inspect the running host, not the session variables: auto can fall back to X11 on a Wayland desktop. + public static bool IsAvailable => Uno.UI.Xaml.WindowHelper.GetNativeWindow(MainWindow.Instance) is X11NativeWindow; + } +} diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs index 83bb7bd1fd07..951c5746066f 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.Linux.cs @@ -140,6 +140,12 @@ private static async Task OpenPathLinuxAsync(string path, IShellPage assoc } } + internal static async Task OpenPreviewFileAsync(string path) + { + if (!await OpenFilesLinuxAsync([path])) + await DialogDisplayHelper.ShowDialogAsync(Strings.LinuxOpenFailedTitle.GetLocalizedResource(), Strings.LinuxOpenFailedText.GetLocalizedFormatResource(DisplaySanitizer.Field(path))); + } + /// /// Opens several files. Files that need a gate (executables, launchers) are opened one by one through /// ; plain documents are passed to their default applications together. diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index f98cff24dd86..b5396cfc74c8 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -291,6 +291,9 @@ Properties + + Open with default app + Open diff --git a/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml b/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml index 0890d99ee63b..b67699eb6e77 100644 --- a/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml +++ b/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml @@ -16,11 +16,15 @@ VerticalAlignment="Stretch" CornerRadius="{StaticResource ControlCornerRadius}"> - + + + + diff --git a/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml.cs b/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml.cs index 097208fd9818..118f34379382 100644 --- a/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml.cs +++ b/src/Files.App/UserControls/FilePreviews/HtmlPreview.xaml.cs @@ -1,5 +1,6 @@ using Files.App.ViewModels.Previews; using Microsoft.UI.Xaml.Controls; +using Microsoft.Extensions.Logging; using System.IO; // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 @@ -8,14 +9,35 @@ namespace Files.App.UserControls.FilePreviews { public sealed partial class HtmlPreview : UserControl { + public bool NativeViewsAvailable { get; } = NativePreviewSupport.IsAvailable; + public HtmlPreview(HtmlPreviewViewModel model) { ViewModel = model; InitializeComponent(); + if (!NativeViewsAvailable) + { + Loaded += LoadFallback; + Unloaded += ViewModel.PreviewControlBase_Unloaded; + } } public HtmlPreviewViewModel ViewModel { get; set; } + private async void LoadFallback(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + Loaded -= LoadFallback; + try + { + FallbackContent.Content = await NativePreviewFallback.CreateHtmlAsync(ViewModel.Item, ViewModel.LoadCancelledTokenSource.Token); + } + catch (OperationCanceledException) when (ViewModel.LoadCancelledTokenSource.IsCancellationRequested) { } + catch (Exception ex) + { + App.Logger.LogWarning(ex, "Failed to load preview fallback"); + } + } + private async void WebViewControl_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { await WebViewControl.EnsureCoreWebView2Async(); diff --git a/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml b/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml index f61ddd8e4596..479aae757626 100644 --- a/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml +++ b/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml @@ -19,19 +19,23 @@ - - - - - + + + + + + + + diff --git a/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml.cs b/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml.cs index f2edff894a2f..fdfcd4eaec29 100644 --- a/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml.cs +++ b/src/Files.App/UserControls/FilePreviews/MediaPreview.xaml.cs @@ -1,6 +1,7 @@ using Files.App.ViewModels.Previews; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.Extensions.Logging; using Microsoft.UI.Xaml.Input; using Windows.Media.Playback; @@ -12,16 +13,35 @@ public sealed partial class MediaPreview : UserControl { private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + public bool NativeViewsAvailable { get; } = NativePreviewSupport.IsAvailable; + public MediaPreview(MediaPreviewViewModel model) { ViewModel = model; InitializeComponent(); - PlayerContext.Loaded += PlayerContext_Loaded; + if (NativeViewsAvailable) + PlayerContext.Loaded += PlayerContext_Loaded; + else + Loaded += LoadFallback; Unloaded += MediaPreview_Unloaded; } public MediaPreviewViewModel ViewModel { get; set; } + private async void LoadFallback(object sender, RoutedEventArgs e) + { + Loaded -= LoadFallback; + try + { + FallbackContent.Content = await NativePreviewFallback.CreateMediaAsync(ViewModel.Item, ViewModel.LoadCancelledTokenSource.Token); + } + catch (OperationCanceledException) when (ViewModel.LoadCancelledTokenSource.IsCancellationRequested) { } + catch (Exception ex) + { + App.Logger.LogWarning(ex, "Failed to load preview fallback"); + } + } + private void PlayerContext_Loaded(object sender, RoutedEventArgs e) { PlayerContext.MediaPlayer.Volume = UserSettingsService.InfoPaneSettingsService.MediaVolume; @@ -31,6 +51,12 @@ private void PlayerContext_Loaded(object sender, RoutedEventArgs e) private void MediaPreview_Unloaded(object sender, RoutedEventArgs e) { + if (!NativeViewsAvailable) + { + ViewModel.PreviewControlBase_Unloaded(sender, e); + return; + } + // The MediaPlayerElement isn't properly disposed by Windows so we set the source to null // to avoid issues the next time the control is used. PlayerContext.Source = null; @@ -53,6 +79,9 @@ private void MediaPlayer_VolumeChanged(MediaPlayer sender, object args) private void TogglePlaybackRequestInvoked(object? sender, EventArgs e) { + if (!NativeViewsAvailable) + return; + if (PlayerContext.MediaPlayer.PlaybackSession.PlaybackState is not MediaPlaybackState.Playing) { PlayerContext.MediaPlayer.Play(); diff --git a/src/Files.App/UserControls/FilePreviews/NativePreviewFallback.xaml b/src/Files.App/UserControls/FilePreviews/NativePreviewFallback.xaml new file mode 100644 index 000000000000..0a311762d47c --- /dev/null +++ b/src/Files.App/UserControls/FilePreviews/NativePreviewFallback.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + +