Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/linux-port/compat-off-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ src/Files.App/Data/Factories/ShellContextFlyoutHelper.cs: error CS0246: The type
src/Files.App/Data/Items/ContextMenu.cs: error CS0246: The type or namespace name 'MENU_ITEM_TYPE' could not be found (are you missing a using directive or an assembly reference?)
src/Files.App/Data/Items/ListedItem.cs: error CS0246: The type or namespace name 'SHOW_WINDOW_CMD' could not be found (are you missing a using directive or an assembly reference?)
src/Files.App/Data/Items/ShellLinkItem.cs: error CS0246: The type or namespace name 'SHOW_WINDOW_CMD' could not be found (are you missing a using directive or an assembly reference?)
src/Files.App/Data/Items/WidgetFolderCardItem.cs: error CS0246: The type or namespace name 'IWindowsStorable' could not be found (are you missing a using directive or an assembly reference?)
src/Files.App/Data/Items/WidgetRecentItem.cs: error CS0246: The type or namespace name 'IShellItem' could not be found (are you missing a using directive or an assembly reference?)
src/Files.App/Data/Models/RemovableDevice.cs: error CS0234: The type or namespace name 'FileSystem' does not exist in the namespace 'Windows.Win32.Storage' (are you missing an assembly reference?)
src/Files.App/Data/Models/RemovableDevice.cs: error CS0234: The type or namespace name 'Win32PInvoke' does not exist in the namespace 'Files.App.Helpers' (are you missing an assembly reference?)
Expand Down Expand Up @@ -37,4 +36,3 @@ src/Files.App/Utils/Storage/StorageItems/StreamWithContentType.cs: error CS0122:
src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs: error CS0234: The type or namespace name 'FileSystem' does not exist in the namespace 'Windows.Win32.Storage' (are you missing an assembly reference?)
src/Files.App/ViewModels/ShellViewModel.cs: error CS0234: The type or namespace name 'Win32PInvoke' does not exist in the namespace 'Files.App.Helpers' (are you missing an assembly reference?)
src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs: error CS0234: The type or namespace name 'FileSystem' does not exist in the namespace 'Windows.Win32.Storage' (are you missing an assembly reference?)
src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs: error CS0234: The type or namespace name 'WinRT' does not exist in the namespace 'Windows.Win32.System' (are you missing an assembly reference?)
92 changes: 92 additions & 0 deletions src/Files.App.Storage/Legacy/HomeFolder/LinuxHomeFolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Files Community
// SPDX-License-Identifier: MPL-2.0

using Files.Platform.Abstractions;
using Files.Platform.Abstractions.Gvfs;
using Files.Platform.Abstractions.Recent;
using Files.Platform.Abstractions.Volumes;
using OwlCore.Storage.System.IO;
using System.Runtime.CompilerServices;

namespace Files.App.Storage.Storables
{
public sealed class LinuxHomeFolder : IHomeFolder
{
private readonly IUserDirectories directories;
private readonly IVolumeService volumes;
private readonly IRecentFilesStore recentFiles;
private readonly INetworkLocationService network;

public string Id => "Home";
public string Name => "Home";

public LinuxHomeFolder(IUserDirectories directories, IVolumeService volumes, IRecentFilesStore recentFiles, INetworkLocationService network)
{
this.directories = directories;
this.volumes = volumes;
this.recentFiles = recentFiles;
this.network = network;
}

public async IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type = StorableType.Folder, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
if (type.HasFlag(StorableType.Folder))
{
await foreach (var item in GetQuickAccessFolderAsync(cancellationToken))
yield return item;
await foreach (var item in GetLogicalDrivesAsync(cancellationToken))
yield return item;
await foreach (var item in GetNetworkLocationsAsync(cancellationToken))
yield return item;
}
if (type.HasFlag(StorableType.File))
{
await foreach (var item in GetRecentFilesAsync(cancellationToken))
yield return item;
}
}

public async IAsyncEnumerable<IStorableChild> GetQuickAccessFolderAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await Task.CompletedTask;
foreach (var path in new[] { directories.Desktop, directories.Downloads, directories.Documents, directories.Pictures, directories.Music, directories.Videos }.Distinct(StringComparer.Ordinal))
{
cancellationToken.ThrowIfCancellationRequested();
if (SystemIO.Directory.Exists(path))
yield return new SystemFolder(path);
}
}

public async IAsyncEnumerable<IStorableChild> GetLogicalDrivesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var paths = (await volumes.GetVolumesAsync(cancellationToken).ConfigureAwait(false))
.SelectMany(volume => volume.MountPoints).Prepend("/").Distinct(StringComparer.Ordinal);
foreach (var path in paths)
{
cancellationToken.ThrowIfCancellationRequested();
yield return new SystemFolder(path);
}
}

public async IAsyncEnumerable<IStorableChild> GetNetworkLocationsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await Task.CompletedTask;
foreach (var mount in network.GetMounts())
{
cancellationToken.ThrowIfCancellationRequested();
yield return new SystemFolder(mount.Path);
}
}

