[RAPPS] Load icons on background thread (#6881)

- Load icons on background thread to massively reduce loading time.

- Use SM_CXICON sized icons consistently instead of hardcoding 32 in some places.
This commit is contained in:
Whindmar Saksit 2024-06-13 02:20:41 +02:00 committed by GitHub
parent 4321c975c7
commit d73a838245
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 158 additions and 51 deletions

View file

@ -13,6 +13,9 @@
using namespace Gdiplus;
HICON g_hDefaultPackageIcon = NULL;
static int g_DefaultPackageIconILIdx = I_IMAGENONE;
// **** CMainToolbar ****
VOID
@ -970,16 +973,89 @@ CAppInfoDisplay::~CAppInfoDisplay()
// **** CAppsListView ****
struct CAsyncLoadIcon {
CAsyncLoadIcon *pNext;
HWND hAppsList;
CAppInfo *AppInfo; // Only used to find the item in the list, do not access on background thread
UINT TaskId;
bool Parse;
WCHAR Location[ANYSIZE_ARRAY];
void Free() { free(this); }
static CAsyncLoadIcon* Queue(HWND hAppsList, CAppInfo &AppInfo, bool Parse);
static void StartTasks();
} *g_AsyncIconTasks = NULL;
static UINT g_AsyncIconTaskId = 0;
static DWORD CALLBACK
AsyncLoadIconProc(LPVOID Param)
{
for (CAsyncLoadIcon *task = (CAsyncLoadIcon*)Param, *old; task; old->Free())
{
if (task->TaskId == g_AsyncIconTaskId)
{
HICON hIcon;
if (!task->Parse)
hIcon = (HICON)LoadImageW(NULL, task->Location, IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
else if (!ExtractIconExW(task->Location, PathParseIconLocationW(task->Location), &hIcon, NULL, 1))
hIcon = NULL;
if (hIcon)
{
SendMessageW(task->hAppsList, WM_RAPPSLIST_ASYNCICON, (WPARAM)hIcon, (LPARAM)task);
DestroyIcon(hIcon);
}
}
old = task;
task = task->pNext;
}
return 0;
}
CAsyncLoadIcon*
CAsyncLoadIcon::Queue(HWND hAppsList, CAppInfo &AppInfo, bool Parse)
{
ATLASSERT(GetCurrentThreadId() == GetWindowThreadProcessId(hAppsList, NULL));
CStringW szIconPath;
if (!AppInfo.RetrieveIcon(szIconPath))
return NULL;
SIZE_T cbstr = (szIconPath.GetLength() + 1) * sizeof(WCHAR);
CAsyncLoadIcon *task = (CAsyncLoadIcon*)malloc(sizeof(CAsyncLoadIcon) + cbstr);
if (!task)
return NULL;
task->hAppsList = hAppsList;
task->AppInfo = &AppInfo;
task->TaskId = g_AsyncIconTaskId;
task->Parse = Parse;
CopyMemory(task->Location, szIconPath.GetBuffer(), cbstr);
szIconPath.ReleaseBuffer();
task->pNext = g_AsyncIconTasks;
g_AsyncIconTasks = task;
return task;
}
void
CAsyncLoadIcon::StartTasks()
{
CAsyncLoadIcon *tasks = g_AsyncIconTasks;
g_AsyncIconTasks = NULL;
if (HANDLE hThread = CreateThread(NULL, 0, AsyncLoadIconProc, tasks, 0, NULL))
CloseHandle(hThread);
else
AsyncLoadIconProc(tasks); // Insist so we at least free the tasks
}
CAppsListView::CAppsListView()
{
m_hImageListView = 0;
}
CAppsListView::~CAppsListView()
{
if (m_hImageListView)
{
ImageList_Destroy(m_hImageListView);
}
if (g_hDefaultPackageIcon)
DestroyIcon(g_hDefaultPackageIcon);
}
LRESULT
@ -1000,6 +1076,32 @@ CAppsListView::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &
return lRes;
}
LRESULT
CAppsListView::OnAsyncIcon(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
CAsyncLoadIcon *task = (CAsyncLoadIcon*)lParam;
bHandled = TRUE;
if (task->TaskId == g_AsyncIconTaskId)
{
LVITEM lvi;
LVFINDINFO lvfi;
lvfi.flags = LVFI_PARAM;
lvfi.lParam = (LPARAM)task->AppInfo;
lvi.iItem = ListView_FindItem(m_hWnd, -1, &lvfi);
if (lvi.iItem != -1)
{
lvi.iImage = ImageList_AddIcon(m_hImageListView, (HICON)wParam);
if (lvi.iImage != -1)
{
lvi.mask = LVIF_IMAGE;
lvi.iSubItem = 0;
ListView_SetItem(m_hWnd, &lvi);
}
}
}
return 0;
}
VOID
CAppsListView::SetWatermark(const CStringW &Text)
{
@ -1154,11 +1256,6 @@ CAppsListView::Create(HWND hwndParent)
SetCheckboxesVisible(FALSE);
}
m_hImageListView = ImageList_Create(LISTVIEW_ICON_SIZE, LISTVIEW_ICON_SIZE, GetSystemColorDepth() | ILC_MASK, 0, 1);
SetImageList(m_hImageListView, LVSIL_SMALL);
SetImageList(m_hImageListView, LVSIL_NORMAL);
#pragma push_macro("SubclassWindow")
#undef SubclassWindow
m_hWnd = NULL;
@ -1215,23 +1312,40 @@ CAppsListView::GetFocusedItemData()
BOOL
CAppsListView::SetDisplayAppType(APPLICATION_VIEW_TYPE AppType)
{
++g_AsyncIconTaskId; // Stop loading icons that are now invalid
if (!DeleteAllItems())
return FALSE;
ApplicationViewType = AppType;
bIsAscending = TRUE;
ItemCount = 0;
CheckedItemCount = 0;
ListView_Scroll(m_hWnd, 0, 0x7fff * -1); // FIXME: a bug in Wine ComCtl32 where VScroll is not reset after deleting items
// delete old columns
while (ColumnCount)
{
DeleteColumn(--ColumnCount);
}
if (!g_hDefaultPackageIcon)
{
ImageList_Destroy(m_hImageListView);
UINT IconSize = GetSystemMetrics(SM_CXICON);
UINT ilc = GetSystemColorDepth() | ILC_MASK;
m_hImageListView = ImageList_Create(IconSize, IconSize, ilc, 0, 1);
SetImageList(m_hImageListView, LVSIL_SMALL);
SetImageList(m_hImageListView, LVSIL_NORMAL);
g_hDefaultPackageIcon = (HICON)LoadImageW(hInst, MAKEINTRESOURCEW(IDI_MAIN),
IMAGE_ICON, IconSize, IconSize, LR_SHARED);
}
ImageList_RemoveAll(m_hImageListView);
g_DefaultPackageIconILIdx = ImageList_AddIcon(m_hImageListView, g_hDefaultPackageIcon);
if (g_DefaultPackageIconILIdx == -1)
g_DefaultPackageIconILIdx = I_IMAGENONE;
// add new columns
CStringW szText;
switch (AppType)
@ -1284,31 +1398,20 @@ CAppsListView::SetViewMode(DWORD ViewMode)
BOOL
CAppsListView::AddApplication(CAppInfo *AppInfo, BOOL InitialCheckState)
{
if (!AppInfo)
{
CAsyncLoadIcon::StartTasks();
return TRUE;
}
int IconIndex = g_DefaultPackageIconILIdx;
if (ApplicationViewType == AppViewTypeInstalledApps)
{
/* Load icon from registry */
HICON hIcon = NULL;
CStringW szIconPath;
int IconIndex;
if (AppInfo->RetrieveIcon(szIconPath))
{
IconIndex = PathParseIconLocationW(szIconPath.GetBuffer());
szIconPath.ReleaseBuffer();
ExtractIconExW(szIconPath.GetString(), IconIndex, &hIcon, NULL, 1);
}
/* Use the default icon if none were found in the file, or if it is not supported (returned 1) */
if (!hIcon || (hIcon == (HICON)1))
{
/* Load default icon */
hIcon = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN));
}
IconIndex = ImageList_AddIcon(m_hImageListView, hIcon);
DestroyIcon(hIcon);
int Index = AddItem(ItemCount, IconIndex, AppInfo->szDisplayName, (LPARAM)AppInfo);
if (Index == -1)
return FALSE;
CAsyncLoadIcon::Queue(m_hWnd, *AppInfo, true);
SetItemText(Index, 1, AppInfo->szDisplayVersion.IsEmpty() ? L"---" : AppInfo->szDisplayVersion);
SetItemText(Index, 2, AppInfo->szComments.IsEmpty() ? L"---" : AppInfo->szComments);
@ -1317,25 +1420,10 @@ CAppsListView::AddApplication(CAppInfo *AppInfo, BOOL InitialCheckState)
}
else if (ApplicationViewType == AppViewTypeAvailableApps)
{
/* Load icon from file */
HICON hIcon = NULL;
CStringW szIconPath;
if (AppInfo->RetrieveIcon(szIconPath))
{
hIcon = (HICON)LoadImageW(
NULL, szIconPath, IMAGE_ICON, LISTVIEW_ICON_SIZE, LISTVIEW_ICON_SIZE, LR_LOADFROMFILE);
}
if (!hIcon)
{
/* Load default icon */
hIcon = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN));
}
int IconIndex = ImageList_AddIcon(m_hImageListView, hIcon);
DestroyIcon(hIcon);
int Index = AddItem(ItemCount, IconIndex, AppInfo->szDisplayName, (LPARAM)AppInfo);
if (Index == -1)
return FALSE;
CAsyncLoadIcon::Queue(m_hWnd, *AppInfo, false);
if (InitialCheckState)
{

View file

@ -459,6 +459,14 @@ CMainWindow::ProcessWindowMessage(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lPa
}
break;
case WM_SETTINGCHANGE:
if (wParam == SPI_SETNONCLIENTMETRICS || wParam == SPI_SETICONMETRICS)
{
DestroyIcon(g_hDefaultPackageIcon);
g_hDefaultPackageIcon = NULL; // Trigger imagelist recreation on next load
}
break;
case WM_TIMER:
if (wParam == SEARCH_TIMER_ID)
{
@ -598,6 +606,7 @@ CMainWindow::AddApplicationsToView(CAtlList<CAppInfo *> &List)
m_ApplicationView->AddApplication(Info, bSelected);
}
}
m_ApplicationView->AddApplication(NULL, FALSE); // Tell the list we are done
}
VOID
@ -605,6 +614,13 @@ CMainWindow::UpdateApplicationsList(AppsCategories EnumType, BOOL bReload, BOOL
{
bUpdating = TRUE;
if (HCURSOR hCursor = LoadCursor(NULL, IDC_APPSTARTING))
{
// The database (.ini files) is parsed on the UI thread, let the user know we are busy
SetCursor(hCursor);
PostMessage(WM_SETCURSOR, (WPARAM)m_hWnd, MAKELONG(HTCLIENT, WM_MOUSEMOVE));
}
if (bCheckAvailable)
CheckAvailable();

View file

@ -18,8 +18,7 @@
#include <gdiplus.h>
#include <math.h>
#define LISTVIEW_ICON_SIZE 32
extern HICON g_hDefaultPackageIcon;
// default broken-image icon size
#define BROKENIMG_ICON_SIZE 96
@ -40,6 +39,7 @@
#define WM_RAPPS_DOWNLOAD_COMPLETE \
(WM_USER + 1) // notify download complete. wParam is error code, and lParam is a pointer to ScrnshotDownloadParam
#define WM_RAPPS_RESIZE_CHILDREN (WM_USER + 2) // ask parent window to resize children.
#define WM_RAPPSLIST_ASYNCICON (WM_APP + 0)
enum SCRNSHOT_STATUS
{
@ -210,10 +210,13 @@ class CAppsListView : public CUiWindow<CWindowImpl<CAppsListView, CListView>>
BEGIN_MSG_MAP(CAppsListView)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_RAPPSLIST_ASYNCICON, OnAsyncIcon)
END_MSG_MAP()
LRESULT
OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
LRESULT
OnAsyncIcon(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
public:
CAppsListView();