Skip to content

Commit 04709ad

Browse files
authored
Added support to display custom folder icons (#2094)
1 parent 5822860 commit 04709ad

File tree

8 files changed

+133
-62
lines changed

8 files changed

+133
-62
lines changed

Files.Launcher/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,14 @@ 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 } });
299+
break;
300+
293301
default:
294302
if (args.Request.Message.ContainsKey("Application"))
295303
{

Files/Filesystem/ListedItem.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ public class ListedItem : ObservableObject
1212
public bool ItemPropertiesInitialized { get; set; } = false;
1313
public string FolderTooltipText { get; set; }
1414
public string FolderRelativeId { get; set; }
15-
public bool LoadFolderGlyph { get; set; }
1615
public bool ContainsFilesOrFolders { get; set; }
16+
private bool _LoadFolderGlyph;
1717
private bool _LoadFileIcon;
1818

1919
public Uri FolderIconSource
@@ -32,6 +32,12 @@ public Uri FolderIconSourceLarge
3232
}
3333
}
3434

35+
public bool LoadFolderGlyph
36+
{
37+
get => _LoadFolderGlyph;
38+
set => SetProperty(ref _LoadFolderGlyph, value);
39+
}
40+
3541
public bool LoadFileIcon
3642
{
3743
get => _LoadFileIcon;

Files/UserControls/FileIcon.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
Height="{x:Bind ItemSize}"
1717
HorizontalAlignment="Stretch"
1818
VerticalAlignment="Stretch"
19-
x:Load="{x:Bind ViewModel.LoadFolderGlyph}"
19+
x:Load="{x:Bind ViewModel.LoadFolderGlyph, Mode=OneWay}"
2020
Stretch="Uniform">
2121
<Image.Source>
2222
<SvgImageSource

Files/View Models/ItemViewModel.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,6 @@ public async void LoadExtendedItemProperties(ListedItem item, uint thumbnailSize
467467
{
468468
if (item.PrimaryItemAttribute == StorageItemTypes.File)
469469
{
470-
BitmapImage icon = new BitmapImage();
471470
var matchingItem = _filesAndFolders.FirstOrDefault(x => x == item);
472471
try
473472
{
@@ -478,8 +477,8 @@ public async void LoadExtendedItemProperties(ListedItem item, uint thumbnailSize
478477
{
479478
if (Thumbnail != null)
480479
{
481-
matchingItem.FileImage = icon;
482-
await icon.SetSourceAsync(Thumbnail);
480+
matchingItem.FileImage = new BitmapImage();
481+
await matchingItem.FileImage.SetSourceAsync(Thumbnail);
483482
matchingItem.LoadUnknownTypeGlyph = false;
484483
matchingItem.LoadFileIcon = true;
485484
}
@@ -514,9 +513,33 @@ public async void LoadExtendedItemProperties(ListedItem item, uint thumbnailSize
514513
var matchingItem = _filesAndFolders.FirstOrDefault(x => x == item);
515514
try
516515
{
517-
StorageFolder matchingStorageItem = await StorageFileExtensions.GetFolderFromPathAsync(item.ItemPath, _workingRoot, _currentStorageFolder);
516+
StorageFolder matchingStorageItem = await StorageFileExtensions.GetFolderFromPathAsync((item as ShortcutItem)?.TargetPath ?? item.ItemPath, _workingRoot, _currentStorageFolder);
518517
if (matchingItem != null && matchingStorageItem != null)
519518
{
519+
if (App.Connection != null)
520+
{
521+
var value = new ValueSet();
522+
value.Add("Arguments", "CheckCustomIcon");
523+
value.Add("folderPath", matchingItem.ItemPath);
524+
var response = await App.Connection.SendMessageAsync(value);
525+
var hasCustomIcon = (response.Status == Windows.ApplicationModel.AppService.AppServiceResponseStatus.Success)
526+
&& response.Message.Get("HasCustomIcon", false);
527+
if (hasCustomIcon)
528+
{
529+
// Only set folder icon if it's a custom icon
530+
using (var Thumbnail = await matchingStorageItem.GetThumbnailAsync(ThumbnailMode.SingleItem, thumbnailSize, ThumbnailOptions.UseCurrentScale))
531+
{
532+
if (Thumbnail != null)
533+
{
534+
matchingItem.FileImage = new BitmapImage();
535+
await matchingItem.FileImage.SetSourceAsync(Thumbnail);
536+
matchingItem.LoadUnknownTypeGlyph = false;
537+
matchingItem.LoadFolderGlyph = false;
538+
matchingItem.LoadFileIcon = true;
539+
}
540+
}
541+
}
542+
}
520543
matchingItem.FolderRelativeId = matchingStorageItem.FolderRelativeId;
521544
matchingItem.ItemType = matchingStorageItem.DisplayType;
522545
var syncStatus = await CheckCloudDriveSyncStatus(matchingStorageItem);

Files/View Models/Properties/FolderProperties.cs

Lines changed: 86 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using ByteSizeLib;
22
using Files.Enums;
3+
using Files.Common;
34
using Files.Filesystem;
45
using Files.Helpers;
56
using Microsoft.Toolkit.Mvvm.Input;
@@ -11,8 +12,10 @@
1112
using Windows.ApplicationModel.Core;
1213
using Windows.Foundation.Collections;
1314
using Windows.Storage;
15+
using Windows.Storage.FileProperties;
1416
using Windows.UI.Core;
1517
using Windows.UI.Xaml;
18+
using Windows.UI.Xaml.Media.Imaging;
1619

1720
namespace Files.View_Models.Properties
1821
{
@@ -76,80 +79,111 @@ public async override void GetSpecialProperties()
7679
+ " (" + ByteSize.FromBytes(Item.FileSizeBytes).Bytes.ToString("#,##0") + " " + ResourceController.GetTranslation("ItemSizeBytes") + ")";
7780
ViewModel.ItemCreatedTimestamp = Item.ItemDateCreated;
7881
ViewModel.ItemAccessedTimestamp = Item.ItemDateAccessed;
79-
// Can't show any other property
80-
return;
82+
if (Item.IsLinkItem || string.IsNullOrWhiteSpace(((ShortcutItem)Item).TargetPath))
83+
{
84+
// Can't show any other property
85+
return;
86+
}
8187
}
82-
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
83-
string returnformat = Enum.Parse<TimeStyle>(localSettings.Values[LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g";
8488

85-
StorageFolder storageFolder;
86-
bool isItemSelected;
89+
var parentDirectory = App.CurrentInstance.FilesystemViewModel.CurrentFolder;
8790

91+
StorageFolder storageFolder = null;
8892
try
8993
{
90-
isItemSelected = await CoreApplication.MainView.ExecuteOnUIThreadAsync(() => App.CurrentInstance.ContentPage.IsItemSelected);
94+
var isItemSelected = await CoreApplication.MainView.ExecuteOnUIThreadAsync(() => App.CurrentInstance?.ContentPage?.IsItemSelected ?? true);
95+
if (isItemSelected)
96+
{
97+
storageFolder = await ItemViewModel.GetFolderFromPathAsync((Item as ShortcutItem)?.TargetPath ?? Item.ItemPath);
98+
}
99+
else if (!parentDirectory.ItemPath.StartsWith(App.AppSettings.RecycleBinPath))
100+
{
101+
storageFolder = await ItemViewModel.GetFolderFromPathAsync(parentDirectory.ItemPath);
102+
}
91103
}
92-
catch
104+
catch (Exception ex)
93105
{
94-
isItemSelected = true;
106+
NLog.LogManager.GetCurrentClassLogger().Error(ex, ex.Message);
107+
// Could not access folder, can't show any other property
108+
return;
95109
}
96110

97-
if (isItemSelected)
111+
if (storageFolder != null)
98112
{
99-
storageFolder = await ItemViewModel.GetFolderFromPathAsync(Item.ItemPath);
113+
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
114+
string returnformat = Enum.Parse<TimeStyle>(localSettings.Values[LocalSettings.DateTimeFormat].ToString()) == TimeStyle.Application ? "D" : "g";
100115
ViewModel.ItemCreatedTimestamp = ListedItem.GetFriendlyDateFromFormat(storageFolder.DateCreated, returnformat);
116+
LoadFolderIcon(storageFolder);
101117
GetOtherProperties(storageFolder.Properties);
102118
GetFolderSize(storageFolder, TokenSource.Token);
103119
}
104-
else
120+
else if (parentDirectory.ItemPath.StartsWith(App.AppSettings.RecycleBinPath))
105121
{
106-
var parentDirectory = App.CurrentInstance.FilesystemViewModel.CurrentFolder;
107-
if (parentDirectory.ItemPath.StartsWith(App.AppSettings.RecycleBinPath))
122+
// GetFolderFromPathAsync cannot access recyclebin folder
123+
if (App.Connection != null)
108124
{
109-
// GetFolderFromPathAsync cannot access recyclebin folder
110-
if (App.Connection != null)
125+
var value = new ValueSet();
126+
value.Add("Arguments", "RecycleBin");
127+
value.Add("action", "Query");
128+
// Send request to fulltrust process to get recyclebin properties
129+
var response = await App.Connection.SendMessageAsync(value);
130+
if (response.Status == Windows.ApplicationModel.AppService.AppServiceResponseStatus.Success)
111131
{
112-
var value = new ValueSet();
113-
value.Add("Arguments", "RecycleBin");
114-
value.Add("action", "Query");
115-
// Send request to fulltrust process to get recyclebin properties
116-
var response = await App.Connection.SendMessageAsync(value);
117-
if (response.Status == Windows.ApplicationModel.AppService.AppServiceResponseStatus.Success)
132+
if (response.Message.TryGetValue("BinSize", out var binSize))
133+
{
134+
ViewModel.ItemSizeBytes = (long)binSize;
135+
ViewModel.ItemSize = ByteSize.FromBytes((long)binSize).ToString();
136+
ViewModel.ItemSizeVisibility = Visibility.Visible;
137+
}
138+
else
118139
{
119-
if (response.Message.TryGetValue("BinSize", out var binSize))
120-
{
121-
ViewModel.ItemSizeBytes = (long)binSize;
122-
ViewModel.ItemSize = ByteSize.FromBytes((long)binSize).ToString();
123-
ViewModel.ItemSizeVisibility = Visibility.Visible;
124-
}
125-
else
126-
{
127-
ViewModel.ItemSizeVisibility = Visibility.Collapsed;
128-
}
129-
if (response.Message.TryGetValue("NumItems", out var numItems))
130-
{
131-
ViewModel.FilesCount = (int)(long)numItems;
132-
SetItemsCountString();
133-
ViewModel.FilesAndFoldersCountVisibility = Visibility.Visible;
134-
}
135-
else
136-
{
137-
ViewModel.FilesAndFoldersCountVisibility = Visibility.Collapsed;
138-
}
139-
ViewModel.ItemCreatedTimestampVisibiity = Visibility.Collapsed;
140-
ViewModel.ItemAccessedTimestampVisibility = Visibility.Collapsed;
141-
ViewModel.ItemModifiedTimestampVisibility = Visibility.Collapsed;
142-
ViewModel.ItemFileOwnerVisibility = Visibility.Collapsed;
143-
ViewModel.LastSeparatorVisibility = Visibility.Collapsed;
140+
ViewModel.ItemSizeVisibility = Visibility.Collapsed;
144141
}
142+
if (response.Message.TryGetValue("NumItems", out var numItems))
143+
{
144+
ViewModel.FilesCount = (int)(long)numItems;
145+
SetItemsCountString();
146+
ViewModel.FilesAndFoldersCountVisibility = Visibility.Visible;
147+
}
148+
else
149+
{
150+
ViewModel.FilesAndFoldersCountVisibility = Visibility.Collapsed;
151+
}
152+
ViewModel.ItemCreatedTimestampVisibiity = Visibility.Collapsed;
153+
ViewModel.ItemAccessedTimestampVisibility = Visibility.Collapsed;
154+
ViewModel.ItemModifiedTimestampVisibility = Visibility.Collapsed;
155+
ViewModel.ItemFileOwnerVisibility = Visibility.Collapsed;
156+
ViewModel.LastSeparatorVisibility = Visibility.Collapsed;
145157
}
146158
}
147-
else
159+
}
160+
}
161+
162+
private async void LoadFolderIcon(StorageFolder storageFolder)
163+
{
164+
if (App.Connection != null)
165+
{
166+
var value = new ValueSet();
167+
value.Add("Arguments", "CheckCustomIcon");
168+
value.Add("folderPath", Item.ItemPath);
169+
var response = await App.Connection.SendMessageAsync(value);
170+
var hasCustomIcon = (response.Status == Windows.ApplicationModel.AppService.AppServiceResponseStatus.Success)
171+
&& response.Message.Get("HasCustomIcon", false);
172+
if (hasCustomIcon)
148173
{
149-
storageFolder = await ItemViewModel.GetFolderFromPathAsync(parentDirectory.ItemPath);
150-
ViewModel.ItemCreatedTimestamp = ListedItem.GetFriendlyDateFromFormat(storageFolder.DateCreated, returnformat);
151-
GetOtherProperties(storageFolder.Properties);
152-
GetFolderSize(storageFolder, TokenSource.Token);
174+
// Only set folder icon if it's a custom icon
175+
using (var Thumbnail = await storageFolder.GetThumbnailAsync(ThumbnailMode.SingleItem, 80, ThumbnailOptions.UseCurrentScale))
176+
{
177+
BitmapImage icon = new BitmapImage();
178+
if (Thumbnail != null)
179+
{
180+
ViewModel.FileIconSource = icon;
181+
await icon.SetSourceAsync(Thumbnail);
182+
ViewModel.LoadUnknownTypeGlyph = false;
183+
ViewModel.LoadFolderGlyph = false;
184+
ViewModel.LoadFileIcon = true;
185+
}
186+
}
153187
}
154188
}
155189
}

Files/Views/LayoutModes/GenericFileBrowser.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@
701701
x:Name="FolderGlyphElement"
702702
Width="24"
703703
Height="24"
704-
x:Load="{x:Bind LoadFolderGlyph}">
704+
x:Load="{x:Bind LoadFolderGlyph, Mode=OneWay}">
705705
<Image.Source>
706706
<SvgImageSource
707707
RasterizePixelHeight="128"

Files/Views/LayoutModes/GridViewBrowser.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@
537537
x:Name="FolderGlyph"
538538
HorizontalAlignment="Stretch"
539539
VerticalAlignment="Stretch"
540-
x:Load="{x:Bind LoadFolderGlyph}"
540+
x:Load="{x:Bind LoadFolderGlyph, Mode=OneWay}"
541541
Stretch="Uniform">
542542
<Image.Source>
543543
<SvgImageSource
@@ -682,7 +682,7 @@
682682
Height="74"
683683
HorizontalAlignment="Stretch"
684684
VerticalAlignment="Stretch"
685-
x:Load="{x:Bind LoadFolderGlyph}"
685+
x:Load="{x:Bind LoadFolderGlyph, Mode=OneWay}"
686686
Stretch="Uniform">
687687
<Image.Source>
688688
<SvgImageSource

Files/Views/Pages/PropertiesDetails.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
Height="70"
5555
HorizontalAlignment="Stretch"
5656
VerticalAlignment="Stretch"
57-
x:Load="{x:Bind ViewModel.LoadFolderGlyph}"
57+
x:Load="{x:Bind ViewModel.LoadFolderGlyph, Mode=OneWay}"
5858
Stretch="Uniform">
5959
<Image.Source>
6060
<SvgImageSource

0 commit comments

Comments
 (0)