Skip to content

Commit a3ceb3d

Browse files
authored
Added support for icon overlays (#2149)
1 parent f3401af commit a3ceb3d

File tree

6 files changed

+106
-8
lines changed

6 files changed

+106
-8
lines changed

Files.Launcher/Program.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,12 @@ private static async Task parseArguments(AppServiceRequestReceivedEventArgs args
290290
await parseFileOperation(args);
291291
break;
292292

293-
case "CheckCustomIcon":
294-
var folderPath = (string)args.Request.Message["folderPath"];
295-
var shfi = new Shell32.SHFILEINFO();
296-
var ret = Shell32.SHGetFileInfo(folderPath, 0, ref shfi, Shell32.SHFILEINFO.Size, Shell32.SHGFI.SHGFI_ICONLOCATION);
297-
var hasCustomIcon = ret != IntPtr.Zero && !shfi.szDisplayName.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.Windows));
298-
await args.Request.SendResponseAsync(new ValueSet() { { "HasCustomIcon", hasCustomIcon } });
293+
case "GetIconOverlay":
294+
var fileIconPath = (string)args.Request.Message["filePath"];
295+
var iconOverlay = Win32API.GetFileOverlayIcon(fileIconPath);
296+
await args.Request.SendResponseAsync(new ValueSet() {
297+
{ "IconOverlay", iconOverlay.icon },
298+
{ "HasCustomIcon", iconOverlay.isCustom } });
299299
break;
300300

301301
default:

Files.Launcher/Win32API.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.ComponentModel;
33
using System.Diagnostics;
4+
using System.Drawing;
45
using System.IO;
56
using System.Linq;
67
using System.Runtime.InteropServices;
@@ -111,6 +112,27 @@ public static void UnlockBitlockerDrive(string drive, string password)
111112
// If user cancels UAC
112113
}
113114
}
115+
116+
public static (string icon, bool isCustom) GetFileOverlayIcon(string path)
117+
{
118+
var shfi = new Shell32.SHFILEINFO();
119+
var ret = Shell32.SHGetFileInfo(path, 0, ref shfi, Shell32.SHFILEINFO.Size, Shell32.SHGFI.SHGFI_OVERLAYINDEX | Shell32.SHGFI.SHGFI_ICON | Shell32.SHGFI.SHGFI_SYSICONINDEX | Shell32.SHGFI.SHGFI_ICONLOCATION);
120+
if (ret == IntPtr.Zero) return (null, false);
121+
bool isCustom = !shfi.szDisplayName.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.Windows));
122+
User32.DestroyIcon(shfi.hIcon);
123+
Shell32.SHGetImageList(Shell32.SHIL.SHIL_EXTRALARGE, typeof(ComCtl32.IImageList).GUID, out var tmp);
124+
using var imageList = ComCtl32.SafeHIMAGELIST.FromIImageList(tmp);
125+
if (imageList.IsNull || imageList.IsInvalid) return (null, isCustom);
126+
var overlay_idx = shfi.iIcon >> 24;
127+
//var icon_idx = shfi.iIcon & 0xFFFFFF;
128+
if (overlay_idx == 0) return (null, isCustom);
129+
var overlay_image = imageList.Interface.GetOverlayImage(overlay_idx);
130+
using var hIcon = imageList.Interface.GetIcon(overlay_image, ComCtl32.IMAGELISTDRAWFLAGS.ILD_TRANSPARENT);
131+
if (hIcon.IsNull || hIcon.IsInvalid) return (null, isCustom);
132+
var image = hIcon.ToIcon().ToBitmap();
133+
byte[] bitmapData = (byte[])new ImageConverter().ConvertTo(image, typeof(byte[]));
134+
return (Convert.ToBase64String(bitmapData, 0, bitmapData.Length), isCustom);
135+
}
114136
}
115137

116138
// There is usually no need to define Win32 COM interfaces/P-Invoke methods here.

Files/Filesystem/ListedItem.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ public BitmapImage FileImage
8282
}
8383
}
8484

85+
private BitmapImage _IconOverlay;
86+
87+
public BitmapImage IconOverlay
88+
{
89+
get => _IconOverlay;
90+
set
91+
{
92+
if (value != null)
93+
{
94+
SetProperty(ref _IconOverlay, value);
95+
}
96+
}
97+
}
98+
8599
private string _ItemPath;
86100

87101
public string ItemPath

