Skip to content

Commit 8e0f0c6

Browse files
committed
Init
1 parent b14b559 commit 8e0f0c6

File tree

7 files changed

+136
-51
lines changed

7 files changed

+136
-51
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,4 @@ SetCurrentProcessExplicitAppUserModelID
222222
GdipCreateBitmapFromScan0
223223
BITMAP
224224
GetObject
225+
_SICHINTF

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace Files.App.Storage
1010
{
11-
public abstract class WindowsStorable : IWindowsStorable, IStorableChild
11+
public abstract class WindowsStorable : IWindowsStorable, IStorableChild, IEquatable<IWindowsStorable>
1212
{
1313
public ComPtr<IShellItem> ThisPtr { get; protected set; }
1414

@@ -65,6 +65,17 @@ public abstract class WindowsStorable : IWindowsStorable, IStorableChild
6565
return Task.FromResult<IFolder?>(new WindowsFolder(pParentFolder));
6666
}
6767

68+
/// <inheritdoc/>
69+
public override bool Equals(object? obj)
70+
{
71+
return Equals(obj as IWindowsStorable);
72+
}
73+
74+
public override int GetHashCode()
75+
{
76+
return HashCode.Combine(Id, Name);
77+
}
78+
6879
/// <inheritdoc/>
6980
public void Dispose()
7081
{
@@ -76,5 +87,20 @@ public override string ToString()
7687
{
7788
return this.GetDisplayName();
7889
}
90+
91+
/// <inheritdoc/>
92+
public unsafe bool Equals(IWindowsStorable? other)
93+
{
94+
if (other is null)
95+
return false;
96+
97+
return ThisPtr.Get()->Compare(other.ThisPtr.Get(), (uint)_SICHINTF.SICHINT_DISPLAY, out int order).Succeeded && order is 0;
98+
}
99+
100+
public static bool operator ==(WindowsStorable left, WindowsStorable right)
101+
=> left.Equals(right);
102+
103+
public static bool operator !=(WindowsStorable left, WindowsStorable right)
104+
=> !(left == right);
79105
}
80106
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Copyright (c) Files Community
22
// Licensed under the MIT License.
33

4+
using System.Text;
45
using Windows.Win32;
56
using Windows.Win32.Foundation;
67
using Windows.Win32.System.SystemServices;
78
using Windows.Win32.UI.Shell;
9+
using Windows.Win32.UI.WindowsAndMessaging;
810

911
namespace Files.App.Storage
1012
{
@@ -51,5 +53,61 @@ public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN
5153
? new string((char*)pszName.Get()) // this is safe as it gets memcpy'd internally
5254
: string.Empty;
5355
}
56+
57+
public unsafe static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable storable, string verbName)
58+
{
59+
Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA);
60+
61+
using ComPtr<IContextMenu> pContextMenu = default;
62+
HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IContextMenu, (void**)pContextMenu.GetAddressOf());
63+
HMENU hMenu = PInvoke.CreatePopupMenu();
64+
hr = pContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 0x7FFF, PInvoke.CMF_OPTIMIZEFORINVOKE);
65+
66+
CMINVOKECOMMANDINFO cmici = default;
67+
cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO);
68+
cmici.nShow = (int)SHOW_WINDOW_CMD.SW_HIDE;
69+
70+
fixed (byte* pszVerbName = Encoding.ASCII.GetBytes(verbName))
71+
{
72+
cmici.lpVerb = new(pszVerbName);
73+
hr = pContextMenu.Get()->InvokeCommand(cmici);
74+
75+
if (!PInvoke.DestroyMenu(hMenu))
76+
return HRESULT.E_FAIL;
77+
78+
return hr;
79+
}
80+
}
81+
82+
public unsafe static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable storable, string[] verbNames, bool earlyReturnOnSuccess)
83+
{
84+
Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA);
85+
86+
using ComPtr<IContextMenu> pContextMenu = default;
87+
HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IContextMenu, (void**)pContextMenu.GetAddressOf());
88+
HMENU hMenu = PInvoke.CreatePopupMenu();
89+
hr = pContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 0x7FFF, PInvoke.CMF_OPTIMIZEFORINVOKE);
90+
91+
CMINVOKECOMMANDINFO cmici = default;
92+
cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO);
93+
cmici.nShow = (int)SHOW_WINDOW_CMD.SW_HIDE;
94+
95+
foreach (var verbName in verbNames)
96+
{
97+
fixed (byte* pszVerbName = Encoding.ASCII.GetBytes(verbName))
98+
{
99+
cmici.lpVerb = new(pszVerbName);
100+
hr = pContextMenu.Get()->InvokeCommand(cmici);
101+
102+
if (!PInvoke.DestroyMenu(hMenu))
103+
return HRESULT.E_FAIL;
104+
105+
if (hr.Succeeded && earlyReturnOnSuccess)
106+
return hr;
107+
}
108+
}
109+
110+
return hr;
111+
}
54112
}
55113
}

