/* * Process Hacker - * extended list view * * Copyright (C) 2010-2012 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ /* * The extended list view adds some functionality to the default list view control, such as sorting, * item colors and fonts, better redraw disabling, and the ability to change the cursor. This is * currently implemented by hooking the window procedure. */ #include #include #include #define PH_MAX_COMPARE_FUNCTIONS 16 typedef struct _PH_EXTLV_CONTEXT { HWND Handle; WNDPROC OldWndProc; PVOID Context; // Sorting BOOLEAN TriState; ULONG SortColumn; PH_SORT_ORDER SortOrder; BOOLEAN SortFast; PPH_COMPARE_FUNCTION TriStateCompareFunction; PPH_COMPARE_FUNCTION CompareFunctions[PH_MAX_COMPARE_FUNCTIONS]; ULONG FallbackColumns[PH_MAX_COMPARE_FUNCTIONS]; ULONG NumberOfFallbackColumns; // Color and Font PPH_EXTLV_GET_ITEM_COLOR ItemColorFunction; PPH_EXTLV_GET_ITEM_FONT ItemFontFunction; // Misc. LONG EnableRedraw; HCURSOR Cursor; } PH_EXTLV_CONTEXT, *PPH_EXTLV_CONTEXT; LRESULT CALLBACK PhpExtendedListViewWndProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ); INT PhpExtendedListViewCompareFunc( _In_ LPARAM lParam1, _In_ LPARAM lParam2, _In_ LPARAM lParamSort ); INT PhpExtendedListViewCompareFastFunc( _In_ LPARAM lParam1, _In_ LPARAM lParam2, _In_ LPARAM lParamSort ); INT PhpCompareListViewItems( _In_ PPH_EXTLV_CONTEXT Context, _In_ INT X, _In_ INT Y, _In_ PVOID XParam, _In_ PVOID YParam, _In_ ULONG Column, _In_ BOOLEAN EnableDefault ); INT PhpDefaultCompareListViewItems( _In_ PPH_EXTLV_CONTEXT Context, _In_ INT X, _In_ INT Y, _In_ ULONG Column ); static PWSTR PhpMakeExtLvContextAtom( VOID ) { PH_DEFINE_MAKE_ATOM(L"PhLib_ExtLvContext"); } /** * Enables extended list view support for a list view control. * * \param hWnd A handle to the list view control. */ VOID PhSetExtendedListView( _In_ HWND hWnd ) { WNDPROC oldWndProc; PPH_EXTLV_CONTEXT context; oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PhpExtendedListViewWndProc); context = PhAllocate(sizeof(PH_EXTLV_CONTEXT)); context->Handle = hWnd; context->OldWndProc = oldWndProc; context->Context = NULL; context->TriState = FALSE; context->SortColumn = 0; context->SortOrder = AscendingSortOrder; context->SortFast = FALSE; context->TriStateCompareFunction = NULL; memset(context->CompareFunctions, 0, sizeof(context->CompareFunctions)); context->NumberOfFallbackColumns = 0; context->ItemColorFunction = NULL; context->ItemFontFunction = NULL; context->EnableRedraw = 1; context->Cursor = NULL; SetProp(hWnd, PhpMakeExtLvContextAtom(), (HANDLE)context); ExtendedListView_Init(hWnd); } LRESULT CALLBACK PhpExtendedListViewWndProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { PPH_EXTLV_CONTEXT context; WNDPROC oldWndProc; context = (PPH_EXTLV_CONTEXT)GetProp(hwnd, PhpMakeExtLvContextAtom()); oldWndProc = context->OldWndProc; switch (uMsg) { case WM_DESTROY: { SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); PhFree(context); RemoveProp(hwnd, PhpMakeExtLvContextAtom()); } break; case WM_NOTIFY: { LPNMHDR header = (LPNMHDR)lParam; switch (header->code) { case HDN_ITEMCLICK: { HWND headerHandle; headerHandle = (HWND)CallWindowProc(context->OldWndProc, hwnd, LVM_GETHEADER, 0, 0); if (header->hwndFrom == headerHandle) { LPNMHEADER header2 = (LPNMHEADER)header; if (header2->iItem == context->SortColumn) { if (context->TriState) { if (context->SortOrder == AscendingSortOrder) context->SortOrder = DescendingSortOrder; else if (context->SortOrder == DescendingSortOrder) context->SortOrder = NoSortOrder; else context->SortOrder = AscendingSortOrder; } else { if (context->SortOrder == AscendingSortOrder) context->SortOrder = DescendingSortOrder; else context->SortOrder = AscendingSortOrder; } } else { context->SortColumn = header2->iItem; context->SortOrder = AscendingSortOrder; } PhSetHeaderSortIcon(headerHandle, context->SortColumn, context->SortOrder); ExtendedListView_SortItems(hwnd); } } break; } } break; case WM_REFLECT + WM_NOTIFY: { LPNMHDR header = (LPNMHDR)lParam; switch (header->code) { case NM_CUSTOMDRAW: { if (header->hwndFrom == hwnd) { LPNMLVCUSTOMDRAW customDraw = (LPNMLVCUSTOMDRAW)header; switch (customDraw->nmcd.dwDrawStage) { case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW; case CDDS_ITEMPREPAINT: { BOOLEAN colorChanged = FALSE; HFONT newFont = NULL; if (context->ItemColorFunction) { customDraw->clrTextBk = context->ItemColorFunction( (INT)customDraw->nmcd.dwItemSpec, (PVOID)customDraw->nmcd.lItemlParam, context->Context ); colorChanged = TRUE; } if (context->ItemFontFunction) { newFont = context->ItemFontFunction( (INT)customDraw->nmcd.dwItemSpec, (PVOID)customDraw->nmcd.lItemlParam, context->Context ); } if (newFont) SelectObject(customDraw->nmcd.hdc, newFont); if (colorChanged) { if (PhGetColorBrightness(customDraw->clrTextBk) > 100) // slightly less than half customDraw->clrText = RGB(0x00, 0x00, 0x00); else customDraw->clrText = RGB(0xff, 0xff, 0xff); } if (!newFont) return CDRF_DODEFAULT; else return CDRF_NEWFONT; } break; } } } break; } } break; case WM_SETCURSOR: { if (context->Cursor) { SetCursor(context->Cursor); return TRUE; } } break; case WM_UPDATEUISTATE: { // Disable focus rectangles by setting or masking out the flag where appropriate. switch (LOWORD(wParam)) { case UIS_SET: wParam |= UISF_HIDEFOCUS << 16; break; case UIS_CLEAR: case UIS_INITIALIZE: wParam &= ~(UISF_HIDEFOCUS << 16); break; } } break; case ELVM_ADDFALLBACKCOLUMN: { if (context->NumberOfFallbackColumns < PH_MAX_COMPARE_FUNCTIONS) context->FallbackColumns[context->NumberOfFallbackColumns++] = (ULONG)wParam; else return FALSE; } return TRUE; case ELVM_ADDFALLBACKCOLUMNS: { ULONG numberOfColumns = (ULONG)wParam; PULONG columns = (PULONG)lParam; if (context->NumberOfFallbackColumns + numberOfColumns <= PH_MAX_COMPARE_FUNCTIONS) { memcpy( &context->FallbackColumns[context->NumberOfFallbackColumns], columns, numberOfColumns * sizeof(ULONG) ); context->NumberOfFallbackColumns += numberOfColumns; } else { return FALSE; } } return TRUE; case ELVM_INIT: { PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder); // HACK to fix tooltips showing behind Always On Top windows. SetWindowPos(ListView_GetToolTips(hwnd), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); // Make sure focus rectangles are disabled. SendMessage(hwnd, WM_CHANGEUISTATE, MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0); } return TRUE; case ELVM_SETCOLUMNWIDTH: { ULONG column = (ULONG)wParam; LONG width = (LONG)lParam; if (width == ELVSCW_AUTOSIZE_REMAININGSPACE) { RECT clientRect; LONG availableWidth; ULONG i; LVCOLUMN lvColumn; GetClientRect(hwnd, &clientRect); availableWidth = clientRect.right; i = 0; lvColumn.mask = LVCF_WIDTH; while (TRUE) { if (i != column) { if (CallWindowProc(oldWndProc, hwnd, LVM_GETCOLUMN, i, (LPARAM)&lvColumn)) { availableWidth -= lvColumn.cx; } else { break; } } i++; } if (availableWidth >= 40) return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, availableWidth); } return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, width); } break; case ELVM_SETCOMPAREFUNCTION: { ULONG column = (ULONG)wParam; PPH_COMPARE_FUNCTION compareFunction = (PPH_COMPARE_FUNCTION)lParam; if (column >= PH_MAX_COMPARE_FUNCTIONS) return FALSE; context->CompareFunctions[column] = compareFunction; } return TRUE; case ELVM_SETCONTEXT: { context->Context = (PVOID)lParam; } return TRUE; case ELVM_SETCURSOR: { context->Cursor = (HCURSOR)lParam; } return TRUE; case ELVM_SETITEMCOLORFUNCTION: { context->ItemColorFunction = (PPH_EXTLV_GET_ITEM_COLOR)lParam; } return TRUE; case ELVM_SETITEMFONTFUNCTION: { context->ItemFontFunction = (PPH_EXTLV_GET_ITEM_FONT)lParam; } return TRUE; case ELVM_SETREDRAW: { if (wParam) context->EnableRedraw++; else context->EnableRedraw--; if (context->EnableRedraw == 1) { SendMessage(hwnd, WM_SETREDRAW, TRUE, 0); InvalidateRect(hwnd, NULL, FALSE); } else if (context->EnableRedraw == 0) { SendMessage(hwnd, WM_SETREDRAW, FALSE, 0); } } return TRUE; case ELVM_SETSORT: { context->SortColumn = (ULONG)wParam; context->SortOrder = (PH_SORT_ORDER)lParam; PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder); } return TRUE; case ELVM_SETSORTFAST: { context->SortFast = !!wParam; } return TRUE; case ELVM_SETTRISTATE: { context->TriState = !!wParam; } return TRUE; case ELVM_SETTRISTATECOMPAREFUNCTION: { context->TriStateCompareFunction = (PPH_COMPARE_FUNCTION)lParam; } return TRUE; case ELVM_SORTITEMS: { if (context->SortFast) { // This sort method is faster than the normal sort because our comparison function // doesn't have to call the list view window procedure to get the item lParam // values. The disadvantage of this method is that default sorting is not available // - if a column doesn't have a comparison function, it doesn't get sorted at all. ListView_SortItems( hwnd, PhpExtendedListViewCompareFastFunc, (LPARAM)context ); } else { ListView_SortItemsEx( hwnd, PhpExtendedListViewCompareFunc, (LPARAM)context ); } } return TRUE; } return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); } /** * Visually indicates the sort order of a header control item. * * \param hwnd A handle to the header control. * \param Index The index of the item. * \param Order The sort order of the item. */ VOID PhSetHeaderSortIcon( _In_ HWND hwnd, _In_ INT Index, _In_ PH_SORT_ORDER Order ) { ULONG count; ULONG i; count = Header_GetItemCount(hwnd); if (count == -1) return; for (i = 0; i < count; i++) { HDITEM item; item.mask = HDI_FORMAT; Header_GetItem(hwnd, i, &item); if (Order != NoSortOrder && i == Index) { if (Order == AscendingSortOrder) { item.fmt &= ~HDF_SORTDOWN; item.fmt |= HDF_SORTUP; } else if (Order == DescendingSortOrder) { item.fmt &= ~HDF_SORTUP; item.fmt |= HDF_SORTDOWN; } } else { item.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); } Header_SetItem(hwnd, i, &item); } } static INT PhpExtendedListViewCompareFunc( _In_ LPARAM lParam1, _In_ LPARAM lParam2, _In_ LPARAM lParamSort ) { PPH_EXTLV_CONTEXT context = (PPH_EXTLV_CONTEXT)lParamSort; INT result; INT x = (INT)lParam1; INT y = (INT)lParam2; ULONG i; PULONG fallbackColumns; LVITEM xItem; LVITEM yItem; // Get the param values. xItem.mask = LVIF_PARAM | LVIF_STATE; xItem.iItem = x; xItem.iSubItem = 0; yItem.mask = LVIF_PARAM | LVIF_STATE; yItem.iItem = y; yItem.iSubItem = 0; // Don't use SendMessage/ListView_* because it will call our new window procedure, which will // use GetProp. This calls NtUserGetProp, and obviously having a system call in a comparison // function is very, very bad for performance. if (!CallWindowProc(context->OldWndProc, context->Handle, LVM_GETITEM, 0, (LPARAM)&xItem)) return 0; if (!CallWindowProc(context->OldWndProc, context->Handle, LVM_GETITEM, 0, (LPARAM)&yItem)) return 0; // First, do tri-state sorting. if ( context->TriState && context->SortOrder == NoSortOrder && context->TriStateCompareFunction ) { result = context->TriStateCompareFunction( (PVOID)xItem.lParam, (PVOID)yItem.lParam, context->Context ); if (result != 0) return result; } // Compare using the user-selected column and move on to the fallback columns if necessary. result = PhpCompareListViewItems(context, x, y, (PVOID)xItem.lParam, (PVOID)yItem.lParam, context->SortColumn, TRUE); if (result != 0) return result; fallbackColumns = context->FallbackColumns; for (i = context->NumberOfFallbackColumns; i != 0; i--) { ULONG fallbackColumn = *fallbackColumns++; if (fallbackColumn == context->SortColumn) continue; result = PhpCompareListViewItems(context, x, y, (PVOID)xItem.lParam, (PVOID)yItem.lParam, fallbackColumn, TRUE); if (result != 0) return result; } return 0; } static INT PhpExtendedListViewCompareFastFunc( _In_ LPARAM lParam1, _In_ LPARAM lParam2, _In_ LPARAM lParamSort ) { PPH_EXTLV_CONTEXT context = (PPH_EXTLV_CONTEXT)lParamSort; INT result; ULONG i; PULONG fallbackColumns; if (!lParam1 || !lParam2) return 0; // First, do tri-state sorting. if ( context->TriState && context->SortOrder == NoSortOrder && context->TriStateCompareFunction ) { result = context->TriStateCompareFunction( (PVOID)lParam1, (PVOID)lParam2, context->Context ); if (result != 0) return result; } // Compare using the user-selected column and move on to the fallback columns if necessary. result = PhpCompareListViewItems(context, 0, 0, (PVOID)lParam1, (PVOID)lParam2, context->SortColumn, FALSE); if (result != 0) return result; fallbackColumns = context->FallbackColumns; for (i = context->NumberOfFallbackColumns; i != 0; i--) { ULONG fallbackColumn = *fallbackColumns++; if (fallbackColumn == context->SortColumn) continue; result = PhpCompareListViewItems(context, 0, 0, (PVOID)lParam1, (PVOID)lParam2, fallbackColumn, FALSE); if (result != 0) return result; } return 0; } static FORCEINLINE INT PhpCompareListViewItems( _In_ PPH_EXTLV_CONTEXT Context, _In_ INT X, _In_ INT Y, _In_ PVOID XParam, _In_ PVOID YParam, _In_ ULONG Column, _In_ BOOLEAN EnableDefault ) { INT result = 0; if ( Column < PH_MAX_COMPARE_FUNCTIONS && Context->CompareFunctions[Column] ) { result = PhModifySort( Context->CompareFunctions[Column](XParam, YParam, Context->Context), Context->SortOrder ); if (result != 0) return result; } if (EnableDefault) { return PhModifySort( PhpDefaultCompareListViewItems(Context, X, Y, Column), Context->SortOrder ); } else { return 0; } } static INT PhpDefaultCompareListViewItems( _In_ PPH_EXTLV_CONTEXT Context, _In_ INT X, _In_ INT Y, _In_ ULONG Column ) { WCHAR xText[261]; WCHAR yText[261]; LVITEM item; // Get the X item text. item.mask = LVIF_TEXT; item.iItem = X; item.iSubItem = Column; item.pszText = xText; item.cchTextMax = 260; xText[0] = 0; CallWindowProc(Context->OldWndProc, Context->Handle, LVM_GETITEM, 0, (LPARAM)&item); // Get the Y item text. item.iItem = Y; item.pszText = yText; item.cchTextMax = 260; yText[0] = 0; CallWindowProc(Context->OldWndProc, Context->Handle, LVM_GETITEM, 0, (LPARAM)&item); // Compare them. #if 1 return PhCompareStringZNatural(xText, yText, TRUE); #else return _wcsicmp(xText, yText); #endif }