Files/View Models/ItemViewModel.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,23 @@ public async void LoadExtendedItemProperties(ListedItem item, uint thumbnailSize
483483
matchingItem.LoadFileIcon = true;
484484
}
485485
}
486+
if (App.Connection != null)
487+
{
488+
var value = new ValueSet();
489+
value.Add("Arguments", "GetIconOverlay");
490+
value.Add("filePath", matchingItem.ItemPath);
491+
var response = await App.Connection.SendMessageAsync(value);
492+
var iconOverlay = response.Message.Get("IconOverlay", (string)null);
493+
if (iconOverlay != null)
494+
{
495+
matchingItem.IconOverlay = new BitmapImage();
496+
byte[] bitmapData = Convert.FromBase64String(iconOverlay);
497+
using (var ms = new MemoryStream(bitmapData))
498+
{
499+
await matchingItem.IconOverlay.SetSourceAsync(ms.AsRandomAccessStream());
500+
}
501+
}
502+
}
486503
if (item.IsShortcutItem)
487504
{
488505
// Reset cloud sync status icon
@@ -519,8 +536,8 @@ public async void LoadExtendedItemProperties(ListedItem item, uint thumbnailSize
519536
if (App.Connection != null)
520537
{
521538
var value = new ValueSet();
522-
value.Add("Arguments", "CheckCustomIcon");
523-
value.Add("folderPath", matchingItem.ItemPath);
539+
value.Add("Arguments", "GetIconOverlay");
540+
value.Add("filePath", matchingItem.ItemPath);
524541
var response = await App.Connection.SendMessageAsync(value);
525542
var hasCustomIcon = (response.Status == Windows.ApplicationModel.AppService.AppServiceResponseStatus.Success)
526543
&& response.Message.Get("HasCustomIcon", false);
@@ -539,6 +556,16 @@ public async void LoadExtendedItemProperties(ListedItem item, uint thumbnailSize
539556
}
540557
}
541558
}
559+
var iconOverlay = response.Message.Get("IconOverlay", (string)null);
560+
if (iconOverlay != null)
561+
{
562+
matchingItem.IconOverlay = new BitmapImage();
563+
byte[] bitmapData = Convert.FromBase64String(iconOverlay);
564+
using (var ms = new MemoryStream(bitmapData))
565+
{
566+
await matchingItem.IconOverlay.SetSourceAsync(ms.AsRandomAccessStream());
567+
}
568+
}
542569
}
543570
matchingItem.FolderRelativeId = matchingStorageItem.FolderRelativeId;
544571
matchingItem.ItemType = matchingStorageItem.DisplayType;

Files/Views/LayoutModes/GenericFileBrowser.xaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,17 @@
730730
x:Load="{x:Bind LoadFileIcon, Mode=OneWay}"
731731
Source="{x:Bind FileImage, Mode=OneWay}"
732732
Stretch="UniformToFill" />
733+
<Image
734+
x:Name="IconOverlay"
735+
Source="{x:Bind IconOverlay, Mode=OneWay}"
736+
Stretch="Uniform"
737+
HorizontalAlignment="Center"
738+
VerticalAlignment="Center"
739+
Width="22"
740+
Height="22"
741+
Margin="2,8,8,2"
742+
x:Load="True">
743+
</Image>
733744
<Border
734745
x:Name="ShortcutGlyphElement"
735746
Margin="2,18,18,2"

Files/Views/LayoutModes/GridViewBrowser.xaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,18 @@
559559
<SymbolIcon Symbol="Page2" />
560560
</Viewbox>
561561
</Grid>
562+
<Grid
563+
x:Name="IconOverlay"
564+
Padding="12"
565+
HorizontalAlignment="Left"
566+
VerticalAlignment="Bottom"
567+
Width="64"
568+
Height="64"
569+
x:Load="True">
570+
<Image
571+
Source="{x:Bind IconOverlay, Mode=OneWay}"
572+
Stretch="Uniform" />
573+
</Grid>
562574
<Grid
563575
x:Name="WebShortcutGlyph"
564576
Padding="12"
@@ -707,6 +719,18 @@
707719
Glyph="&#xEA00;" />
708720
</Grid>
709721
</Grid>
722+
<Grid
723+
x:Name="IconOverlay"
724+
Padding="8"
725+
HorizontalAlignment="Left"
726+
VerticalAlignment="Bottom"
727+
Width="48"
728+
Height="48"
729+
x:Load="True">
730+
<Image
731+
Source="{x:Bind IconOverlay, Mode=OneWay}"
732+
Stretch="Uniform" />
733+
</Grid>
710734
<Grid
711735
x:Name="WebShortcutGlyph"
712736
Width="74"

0 commit comments

Comments
 (0)