mirror of
https://github.com/mirror/processhacker
synced 2026-06-08 16:03:24 +00:00
284692d610
git-svn-id: svn://svn.code.sf.net/p/processhacker/code@6184 21ef857c-d57f-4fe0-8362-d861dc6d29cd
2235 lines
70 KiB
C
2235 lines
70 KiB
C
/*
|
|
* Process Hacker -
|
|
* mini information window
|
|
*
|
|
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <phapp.h>
|
|
#include <settings.h>
|
|
#include <emenu.h>
|
|
#include <miniinfo.h>
|
|
#include <phplug.h>
|
|
#include <notifico.h>
|
|
#include <windowsx.h>
|
|
#include <uxtheme.h>
|
|
#include <miniinfop.h>
|
|
|
|
static HWND PhMipContainerWindow = NULL;
|
|
static POINT PhMipSourcePoint;
|
|
static LONG PhMipPinCounts[MaxMiniInfoPinType];
|
|
static LONG PhMipMaxPinCounts[] =
|
|
{
|
|
1, // MiniInfoManualPinType
|
|
1, // MiniInfoIconPinType
|
|
1, // MiniInfoActivePinType
|
|
1, // MiniInfoHoverPinType
|
|
1, // MiniInfoChildControlPinType
|
|
};
|
|
C_ASSERT(sizeof(PhMipMaxPinCounts) / sizeof(LONG) == MaxMiniInfoPinType);
|
|
static LONG PhMipDelayedPinAdjustments[MaxMiniInfoPinType];
|
|
static PPH_MESSAGE_LOOP_FILTER_ENTRY PhMipMessageLoopFilterEntry;
|
|
static HWND PhMipLastTrackedWindow;
|
|
static HWND PhMipLastNcTrackedWindow;
|
|
static ULONG PhMipRefreshAutomatically;
|
|
static BOOLEAN PhMipPinned;
|
|
|
|
static HWND PhMipWindow = NULL;
|
|
static PH_LAYOUT_MANAGER PhMipLayoutManager;
|
|
static RECT MinimumSize;
|
|
static PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration;
|
|
static PH_STRINGREF DownArrowPrefix = PH_STRINGREF_INIT(L"\u25be ");
|
|
static WNDPROC SectionControlOldWndProc;
|
|
|
|
static PPH_LIST SectionList;
|
|
static PH_MINIINFO_PARAMETERS CurrentParameters;
|
|
static PPH_MINIINFO_SECTION CurrentSection;
|
|
|
|
VOID PhPinMiniInformation(
|
|
_In_ PH_MINIINFO_PIN_TYPE PinType,
|
|
_In_ LONG PinCount,
|
|
_In_opt_ ULONG PinDelayMs,
|
|
_In_ ULONG Flags,
|
|
_In_opt_ PWSTR SectionName,
|
|
_In_opt_ PPOINT SourcePoint
|
|
)
|
|
{
|
|
PH_MIP_ADJUST_PIN_RESULT adjustPinResult;
|
|
|
|
if (PinDelayMs && PinCount < 0)
|
|
{
|
|
PhMipDelayedPinAdjustments[PinType] = PinCount;
|
|
SetTimer(PhMipContainerWindow, MIP_TIMER_PIN_FIRST + PinType, PinDelayMs, NULL);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
PhMipDelayedPinAdjustments[PinType] = 0;
|
|
KillTimer(PhMipContainerWindow, MIP_TIMER_PIN_FIRST + PinType);
|
|
}
|
|
|
|
adjustPinResult = PhMipAdjustPin(PinType, PinCount);
|
|
|
|
if (adjustPinResult == ShowAdjustPinResult)
|
|
{
|
|
PH_RECTANGLE windowRectangle;
|
|
ULONG opacity;
|
|
|
|
if (SourcePoint)
|
|
PhMipSourcePoint = *SourcePoint;
|
|
|
|
if (!PhMipContainerWindow)
|
|
{
|
|
WNDCLASSEX wcex;
|
|
|
|
memset(&wcex, 0, sizeof(WNDCLASSEX));
|
|
wcex.cbSize = sizeof(WNDCLASSEX);
|
|
wcex.style = 0;
|
|
wcex.lpfnWndProc = PhMipContainerWndProc;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = 0;
|
|
wcex.hInstance = PhInstanceHandle;
|
|
wcex.hIcon = LoadIcon(PhInstanceHandle, MAKEINTRESOURCE(IDI_PROCESSHACKER));
|
|
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
|
|
wcex.lpszClassName = MIP_CONTAINER_CLASSNAME;
|
|
wcex.hIconSm = (HICON)LoadImage(PhInstanceHandle, MAKEINTRESOURCE(IDI_PROCESSHACKER), IMAGE_ICON, 16, 16, 0);
|
|
RegisterClassEx(&wcex);
|
|
|
|
PhMipContainerWindow = CreateWindow(
|
|
MIP_CONTAINER_CLASSNAME,
|
|
L"Process Hacker",
|
|
WS_BORDER | WS_THICKFRAME | WS_POPUP,
|
|
0,
|
|
0,
|
|
400,
|
|
400,
|
|
NULL,
|
|
NULL,
|
|
PhInstanceHandle,
|
|
NULL
|
|
);
|
|
PhSetWindowExStyle(PhMipContainerWindow, WS_EX_TOOLWINDOW, WS_EX_TOOLWINDOW);
|
|
PhMipWindow = CreateDialog(
|
|
PhInstanceHandle,
|
|
MAKEINTRESOURCE(IDD_MINIINFO),
|
|
PhMipContainerWindow,
|
|
PhMipMiniInfoDialogProc
|
|
);
|
|
ShowWindow(PhMipWindow, SW_SHOW);
|
|
|
|
if (PhGetIntegerSetting(L"MiniInfoWindowPinned"))
|
|
PhMipSetPinned(TRUE);
|
|
|
|
PhMipRefreshAutomatically = PhGetIntegerSetting(L"MiniInfoWindowRefreshAutomatically");
|
|
|
|
opacity = PhGetIntegerSetting(L"MiniInfoWindowOpacity");
|
|
|
|
if (opacity != 0)
|
|
PhSetWindowOpacity(PhMipContainerWindow, opacity);
|
|
|
|
MinimumSize.left = 0;
|
|
MinimumSize.top = 0;
|
|
MinimumSize.right = 210;
|
|
MinimumSize.bottom = 60;
|
|
MapDialogRect(PhMipWindow, &MinimumSize);
|
|
}
|
|
|
|
if (!(Flags & PH_MINIINFO_LOAD_POSITION))
|
|
{
|
|
PhMipCalculateWindowRectangle(&PhMipSourcePoint, &windowRectangle);
|
|
SetWindowPos(
|
|
PhMipContainerWindow,
|
|
HWND_TOPMOST,
|
|
windowRectangle.Left,
|
|
windowRectangle.Top,
|
|
windowRectangle.Width,
|
|
windowRectangle.Height,
|
|
SWP_NOACTIVATE
|
|
);
|
|
}
|
|
else
|
|
{
|
|
PhLoadWindowPlacementFromSetting(L"MiniInfoWindowPosition", L"MiniInfoWindowSize", PhMipContainerWindow);
|
|
SetWindowPos(PhMipContainerWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
|
}
|
|
|
|
ShowWindow(PhMipContainerWindow, (Flags & PH_MINIINFO_ACTIVATE_WINDOW) ? SW_SHOW : SW_SHOWNOACTIVATE);
|
|
}
|
|
else if (adjustPinResult == HideAdjustPinResult)
|
|
{
|
|
if (PhMipContainerWindow)
|
|
ShowWindow(PhMipContainerWindow, SW_HIDE);
|
|
}
|
|
else
|
|
{
|
|
if ((Flags & PH_MINIINFO_ACTIVATE_WINDOW) && IsWindowVisible(PhMipContainerWindow))
|
|
SetActiveWindow(PhMipContainerWindow);
|
|
}
|
|
|
|
if (SectionName && (!PhMipPinned || !(Flags & PH_MINIINFO_DONT_CHANGE_SECTION_IF_PINNED)))
|
|
{
|
|
PH_STRINGREF sectionName;
|
|
PPH_MINIINFO_SECTION section;
|
|
|
|
PhInitializeStringRefLongHint(§ionName, SectionName);
|
|
|
|
if (section = PhMipFindSection(§ionName))
|
|
PhMipChangeSection(section);
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK PhMipContainerWndProc(
|
|
_In_ HWND hWnd,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_SHOWWINDOW:
|
|
{
|
|
PhMipContainerOnShowWindow(!!wParam, (ULONG)lParam);
|
|
}
|
|
break;
|
|
case WM_ACTIVATE:
|
|
{
|
|
PhMipContainerOnActivate(LOWORD(wParam), !!HIWORD(wParam));
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
{
|
|
PhMipContainerOnSize();
|
|
}
|
|
break;
|
|
case WM_SIZING:
|
|
{
|
|
PhMipContainerOnSizing((ULONG)wParam, (PRECT)lParam);
|
|
}
|
|
break;
|
|
case WM_EXITSIZEMOVE:
|
|
{
|
|
PhMipContainerOnExitSizeMove();
|
|
}
|
|
break;
|
|
case WM_CLOSE:
|
|
{
|
|
// Hide, don't close.
|
|
ShowWindow(hWnd, SW_HIDE);
|
|
SetWindowLongPtr(hWnd, DWLP_MSGRESULT, 0);
|
|
}
|
|
return TRUE;
|
|
case WM_ERASEBKGND:
|
|
{
|
|
if (PhMipContainerOnEraseBkgnd((HDC)wParam))
|
|
return TRUE;
|
|
}
|
|
break;
|
|
case WM_TIMER:
|
|
{
|
|
PhMipContainerOnTimer((ULONG)wParam);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
INT_PTR CALLBACK PhMipMiniInfoDialogProc(
|
|
_In_ HWND hwndDlg,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
PhMipWindow = hwndDlg;
|
|
PhMipOnInitDialog();
|
|
}
|
|
break;
|
|
case WM_SHOWWINDOW:
|
|
{
|
|
PhMipOnShowWindow(!!wParam, (ULONG)lParam);
|
|
}
|
|
break;
|
|
case WM_COMMAND:
|
|
{
|
|
PhMipOnCommand(LOWORD(wParam), HIWORD(wParam));
|
|
}
|
|
break;
|
|
case WM_NOTIFY:
|
|
{
|
|
LRESULT result;
|
|
|
|
if (PhMipOnNotify((NMHDR *)lParam, &result))
|
|
{
|
|
SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, result);
|
|
return TRUE;
|
|
}
|
|
}
|
|
break;
|
|
case WM_CTLCOLORBTN:
|
|
case WM_CTLCOLORDLG:
|
|
case WM_CTLCOLORSTATIC:
|
|
{
|
|
HBRUSH brush;
|
|
|
|
if (PhMipOnCtlColorXxx(uMsg, (HWND)lParam, (HDC)wParam, &brush))
|
|
return (INT_PTR)brush;
|
|
}
|
|
break;
|
|
case WM_DRAWITEM:
|
|
{
|
|
if (PhMipOnDrawItem(wParam, (DRAWITEMSTRUCT *)lParam))
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (uMsg >= MIP_MSG_FIRST && uMsg <= MIP_MSG_LAST)
|
|
{
|
|
PhMipOnUserMessage(uMsg, wParam, lParam);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID PhMipContainerOnShowWindow(
|
|
_In_ BOOLEAN Showing,
|
|
_In_ ULONG State
|
|
)
|
|
{
|
|
ULONG i;
|
|
PPH_MINIINFO_SECTION section;
|
|
|
|
if (Showing)
|
|
{
|
|
PostMessage(PhMipWindow, MIP_MSG_UPDATE, 0, 0);
|
|
|
|
PhMipMessageLoopFilterEntry = PhRegisterMessageLoopFilter(PhMipMessageLoopFilter, NULL);
|
|
|
|
PhRegisterCallback(
|
|
&PhProcessesUpdatedEvent,
|
|
PhMipUpdateHandler,
|
|
NULL,
|
|
&ProcessesUpdatedRegistration
|
|
);
|
|
|
|
PhMipContainerOnSize();
|
|
}
|
|
else
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < MaxMiniInfoPinType; i++)
|
|
PhMipPinCounts[i] = 0;
|
|
|
|
Button_SetCheck(GetDlgItem(PhMipWindow, IDC_PIN), BST_UNCHECKED);
|
|
PhMipSetPinned(FALSE);
|
|
PhSetIntegerSetting(L"MiniInfoWindowPinned", FALSE);
|
|
|
|
PhUnregisterCallback(
|
|
&PhProcessesUpdatedEvent,
|
|
&ProcessesUpdatedRegistration
|
|
);
|
|
|
|
if (PhMipMessageLoopFilterEntry)
|
|
{
|
|
PhUnregisterMessageLoopFilter(PhMipMessageLoopFilterEntry);
|
|
PhMipMessageLoopFilterEntry = NULL;
|
|
}
|
|
|
|
PhSaveWindowPlacementToSetting(L"MiniInfoWindowPosition", L"MiniInfoWindowSize", PhMipContainerWindow);
|
|
}
|
|
|
|
if (SectionList)
|
|
{
|
|
for (i = 0; i < SectionList->Count; i++)
|
|
{
|
|
section = SectionList->Items[i];
|
|
section->Callback(section, MiniInfoShowing, (PVOID)Showing, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID PhMipContainerOnActivate(
|
|
_In_ ULONG Type,
|
|
_In_ BOOLEAN Minimized
|
|
)
|
|
{
|
|
if (Type == WA_ACTIVE || Type == WA_CLICKACTIVE)
|
|
{
|
|
PhPinMiniInformation(MiniInfoActivePinType, 1, 0, 0, NULL, NULL);
|
|
}
|
|
else if (Type == WA_INACTIVE)
|
|
{
|
|
PhPinMiniInformation(MiniInfoActivePinType, -1, 0, 0, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
VOID PhMipContainerOnSize(
|
|
VOID
|
|
)
|
|
{
|
|
if (PhMipWindow)
|
|
{
|
|
InvalidateRect(PhMipContainerWindow, NULL, FALSE);
|
|
PhMipLayout();
|
|
}
|
|
}
|
|
|
|
VOID PhMipContainerOnSizing(
|
|
_In_ ULONG Edge,
|
|
_In_ PRECT DragRectangle
|
|
)
|
|
{
|
|
PhResizingMinimumSize(DragRectangle, Edge, MinimumSize.right, MinimumSize.bottom);
|
|
}
|
|
|
|
VOID PhMipContainerOnExitSizeMove(
|
|
VOID
|
|
)
|
|
{
|
|
PhSaveWindowPlacementToSetting(L"MiniInfoWindowPosition", L"MiniInfoWindowSize", PhMipContainerWindow);
|
|
}
|
|
|
|
BOOLEAN PhMipContainerOnEraseBkgnd(
|
|
_In_ HDC hdc
|
|
)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
VOID PhMipContainerOnTimer(
|
|
_In_ ULONG Id
|
|
)
|
|
{
|
|
if (Id >= MIP_TIMER_PIN_FIRST && Id <= MIP_TIMER_PIN_LAST)
|
|
{
|
|
PH_MINIINFO_PIN_TYPE pinType = Id - MIP_TIMER_PIN_FIRST;
|
|
|
|
// PhPinMiniInformation kills the timer for us.
|
|
PhPinMiniInformation(pinType, PhMipDelayedPinAdjustments[pinType], 0, 0, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
VOID PhMipOnInitDialog(
|
|
VOID
|
|
)
|
|
{
|
|
HBITMAP cog;
|
|
HBITMAP pin;
|
|
|
|
cog = PH_LOAD_SHARED_IMAGE(MAKEINTRESOURCE(IDB_COG), IMAGE_BITMAP);
|
|
SET_BUTTON_BITMAP(PhMipWindow, IDC_OPTIONS, cog);
|
|
|
|
pin = PH_LOAD_SHARED_IMAGE(MAKEINTRESOURCE(IDB_PIN), IMAGE_BITMAP);
|
|
SET_BUTTON_BITMAP(PhMipWindow, IDC_PIN, pin);
|
|
|
|
PhInitializeLayoutManager(&PhMipLayoutManager, PhMipWindow);
|
|
PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_LAYOUT), NULL,
|
|
PH_ANCHOR_ALL);
|
|
PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_SECTION), NULL,
|
|
PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM | PH_LAYOUT_FORCE_INVALIDATE);
|
|
PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_OPTIONS), NULL,
|
|
PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM);
|
|
PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_PIN), NULL,
|
|
PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM);
|
|
|
|
SectionControlOldWndProc = (WNDPROC)GetWindowLongPtr(GetDlgItem(PhMipWindow, IDC_SECTION), GWLP_WNDPROC);
|
|
SetWindowLongPtr(GetDlgItem(PhMipWindow, IDC_SECTION), GWLP_WNDPROC, (LONG_PTR)PhMipSectionControlHookWndProc);
|
|
|
|
Button_SetCheck(GetDlgItem(PhMipWindow, IDC_PIN), !!PhGetIntegerSetting(L"MiniInfoWindowPinned"));
|
|
}
|
|
|
|
VOID PhMipOnShowWindow(
|
|
_In_ BOOLEAN Showing,
|
|
_In_ ULONG State
|
|
)
|
|
{
|
|
if (SectionList)
|
|
return;
|
|
|
|
SectionList = PhCreateList(8);
|
|
PhMipInitializeParameters();
|
|
|
|
SendMessage(GetDlgItem(PhMipWindow, IDC_SECTION), WM_SETFONT, (WPARAM)CurrentParameters.MediumFont, FALSE);
|
|
|
|
PhMipCreateInternalListSection(L"CPU", 0, PhMipCpuListSectionCallback);
|
|
PhMipCreateInternalListSection(L"Commit Charge", 0, PhMipCommitListSectionCallback);
|
|
PhMipCreateInternalListSection(L"Physical Memory", 0, PhMipPhysicalListSectionCallback);
|
|
PhMipCreateInternalListSection(L"I/O", 0, PhMipIoListSectionCallback);
|
|
|
|
if (PhPluginsEnabled)
|
|
{
|
|
PH_PLUGIN_MINIINFO_POINTERS pointers;
|
|
|
|
pointers.WindowHandle = PhMipContainerWindow;
|
|
pointers.CreateSection = PhMipCreateSection;
|
|
pointers.FindSection = PhMipFindSection;
|
|
pointers.CreateListSection = PhMipCreateListSection;
|
|
PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMiniInformationInitializing), &pointers);
|
|
}
|
|
|
|
PhMipChangeSection(SectionList->Items[0]);
|
|
}
|
|
|
|
VOID PhMipOnCommand(
|
|
_In_ ULONG Id,
|
|
_In_ ULONG Code
|
|
)
|
|
{
|
|
switch (Id)
|
|
{
|
|
case IDC_SECTION:
|
|
switch (Code)
|
|
{
|
|
case STN_CLICKED:
|
|
PhMipShowSectionMenu();
|
|
break;
|
|
}
|
|
break;
|
|
case IDC_OPTIONS:
|
|
PhMipShowOptionsMenu();
|
|
break;
|
|
case IDC_PIN:
|
|
{
|
|
BOOLEAN pinned;
|
|
|
|
pinned = Button_GetCheck(GetDlgItem(PhMipWindow, IDC_PIN)) == BST_CHECKED;
|
|
PhPinMiniInformation(MiniInfoManualPinType, pinned ? 1 : -1, 0, 0, NULL, NULL);
|
|
PhMipSetPinned(pinned);
|
|
PhSetIntegerSetting(L"MiniInfoWindowPinned", pinned);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOLEAN PhMipOnNotify(
|
|
_In_ NMHDR *Header,
|
|
_Out_ LRESULT *Result
|
|
)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN PhMipOnCtlColorXxx(
|
|
_In_ ULONG Message,
|
|
_In_ HWND hwnd,
|
|
_In_ HDC hdc,
|
|
_Out_ HBRUSH *Brush
|
|
)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN PhMipOnDrawItem(
|
|
_In_ ULONG_PTR Id,
|
|
_In_ DRAWITEMSTRUCT *DrawItemStruct
|
|
)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
VOID PhMipOnUserMessage(
|
|
_In_ ULONG Message,
|
|
_In_ ULONG_PTR WParam,
|
|
_In_ ULONG_PTR LParam
|
|
)
|
|
{
|
|
switch (Message)
|
|
{
|
|
case MIP_MSG_UPDATE:
|
|
{
|
|
ULONG i;
|
|
PPH_MINIINFO_SECTION section;
|
|
|
|
if (SectionList)
|
|
{
|
|
for (i = 0; i < SectionList->Count; i++)
|
|
{
|
|
section = SectionList->Items[i];
|
|
section->Callback(section, MiniInfoTick, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOLEAN PhMipMessageLoopFilter(
|
|
_In_ PMSG Message,
|
|
_In_ PVOID Context
|
|
)
|
|
{
|
|
if (Message->hwnd == PhMipContainerWindow || IsChild(PhMipContainerWindow, Message->hwnd))
|
|
{
|
|
if (Message->message == WM_MOUSEMOVE || Message->message == WM_NCMOUSEMOVE)
|
|
{
|
|
TRACKMOUSEEVENT trackMouseEvent;
|
|
|
|
trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
trackMouseEvent.dwFlags = TME_LEAVE | (Message->message == WM_NCMOUSEMOVE ? TME_NONCLIENT : 0);
|
|
trackMouseEvent.hwndTrack = Message->hwnd;
|
|
trackMouseEvent.dwHoverTime = 0;
|
|
TrackMouseEvent(&trackMouseEvent);
|
|
|
|
if (Message->message == WM_MOUSEMOVE)
|
|
PhMipLastTrackedWindow = Message->hwnd;
|
|
else
|
|
PhMipLastNcTrackedWindow = Message->hwnd;
|
|
|
|
PhPinMiniInformation(MiniInfoHoverPinType, 1, 0, 0, NULL, NULL);
|
|
}
|
|
else if (Message->message == WM_MOUSELEAVE && Message->hwnd == PhMipLastTrackedWindow)
|
|
{
|
|
PhPinMiniInformation(MiniInfoHoverPinType, -1, MIP_UNPIN_HOVER_DELAY, 0, NULL, NULL);
|
|
}
|
|
else if (Message->message == WM_NCMOUSELEAVE && Message->hwnd == PhMipLastNcTrackedWindow)
|
|
{
|
|
PhPinMiniInformation(MiniInfoHoverPinType, -1, MIP_UNPIN_HOVER_DELAY, 0, NULL, NULL);
|
|
}
|
|
else if (Message->message == WM_KEYDOWN)
|
|
{
|
|
switch (Message->wParam)
|
|
{
|
|
case VK_F5:
|
|
PhMipRefresh();
|
|
break;
|
|
case VK_F6:
|
|
case VK_PAUSE:
|
|
PhMipToggleRefreshAutomatically();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID NTAPI PhMipUpdateHandler(
|
|
_In_opt_ PVOID Parameter,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
if (PhMipRefreshAutomatically & MIP_REFRESH_AUTOMATICALLY_FLAG(PhMipPinned))
|
|
PostMessage(PhMipWindow, MIP_MSG_UPDATE, 0, 0);
|
|
}
|
|
|
|
PH_MIP_ADJUST_PIN_RESULT PhMipAdjustPin(
|
|
_In_ PH_MINIINFO_PIN_TYPE PinType,
|
|
_In_ LONG PinCount
|
|
)
|
|
{
|
|
LONG oldTotalPinCount;
|
|
LONG oldPinCount;
|
|
LONG newPinCount;
|
|
ULONG i;
|
|
|
|
oldTotalPinCount = 0;
|
|
|
|
for (i = 0; i < MaxMiniInfoPinType; i++)
|
|
oldTotalPinCount += PhMipPinCounts[i];
|
|
|
|
oldPinCount = PhMipPinCounts[PinType];
|
|
newPinCount = max(oldPinCount + PinCount, 0);
|
|
newPinCount = min(newPinCount, PhMipMaxPinCounts[PinType]);
|
|
PhMipPinCounts[PinType] = newPinCount;
|
|
|
|
if (oldTotalPinCount == 0 && newPinCount > oldPinCount)
|
|
return ShowAdjustPinResult;
|
|
else if (oldTotalPinCount > 0 && oldTotalPinCount - oldPinCount + newPinCount == 0)
|
|
return HideAdjustPinResult;
|
|
else
|
|
return NoAdjustPinResult;
|
|
}
|
|
|
|
VOID PhMipCalculateWindowRectangle(
|
|
_In_ PPOINT SourcePoint,
|
|
_Out_ PPH_RECTANGLE WindowRectangle
|
|
)
|
|
{
|
|
RECT windowRect;
|
|
PH_RECTANGLE windowRectangle;
|
|
PH_RECTANGLE point;
|
|
MONITORINFO monitorInfo = { sizeof(monitorInfo) };
|
|
|
|
PhLoadWindowPlacementFromSetting(NULL, L"MiniInfoWindowSize", PhMipContainerWindow);
|
|
GetWindowRect(PhMipContainerWindow, &windowRect);
|
|
SendMessage(PhMipContainerWindow, WM_SIZING, WMSZ_BOTTOMRIGHT, (LPARAM)&windowRect); // Adjust for the minimum size.
|
|
windowRectangle = PhRectToRectangle(windowRect);
|
|
|
|
point.Left = SourcePoint->x;
|
|
point.Top = SourcePoint->y;
|
|
point.Width = 0;
|
|
point.Height = 0;
|
|
PhCenterRectangle(&windowRectangle, &point);
|
|
|
|
if (GetMonitorInfo(
|
|
MonitorFromPoint(*SourcePoint, MONITOR_DEFAULTTOPRIMARY),
|
|
&monitorInfo
|
|
))
|
|
{
|
|
PH_RECTANGLE bounds;
|
|
|
|
if (memcmp(&monitorInfo.rcWork, &monitorInfo.rcMonitor, sizeof(RECT)) == 0)
|
|
{
|
|
HWND trayWindow;
|
|
RECT taskbarRect;
|
|
|
|
// The taskbar probably has auto-hide enabled. We need to adjust for that.
|
|
if ((trayWindow = FindWindow(L"Shell_TrayWnd", NULL)) &&
|
|
GetMonitorInfo(MonitorFromWindow(trayWindow, MONITOR_DEFAULTTOPRIMARY), &monitorInfo) && // Just in case
|
|
GetWindowRect(trayWindow, &taskbarRect))
|
|
{
|
|
LONG monitorMidX = (monitorInfo.rcMonitor.left + monitorInfo.rcMonitor.right) / 2;
|
|
LONG monitorMidY = (monitorInfo.rcMonitor.top + monitorInfo.rcMonitor.bottom) / 2;
|
|
|
|
if (taskbarRect.right < monitorMidX)
|
|
{
|
|
// Left
|
|
monitorInfo.rcWork.left += taskbarRect.right - taskbarRect.left;
|
|
}
|
|
else if (taskbarRect.bottom < monitorMidY)
|
|
{
|
|
// Top
|
|
monitorInfo.rcWork.top += taskbarRect.bottom - taskbarRect.top;
|
|
}
|
|
else if (taskbarRect.left > monitorMidX)
|
|
{
|
|
// Right
|
|
monitorInfo.rcWork.right -= taskbarRect.right - taskbarRect.left;
|
|
}
|
|
else if (taskbarRect.top > monitorMidY)
|
|
{
|
|
// Bottom
|
|
monitorInfo.rcWork.bottom -= taskbarRect.bottom - taskbarRect.top;
|
|
}
|
|
}
|
|
}
|
|
|
|
bounds = PhRectToRectangle(monitorInfo.rcWork);
|
|
|
|
PhAdjustRectangleToBounds(&windowRectangle, &bounds);
|
|
}
|
|
|
|
*WindowRectangle = windowRectangle;
|
|
}
|
|
|
|
VOID PhMipInitializeParameters(
|
|
VOID
|
|
)
|
|
{
|
|
LOGFONT logFont;
|
|
HDC hdc;
|
|
TEXTMETRIC textMetrics;
|
|
HFONT originalFont;
|
|
|
|
memset(&CurrentParameters, 0, sizeof(PH_MINIINFO_PARAMETERS));
|
|
|
|
CurrentParameters.ContainerWindowHandle = PhMipContainerWindow;
|
|
CurrentParameters.MiniInfoWindowHandle = PhMipWindow;
|
|
|
|
if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0))
|
|
{
|
|
CurrentParameters.Font = CreateFontIndirect(&logFont);
|
|
}
|
|
else
|
|
{
|
|
CurrentParameters.Font = PhApplicationFont;
|
|
GetObject(PhApplicationFont, sizeof(LOGFONT), &logFont);
|
|
}
|
|
|
|
hdc = GetDC(PhMipWindow);
|
|
|
|
logFont.lfHeight -= MulDiv(2, GetDeviceCaps(hdc, LOGPIXELSY), 72);
|
|
CurrentParameters.MediumFont = CreateFontIndirect(&logFont);
|
|
|
|
originalFont = SelectObject(hdc, CurrentParameters.Font);
|
|
GetTextMetrics(hdc, &textMetrics);
|
|
CurrentParameters.FontHeight = textMetrics.tmHeight;
|
|
CurrentParameters.FontAverageWidth = textMetrics.tmAveCharWidth;
|
|
|
|
SelectObject(hdc, CurrentParameters.MediumFont);
|
|
GetTextMetrics(hdc, &textMetrics);
|
|
CurrentParameters.MediumFontHeight = textMetrics.tmHeight;
|
|
CurrentParameters.MediumFontAverageWidth = textMetrics.tmAveCharWidth;
|
|
|
|
CurrentParameters.SetSectionText = PhMipSetSectionText;
|
|
|
|
SelectObject(hdc, originalFont);
|
|
ReleaseDC(PhMipWindow, hdc);
|
|
}
|
|
|
|
PPH_MINIINFO_SECTION PhMipCreateSection(
|
|
_In_ PPH_MINIINFO_SECTION Template
|
|
)
|
|
{
|
|
PPH_MINIINFO_SECTION section;
|
|
|
|
section = PhAllocate(sizeof(PH_MINIINFO_SECTION));
|
|
memset(section, 0, sizeof(PH_MINIINFO_SECTION));
|
|
|
|
section->Name = Template->Name;
|
|
section->Flags = Template->Flags;
|
|
section->Callback = Template->Callback;
|
|
section->Context = Template->Context;
|
|
section->Parameters = &CurrentParameters;
|
|
|
|
PhAddItemList(SectionList, section);
|
|
|
|
section->Callback(section, MiniInfoCreate, NULL, NULL);
|
|
|
|
return section;
|
|
}
|
|
|
|
VOID PhMipDestroySection(
|
|
_In_ PPH_MINIINFO_SECTION Section
|
|
)
|
|
{
|
|
Section->Callback(Section, MiniInfoDestroy, NULL, NULL);
|
|
|
|
PhClearReference(&Section->Text);
|
|
PhFree(Section);
|
|
}
|
|
|
|
PPH_MINIINFO_SECTION PhMipFindSection(
|
|
_In_ PPH_STRINGREF Name
|
|
)
|
|
{
|
|
ULONG i;
|
|
PPH_MINIINFO_SECTION section;
|
|
|
|
for (i = 0; i < SectionList->Count; i++)
|
|
{
|
|
section = SectionList->Items[i];
|
|
|
|
if (PhEqualStringRef(§ion->Name, Name, TRUE))
|
|
return section;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PPH_MINIINFO_SECTION PhMipCreateInternalSection(
|
|
_In_ PWSTR Name,
|
|
_In_ ULONG Flags,
|
|
_In_ PPH_MINIINFO_SECTION_CALLBACK Callback
|
|
)
|
|
{
|
|
PH_MINIINFO_SECTION section;
|
|
|
|
memset(§ion, 0, sizeof(PH_MINIINFO_SECTION));
|
|
PhInitializeStringRef(§ion.Name, Name);
|
|
section.Flags = Flags;
|
|
section.Callback = Callback;
|
|
|
|
return PhMipCreateSection(§ion);
|
|
}
|
|
|
|
VOID PhMipCreateSectionDialog(
|
|
_In_ PPH_MINIINFO_SECTION Section
|
|
)
|
|
{
|
|
PH_MINIINFO_CREATE_DIALOG createDialog;
|
|
|
|
memset(&createDialog, 0, sizeof(PH_MINIINFO_CREATE_DIALOG));
|
|
|
|
if (Section->Callback(Section, MiniInfoCreateDialog, &createDialog, NULL))
|
|
{
|
|
if (!createDialog.CustomCreate)
|
|
{
|
|
Section->DialogHandle = PhCreateDialogFromTemplate(
|
|
PhMipWindow,
|
|
DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD,
|
|
createDialog.Instance,
|
|
createDialog.Template,
|
|
createDialog.DialogProc,
|
|
createDialog.Parameter
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID PhMipChangeSection(
|
|
_In_ PPH_MINIINFO_SECTION NewSection
|
|
)
|
|
{
|
|
PPH_MINIINFO_SECTION oldSection;
|
|
|
|
if (NewSection == CurrentSection)
|
|
return;
|
|
|
|
oldSection = CurrentSection;
|
|
CurrentSection = NewSection;
|
|
|
|
if (oldSection)
|
|
{
|
|
oldSection->Callback(oldSection, MiniInfoSectionChanging, CurrentSection, NULL);
|
|
|
|
if (oldSection->DialogHandle)
|
|
ShowWindow(oldSection->DialogHandle, SW_HIDE);
|
|
}
|
|
|
|
if (!NewSection->DialogHandle)
|
|
PhMipCreateSectionDialog(NewSection);
|
|
if (NewSection->DialogHandle)
|
|
ShowWindow(NewSection->DialogHandle, SW_SHOW);
|
|
|
|
PhMipUpdateSectionText(NewSection);
|
|
PhMipLayout();
|
|
|
|
NewSection->Callback(NewSection, MiniInfoTick, NULL, NULL);
|
|
}
|
|
|
|
VOID PhMipSetSectionText(
|
|
_In_ struct _PH_MINIINFO_SECTION *Section,
|
|
_In_opt_ PPH_STRING Text
|
|
)
|
|
{
|
|
PhSwapReference(&Section->Text, Text);
|
|
|
|
if (Section == CurrentSection)
|
|
PhMipUpdateSectionText(Section);
|
|
}
|
|
|
|
VOID PhMipUpdateSectionText(
|
|
_In_ PPH_MINIINFO_SECTION Section
|
|
)
|
|
{
|
|
if (Section->Text)
|
|
{
|
|
SetDlgItemText(PhMipWindow, IDC_SECTION, ((PPH_STRING)PhAutoDereferenceObject(
|
|
PhConcatStringRef2(&DownArrowPrefix, &Section->Text->sr)))->Buffer);
|
|
}
|
|
else
|
|
{
|
|
SetDlgItemText(PhMipWindow, IDC_SECTION, ((PPH_STRING)PhAutoDereferenceObject(
|
|
PhConcatStringRef2(&DownArrowPrefix, &Section->Name)))->Buffer);
|
|
}
|
|
}
|
|
|
|
VOID PhMipLayout(
|
|
VOID
|
|
)
|
|
{
|
|
RECT clientRect;
|
|
RECT rect;
|
|
|
|
GetClientRect(PhMipContainerWindow, &clientRect);
|
|
MoveWindow(
|
|
PhMipWindow,
|
|
clientRect.left, clientRect.top,
|
|
clientRect.right - clientRect.left, clientRect.bottom - clientRect.top,
|
|
FALSE
|
|
);
|
|
|
|
PhLayoutManagerLayout(&PhMipLayoutManager);
|
|
|
|
GetWindowRect(GetDlgItem(PhMipWindow, IDC_LAYOUT), &rect);
|
|
MapWindowPoints(NULL, PhMipWindow, (POINT *)&rect, 2);
|
|
|
|
if (CurrentSection && CurrentSection->DialogHandle)
|
|
{
|
|
if (CurrentSection->Flags & PH_MINIINFO_SECTION_NO_UPPER_MARGINS)
|
|
{
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = clientRect.right;
|
|
}
|
|
else
|
|
{
|
|
LONG leftDistance = rect.left - clientRect.left;
|
|
LONG rightDistance = clientRect.right - rect.right;
|
|
LONG minDistance;
|
|
|
|
if (leftDistance != rightDistance)
|
|
{
|
|
// HACK: Enforce symmetry. Sometimes these are off by a pixel.
|
|
minDistance = min(leftDistance, rightDistance);
|
|
rect.left = clientRect.left + minDistance;
|
|
rect.right = clientRect.right - minDistance;
|
|
}
|
|
}
|
|
|
|
MoveWindow(
|
|
CurrentSection->DialogHandle,
|
|
rect.left, rect.top,
|
|
rect.right - rect.left, rect.bottom - rect.top,
|
|
TRUE
|
|
);
|
|
}
|
|
|
|
GetWindowRect(GetDlgItem(PhMipWindow, IDC_PIN), &rect);
|
|
MapWindowPoints(NULL, PhMipWindow, (POINT *)&rect, 2);
|
|
}
|
|
|
|
VOID PhMipBeginChildControlPin(
|
|
VOID
|
|
)
|
|
{
|
|
PhPinMiniInformation(MiniInfoChildControlPinType, 1, 0, 0, NULL, NULL);
|
|
}
|
|
|
|
VOID PhMipEndChildControlPin(
|
|
VOID
|
|
)
|
|
{
|
|
PhPinMiniInformation(MiniInfoChildControlPinType, -1, MIP_UNPIN_CHILD_CONTROL_DELAY, 0, NULL, NULL);
|
|
PostMessage(PhMipWindow, WM_MOUSEMOVE, 0, 0); // Re-evaluate hover pin
|
|
}
|
|
|
|
VOID PhMipRefresh(
|
|
VOID
|
|
)
|
|
{
|
|
if (PhMipPinned)
|
|
ProcessHacker_Refresh(PhMainWndHandle);
|
|
|
|
PostMessage(PhMipWindow, MIP_MSG_UPDATE, 0, 0);
|
|
}
|
|
|
|
VOID PhMipToggleRefreshAutomatically(
|
|
VOID
|
|
)
|
|
{
|
|
PhMipRefreshAutomatically ^= MIP_REFRESH_AUTOMATICALLY_FLAG(PhMipPinned);
|
|
PhSetIntegerSetting(L"MiniInfoWindowRefreshAutomatically", PhMipRefreshAutomatically);
|
|
}
|
|
|
|
VOID PhMipSetPinned(
|
|
_In_ BOOLEAN Pinned
|
|
)
|
|
{
|
|
PhSetWindowStyle(PhMipContainerWindow, WS_DLGFRAME | WS_SYSMENU, Pinned ? (WS_DLGFRAME | WS_SYSMENU) : 0);
|
|
SetWindowPos(PhMipContainerWindow, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
|
PhMipPinned = Pinned;
|
|
|
|
PhNfNotifyMiniInfoPinned(Pinned);
|
|
}
|
|
|
|
VOID PhMipShowSectionMenu(
|
|
VOID
|
|
)
|
|
{
|
|
PPH_EMENU menu;
|
|
ULONG i;
|
|
PPH_MINIINFO_SECTION section;
|
|
PPH_EMENU_ITEM menuItem;
|
|
POINT point;
|
|
|
|
PhMipBeginChildControlPin();
|
|
menu = PhCreateEMenu();
|
|
|
|
for (i = 0; i < SectionList->Count; i++)
|
|
{
|
|
section = SectionList->Items[i];
|
|
menuItem = PhCreateEMenuItem(
|
|
(section == CurrentSection ? (PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK) : 0),
|
|
0,
|
|
((PPH_STRING)PhAutoDereferenceObject(PhCreateString2(§ion->Name)))->Buffer,
|
|
NULL,
|
|
section
|
|
);
|
|
PhInsertEMenuItem(menu, menuItem, -1);
|
|
}
|
|
|
|
GetCursorPos(&point);
|
|
menuItem = PhShowEMenu(menu, PhMipWindow, PH_EMENU_SHOW_LEFTRIGHT,
|
|
PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y);
|
|
|
|
if (menuItem)
|
|
{
|
|
PhMipChangeSection(menuItem->Context);
|
|
}
|
|
|
|
PhDestroyEMenu(menu);
|
|
PhMipEndChildControlPin();
|
|
}
|
|
|
|
VOID PhMipShowOptionsMenu(
|
|
VOID
|
|
)
|
|
{
|
|
PPH_EMENU menu;
|
|
PPH_EMENU_ITEM menuItem;
|
|
ULONG id;
|
|
RECT rect;
|
|
|
|
PhMipBeginChildControlPin();
|
|
menu = PhCreateEMenu();
|
|
PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MINIINFO), 0);
|
|
|
|
// Opacity
|
|
|
|
id = PH_OPACITY_TO_ID(PhGetIntegerSetting(L"MiniInfoWindowOpacity"));
|
|
|
|
if (menuItem = PhFindEMenuItem(menu, PH_EMENU_FIND_DESCEND, NULL, id))
|
|
menuItem->Flags |= PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK;
|
|
|
|
// Refresh Automatically
|
|
|
|
if (PhMipRefreshAutomatically & MIP_REFRESH_AUTOMATICALLY_FLAG(PhMipPinned))
|
|
PhSetFlagsEMenuItem(menu, ID_MINIINFO_REFRESHAUTOMATICALLY, PH_EMENU_CHECKED, PH_EMENU_CHECKED);
|
|
|
|
// Show the menu.
|
|
|
|
GetWindowRect(GetDlgItem(PhMipWindow, IDC_OPTIONS), &rect);
|
|
menuItem = PhShowEMenu(menu, PhMipWindow, PH_EMENU_SHOW_LEFTRIGHT,
|
|
PH_ALIGN_LEFT | PH_ALIGN_BOTTOM, rect.left, rect.top);
|
|
|
|
if (menuItem)
|
|
{
|
|
switch (menuItem->Id)
|
|
{
|
|
case ID_OPACITY_10:
|
|
case ID_OPACITY_20:
|
|
case ID_OPACITY_30:
|
|
case ID_OPACITY_40:
|
|
case ID_OPACITY_50:
|
|
case ID_OPACITY_60:
|
|
case ID_OPACITY_70:
|
|
case ID_OPACITY_80:
|
|
case ID_OPACITY_90:
|
|
case ID_OPACITY_OPAQUE:
|
|
{
|
|
ULONG opacity;
|
|
|
|
opacity = PH_ID_TO_OPACITY(menuItem->Id);
|
|
PhSetIntegerSetting(L"MiniInfoWindowOpacity", opacity);
|
|
PhSetWindowOpacity(PhMipContainerWindow, opacity);
|
|
}
|
|
break;
|
|
case ID_MINIINFO_REFRESH:
|
|
PhMipRefresh();
|
|
break;
|
|
case ID_MINIINFO_REFRESHAUTOMATICALLY:
|
|
PhMipToggleRefreshAutomatically();
|
|
break;
|
|
}
|
|
}
|
|
|
|
PhDestroyEMenu(menu);
|
|
PhMipEndChildControlPin();
|
|
}
|
|
|
|
LRESULT CALLBACK PhMipSectionControlHookWndProc(
|
|
_In_ HWND hwnd,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_SETCURSOR:
|
|
{
|
|
SetCursor(LoadCursor(NULL, IDC_HAND));
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return CallWindowProc(SectionControlOldWndProc, hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
PPH_MINIINFO_LIST_SECTION PhMipCreateListSection(
|
|
_In_ PWSTR Name,
|
|
_In_ ULONG Flags,
|
|
_In_ PPH_MINIINFO_LIST_SECTION Template
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION listSection;
|
|
PH_MINIINFO_SECTION section;
|
|
|
|
listSection = PhAllocate(sizeof(PH_MINIINFO_LIST_SECTION));
|
|
memset(listSection, 0, sizeof(PH_MINIINFO_LIST_SECTION));
|
|
|
|
listSection->Context = Template->Context;
|
|
listSection->Callback = Template->Callback;
|
|
|
|
memset(§ion, 0, sizeof(PH_MINIINFO_SECTION));
|
|
PhInitializeStringRef(§ion.Name, Name);
|
|
section.Flags = PH_MINIINFO_SECTION_NO_UPPER_MARGINS;
|
|
section.Callback = PhMipListSectionCallback;
|
|
section.Context = listSection;
|
|
listSection->Section = PhMipCreateSection(§ion);
|
|
|
|
return listSection;
|
|
}
|
|
|
|
PPH_MINIINFO_LIST_SECTION PhMipCreateInternalListSection(
|
|
_In_ PWSTR Name,
|
|
_In_ ULONG Flags,
|
|
_In_ PPH_MINIINFO_LIST_SECTION_CALLBACK Callback
|
|
)
|
|
{
|
|
PH_MINIINFO_LIST_SECTION listSection;
|
|
|
|
memset(&listSection, 0, sizeof(PH_MINIINFO_LIST_SECTION));
|
|
listSection.Callback = Callback;
|
|
|
|
return PhMipCreateListSection(Name, Flags, &listSection);
|
|
}
|
|
|
|
BOOLEAN PhMipListSectionCallback(
|
|
_In_ PPH_MINIINFO_SECTION Section,
|
|
_In_ PH_MINIINFO_SECTION_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION listSection = Section->Context;
|
|
|
|
switch (Message)
|
|
{
|
|
case MiniInfoCreate:
|
|
{
|
|
listSection->NodeList = PhCreateList(2);
|
|
listSection->Callback(listSection, MiListSectionCreate, NULL, NULL);
|
|
}
|
|
break;
|
|
case MiniInfoDestroy:
|
|
{
|
|
listSection->Callback(listSection, MiListSectionDestroy, NULL, NULL);
|
|
|
|
PhMipClearListSection(listSection);
|
|
PhDereferenceObject(listSection->NodeList);
|
|
PhFree(listSection);
|
|
}
|
|
break;
|
|
case MiniInfoTick:
|
|
if (listSection->SuspendUpdate == 0)
|
|
PhMipTickListSection(listSection);
|
|
break;
|
|
case MiniInfoShowing:
|
|
{
|
|
listSection->Callback(listSection, MiListSectionShowing, Parameter1, Parameter2);
|
|
|
|
if (!Parameter1) // Showing
|
|
{
|
|
// We don't want to hold process item references while the mini info window
|
|
// is hidden.
|
|
PhMipClearListSection(listSection);
|
|
TreeNew_NodesStructured(listSection->TreeNewHandle);
|
|
}
|
|
}
|
|
break;
|
|
case MiniInfoCreateDialog:
|
|
{
|
|
PPH_MINIINFO_CREATE_DIALOG createDialog = Parameter1;
|
|
|
|
createDialog->Instance = PhInstanceHandle;
|
|
createDialog->Template = MAKEINTRESOURCE(IDD_MINIINFO_LIST);
|
|
createDialog->DialogProc = PhMipListSectionDialogProc;
|
|
createDialog->Parameter = listSection;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
INT_PTR CALLBACK PhMipListSectionDialogProc(
|
|
_In_ HWND hwndDlg,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION listSection = (PPH_MINIINFO_LIST_SECTION)GetProp(hwndDlg, PhMakeContextAtom());
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
PPH_LAYOUT_ITEM layoutItem;
|
|
|
|
listSection = (PPH_MINIINFO_LIST_SECTION)lParam;
|
|
SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)listSection);
|
|
|
|
listSection->DialogHandle = hwndDlg;
|
|
listSection->TreeNewHandle = GetDlgItem(hwndDlg, IDC_LIST);
|
|
|
|
PhInitializeLayoutManager(&listSection->LayoutManager, hwndDlg);
|
|
layoutItem = PhAddLayoutItem(&listSection->LayoutManager, listSection->TreeNewHandle, NULL, PH_ANCHOR_ALL);
|
|
|
|
// Use negative margins to maximize our use of the window area.
|
|
layoutItem->Margin.left = -1;
|
|
layoutItem->Margin.top = -1;
|
|
layoutItem->Margin.right = -1;
|
|
|
|
PhSetControlTheme(listSection->TreeNewHandle, L"explorer");
|
|
TreeNew_SetCallback(listSection->TreeNewHandle, PhMipListSectionTreeNewCallback, listSection);
|
|
TreeNew_SetRowHeight(listSection->TreeNewHandle, PhMipCalculateRowHeight());
|
|
PhAddTreeNewColumnEx2(listSection->TreeNewHandle, MIP_SINGLE_COLUMN_ID, TRUE, L"Process", 1,
|
|
PH_ALIGN_LEFT, 0, 0, TN_COLUMN_FLAG_CUSTOMDRAW);
|
|
|
|
listSection->Callback(listSection, MiListSectionDialogCreated, hwndDlg, NULL);
|
|
PhMipTickListSection(listSection);
|
|
}
|
|
break;
|
|
case WM_DESTROY:
|
|
{
|
|
PhDeleteLayoutManager(&listSection->LayoutManager);
|
|
RemoveProp(hwndDlg, PhMakeContextAtom());
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
{
|
|
PhLayoutManagerLayout(&listSection->LayoutManager);
|
|
TreeNew_AutoSizeColumn(listSection->TreeNewHandle, MIP_SINGLE_COLUMN_ID, TN_AUTOSIZE_REMAINING_SPACE);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID PhMipListSectionSortFunction(
|
|
_In_ PPH_LIST List,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION listSection = Context;
|
|
PH_MINIINFO_LIST_SECTION_SORT_LIST sortList;
|
|
|
|
sortList.List = List;
|
|
listSection->Callback(listSection, MiListSectionSortProcessList, &sortList, NULL);
|
|
}
|
|
|
|
VOID PhMipTickListSection(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection
|
|
)
|
|
{
|
|
ULONG i;
|
|
PPH_MIP_GROUP_NODE node;
|
|
PH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData;
|
|
|
|
PhMipClearListSection(ListSection);
|
|
|
|
ListSection->ProcessGroupList = PhCreateProcessGroupList(
|
|
PhMipListSectionSortFunction,
|
|
ListSection,
|
|
MIP_MAX_PROCESS_GROUPS,
|
|
0
|
|
);
|
|
|
|
if (!ListSection->ProcessGroupList)
|
|
return;
|
|
|
|
for (i = 0; i < ListSection->ProcessGroupList->Count; i++)
|
|
{
|
|
node = PhMipAddGroupNode(ListSection, ListSection->ProcessGroupList->Items[i]);
|
|
|
|
if (node->RepresentativeProcessId == ListSection->SelectedRepresentativeProcessId &&
|
|
node->RepresentativeCreateTime.QuadPart == ListSection->SelectedRepresentativeCreateTime.QuadPart)
|
|
{
|
|
node->Node.Selected = TRUE;
|
|
}
|
|
|
|
assignSortData.ProcessGroup = node->ProcessGroup;
|
|
assignSortData.SortData = &node->SortData;
|
|
ListSection->Callback(ListSection, MiListSectionAssignSortData, &assignSortData, NULL);
|
|
}
|
|
|
|
TreeNew_NodesStructured(ListSection->TreeNewHandle);
|
|
TreeNew_AutoSizeColumn(ListSection->TreeNewHandle, MIP_SINGLE_COLUMN_ID, TN_AUTOSIZE_REMAINING_SPACE);
|
|
|
|
ListSection->Callback(ListSection, MiListSectionTick, NULL, NULL);
|
|
}
|
|
|
|
VOID PhMipClearListSection(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
if (ListSection->ProcessGroupList)
|
|
{
|
|
PhFreeProcessGroupList(ListSection->ProcessGroupList);
|
|
ListSection->ProcessGroupList = NULL;
|
|
}
|
|
|
|
for (i = 0; i < ListSection->NodeList->Count; i++)
|
|
PhMipDestroyGroupNode(ListSection->NodeList->Items[i]);
|
|
|
|
PhClearList(ListSection->NodeList);
|
|
}
|
|
|
|
LONG PhMipCalculateRowHeight(
|
|
VOID
|
|
)
|
|
{
|
|
LONG iconHeight;
|
|
LONG titleAndSubtitleHeight;
|
|
|
|
iconHeight = MIP_ICON_PADDING + PhLargeIconSize.Y + MIP_CELL_PADDING;
|
|
titleAndSubtitleHeight =
|
|
MIP_CELL_PADDING * 2 + CurrentParameters.FontHeight + MIP_INNER_PADDING + CurrentParameters.FontHeight;
|
|
|
|
return max(iconHeight, titleAndSubtitleHeight);
|
|
}
|
|
|
|
PPH_MIP_GROUP_NODE PhMipAddGroupNode(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection,
|
|
_In_ PPH_PROCESS_GROUP ProcessGroup
|
|
)
|
|
{
|
|
PPH_MIP_GROUP_NODE node;
|
|
|
|
node = PhAllocate(sizeof(PH_MIP_GROUP_NODE));
|
|
memset(node, 0, sizeof(PH_MIP_GROUP_NODE));
|
|
|
|
PhInitializeTreeNewNode(&node->Node);
|
|
node->ProcessGroup = ProcessGroup;
|
|
node->RepresentativeProcessId = ProcessGroup->Representative->ProcessId;
|
|
node->RepresentativeCreateTime = ProcessGroup->Representative->CreateTime;
|
|
|
|
PhAddItemList(ListSection->NodeList, node);
|
|
|
|
return node;
|
|
}
|
|
|
|
VOID PhMipDestroyGroupNode(
|
|
_In_ PPH_MIP_GROUP_NODE Node
|
|
)
|
|
{
|
|
PhClearReference(&Node->TooltipText);
|
|
PhFree(Node);
|
|
}
|
|
|
|
BOOLEAN PhMipListSectionTreeNewCallback(
|
|
_In_ HWND hwnd,
|
|
_In_ PH_TREENEW_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION listSection = Context;
|
|
|
|
switch (Message)
|
|
{
|
|
case TreeNewGetChildren:
|
|
{
|
|
PPH_TREENEW_GET_CHILDREN getChildren = Parameter1;
|
|
PH_MINIINFO_LIST_SECTION_SORT_LIST sortList;
|
|
|
|
if (!getChildren->Node)
|
|
{
|
|
getChildren->Children = (PPH_TREENEW_NODE *)listSection->NodeList->Items;
|
|
getChildren->NumberOfChildren = listSection->NodeList->Count;
|
|
|
|
sortList.List = listSection->NodeList;
|
|
listSection->Callback(listSection, MiListSectionSortGroupList, &sortList, NULL);
|
|
}
|
|
}
|
|
return TRUE;
|
|
case TreeNewIsLeaf:
|
|
{
|
|
PPH_TREENEW_IS_LEAF isLeaf = Parameter1;
|
|
isLeaf->IsLeaf = TRUE;
|
|
}
|
|
return TRUE;
|
|
case TreeNewCustomDraw:
|
|
{
|
|
PPH_TREENEW_CUSTOM_DRAW customDraw = Parameter1;
|
|
PPH_MIP_GROUP_NODE node = (PPH_MIP_GROUP_NODE)customDraw->Node;
|
|
PPH_PROCESS_ITEM processItem = node->ProcessGroup->Representative;
|
|
HDC hdc = customDraw->Dc;
|
|
RECT rect = customDraw->CellRect;
|
|
ULONG baseTextFlags = DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE;
|
|
HICON icon;
|
|
COLORREF originalTextColor;
|
|
RECT topRect;
|
|
RECT bottomRect;
|
|
PH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText;
|
|
ULONG usageTextTopWidth = 0;
|
|
ULONG usageTextBottomWidth = 0;
|
|
PH_MINIINFO_LIST_SECTION_GET_TITLE_TEXT getTitleText;
|
|
|
|
rect.left += MIP_ICON_PADDING;
|
|
rect.top += MIP_ICON_PADDING;
|
|
rect.right -= MIP_CELL_PADDING;
|
|
rect.bottom -= MIP_CELL_PADDING;
|
|
|
|
if (processItem->LargeIcon)
|
|
icon = processItem->LargeIcon;
|
|
else
|
|
PhGetStockApplicationIcon(NULL, &icon);
|
|
|
|
DrawIconEx(hdc, rect.left, rect.top, icon, PhLargeIconSize.X, PhLargeIconSize.Y,
|
|
0, NULL, DI_NORMAL);
|
|
rect.left += (MIP_CELL_PADDING - MIP_ICON_PADDING) + PhLargeIconSize.X + MIP_CELL_PADDING;
|
|
rect.top += MIP_CELL_PADDING - MIP_ICON_PADDING;
|
|
SelectObject(hdc, CurrentParameters.Font);
|
|
|
|
// This color changes depending on whether the node is selected, etc.
|
|
originalTextColor = GetTextColor(hdc);
|
|
|
|
// Usage text
|
|
|
|
topRect = rect;
|
|
topRect.bottom = topRect.top + CurrentParameters.FontHeight;
|
|
bottomRect = rect;
|
|
bottomRect.top = bottomRect.bottom - CurrentParameters.FontHeight;
|
|
|
|
getUsageText.ProcessGroup = node->ProcessGroup;
|
|
getUsageText.SortData = &node->SortData;
|
|
getUsageText.Line1 = NULL;
|
|
getUsageText.Line2 = NULL;
|
|
getUsageText.Line1Color = originalTextColor;
|
|
getUsageText.Line2Color = originalTextColor;
|
|
|
|
if (listSection->Callback(listSection, MiListSectionGetUsageText, &getUsageText, NULL))
|
|
{
|
|
PH_STRINGREF text;
|
|
RECT textRect;
|
|
SIZE textSize;
|
|
|
|
// Top
|
|
text = PhGetStringRef(getUsageText.Line1);
|
|
GetTextExtentPoint32(hdc, text.Buffer, (ULONG)text.Length / 2, &textSize);
|
|
usageTextTopWidth = textSize.cx;
|
|
textRect = topRect;
|
|
textRect.left = textRect.right - textSize.cx;
|
|
SetTextColor(hdc, getUsageText.Line1Color);
|
|
DrawText(hdc, text.Buffer, (ULONG)text.Length / 2, &textRect, baseTextFlags | DT_RIGHT);
|
|
PhClearReference(&getUsageText.Line1);
|
|
|
|
// Bottom
|
|
text = PhGetStringRef(getUsageText.Line2);
|
|
GetTextExtentPoint32(hdc, text.Buffer, (ULONG)text.Length / 2, &textSize);
|
|
usageTextBottomWidth = textSize.cx;
|
|
textRect = bottomRect;
|
|
textRect.left = textRect.right - textSize.cx;
|
|
SetTextColor(hdc, getUsageText.Line2Color);
|
|
DrawText(hdc, text.Buffer, (ULONG)text.Length / 2, &textRect, baseTextFlags | DT_RIGHT);
|
|
PhClearReference(&getUsageText.Line2);
|
|
}
|
|
|
|
// Title, subtitle
|
|
|
|
getTitleText.ProcessGroup = node->ProcessGroup;
|
|
getTitleText.SortData = &node->SortData;
|
|
|
|
if (!PhIsNullOrEmptyString(processItem->VersionInfo.FileDescription))
|
|
PhSetReference(&getTitleText.Title, processItem->VersionInfo.FileDescription);
|
|
else
|
|
PhSetReference(&getTitleText.Title, processItem->ProcessName);
|
|
|
|
if (node->ProcessGroup->Processes->Count == 1)
|
|
{
|
|
PhSetReference(&getTitleText.Subtitle, processItem->ProcessName);
|
|
}
|
|
else
|
|
{
|
|
getTitleText.Subtitle = PhFormatString(
|
|
L"%s (%u processes)",
|
|
processItem->ProcessName->Buffer,
|
|
node->ProcessGroup->Processes->Count
|
|
);
|
|
}
|
|
|
|
getTitleText.TitleColor = originalTextColor;
|
|
getTitleText.SubtitleColor = GetSysColor(COLOR_GRAYTEXT);
|
|
|
|
listSection->Callback(listSection, MiListSectionGetTitleText, &getTitleText, NULL);
|
|
|
|
if (!PhIsNullOrEmptyString(getTitleText.Title))
|
|
{
|
|
RECT textRect;
|
|
|
|
textRect = topRect;
|
|
textRect.right -= usageTextTopWidth + MIP_INNER_PADDING;
|
|
SetTextColor(hdc, getTitleText.TitleColor);
|
|
DrawText(
|
|
hdc,
|
|
getTitleText.Title->Buffer,
|
|
(ULONG)getTitleText.Title->Length / 2,
|
|
&textRect,
|
|
baseTextFlags | DT_END_ELLIPSIS
|
|
);
|
|
}
|
|
|
|
if (!PhIsNullOrEmptyString(getTitleText.Subtitle))
|
|
{
|
|
RECT textRect;
|
|
|
|
textRect = bottomRect;
|
|
textRect.right -= usageTextBottomWidth + MIP_INNER_PADDING;
|
|
SetTextColor(hdc, getTitleText.SubtitleColor);
|
|
DrawText(
|
|
hdc,
|
|
getTitleText.Subtitle->Buffer,
|
|
(ULONG)getTitleText.Subtitle->Length / 2,
|
|
&textRect,
|
|
baseTextFlags | DT_END_ELLIPSIS
|
|
);
|
|
}
|
|
|
|
PhClearReference(&getTitleText.Title);
|
|
PhClearReference(&getTitleText.Subtitle);
|
|
}
|
|
return TRUE;
|
|
case TreeNewGetCellTooltip:
|
|
{
|
|
PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1;
|
|
PPH_MIP_GROUP_NODE node = (PPH_MIP_GROUP_NODE)getCellTooltip->Node;
|
|
ULONG tickCount;
|
|
|
|
tickCount = GetTickCount();
|
|
|
|
// This is useless most of the time because the tooltip doesn't display unless the window is active.
|
|
// TODO: Find a way to make the tooltip display all the time.
|
|
|
|
if (!node->TooltipText)
|
|
node->TooltipText = PhMipGetGroupNodeTooltip(listSection, node);
|
|
|
|
if (!PhIsNullOrEmptyString(node->TooltipText))
|
|
{
|
|
getCellTooltip->Text = node->TooltipText->sr;
|
|
getCellTooltip->Unfolding = FALSE;
|
|
getCellTooltip->MaximumWidth = -1;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
case TreeNewSelectionChanged:
|
|
{
|
|
ULONG i;
|
|
PPH_MIP_GROUP_NODE node;
|
|
|
|
listSection->SelectedRepresentativeProcessId = NULL;
|
|
listSection->SelectedRepresentativeCreateTime.QuadPart = 0;
|
|
|
|
for (i = 0; i < listSection->NodeList->Count; i++)
|
|
{
|
|
node = listSection->NodeList->Items[i];
|
|
|
|
if (node->Node.Selected)
|
|
{
|
|
listSection->SelectedRepresentativeProcessId = node->RepresentativeProcessId;
|
|
listSection->SelectedRepresentativeCreateTime = node->RepresentativeCreateTime;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TreeNewKeyDown:
|
|
{
|
|
PPH_TREENEW_KEY_EVENT keyEvent = Parameter1;
|
|
PPH_MIP_GROUP_NODE node;
|
|
|
|
listSection->SuspendUpdate++;
|
|
|
|
switch (keyEvent->VirtualKey)
|
|
{
|
|
case VK_DELETE:
|
|
if (node = PhMipGetSelectedGroupNode(listSection))
|
|
PhUiTerminateProcesses(listSection->DialogHandle, &node->ProcessGroup->Representative, 1);
|
|
break;
|
|
case VK_RETURN:
|
|
if (node = PhMipGetSelectedGroupNode(listSection))
|
|
{
|
|
if (GetKeyState(VK_CONTROL) >= 0)
|
|
{
|
|
PhMipHandleListSectionCommand(listSection, node->ProcessGroup, ID_PROCESS_GOTOPROCESS);
|
|
}
|
|
else
|
|
{
|
|
if (node->ProcessGroup->Representative->FileName)
|
|
PhShellExploreFile(listSection->DialogHandle, node->ProcessGroup->Representative->FileName->Buffer);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
listSection->SuspendUpdate--;
|
|
}
|
|
return TRUE;
|
|
case TreeNewLeftDoubleClick:
|
|
{
|
|
PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1;
|
|
PPH_MIP_GROUP_NODE node = (PPH_MIP_GROUP_NODE)mouseEvent->Node;
|
|
|
|
if (node)
|
|
{
|
|
listSection->SuspendUpdate++;
|
|
PhMipHandleListSectionCommand(listSection, node->ProcessGroup, ID_PROCESS_GOTOPROCESS);
|
|
listSection->SuspendUpdate--;
|
|
}
|
|
}
|
|
break;
|
|
case TreeNewContextMenu:
|
|
{
|
|
PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1;
|
|
|
|
// Prevent the node list from being updated (otherwise any nodes we're using might be destroyed while we're
|
|
// in a modal message loop).
|
|
listSection->SuspendUpdate++;
|
|
PhMipBeginChildControlPin();
|
|
PhMipShowListSectionContextMenu(listSection, contextMenu);
|
|
PhMipEndChildControlPin();
|
|
listSection->SuspendUpdate--;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
PPH_STRING PhMipGetGroupNodeTooltip(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection,
|
|
_In_ PPH_MIP_GROUP_NODE Node
|
|
)
|
|
{
|
|
PH_STRING_BUILDER sb;
|
|
|
|
PhInitializeStringBuilder(&sb, 100);
|
|
|
|
// TODO
|
|
|
|
return PhFinalStringBuilderString(&sb);
|
|
}
|
|
|
|
PPH_MIP_GROUP_NODE PhMipGetSelectedGroupNode(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection
|
|
)
|
|
{
|
|
ULONG i;
|
|
PPH_MIP_GROUP_NODE node;
|
|
|
|
for (i = 0; i < ListSection->NodeList->Count; i++)
|
|
{
|
|
node = ListSection->NodeList->Items[i];
|
|
|
|
if (node->Node.Selected)
|
|
return node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID PhMipShowListSectionContextMenu(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection,
|
|
_In_ PPH_TREENEW_CONTEXT_MENU ContextMenu
|
|
)
|
|
{
|
|
PPH_MIP_GROUP_NODE selectedNode;
|
|
PPH_EMENU menu;
|
|
PPH_EMENU_ITEM item;
|
|
PH_MINIINFO_LIST_SECTION_MENU_INFORMATION menuInfo;
|
|
PH_PLUGIN_MENU_INFORMATION pluginMenuInfo;
|
|
|
|
selectedNode = PhMipGetSelectedGroupNode(ListSection);
|
|
|
|
if (!selectedNode)
|
|
return;
|
|
|
|
menu = PhCreateEMenu();
|
|
// TODO: If there are multiple processes, then create submenus for each process.
|
|
PhAddMiniProcessMenuItems(menu, ListSection->SelectedRepresentativeProcessId);
|
|
PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MINIINFO_PROCESS), 0);
|
|
PhSetFlagsEMenuItem(menu, ID_PROCESS_GOTOPROCESS, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT);
|
|
|
|
if (selectedNode->ProcessGroup->Processes->Count != 1)
|
|
{
|
|
if (item = PhFindEMenuItem(menu, 0, NULL, ID_PROCESS_GOTOPROCESS))
|
|
PhModifyEMenuItem(item, PH_EMENU_MODIFY_TEXT, 0, L"&Go to Processes", NULL);
|
|
}
|
|
|
|
memset(&menuInfo, 0, sizeof(PH_MINIINFO_LIST_SECTION_MENU_INFORMATION));
|
|
menuInfo.ProcessGroup = selectedNode->ProcessGroup;
|
|
menuInfo.SortData = &selectedNode->SortData;
|
|
menuInfo.ContextMenu = ContextMenu;
|
|
ListSection->Callback(ListSection, MiListSectionInitializeContextMenu, &menuInfo, NULL);
|
|
|
|
if (PhPluginsEnabled)
|
|
{
|
|
PhPluginInitializeMenuInfo(&pluginMenuInfo, menu, ListSection->DialogHandle, 0);
|
|
pluginMenuInfo.Menu = menu;
|
|
pluginMenuInfo.OwnerWindow = PhMipWindow;
|
|
pluginMenuInfo.u.MiListSection.SectionName = &ListSection->Section->Name;
|
|
pluginMenuInfo.u.MiListSection.ProcessGroup = selectedNode->ProcessGroup;
|
|
|
|
PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMiListSectionMenuInitializing), &pluginMenuInfo);
|
|
}
|
|
|
|
item = PhShowEMenu(
|
|
menu,
|
|
PhMipWindow,
|
|
PH_EMENU_SHOW_LEFTRIGHT,
|
|
PH_ALIGN_LEFT | PH_ALIGN_TOP,
|
|
ContextMenu->Location.x,
|
|
ContextMenu->Location.y
|
|
);
|
|
|
|
if (item)
|
|
{
|
|
BOOLEAN handled = FALSE;
|
|
|
|
if (!handled && PhPluginsEnabled)
|
|
handled = PhPluginTriggerEMenuItem(&pluginMenuInfo, item);
|
|
|
|
if (!handled)
|
|
{
|
|
menuInfo.SelectedItem = item;
|
|
handled = ListSection->Callback(ListSection, MiListSectionHandleContextMenu, &menuInfo, NULL);
|
|
}
|
|
|
|
if (!handled)
|
|
PhHandleMiniProcessMenuItem(item);
|
|
|
|
if (!handled)
|
|
PhMipHandleListSectionCommand(ListSection, selectedNode->ProcessGroup, item->Id);
|
|
}
|
|
|
|
PhDestroyEMenu(menu);
|
|
}
|
|
|
|
VOID PhMipHandleListSectionCommand(
|
|
_In_ PPH_MINIINFO_LIST_SECTION ListSection,
|
|
_In_ PPH_PROCESS_GROUP ProcessGroup,
|
|
_In_ ULONG Id
|
|
)
|
|
{
|
|
switch (Id)
|
|
{
|
|
case ID_PROCESS_GOTOPROCESS:
|
|
{
|
|
PPH_LIST nodes;
|
|
ULONG i;
|
|
|
|
nodes = PhCreateList(ProcessGroup->Processes->Count);
|
|
|
|
for (i = 0; i < ProcessGroup->Processes->Count; i++)
|
|
{
|
|
PPH_PROCESS_NODE node;
|
|
|
|
if (node = PhFindProcessNode(((PPH_PROCESS_ITEM)ProcessGroup->Processes->Items[i])->ProcessId))
|
|
PhAddItemList(nodes, node);
|
|
}
|
|
|
|
PhPinMiniInformation(MiniInfoIconPinType, -1, 0, 0, NULL, NULL);
|
|
PhPinMiniInformation(MiniInfoActivePinType, -1, 0, 0, NULL, NULL);
|
|
PhPinMiniInformation(MiniInfoHoverPinType, -1, 0, 0, NULL, NULL);
|
|
|
|
ProcessHacker_ToggleVisible(PhMainWndHandle, TRUE);
|
|
ProcessHacker_SelectTabPage(PhMainWndHandle, 0);
|
|
PhSelectAndEnsureVisibleProcessNodes((PPH_PROCESS_NODE *)nodes->Items, nodes->Count);
|
|
PhDereferenceObject(nodes);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOLEAN PhMipCpuListSectionCallback(
|
|
_In_ struct _PH_MINIINFO_LIST_SECTION *ListSection,
|
|
_In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2
|
|
)
|
|
{
|
|
switch (Message)
|
|
{
|
|
case MiListSectionTick:
|
|
ListSection->Section->Parameters->SetSectionText(ListSection->Section,
|
|
PhaFormatString(L"CPU %.2f%%", (PhCpuUserUsage + PhCpuKernelUsage) * 100));
|
|
break;
|
|
case MiListSectionSortProcessList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_PROCESS_NODE), PhMipCpuListSectionProcessCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionAssignSortData:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1;
|
|
PPH_LIST processes = assignSortData->ProcessGroup->Processes;
|
|
FLOAT cpuUsage = 0;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < processes->Count; i++)
|
|
cpuUsage += ((PPH_PROCESS_ITEM)processes->Items[i])->CpuUsage;
|
|
|
|
*(PFLOAT)assignSortData->SortData->UserData = cpuUsage;
|
|
}
|
|
return TRUE;
|
|
case MiListSectionSortGroupList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipCpuListSectionNodeCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionGetUsageText:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1;
|
|
PPH_LIST processes = getUsageText->ProcessGroup->Processes;
|
|
FLOAT cpuUsage = *(PFLOAT)getUsageText->SortData->UserData * 100;
|
|
PPH_STRING cpuUsageText;
|
|
|
|
if (cpuUsage >= 0.01)
|
|
cpuUsageText = PhFormatString(L"%.2f%%", cpuUsage);
|
|
else if (cpuUsage != 0)
|
|
cpuUsageText = PhCreateString(L"< 0.01%");
|
|
else
|
|
cpuUsageText = NULL;
|
|
|
|
PhMoveReference(&getUsageText->Line1, cpuUsageText);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
int __cdecl PhMipCpuListSectionProcessCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
int result;
|
|
PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1;
|
|
PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2;
|
|
|
|
result = singlecmp(node2->ProcessItem->CpuUsage, node1->ProcessItem->CpuUsage);
|
|
|
|
if (result == 0)
|
|
result = uint64cmp(node2->ProcessItem->UserTime.QuadPart, node1->ProcessItem->UserTime.QuadPart);
|
|
|
|
return result;
|
|
}
|
|
|
|
int __cdecl PhMipCpuListSectionNodeCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1;
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2;
|
|
|
|
return singlecmp(*(PFLOAT)data2->UserData, *(PFLOAT)data1->UserData);
|
|
}
|
|
|
|
BOOLEAN PhMipCommitListSectionCallback(
|
|
_In_ struct _PH_MINIINFO_LIST_SECTION *ListSection,
|
|
_In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2
|
|
)
|
|
{
|
|
switch (Message)
|
|
{
|
|
case MiListSectionTick:
|
|
{
|
|
PH_FORMAT format[5];
|
|
DOUBLE commitFraction = (DOUBLE)PhPerfInformation.CommittedPages / PhPerfInformation.CommitLimit;
|
|
|
|
PhInitFormatS(&format[0], L"Commit ");
|
|
PhInitFormatSize(&format[1], UInt32x32To64(PhPerfInformation.CommittedPages, PAGE_SIZE));
|
|
PhInitFormatS(&format[2], L" (");
|
|
PhInitFormatF(&format[3], commitFraction * 100, 2);
|
|
PhInitFormatS(&format[4], L"%)");
|
|
ListSection->Section->Parameters->SetSectionText(ListSection->Section,
|
|
PhAutoDereferenceObject(PhFormat(format, 5, 96)));
|
|
}
|
|
break;
|
|
case MiListSectionSortProcessList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_PROCESS_NODE), PhMipCommitListSectionProcessCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionAssignSortData:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1;
|
|
PPH_LIST processes = assignSortData->ProcessGroup->Processes;
|
|
ULONG64 privateBytes = 0;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < processes->Count; i++)
|
|
privateBytes += ((PPH_PROCESS_ITEM)processes->Items[i])->VmCounters.PagefileUsage;
|
|
|
|
*(PULONG64)assignSortData->SortData->UserData = privateBytes;
|
|
}
|
|
return TRUE;
|
|
case MiListSectionSortGroupList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipCommitListSectionNodeCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionGetUsageText:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1;
|
|
PPH_LIST processes = getUsageText->ProcessGroup->Processes;
|
|
ULONG64 privateBytes = *(PULONG64)getUsageText->SortData->UserData;
|
|
|
|
PhMoveReference(&getUsageText->Line1, PhFormatSize(privateBytes, -1));
|
|
PhMoveReference(&getUsageText->Line2, PhCreateString(L"Private Bytes"));
|
|
getUsageText->Line2Color = GetSysColor(COLOR_GRAYTEXT);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
int __cdecl PhMipCommitListSectionProcessCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1;
|
|
PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2;
|
|
|
|
return uintptrcmp(node2->ProcessItem->VmCounters.PagefileUsage, node1->ProcessItem->VmCounters.PagefileUsage);
|
|
}
|
|
|
|
int __cdecl PhMipCommitListSectionNodeCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1;
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2;
|
|
|
|
return uint64cmp(*(PULONG64)data2->UserData, *(PULONG64)data1->UserData);
|
|
}
|
|
|
|
BOOLEAN PhMipPhysicalListSectionCallback(
|
|
_In_ struct _PH_MINIINFO_LIST_SECTION *ListSection,
|
|
_In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2
|
|
)
|
|
{
|
|
switch (Message)
|
|
{
|
|
case MiListSectionTick:
|
|
{
|
|
PH_FORMAT format[5];
|
|
ULONG physicalUsage = PhSystemBasicInformation.NumberOfPhysicalPages - PhPerfInformation.AvailablePages;
|
|
FLOAT physicalFraction = (FLOAT)physicalUsage / PhSystemBasicInformation.NumberOfPhysicalPages;
|
|
|
|
PhInitFormatS(&format[0], L"Physical ");
|
|
PhInitFormatSize(&format[1], UInt32x32To64(physicalUsage, PAGE_SIZE));
|
|
PhInitFormatS(&format[2], L" (");
|
|
PhInitFormatF(&format[3], physicalFraction * 100, 2);
|
|
PhInitFormatS(&format[4], L"%)");
|
|
ListSection->Section->Parameters->SetSectionText(ListSection->Section,
|
|
PhAutoDereferenceObject(PhFormat(format, 5, 96)));
|
|
}
|
|
break;
|
|
case MiListSectionSortProcessList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_PROCESS_NODE), PhMipPhysicalListSectionProcessCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionAssignSortData:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1;
|
|
PPH_LIST processes = assignSortData->ProcessGroup->Processes;
|
|
ULONG64 workingSet = 0;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < processes->Count; i++)
|
|
workingSet += ((PPH_PROCESS_ITEM)processes->Items[i])->VmCounters.WorkingSetSize;
|
|
|
|
*(PULONG64)assignSortData->SortData->UserData = workingSet;
|
|
}
|
|
return TRUE;
|
|
case MiListSectionSortGroupList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipPhysicalListSectionNodeCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionGetUsageText:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1;
|
|
PPH_LIST processes = getUsageText->ProcessGroup->Processes;
|
|
ULONG64 privateBytes = *(PULONG64)getUsageText->SortData->UserData;
|
|
|
|
PhMoveReference(&getUsageText->Line1, PhFormatSize(privateBytes, -1));
|
|
PhMoveReference(&getUsageText->Line2, PhCreateString(L"Working Set"));
|
|
getUsageText->Line2Color = GetSysColor(COLOR_GRAYTEXT);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
int __cdecl PhMipPhysicalListSectionProcessCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1;
|
|
PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2;
|
|
|
|
return uintptrcmp(node2->ProcessItem->VmCounters.WorkingSetSize, node1->ProcessItem->VmCounters.WorkingSetSize);
|
|
}
|
|
|
|
int __cdecl PhMipPhysicalListSectionNodeCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1;
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2;
|
|
|
|
return uint64cmp(*(PULONG64)data2->UserData, *(PULONG64)data1->UserData);
|
|
}
|
|
|
|
BOOLEAN PhMipIoListSectionCallback(
|
|
_In_ struct _PH_MINIINFO_LIST_SECTION *ListSection,
|
|
_In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2
|
|
)
|
|
{
|
|
switch (Message)
|
|
{
|
|
case MiListSectionTick:
|
|
{
|
|
PH_FORMAT format[6];
|
|
|
|
PhInitFormatS(&format[0], L"I/O R: ");
|
|
PhInitFormatSize(&format[1], PhIoReadDelta.Delta);
|
|
format[1].Type |= FormatUsePrecision;
|
|
format[1].Precision = 0;
|
|
PhInitFormatS(&format[2], L" W: ");
|
|
PhInitFormatSize(&format[3], PhIoWriteDelta.Delta);
|
|
format[3].Type |= FormatUsePrecision;
|
|
format[3].Precision = 0;
|
|
PhInitFormatS(&format[4], L" O: ");
|
|
PhInitFormatSize(&format[5], PhIoOtherDelta.Delta);
|
|
format[5].Type |= FormatUsePrecision;
|
|
format[5].Precision = 0;
|
|
ListSection->Section->Parameters->SetSectionText(ListSection->Section,
|
|
PhAutoDereferenceObject(PhFormat(format, 6, 80)));
|
|
}
|
|
break;
|
|
case MiListSectionSortProcessList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_PROCESS_NODE), PhMipIoListSectionProcessCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionAssignSortData:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1;
|
|
PPH_LIST processes = assignSortData->ProcessGroup->Processes;
|
|
ULONG64 ioReadOtherDelta = 0;
|
|
ULONG64 ioWriteDelta = 0;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < processes->Count; i++)
|
|
{
|
|
PPH_PROCESS_ITEM processItem = processes->Items[i];
|
|
ioReadOtherDelta += processItem->IoReadDelta.Delta;
|
|
ioWriteDelta += processItem->IoWriteDelta.Delta;
|
|
ioReadOtherDelta += processItem->IoOtherDelta.Delta;
|
|
}
|
|
|
|
assignSortData->SortData->UserData[0] = ioReadOtherDelta;
|
|
assignSortData->SortData->UserData[1] = ioWriteDelta;
|
|
}
|
|
return TRUE;
|
|
case MiListSectionSortGroupList:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1;
|
|
|
|
qsort(sortList->List->Items, sortList->List->Count,
|
|
sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipIoListSectionNodeCompareFunction);
|
|
}
|
|
return TRUE;
|
|
case MiListSectionGetUsageText:
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1;
|
|
PPH_LIST processes = getUsageText->ProcessGroup->Processes;
|
|
ULONG64 ioReadOtherDelta = getUsageText->SortData->UserData[0];
|
|
ULONG64 ioWriteDelta = getUsageText->SortData->UserData[1];
|
|
PH_FORMAT format[1];
|
|
|
|
PhInitFormatSize(&format[0], ioReadOtherDelta + ioWriteDelta);
|
|
PhMoveReference(&getUsageText->Line1, PhFormat(format, 1, 16));
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
int __cdecl PhMipIoListSectionProcessCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
int result;
|
|
PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1;
|
|
PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2;
|
|
ULONG64 delta1 = node1->ProcessItem->IoReadDelta.Delta + node1->ProcessItem->IoWriteDelta.Delta + node1->ProcessItem->IoOtherDelta.Delta;
|
|
ULONG64 delta2 = node2->ProcessItem->IoReadDelta.Delta + node2->ProcessItem->IoWriteDelta.Delta + node2->ProcessItem->IoOtherDelta.Delta;
|
|
ULONG64 value1 = node1->ProcessItem->IoReadDelta.Value + node1->ProcessItem->IoWriteDelta.Value + node1->ProcessItem->IoOtherDelta.Value;
|
|
ULONG64 value2 = node2->ProcessItem->IoReadDelta.Value + node2->ProcessItem->IoWriteDelta.Value + node2->ProcessItem->IoOtherDelta.Value;
|
|
|
|
result = uint64cmp(delta2, delta1);
|
|
|
|
if (result == 0)
|
|
result = uint64cmp(value2, value1);
|
|
|
|
return result;
|
|
}
|
|
|
|
int __cdecl PhMipIoListSectionNodeCompareFunction(
|
|
_In_ const void *elem1,
|
|
_In_ const void *elem2
|
|
)
|
|
{
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1;
|
|
PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2;
|
|
|
|
return uint64cmp(data2->UserData[0] + data2->UserData[1], data1->UserData[0] + data1->UserData[1]);
|
|
}
|