src/Files.App/Data/Contexts/HomePage/HomePageContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public sealed partial class HomePageContext : ObservableObject, IHomePageContext
1313

1414
public bool IsAnyItemRightClicked => rightClickedItem is not null;
1515

16+
public IHomeFolder HomeFolder { get; } = new HomeFolder();
17+
1618
private WidgetCardItem? rightClickedItem = null;
1719
public WidgetCardItem? RightClickedItem => rightClickedItem;
1820

src/Files.App/Data/Contexts/HomePage/IHomePageContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,10 @@ public interface IHomePageContext
2626
/// Tells whether any item has been right clicked
2727
/// </summary>
2828
bool IsAnyItemRightClicked { get; }
29+
30+
/// <summary>
31+
/// Gets the instance of <see cref="IHomeFolder"/>.
32+
/// </summary>
33+
IHomeFolder HomeFolder { get; }
2934
}
3035
}

src/Files.App/Data/Items/WidgetFolderCardItem.cs

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,51 @@
22
// Licensed under the MIT License.
33

44
using Microsoft.UI.Xaml.Media.Imaging;
5+
using Windows.Win32;
6+
using Windows.Win32.UI.Shell;
57

68
namespace Files.App.Data.Items
79
{
8-
public sealed partial class WidgetFolderCardItem : WidgetCardItem, IWidgetCardItem<LocationItem>
10+
public sealed partial class WidgetFolderCardItem : WidgetCardItem, IWidgetCardItem<IWindowsStorable>
911
{
10-
// Fields
11-
12-
private byte[] _thumbnailData;
13-
1412
// Properties
1513

1614
public string? AutomationProperties { get; set; }
1715

18-
public LocationItem? Item { get; private set; }
16+
public IWindowsStorable Item { get; private set; }
1917

2018
public string? Text { get; set; }
2119

2220
public bool IsPinned { get; set; }
2321

24-
public bool HasPath
25-
=> !string.IsNullOrEmpty(Path);
26-
2722
private BitmapImage? _Thumbnail;
28-
public BitmapImage? Thumbnail
29-
{
30-
get => _Thumbnail;
31-
set => SetProperty(ref _Thumbnail, value);
32-
}
23+
public BitmapImage? Thumbnail { get => _Thumbnail; set => SetProperty(ref _Thumbnail, value); }
3324

3425
// Constructor
3526

36-
public WidgetFolderCardItem(LocationItem item, string text, bool isPinned)
27+
public WidgetFolderCardItem(IWindowsStorable item, string text, bool isPinned)
3728
{
38-
if (!string.IsNullOrWhiteSpace(text))
39-
{
40-
Text = text;
41-
AutomationProperties = Text;
42-
}
43-
44-
IsPinned = isPinned;
29+
AutomationProperties = Text;
4530
Item = item;
46-
Path = item.Path;
31+
Text = text;
32+
IsPinned = isPinned;
33+
Path = item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING);
4734
}
4835

4936
// Methods
5037

5138
public async Task LoadCardThumbnailAsync()
5239
{
53-
var result = await FileThumbnailHelper.GetIconAsync(
54-
Path,
55-
Constants.ShellIconSizes.Large,
56-
true,
57-
IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale);
40+
if (string.IsNullOrEmpty(Path))
41+
return;
42+
43+
var rawThumbnailData = await Item.GetThumbnailAsync((int)(32f * App.AppModel.AppWindowDPI), SIIGBF.SIIGBF_ICONONLY);
44+
if (rawThumbnailData is null)
45+
return;
5846

59-
_thumbnailData = result;
60-
if (_thumbnailData is not null)
61-
Thumbnail = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => _thumbnailData.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
47+
Thumbnail = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(
48+
() => rawThumbnailData?.ToBitmapAsync(),
49+
Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal);
6250
}
6351
}
6452
}

