Skip to content

Commit 923da56

Browse files
committed
Update
1 parent 1cf721a commit 923da56

File tree

6 files changed

+262
-16
lines changed

6 files changed

+262
-16
lines changed

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,18 @@ SetFileAttributes
204204
INVALID_FILE_ATTRIBUTES
205205
SHDefExtractIconW
206206
GdipCreateBitmapFromHICON
207+
SHGetSetFolderCustomSettings
208+
FCSM_ICONFILE
209+
FCS_FORCEWRITE
210+
IShellLinkW
211+
SHFormatDrive
212+
ITaskbarList
213+
ITaskbarList2
214+
ITaskbarList3
215+
ITaskbarList4
216+
TaskbarList
217+
ICustomDestinationList
218+
DestinationList
219+
IObjectArray
220+
GetCurrentProcessExplicitAppUserModelID
221+
SetCurrentProcessExplicitAppUserModelID
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using Windows.Win32;
5+
using Windows.Win32.Foundation;
6+
using Windows.Win32.System.Com;
7+
using Windows.Win32.UI.Shell;
8+
using Windows.Win32.UI.Shell.Common;
9+
10+
namespace Files.App.Storage.Storables
11+
{
12+
public unsafe class JumpListManager : IDisposable
13+
{
14+
private ComPtr<ICustomDestinationList> pCustomDestinationList = default;
15+
16+
private static string? AppId
17+
{
18+
get
19+
{
20+
PWSTR pszAppId = default;
21+
HRESULT hr = PInvoke.GetCurrentProcessExplicitAppUserModelID(&pszAppId);
22+
if (hr == HRESULT.E_FAIL)
23+
hr = HRESULT.S_OK;
24+
25+
hr.ThrowIfFailedOnDebug();
26+
27+
return pszAppId.ToString();
28+
}
29+
}
30+
31+
// A special "Frequent" category managed by Windows
32+
public bool ShowFrequentCategory { get; set; }
33+
34+
// A special "Recent" category managed by Windows
35+
public bool ShowRecentCategory { get; set; }
36+
37+
private static JumpListManager? _Default = null;
38+
public static JumpListManager Default { get; } = _Default ??= new JumpListManager();
39+
40+
public JumpListManager()
41+
{
42+
Guid CLSID_CustomDestinationList = typeof(DestinationList).GUID;
43+
Guid IID_ICustomDestinationList = ICustomDestinationList.IID_Guid;
44+
HRESULT hr = PInvoke.CoCreateInstance(
45+
&CLSID_CustomDestinationList,
46+
null,
47+
CLSCTX.CLSCTX_INPROC_SERVER,
48+
&IID_ICustomDestinationList,
49+
(void**)pCustomDestinationList.GetAddressOf());
50+
}
51+
52+
public HRESULT Save()
53+
{
54+
Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA);
55+
56+
HRESULT hr = pCustomDestinationList.Get()->SetAppID(AppId);
57+
58+
uint cMinSlots = 0;
59+
ComPtr<IObjectArray> pObjectArray = default;
60+
Guid IID_IObjectArray = IObjectArray.IID_Guid;
61+
62+
hr = pCustomDestinationList.Get()->BeginList(&cMinSlots, &IID_IObjectArray, (void**)pObjectArray.GetAddressOf());
63+
64+
65+
// TODO..............
66+
67+
68+
if (ShowFrequentCategory)
69+
pCustomDestinationList.Get()->AppendKnownCategory(KNOWNDESTCATEGORY.KDC_FREQUENT);
70+
71+
if (ShowRecentCategory)
72+
pCustomDestinationList.Get()->AppendKnownCategory(KNOWNDESTCATEGORY.KDC_RECENT);
73+
74+
return HRESULT.S_OK;
75+
}
76+
77+
public void Dispose()
78+
{
79+
pCustomDestinationList.Dispose();
80+
}
81+
}
82+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using Windows.Win32;
5+
using Windows.Win32.Foundation;
6+
using Windows.Win32.System.Com;
7+
using Windows.Win32.UI.Shell;
8+
9+
namespace Files.App.Storage.Storables
10+
{
11+
public unsafe class TaskbarManager : IDisposable
12+
{
13+
private ComPtr<ITaskbarList3> pTaskbarList = default;
14+
15+
private static TaskbarManager? _Default = null;
16+
public static TaskbarManager Default { get; } = _Default ??= new TaskbarManager();
17+
18+
public TaskbarManager()
19+
{
20+
Guid CLSID_TaskbarList = typeof(TaskbarList).GUID;
21+
Guid IID_ITaskbarList3 = ITaskbarList3.IID_Guid;
22+
HRESULT hr = PInvoke.CoCreateInstance(
23+
&CLSID_TaskbarList,
24+
null,
25+
CLSCTX.CLSCTX_INPROC_SERVER,
26+
&IID_ITaskbarList3,
27+
(void**)pTaskbarList.GetAddressOf());
28+
29+
if (hr.ThrowIfFailedOnDebug().Succeeded)
30+
hr = pTaskbarList.Get()->HrInit().ThrowIfFailedOnDebug();
31+
}
32+
33+
public HRESULT SetProgressValue(HWND hwnd, ulong ullCompleted, ulong ullTotal)
34+
{
35+
return pTaskbarList.Get()->SetProgressValue(hwnd, ullCompleted, ullTotal);
36+
}
37+
38+
public HRESULT SetProgressState(HWND hwnd, TBPFLAG tbpFlags)
39+
{
40+
return pTaskbarList.Get()->SetProgressState(hwnd, tbpFlags);
41+
}
42+
43+
public void Dispose()
44+
{
45+
pTaskbarList.Dispose();
46+
}
47+
}
48+
}

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

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static partial class WindowsStorableHelpers
2727
{
2828
return await STATask.Run(() =>
2929
{
30-
storable.TryGetThumbnail(size, options, out var thumbnailData);
30+
HRESULT hr = storable.TryGetThumbnail(size, options, out var thumbnailData).ThrowIfFailedOnDebug();
3131
return thumbnailData;
3232
});
3333
}
@@ -40,21 +40,21 @@ public static partial class WindowsStorableHelpers
4040
/// <param name="options">A combination of <see cref="SIIGBF"/> flags that specify how the thumbnail should be retrieved.</param>
4141
/// <returns>A byte array containing the thumbnail image in its native format (e.g., PNG, JPEG).</returns>
4242
/// <remarks>If the thumbnail is JPEG, this tries to decoded as a PNG instead because JPEG loses data.</remarks>
43-
public unsafe static bool TryGetThumbnail(this IWindowsStorable storable, int size, SIIGBF options, out byte[]? thumbnailData)
43+
public unsafe static HRESULT TryGetThumbnail(this IWindowsStorable storable, int size, SIIGBF options, out byte[]? thumbnailData)
4444
{
4545
thumbnailData = null;
4646

4747
using ComPtr<IShellItemImageFactory> pShellItemImageFactory = storable.ThisPtr.As<IShellItemImageFactory>();
4848
if (pShellItemImageFactory.IsNull)
49-
return false;
49+
return HRESULT.E_NOINTERFACE;
5050

5151
// Get HBITMAP
5252
HBITMAP hBitmap = default;
5353
HRESULT hr = pShellItemImageFactory.Get()->GetImage(new(size, size), options, &hBitmap);
5454
if (hr.ThrowIfFailedOnDebug().Failed)
5555
{
5656
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
57-
return false;
57+
return hr;
5858
}
5959

6060
// Convert to GpBitmap of GDI+
@@ -63,30 +63,30 @@ public unsafe static bool TryGetThumbnail(this IWindowsStorable storable, int si
6363
{
6464
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
6565
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
66-
return false;
66+
return HRESULT.E_FAIL;
6767
}
6868

6969
if (TryConvertGpBitmapToByteArray(gpBitmap, out thumbnailData))
7070
{
7171
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
72-
return false;
72+
return HRESULT.E_FAIL;
7373
}
7474

75-
return true;
75+
return HRESULT.S_OK;
7676
}
7777

78-
public unsafe static bool TryExtractImageFromDll(this IWindowsStorable storable, int size, int index, out byte[]? imageData)
78+
public unsafe static HRESULT TryExtractImageFromDll(this IWindowsStorable storable, int size, int index, out byte[]? imageData)
7979
{
8080
DllIconCache ??= [];
8181
imageData = null;
8282

8383
if (storable.ToString() is not { } path)
84-
return false;
84+
return HRESULT.E_INVALIDARG;
8585

8686
if (DllIconCache.TryGetValue((path, index, size), out var cachedImageData))
8787
{
8888
imageData = cachedImageData;
89-
return true;
89+
return HRESULT.S_OK;
9090
}
9191
else
9292
{
@@ -99,27 +99,27 @@ public unsafe static bool TryExtractImageFromDll(this IWindowsStorable storable,
9999
if (hr.ThrowIfFailedOnDebug().Failed)
100100
{
101101
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
102-
return false;
102+
return hr;
103103
}
104104

105105
// Convert to GpBitmap of GDI+
106106
GpBitmap* gpBitmap = default;
107107
if (PInvoke.GdipCreateBitmapFromHICON(hIcon, &gpBitmap) is not Status.Ok)
108108
{
109109
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
110-
return false;
110+
return HRESULT.E_FAIL;
111111
}
112112

113113
if (!TryConvertGpBitmapToByteArray(gpBitmap, out imageData))
114114
{
115115
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
116-
return false;
116+
return HRESULT.E_FAIL;
117117
}
118118

119119
DllIconCache[(path, index, size)] = imageData;
120120
PInvoke.DestroyIcon(hIcon);
121121

122-
return true;
122+
return HRESULT.S_OK;
123123
}
124124
}
125125

@@ -211,5 +211,49 @@ Guid GetEncoderClsid(Guid format)
211211
return GdiEncoders;
212212
}
213213
}
214+
215+
public unsafe static HRESULT TrySetFolderIcon(this IWindowsStorable storable, IWindowsStorable iconFile, int index)
216+
{
217+
if (storable.GetDisplayName() is not { } folderPath ||
218+
iconFile.GetDisplayName() is not { } filePath)
219+
return HRESULT.E_INVALIDARG;
220+
221+
fixed (char* pszFolderPath = folderPath, pszIconFile = filePath)
222+
{
223+
SHFOLDERCUSTOMSETTINGS settings = default;
224+
settings.dwSize = (uint)sizeof(SHFOLDERCUSTOMSETTINGS);
225+
settings.dwMask = PInvoke.FCSM_ICONFILE;
226+
settings.pszIconFile = pszIconFile;
227+
settings.cchIconFile = 0;
228+
settings.iIconIndex = index;
229+
230+
HRESULT hr = PInvoke.SHGetSetFolderCustomSettings(&settings, pszFolderPath, PInvoke.FCS_FORCEWRITE);
231+
if (hr.ThrowIfFailedOnDebug().Failed)
232+
return hr;
233+
}
234+
235+
return HRESULT.S_OK;
236+
}
237+
238+
public unsafe static HRESULT TrySetShortcutIcon(this IWindowsStorable storable, IWindowsStorable iconFile, int index)
239+
{
240+
if (iconFile.ToString() is not { } iconFilePath)
241+
return HRESULT.E_INVALIDARG;
242+
243+
using ComPtr<IShellLinkW> pShellLink = default;
244+
Guid IID_IShellLink = IShellLinkW.IID_Guid;
245+
Guid BHID_SFUIObject = PInvoke.BHID_SFUIObject;
246+
247+
HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, &BHID_SFUIObject, &IID_IShellLink, (void**)pShellLink.GetAddressOf());
248+
if (hr.ThrowIfFailedOnDebug().Failed)
249+
return hr;
250+
251+
fixed (char* pszIconFilePath = iconFilePath)
252+
hr = pShellLink.Get()->SetIconLocation(iconFilePath, index);
253+
if (hr.ThrowIfFailedOnDebug().Failed)
254+
return hr;
255+
256+
return HRESULT.S_OK;
257+
}
214258
}
215259
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Concurrent;
5+
using System.Runtime.InteropServices;
6+
using Windows.Win32;
7+
using Windows.Win32.Foundation;
8+
using Windows.Win32.Graphics.Gdi;
9+
using Windows.Win32.Graphics.GdiPlus;
10+
using Windows.Win32.System.Com;
11+
using Windows.Win32.UI.Shell;
12+
using Windows.Win32.UI.WindowsAndMessaging;
13+
14+
namespace Files.App.Storage.Storables
15+
{
16+
public static partial class WindowsStorableHelpers
17+
{
18+
public static async Task<bool> TrySetShortcutIconOnPowerShellAsElevatedAsync(this IWindowsStorable storable, IWindowsStorable iconFile, int index)
19+
{
20+
string psScript =
21+
$@"$FilePath = '{storable}'
22+
$IconFile = '{iconFile}'
23+
$IconIndex = '{index}'
24+
25+
$Shell = New-Object -ComObject WScript.Shell
26+
$Shortcut = $Shell.CreateShortcut($FilePath)
27+
$Shortcut.IconLocation = ""$IconFile, $IconIndex""
28+
$Shortcut.Save()";
29+
30+
var process = new Process()
31+
{
32+
StartInfo = new ProcessStartInfo()
33+
{
34+
FileName = "PowerShell.exe",
35+
Arguments = $"-NoProfile -EncodedCommand {Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(psScript))}",
36+
Verb = "RunAs",
37+
CreateNoWindow = true,
38+
WindowStyle = ProcessWindowStyle.Hidden,
39+
UseShellExecute = true
40+
},
41+
};
42+
43+
process.Start();
44+
await process.WaitForExitAsync();
45+
46+
return true;
47+
}
48+
}
49+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.Collections.Concurrent;
5+
using System.Numerics;
56
using Windows.Networking.BackgroundTransfer;
67
using Windows.System;
78
using Windows.Win32;
@@ -13,7 +14,7 @@
1314

1415
namespace Files.App.Storage.Storables
1516
{
16-
public static partial class WindowsStorableHelpers
17+
public unsafe static partial class WindowsStorableHelpers
1718
{
1819
public static bool TryGetFileAttributes(this IWindowsStorable storable, out FILE_FLAGS_AND_ATTRIBUTES attributes)
1920
{
@@ -50,7 +51,7 @@ public static bool TryUnsetFileAttributes(this IWindowsStorable storable, FILE_F
5051
return PInvoke.SetFileAttributes(storable.GetDisplayName(), previousAttributes & ~attributes);
5152
}
5253

53-
public unsafe static bool TryToggleFileCompressedAttribute(this IWindowsStorable storable, bool value)
54+
public static bool TryToggleFileCompressedAttribute(this IWindowsStorable storable, bool value)
5455
{
5556
// GENERIC_READ | GENERIC_WRITE flags are needed here
5657
// FILE_FLAG_BACKUP_SEMANTICS is used to open directories
@@ -82,5 +83,12 @@ public unsafe static bool TryToggleFileCompressedAttribute(this IWindowsStorable
8283

8384
return result;
8485
}
86+
87+
public static bool TryShowFormatDriveDialog(HWND hWnd, uint driveLetterIndex, SHFMT_ID id, SHFMT_OPT options)
88+
{
89+
// NOTE: This calls an undocumented elevatable COM class, shell32.dll!CFormatEngine so this doesn't need to be elevated beforehand.
90+
var result = PInvoke.SHFormatDrive(hWnd, driveLetterIndex, id, options);
91+
return result is 0xFFFF;
92+
}
8593
}
8694
}

0 commit comments

Comments
 (0)