public async IAsyncEnumerable<IStorableChild> GetRecentFilesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await Task.CompletedTask;
foreach (var entry in recentFiles.Read())
{
cancellationToken.ThrowIfCancellationRequested();
if (SystemIO.File.Exists(entry.Path))
yield return new SystemFile(entry.Path);
}
}
}
}
17 changes: 15 additions & 2 deletions src/Files.App/Data/Contexts/HomePage/HomePageContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Files Community
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Xaml.Controls.Primitives;
Expand All @@ -12,7 +12,20 @@ public sealed partial class HomePageContext : ObservableObject, IHomePageContext

public bool IsAnyItemRightClicked => rightClickedItem is not null;

public IHomeFolder HomeFolder { get; } = new HomeFolder();
public IHomeFolder HomeFolder { get; } = CreateHomeFolder();

private static IHomeFolder CreateHomeFolder()
{
#if WINDOWS
return new HomeFolder();
#else
return new LinuxHomeFolder(
Ioc.Default.GetRequiredService<IUserDirectories>(),
Ioc.Default.GetRequiredService<Files.Platform.Abstractions.Volumes.IVolumeService>(),
Ioc.Default.GetRequiredService<Files.Platform.Abstractions.Recent.IRecentFilesStore>(),
Ioc.Default.GetRequiredService<Files.Platform.Abstractions.Gvfs.INetworkLocationService>());
#endif
}

private WidgetCardItem? rightClickedItem = null;
public WidgetCardItem? RightClickedItem => rightClickedItem;
Expand Down
54 changes: 54 additions & 0 deletions src/Files.App/Data/Items/WidgetFolderCardItem.Windows.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Win32;
using Windows.Win32.System.Com;
using Windows.Win32.System.WinRT;
using Windows.Win32.UI.Shell;

namespace Files.App.Data.Items
{
public sealed partial class WidgetFolderCardItem
{
public async Task LoadCardThumbnailAsync()
{
if (_isDisposed || string.IsNullOrEmpty(Path))
return;

if (Item is null)
{
byte[]? icon = Path == Constants.UserEnvironmentPaths.RecycleBinPath
? await DriveHelpers.GetDriveIconAsync(null, Constants.ShellIconSizes.Large)
: await FileThumbnailHelper.GetIconAsync(Path, (uint)Constants.ShellIconSizes.Large, true, IconOptions.ReturnIconOnly);
if (icon is not null)
Thumbnail = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => icon.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
return;
}

var thumbnailSize = (int)(Constants.ShellIconSizes.Large * App.AppModel.AppWindowDPI);
// Ensure thumbnail size is at least 1 to prevent layout errors
thumbnailSize = Math.Max(1, thumbnailSize);
var hr = PInvoke.RoGetAgileReference(AgileReferenceOptions.AGILEREFERENCE_DEFAULT, typeof(IShellItem).GUID, ((IWindowsStorable)Item).ThisPtr, out IAgileReference shellItemReference);
if (hr.ThrowIfFailedOnDebug().Failed)
return;

var rawThumbnailData = await STATask.RunPooled(() =>
{
if (shellItemReference.Resolve(out IShellItem shellItem).ThrowIfFailedOnDebug().Failed)
return null;

using var folder = new WindowsFolder(shellItem);
folder.TryGetThumbnail(thumbnailSize, SIIGBF.SIIGBF_ICONONLY, out var data);
return data;
}, App.Logger);
if (_isDisposed || rawThumbnailData is null)
return;

var thumbnail = await rawThumbnailData.ToBitmapAsync();
if (!_isDisposed)
Thumbnail = thumbnail;
}

}
}
60 changes: 21 additions & 39 deletions src/Files.App/Data/Items/WidgetFolderCardItem.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
// Copyright (c) Files Community
// Copyright (c) Files Community
// Licensed under the MIT License.

using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Win32;
using Windows.Win32.System.Com;
using Windows.Win32.System.WinRT;
using Windows.Win32.UI.Shell;

