/* * 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 . */ #include #include #include #include #include #include #include #include #include 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]); }