Process Hacker
extlv.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * extended list view
4  *
5  * Copyright (C) 2010-2012 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /*
24  * The extended list view adds some functionality to the default list view control, such
25  * as sorting, item colors and fonts, better redraw disabling, and the ability to change the
26  * cursor. This is currently implemented by hooking the window procedure.
27  */
28 
29 #include <phgui.h>
30 #include <windowsx.h>
31 
32 #define PH_MAX_COMPARE_FUNCTIONS 16
33 
34 typedef struct _PH_EXTLV_CONTEXT
35 {
36  HWND Handle;
37  WNDPROC OldWndProc;
38  PVOID Context;
39 
40  // Sorting
41 
42  BOOLEAN TriState;
43  ULONG SortColumn;
44  PH_SORT_ORDER SortOrder;
45  BOOLEAN SortFast;
46 
47  PPH_COMPARE_FUNCTION TriStateCompareFunction;
49  ULONG FallbackColumns[PH_MAX_COMPARE_FUNCTIONS];
50  ULONG NumberOfFallbackColumns;
51 
52  // Color and Font
53  PPH_EXTLV_GET_ITEM_COLOR ItemColorFunction;
54  PPH_EXTLV_GET_ITEM_FONT ItemFontFunction;
55 
56  // Misc.
57 
58  LONG EnableRedraw;
59  HCURSOR Cursor;
61 
62 LRESULT CALLBACK PhpExtendedListViewWndProc(
63  _In_ HWND hwnd,
64  _In_ UINT uMsg,
65  _In_ WPARAM wParam,
66  _In_ LPARAM lParam
67  );
68 
70  _In_ LPARAM lParam1,
71  _In_ LPARAM lParam2,
72  _In_ LPARAM lParamSort
73  );
74 
76  _In_ LPARAM lParam1,
77  _In_ LPARAM lParam2,
78  _In_ LPARAM lParamSort
79  );
80 
82  _In_ PPH_EXTLV_CONTEXT Context,
83  _In_ INT X,
84  _In_ INT Y,
85  _In_ PVOID XParam,
86  _In_ PVOID YParam,
87  _In_ ULONG Column,
88  _In_ BOOLEAN EnableDefault
89  );
90 
92  _In_ PPH_EXTLV_CONTEXT Context,
93  _In_ INT X,
94  _In_ INT Y,
95  _In_ ULONG Column
96  );
97 
98 static PWSTR PhpMakeExtLvContextAtom(
99  VOID
100  )
101 {
102  PH_DEFINE_MAKE_ATOM(L"PhLib_ExtLvContext");
103 }
104 
111  _In_ HWND hWnd
112  )
113 {
114  WNDPROC oldWndProc;
115  PPH_EXTLV_CONTEXT context;
116 
117  oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);
118  SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PhpExtendedListViewWndProc);
119 
120  context = PhAllocate(sizeof(PH_EXTLV_CONTEXT));
121 
122  context->Handle = hWnd;
123  context->OldWndProc = oldWndProc;
124  context->Context = NULL;
125  context->TriState = FALSE;
126  context->SortColumn = 0;
127  context->SortOrder = AscendingSortOrder;
128  context->SortFast = FALSE;
129  context->TriStateCompareFunction = NULL;
130  memset(context->CompareFunctions, 0, sizeof(context->CompareFunctions));
131  context->NumberOfFallbackColumns = 0;
132 
133  context->ItemColorFunction = NULL;
134  context->ItemFontFunction = NULL;
135 
136  context->EnableRedraw = 1;
137  context->Cursor = NULL;
138 
139  SetProp(hWnd, PhpMakeExtLvContextAtom(), (HANDLE)context);
140 
141  ExtendedListView_Init(hWnd);
142 }
143 
144 LRESULT CALLBACK PhpExtendedListViewWndProc(
145  _In_ HWND hwnd,
146  _In_ UINT uMsg,
147  _In_ WPARAM wParam,
148  _In_ LPARAM lParam
149  )
150 {
151  PPH_EXTLV_CONTEXT context;
152  WNDPROC oldWndProc;
153 
154  context = (PPH_EXTLV_CONTEXT)GetProp(hwnd, PhpMakeExtLvContextAtom());
155  oldWndProc = context->OldWndProc;
156 
157  switch (uMsg)
158  {
159  case WM_DESTROY:
160  {
161  SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc);
162  PhFree(context);
163  RemoveProp(hwnd, PhpMakeExtLvContextAtom());
164  }
165  break;
166  case WM_NOTIFY:
167  {
168  LPNMHDR header = (LPNMHDR)lParam;
169 
170  switch (header->code)
171  {
172  case HDN_ITEMCLICK:
173  {
174  HWND headerHandle;
175 
176  headerHandle = (HWND)CallWindowProc(context->OldWndProc, hwnd, LVM_GETHEADER, 0, 0);
177 
178  if (header->hwndFrom == headerHandle)
179  {
180  LPNMHEADER header2 = (LPNMHEADER)header;
181 
182  if (header2->iItem == context->SortColumn)
183  {
184  if (context->TriState)
185  {
186  if (context->SortOrder == AscendingSortOrder)
187  context->SortOrder = DescendingSortOrder;
188  else if (context->SortOrder == DescendingSortOrder)
189  context->SortOrder = NoSortOrder;
190  else
191  context->SortOrder = AscendingSortOrder;
192  }
193  else
194  {
195  if (context->SortOrder == AscendingSortOrder)
196  context->SortOrder = DescendingSortOrder;
197  else
198  context->SortOrder = AscendingSortOrder;
199  }
200  }
201  else
202  {
203  context->SortColumn = header2->iItem;
204  context->SortOrder = AscendingSortOrder;
205  }
206 
207  PhSetHeaderSortIcon(headerHandle, context->SortColumn, context->SortOrder);
209  }
210  }
211  break;
212  }
213  }
214  break;
215  case WM_REFLECT + WM_NOTIFY:
216  {
217  LPNMHDR header = (LPNMHDR)lParam;
218 
219  switch (header->code)
220  {
221  case NM_CUSTOMDRAW:
222  {
223  if (header->hwndFrom == hwnd)
224  {
225  LPNMLVCUSTOMDRAW customDraw = (LPNMLVCUSTOMDRAW)header;
226 
227  switch (customDraw->nmcd.dwDrawStage)
228  {
229  case CDDS_PREPAINT:
230  return CDRF_NOTIFYITEMDRAW;
231  case CDDS_ITEMPREPAINT:
232  {
233  BOOLEAN colorChanged = FALSE;
234  HFONT newFont = NULL;
235 
236  if (context->ItemColorFunction)
237  {
238  customDraw->clrTextBk = context->ItemColorFunction(
239  (INT)customDraw->nmcd.dwItemSpec,
240  (PVOID)customDraw->nmcd.lItemlParam,
241  context->Context
242  );
243  colorChanged = TRUE;
244  }
245 
246  if (context->ItemFontFunction)
247  {
248  newFont = context->ItemFontFunction(
249  (INT)customDraw->nmcd.dwItemSpec,
250  (PVOID)customDraw->nmcd.lItemlParam,
251  context->Context
252  );
253  }
254 
255  if (newFont)
256  SelectObject(customDraw->nmcd.hdc, newFont);
257 
258  if (colorChanged)
259  {
260  if (PhGetColorBrightness(customDraw->clrTextBk) > 100) // slightly less than half
261  customDraw->clrText = RGB(0x00, 0x00, 0x00);
262  else
263  customDraw->clrText = RGB(0xff, 0xff, 0xff);
264  }
265 
266  if (!newFont)
267  return CDRF_DODEFAULT;
268  else
269  return CDRF_NEWFONT;
270  }
271  break;
272  }
273  }
274  }
275  break;
276  }
277  }
278  break;
279  case WM_SETCURSOR:
280  {
281  if (context->Cursor)
282  {
283  SetCursor(context->Cursor);
284  return TRUE;
285  }
286  }
287  break;
288  case WM_UPDATEUISTATE:
289  {
290  // Disable focus rectangles by setting or masking out the flag where appropriate.
291  switch (LOWORD(wParam))
292  {
293  case UIS_SET:
294  wParam |= UISF_HIDEFOCUS << 16;
295  break;
296  case UIS_CLEAR:
297  case UIS_INITIALIZE:
298  wParam &= ~(UISF_HIDEFOCUS << 16);
299  break;
300  }
301  }
302  break;
304  {
305  if (context->NumberOfFallbackColumns < PH_MAX_COMPARE_FUNCTIONS)
306  context->FallbackColumns[context->NumberOfFallbackColumns++] = (ULONG)wParam;
307  else
308  return FALSE;
309  }
310  return TRUE;
312  {
313  ULONG numberOfColumns = (ULONG)wParam;
314  PULONG columns = (PULONG)lParam;
315 
316  if (context->NumberOfFallbackColumns + numberOfColumns <= PH_MAX_COMPARE_FUNCTIONS)
317  {
318  memcpy(
319  &context->FallbackColumns[context->NumberOfFallbackColumns],
320  columns,
321  numberOfColumns * sizeof(ULONG)
322  );
323  context->NumberOfFallbackColumns += numberOfColumns;
324  }
325  else
326  {
327  return FALSE;
328  }
329  }
330  return TRUE;
331  case ELVM_INIT:
332  {
333  PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder);
334 
335  // HACK to fix tooltips showing behind Always On Top windows.
336  SetWindowPos(ListView_GetToolTips(hwnd), HWND_TOPMOST, 0, 0, 0, 0,
337  SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
338 
339  // Make sure focus rectangles are disabled.
340  SendMessage(hwnd, WM_CHANGEUISTATE, MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0);
341  }
342  return TRUE;
343  case ELVM_SETCOLUMNWIDTH:
344  {
345  ULONG column = (ULONG)wParam;
346  LONG width = (LONG)lParam;
347 
348  if (width == ELVSCW_AUTOSIZE_REMAININGSPACE)
349  {
350  RECT clientRect;
351  LONG availableWidth;
352  ULONG i;
353  LVCOLUMN lvColumn;
354 
355  GetClientRect(hwnd, &clientRect);
356  availableWidth = clientRect.right;
357  i = 0;
358  lvColumn.mask = LVCF_WIDTH;
359 
360  while (TRUE)
361  {
362  if (i != column)
363  {
364  if (CallWindowProc(oldWndProc, hwnd, LVM_GETCOLUMN, i, (LPARAM)&lvColumn))
365  {
366  availableWidth -= lvColumn.cx;
367  }
368  else
369  {
370  break;
371  }
372  }
373 
374  i++;
375  }
376 
377  if (availableWidth >= 40)
378  return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, availableWidth);
379  }
380 
381  return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, width);
382  }
383  break;
385  {
386  ULONG column = (ULONG)wParam;
387  PPH_COMPARE_FUNCTION compareFunction = (PPH_COMPARE_FUNCTION)lParam;
388 
389  if (column >= PH_MAX_COMPARE_FUNCTIONS)
390  return FALSE;
391 
392  context->CompareFunctions[column] = compareFunction;
393  }
394  return TRUE;
395  case ELVM_SETCONTEXT:
396  {
397  context->Context = (PVOID)lParam;
398  }
399  return TRUE;
400  case ELVM_SETCURSOR:
401  {
402  context->Cursor = (HCURSOR)lParam;
403  }
404  return TRUE;
406  {
407  context->ItemColorFunction = (PPH_EXTLV_GET_ITEM_COLOR)lParam;
408  }
409  return TRUE;
411  {
412  context->ItemFontFunction = (PPH_EXTLV_GET_ITEM_FONT)lParam;
413  }
414  return TRUE;
415  case ELVM_SETREDRAW:
416  {
417  if (wParam)
418  context->EnableRedraw++;
419  else
420  context->EnableRedraw--;
421 
422  if (context->EnableRedraw == 1)
423  {
424  SendMessage(hwnd, WM_SETREDRAW, TRUE, 0);
425  InvalidateRect(hwnd, NULL, FALSE);
426  }
427  else if (context->EnableRedraw == 0)
428  {
429  SendMessage(hwnd, WM_SETREDRAW, FALSE, 0);
430  }
431  }
432  return TRUE;
433  case ELVM_SETSORT:
434  {
435  context->SortColumn = (ULONG)wParam;
436  context->SortOrder = (PH_SORT_ORDER)lParam;
437 
438  PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder);
439  }
440  return TRUE;
441  case ELVM_SETSORTFAST:
442  {
443  context->SortFast = !!wParam;
444  }
445  return TRUE;
446  case ELVM_SETTRISTATE:
447  {
448  context->TriState = !!wParam;
449  }
450  return TRUE;
452  {
453  context->TriStateCompareFunction = (PPH_COMPARE_FUNCTION)lParam;
454  }
455  return TRUE;
456  case ELVM_SORTITEMS:
457  {
458  if (context->SortFast)
459  {
460  // This sort method is faster than the normal sort because our comparison function
461  // doesn't have to call the list view window procedure to get the item lParam values.
462  // The disadvantage of this method is that default sorting is not available - if a
463  // column doesn't have a comparison function, it doesn't get sorted at all.
464 
465  ListView_SortItems(
466  hwnd,
468  (LPARAM)context
469  );
470  }
471  else
472  {
473  ListView_SortItemsEx(
474  hwnd,
476  (LPARAM)context
477  );
478  }
479  }
480  return TRUE;
481  }
482 
483  return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam);
484 }
485 
494  _In_ HWND hwnd,
495  _In_ INT Index,
496  _In_ PH_SORT_ORDER Order
497  )
498 {
499  ULONG count;
500  ULONG i;
501 
502  count = Header_GetItemCount(hwnd);
503 
504  if (count == -1)
505  return;
506 
507  for (i = 0; i < count; i++)
508  {
509  HDITEM item;
510 
511  item.mask = HDI_FORMAT;
512  Header_GetItem(hwnd, i, &item);
513 
514  if (Order != NoSortOrder && i == Index)
515  {
516  if (Order == AscendingSortOrder)
517  {
518  item.fmt &= ~HDF_SORTDOWN;
519  item.fmt |= HDF_SORTUP;
520  }
521  else if (Order == DescendingSortOrder)
522  {
523  item.fmt &= ~HDF_SORTUP;
524  item.fmt |= HDF_SORTDOWN;
525  }
526  }
527  else
528  {
529  item.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
530  }
531 
532  Header_SetItem(hwnd, i, &item);
533  }
534 }
535 
537  _In_ LPARAM lParam1,
538  _In_ LPARAM lParam2,
539  _In_ LPARAM lParamSort
540  )
541 {
542  PPH_EXTLV_CONTEXT context = (PPH_EXTLV_CONTEXT)lParamSort;
543  INT result;
544  INT x = (INT)lParam1;
545  INT y = (INT)lParam2;
546  ULONG i;
547  PULONG fallbackColumns;
548  LVITEM xItem;
549  LVITEM yItem;
550 
551  // Get the param values.
552 
553  xItem.mask = LVIF_PARAM | LVIF_STATE;
554  xItem.iItem = x;
555  xItem.iSubItem = 0;
556 
557  yItem.mask = LVIF_PARAM | LVIF_STATE;
558  yItem.iItem = y;
559  yItem.iSubItem = 0;
560 
561  // Don't use SendMessage/ListView_* because it will call our new window procedure,
562  // which will use GetProp. This calls NtUserGetProp, and obviously having a system call
563  // in a comparison function is very, very bad for performance.
564 
565  if (!CallWindowProc(context->OldWndProc, context->Handle, LVM_GETITEM, 0, (LPARAM)&xItem))
566  return 0;
567  if (!CallWindowProc(context->OldWndProc, context->Handle, LVM_GETITEM, 0, (LPARAM)&yItem))
568  return 0;
569 
570  // First, do tri-state sorting.
571 
572  if (
573  context->TriState &&
574  context->SortOrder == NoSortOrder &&
575  context->TriStateCompareFunction
576  )
577  {
578  result = context->TriStateCompareFunction(
579  (PVOID)xItem.lParam,
580  (PVOID)yItem.lParam,
581  context->Context
582  );
583 
584  if (result != 0)
585  return result;
586  }
587 
588  // Compare using the user-selected column and move on to the fallback columns if necessary.
589 
590  result = PhpCompareListViewItems(context, x, y, (PVOID)xItem.lParam, (PVOID)yItem.lParam, context->SortColumn, TRUE);
591 
592  if (result != 0)
593  return result;
594 
595  fallbackColumns = context->FallbackColumns;
596 
597  for (i = context->NumberOfFallbackColumns; i != 0; i--)
598  {
599  ULONG fallbackColumn = *fallbackColumns++;
600 
601  if (fallbackColumn == context->SortColumn)
602  continue;
603 
604  result = PhpCompareListViewItems(context, x, y, (PVOID)xItem.lParam, (PVOID)yItem.lParam, fallbackColumn, TRUE);
605 
606  if (result != 0)
607  return result;
608  }
609 
610  return 0;
611 }
612 
614  _In_ LPARAM lParam1,
615  _In_ LPARAM lParam2,
616  _In_ LPARAM lParamSort
617  )
618 {
619  PPH_EXTLV_CONTEXT context = (PPH_EXTLV_CONTEXT)lParamSort;
620  INT result;
621  ULONG i;
622  PULONG fallbackColumns;
623 
624  if (!lParam1 || !lParam2)
625  return 0;
626 
627  // First, do tri-state sorting.
628 
629  if (
630  context->TriState &&
631  context->SortOrder == NoSortOrder &&
632  context->TriStateCompareFunction
633  )
634  {
635  result = context->TriStateCompareFunction(
636  (PVOID)lParam1,
637  (PVOID)lParam2,
638  context->Context
639  );
640 
641  if (result != 0)
642  return result;
643  }
644 
645  // Compare using the user-selected column and move on to the fallback columns if necessary.
646 
647  result = PhpCompareListViewItems(context, 0, 0, (PVOID)lParam1, (PVOID)lParam2, context->SortColumn, FALSE);
648 
649  if (result != 0)
650  return result;
651 
652  fallbackColumns = context->FallbackColumns;
653 
654  for (i = context->NumberOfFallbackColumns; i != 0; i--)
655  {
656  ULONG fallbackColumn = *fallbackColumns++;
657 
658  if (fallbackColumn == context->SortColumn)
659  continue;
660 
661  result = PhpCompareListViewItems(context, 0, 0, (PVOID)lParam1, (PVOID)lParam2, fallbackColumn, FALSE);
662 
663  if (result != 0)
664  return result;
665  }
666 
667  return 0;
668 }
669 
670 static FORCEINLINE INT PhpCompareListViewItems(
671  _In_ PPH_EXTLV_CONTEXT Context,
672  _In_ INT X,
673  _In_ INT Y,
674  _In_ PVOID XParam,
675  _In_ PVOID YParam,
676  _In_ ULONG Column,
677  _In_ BOOLEAN EnableDefault
678  )
679 {
680  INT result = 0;
681 
682  if (
683  Column < PH_MAX_COMPARE_FUNCTIONS &&
684  Context->CompareFunctions[Column]
685  )
686  {
687  result = PhModifySort(
688  Context->CompareFunctions[Column](XParam, YParam, Context->Context),
689  Context->SortOrder
690  );
691 
692  if (result != 0)
693  return result;
694  }
695 
696  if (EnableDefault)
697  {
698  return PhModifySort(
699  PhpDefaultCompareListViewItems(Context, X, Y, Column),
700  Context->SortOrder
701  );
702  }
703  else
704  {
705  return 0;
706  }
707 }
708 
710  _In_ PPH_EXTLV_CONTEXT Context,
711  _In_ INT X,
712  _In_ INT Y,
713  _In_ ULONG Column
714  )
715 {
716  WCHAR xText[261];
717  WCHAR yText[261];
718  LVITEM item;
719 
720  // Get the X item text.
721 
722  item.mask = LVIF_TEXT;
723  item.iItem = X;
724  item.iSubItem = Column;
725  item.pszText = xText;
726  item.cchTextMax = 260;
727 
728  xText[0] = 0;
729  CallWindowProc(Context->OldWndProc, Context->Handle, LVM_GETITEM, 0, (LPARAM)&item);
730 
731  // Get the Y item text.
732 
733  item.iItem = Y;
734  item.pszText = yText;
735  item.cchTextMax = 260;
736 
737  yText[0] = 0;
738  CallWindowProc(Context->OldWndProc, Context->Handle, LVM_GETITEM, 0, (LPARAM)&item);
739 
740  // Compare them.
741 
742 #if 1
743  return PhCompareStringZNatural(xText, yText, TRUE);
744 #else
745  return wcsicmp(xText, yText);
746 #endif
747 }