src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Windows.Storage;
1010
using Windows.System;
1111
using Windows.UI.Core;
12+
using Windows.Win32.Foundation;
1213

1314
namespace Files.App.ViewModels.UserControls.Widgets
1415
{
@@ -199,8 +200,10 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(async () =>
199200
}
200201
}
201202
else
202-
foreach (var itemToRemove in Items.ToList().Where(x => e.Paths.Contains(x.Path)))
203+
{
204+
foreach (var itemToRemove in Items.ToList().Where(x => e.Item.Contains(x.Path)))
203205
Items.Remove(itemToRemove);
206+
}
204207
});
205208
}
206209

@@ -233,31 +236,33 @@ private async void Items_CollectionChanged(object? sender, NotifyCollectionChang
233236

234237
public override async Task ExecutePinToSidebarCommand(WidgetCardItem? item)
235238
{
236-
if (item is null || item.Path is null)
239+
if (item is not WidgetFolderCardItem folderCardItem || folderCardItem.Path is null)
237240
return;
238241

239-
await QuickAccessService.PinToSidebarAsync(item.Path);
240-
241-
ModifyItemAsync(this, new(new[] { item.Path }, false));
242+
// Pin to Quick Access on Windows
243+
HRESULT hr = await STATask.Run(() => folderCardItem.Item.TryInvokeContextMenuVerb("pintohome"));
244+
if (hr.ThrowIfFailedOnDebug().Failed)
245+
return;
242246

243-
var items = (await QuickAccessService.GetPinnedFoldersAsync())
244-
.Where(link => !((bool?)link.Properties["System.Home.IsPinned"] ?? false));
247+
// Remove it from the collection
248+
Items.Remove(folderCardItem);
245249

246-
var recentItem = items.Where(x => !Items.ToList().Select(y => y.Path).Contains(x.FilePath)).FirstOrDefault();
247-
if (recentItem is not null)
248-
{
249-
ModifyItemAsync(this, new(new[] { recentItem.FilePath }, true) { Pin = false });
250-
}
250+
// Add this to right before the last pinned item
251+
var theLastPinnedItem = Items.LastOrDefault(x => x.IsPinned);
252+
Items.Insert(theLastPinnedItem is not null ? Items.IndexOf(theLastPinnedItem) : 0, folderCardItem);
251253
}
252254

253255
public override async Task ExecuteUnpinFromSidebarCommand(WidgetCardItem? item)
254256
{
255-
if (item is null || item.Path is null)
257+
if (item is not WidgetFolderCardItem folderCardItem || item.Path is null)
256258
return;
257259

258-
await QuickAccessService.UnpinFromSidebarAsync(item.Path);
260+
// Unpin from Quick Access on Windows
261+
HRESULT hr = await STATask.Run(() => folderCardItem.Item.TryInvokeContextMenuVerbs(["unpinfromhome", "remove"], true));
262+
if (hr.ThrowIfFailedOnDebug().Failed)
263+
return;
259264

260-
ModifyItemAsync(this, new(new[] { item.Path }, false));
265+
Items.Remove(folderCardItem);
261266
}
262267

263268
private void ExecuteOpenPropertiesCommand(WidgetFolderCardItem? item)
@@ -274,15 +279,15 @@ private void ExecuteOpenPropertiesCommand(WidgetFolderCardItem? item)
274279

275280
ListedItem listedItem = new(null!)
276281
{
277-
ItemPath = item.Item.Path,
278-
ItemNameRaw = item.Item.Text,
282+
ItemPath = item.Path,
283+
ItemNameRaw = item.Text,
279284
PrimaryItemAttribute = StorageItemTypes.Folder,
280285
ItemType = Strings.Folder.GetLocalizedResource(),
281286
};
282287

283-
if (!string.Equals(item.Item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase))
288+
if (!string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase))
284289
{
285-
BaseStorageFolder matchingStorageFolder = await ContentPageContext.ShellPage!.ShellViewModel.GetFolderFromPathAsync(item.Item.Path);
290+
BaseStorageFolder matchingStorageFolder = await ContentPageContext.ShellPage!.ShellViewModel.GetFolderFromPathAsync(item.Path);
286291
if (matchingStorageFolder is not null)
287292
{
288293
var syncStatus = await ContentPageContext.ShellPage!.ShellViewModel.CheckCloudDriveSyncStatusAsync(matchingStorageFolder);

0 commit comments

Comments
 (0)