namespace Files.App.Data.Items
{
public sealed partial class WidgetFolderCardItem : WidgetCardItem, IWidgetCardItem<IWindowsStorable>, IDisposable
public sealed partial class WidgetFolderCardItem : WidgetCardItem, IWidgetCardItem<IStorable>, IDisposable
{
// Properties

public string? AutomationProperties { get; set; }

public new IWindowsStorable Item { get; private set; }
public new IStorable Item { get; private set; }

public string? Text { get; set; }

Expand All @@ -29,7 +25,7 @@ public sealed partial class WidgetFolderCardItem : WidgetCardItem, IWidgetCardIt

// Constructor

public WidgetFolderCardItem(IWindowsStorable item, string text, string path, bool isPinned, string tooltip)
public WidgetFolderCardItem(IStorable item, string text, string path, bool isPinned, string tooltip)
{
AutomationProperties = text;
Item = item;
Expand All @@ -46,59 +42,45 @@ public WidgetFolderCardItem(IWindowsStorable item, string text, string path, boo
public WidgetFolderCardItem(string path, string text, bool isPinned, string tooltip)
{
AutomationProperties = text;
Item = null!;
Item = SystemIO.Path.IsPathFullyQualified(path)
? new OwlCore.Storage.System.IO.SystemFolder(path)
: new FolderPath(path, text);
Text = text;
IsPinned = isPinned;
Path = path;
Tooltip = tooltip;
}
#endif

private sealed record FolderPath(string Id, string Name) : IStorable;

// Methods

#if !WINDOWS
public async Task LoadCardThumbnailAsync()
{
if (_isDisposed || string.IsNullOrEmpty(Path))
return;

if (Item is null)
{
byte[]? icon = Path == Constants.UserEnvironmentPaths.RecycleBinPath
? await DriveHelpers.GetDriveIconAsync(null, Constants.ShellIconSizes.Large)
: await FileThumbnailHelper.GetIconAsync(Path, (uint)Constants.ShellIconSizes.Large, true, IconOptions.ReturnIconOnly);
if (icon is not null)
Thumbnail = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => icon.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
return;
}

var thumbnailSize = (int)(Constants.ShellIconSizes.Large * App.AppModel.AppWindowDPI);
// Ensure thumbnail size is at least 1 to prevent layout errors
thumbnailSize = Math.Max(1, thumbnailSize);
var hr = PInvoke.RoGetAgileReference(AgileReferenceOptions.AGILEREFERENCE_DEFAULT, typeof(IShellItem).GUID, Item.ThisPtr, out IAgileReference shellItemReference);
if (hr.ThrowIfFailedOnDebug().Failed)
byte[]? icon = Path == Constants.UserEnvironmentPaths.RecycleBinPath
? await DriveHelpers.GetDriveIconAsync(null, Constants.ShellIconSizes.Large)
: await FileThumbnailHelper.GetIconAsync(Path, (uint)Constants.ShellIconSizes.Large, true, IconOptions.ReturnIconOnly);
if (_isDisposed || icon is null)
return;

var rawThumbnailData = await STATask.RunPooled(() =>
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () =>
{
if (shellItemReference.Resolve(out IShellItem shellItem).ThrowIfFailedOnDebug().Failed)
return null;

using var folder = new WindowsFolder(shellItem);
folder.TryGetThumbnail(thumbnailSize, SIIGBF.SIIGBF_ICONONLY, out var data);
return data;
}, App.Logger);
if (_isDisposed || rawThumbnailData is null)
return;

var thumbnail = await rawThumbnailData.ToBitmapAsync();
if (!_isDisposed)
Thumbnail = thumbnail;
var thumbnail = await icon.ToBitmapAsync();
if (!_isDisposed)
Thumbnail = thumbnail;
});
}
#endif

public void Dispose()
{
_isDisposed = true;
Item?.Dispose();
(Item as IDisposable)?.Dispose();
}
}
}
18 changes: 9 additions & 9 deletions src/Files.App/Data/Items/WidgetRecentItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace Files.App.Data.Items
{
/// <summary>
/// Represents an item for recent item of File Explorer on Windows.
/// Represents a recently used file.
/// </summary>
public sealed partial class RecentItem : WidgetCardItem, IEquatable<RecentItem>, IDisposable
{
Expand All @@ -32,12 +32,9 @@ public BitmapImage? Icon
public required DateTime LastModified { get; set; }

/// <summary>
/// Gets or initializes PIDL of the recent item.
/// Gets or initializes the Windows shell item of the recent item; unused on Linux.
/// </summary>
/// <remarks>
/// This has to be removed in the future.
/// </remarks>
public required IShellItem ShellItem { get; init; }
public IShellItem? ShellItem { get; init; }

/// <summary>
/// Loads thumbnail icon of the recent item.
Expand All @@ -47,9 +44,12 @@ public async Task LoadRecentItemIconAsync()
{
var result = await FileThumbnailHelper.GetIconAsync(Path, Constants.ShellIconSizes.Small, false, IconOptions.None);

var bitmapImage = await result.ToBitmapAsync();
if (bitmapImage is not null)
Icon = bitmapImage;
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () =>
{
var bitmapImage = await result.ToBitmapAsync();
if (bitmapImage is not null)
Icon = bitmapImage;
});
}

public override int GetHashCode() => (Path, Name).GetHashCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ private bool Update(bool isFolder)
Path = entry.Path,
Name = string.IsNullOrEmpty(name) ? entry.Path : name,
LastModified = entry.LastUsedUtc.ToLocalTime(),
// The Windows shell item is not used on Linux
ShellItem = null!,
});

if (items.Count == MaxItems)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public unsafe bool Remove(RecentItem item)
{
try
{
HRESULT hr = item.ShellItem.BindToHandler(null, PInvoke.BHID_SFUIObject, out IContextMenu? pContextMenu);
HRESULT hr = item.ShellItem!.BindToHandler(null, PInvoke.BHID_SFUIObject, out IContextMenu? pContextMenu);
if (hr.Failed || pContextMenu is null)
return false;

Expand Down
Loading
Loading