/* * Process Hacker Window Explorer - * window tree dialog * * Copyright (C) 2016 dmex * Copyright (C) 2011 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 "wndexp.h" #include "resource.h" #include typedef struct _WINDOWS_CONTEXT { HWND TreeNewHandle; WE_WINDOW_TREE_CONTEXT TreeContext; WE_WINDOW_SELECTOR Selector; PH_LAYOUT_MANAGER LayoutManager; HWND HighlightingWindow; ULONG HighlightingWindowCount; } WINDOWS_CONTEXT, *PWINDOWS_CONTEXT; VOID WepShowWindowsDialogCallback( _In_ PVOID Parameter ); INT_PTR CALLBACK WepWindowsDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ); static RECT MinimumSize = { -1, -1, -1, -1 }; VOID WeShowWindowsDialog( _In_ HWND ParentWindowHandle, _In_ PWE_WINDOW_SELECTOR Selector ) { PWINDOWS_CONTEXT context; context = PhAllocate(sizeof(WINDOWS_CONTEXT)); memset(context, 0, sizeof(WINDOWS_CONTEXT)); memcpy(&context->Selector, Selector, sizeof(WE_WINDOW_SELECTOR)); ProcessHacker_Invoke(WE_PhMainWndHandle, WepShowWindowsDialogCallback, context); } VOID WepShowWindowsDialogCallback( _In_ PVOID Parameter ) { HWND hwnd; PWINDOWS_CONTEXT context = Parameter; hwnd = CreateDialogParam( PluginInstance->DllBase, MAKEINTRESOURCE(IDD_WNDLIST), NULL, WepWindowsDlgProc, (LPARAM)context ); ShowWindow(hwnd, SW_SHOW); } VOID WepDeleteWindowSelector( _In_ PWE_WINDOW_SELECTOR Selector ) { switch (Selector->Type) { case WeWindowSelectorDesktop: PhDereferenceObject(Selector->Desktop.DesktopName); break; } } VOID WepFillWindowInfo( _In_ PWE_WINDOW_NODE Node ) { HWND hwnd; ULONG threadId; ULONG processId; hwnd = Node->WindowHandle; GetClassName(hwnd, Node->WindowClass, sizeof(Node->WindowClass) / sizeof(WCHAR)); Node->WindowText = PhGetWindowText(hwnd); if (!Node->WindowText) Node->WindowText = PhReferenceEmptyString(); threadId = GetWindowThreadProcessId(hwnd, &processId); Node->ClientId.UniqueProcess = UlongToHandle(processId); Node->ClientId.UniqueThread = UlongToHandle(threadId); Node->WindowVisible = !!IsWindowVisible(hwnd); Node->HasChildren = !!FindWindowEx(hwnd, NULL, NULL, NULL); } VOID WepAddChildWindowNode( _In_ PWE_WINDOW_TREE_CONTEXT Context, _In_opt_ PWE_WINDOW_NODE ParentNode, _In_ HWND hwnd ) { PWE_WINDOW_NODE childNode; childNode = WeAddWindowNode(Context); childNode->WindowHandle = hwnd; WepFillWindowInfo(childNode); childNode->Node.Expanded = FALSE; if (ParentNode) { // This is a child node. childNode->Parent = ParentNode; PhAddItemList(ParentNode->Children, childNode); } else { // This is a root node. PhAddItemList(Context->NodeRootList, childNode); } } VOID WepAddChildWindows( _In_ PWINDOWS_CONTEXT Context, _In_opt_ PWE_WINDOW_NODE ParentNode, _In_ HWND hwnd, _In_opt_ HANDLE FilterProcessId, _In_opt_ HANDLE FilterThreadId ) { HWND childWindow = NULL; ULONG i = 0; // We use FindWindowEx because EnumWindows doesn't return Metro app windows. // Set a reasonable limit to prevent infinite loops. while (i < 0x800 && (childWindow = FindWindowEx(hwnd, childWindow, NULL, NULL))) { ULONG processId; ULONG threadId; threadId = GetWindowThreadProcessId(childWindow, &processId); if ( (!FilterProcessId || UlongToHandle(processId) == FilterProcessId) && (!FilterThreadId || UlongToHandle(threadId) == FilterThreadId) ) { WepAddChildWindowNode(&Context->TreeContext, ParentNode, childWindow); } i++; } } BOOL CALLBACK WepEnumDesktopWindowsProc( _In_ HWND hwnd, _In_ LPARAM lParam ) { PWINDOWS_CONTEXT context = (PWINDOWS_CONTEXT)lParam; WepAddChildWindowNode(&context->TreeContext, NULL, hwnd); return TRUE; } VOID WepAddDesktopWindows( _In_ PWINDOWS_CONTEXT Context, _In_ PWSTR DesktopName ) { HDESK desktopHandle; if (desktopHandle = OpenDesktop(DesktopName, 0, FALSE, DESKTOP_ENUMERATE)) { EnumDesktopWindows(desktopHandle, WepEnumDesktopWindowsProc, (LPARAM)Context); CloseDesktop(desktopHandle); } } VOID WepRefreshWindows( _In_ PWINDOWS_CONTEXT Context ) { TreeNew_SetRedraw(Context->TreeNewHandle, FALSE); WeClearWindowTree(&Context->TreeContext); TreeNew_NodesStructured(Context->TreeNewHandle); switch (Context->Selector.Type) { case WeWindowSelectorAll: { PWE_WINDOW_NODE desktopNode; desktopNode = WeAddWindowNode(&Context->TreeContext); desktopNode->WindowHandle = GetDesktopWindow(); WepFillWindowInfo(desktopNode); PhAddItemList(Context->TreeContext.NodeRootList, desktopNode); WepAddChildWindows(Context, desktopNode, desktopNode->WindowHandle, NULL, NULL); desktopNode->HasChildren = TRUE; desktopNode->Opened = TRUE; } break; case WeWindowSelectorThread: { WepAddChildWindows(Context, NULL, GetDesktopWindow(), NULL, Context->Selector.Thread.ThreadId); } break; case WeWindowSelectorProcess: { WepAddChildWindows(Context, NULL, GetDesktopWindow(), Context->Selector.Process.ProcessId, NULL); } break; case WeWindowSelectorDesktop: { WepAddDesktopWindows(Context, Context->Selector.Desktop.DesktopName->Buffer); } break; } TreeNew_SetRedraw(Context->TreeNewHandle, TRUE); } PPH_STRING WepGetWindowTitleForSelector( _In_ PWE_WINDOW_SELECTOR Selector ) { switch (Selector->Type) { case WeWindowSelectorAll: { return PhCreateString(L"Windows - All"); } break; case WeWindowSelectorThread: { return PhFormatString(L"Windows - Thread %lu", HandleToUlong(Selector->Thread.ThreadId)); } break; case WeWindowSelectorProcess: { CLIENT_ID clientId; clientId.UniqueProcess = Selector->Process.ProcessId; clientId.UniqueThread = NULL; return PhConcatStrings2(L"Windows - ", PH_AUTO_T(PH_STRING, PhGetClientIdName(&clientId))->Buffer); } break; case WeWindowSelectorDesktop: { return PhFormatString(L"Windows - Desktop \"%s\"", Selector->Desktop.DesktopName->Buffer); } break; default: return PhCreateString(L"Windows"); } } INT_PTR CALLBACK WepWindowsDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { PWINDOWS_CONTEXT context; if (uMsg == WM_INITDIALOG) { context = (PWINDOWS_CONTEXT)lParam; SetProp(hwndDlg, L"Context", (HANDLE)context); } else { context = (PWINDOWS_CONTEXT)GetProp(hwndDlg, L"Context"); if (uMsg == WM_DESTROY) RemoveProp(hwndDlg, L"Context"); } if (!context) return FALSE; switch (uMsg) { case WM_INITDIALOG: { PPH_STRING windowTitle; PH_RECTANGLE windowRectangle; context->TreeNewHandle = GetDlgItem(hwndDlg, IDC_LIST); WeInitializeWindowTree(hwndDlg, context->TreeNewHandle, &context->TreeContext); PhRegisterDialog(hwndDlg); PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_LIST), NULL, PH_ANCHOR_ALL); if (MinimumSize.left == -1) { RECT rect; rect.left = 0; rect.top = 0; rect.right = 160; rect.bottom = 100; MapDialogRect(hwndDlg, &rect); MinimumSize = rect; MinimumSize.left = 0; } // Set up the window position and size. windowRectangle.Position = PhGetIntegerPairSetting(SETTING_NAME_WINDOWS_WINDOW_POSITION); windowRectangle.Size = PhGetScalableIntegerPairSetting(SETTING_NAME_WINDOWS_WINDOW_SIZE, TRUE).Pair; PhAdjustRectangleToWorkingArea(NULL, &windowRectangle); MoveWindow(hwndDlg, windowRectangle.Left, windowRectangle.Top, windowRectangle.Width, windowRectangle.Height, FALSE); // Implement cascading by saving an offsetted rectangle. windowRectangle.Left += 20; windowRectangle.Top += 20; PhSetIntegerPairSetting(SETTING_NAME_WINDOWS_WINDOW_POSITION, windowRectangle.Position); windowTitle = PH_AUTO(WepGetWindowTitleForSelector(&context->Selector)); SetWindowText(hwndDlg, windowTitle->Buffer); WepRefreshWindows(context); } break; case WM_DESTROY: { PhSaveWindowPlacementToSetting(SETTING_NAME_WINDOWS_WINDOW_POSITION, SETTING_NAME_WINDOWS_WINDOW_SIZE, hwndDlg); PhDeleteLayoutManager(&context->LayoutManager); PhUnregisterDialog(hwndDlg); WeDeleteWindowTree(&context->TreeContext); WepDeleteWindowSelector(&context->Selector); PhFree(context); } break; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: //case IDOK: DestroyWindow(hwndDlg); break; case IDC_REFRESH: WepRefreshWindows(context); break; case ID_SHOWCONTEXTMENU: { POINT point; PWE_WINDOW_NODE *windows; ULONG numberOfWindows; PPH_EMENU menu; point.x = (SHORT)LOWORD(lParam); point.y = (SHORT)HIWORD(lParam); WeGetSelectedWindowNodes( &context->TreeContext, &windows, &numberOfWindows ); if (numberOfWindows != 0) { menu = PhCreateEMenu(); PhLoadResourceEMenuItem(menu, PluginInstance->DllBase, MAKEINTRESOURCE(IDR_WINDOW), 0); PhSetFlagsEMenuItem(menu, ID_WINDOW_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); if (numberOfWindows == 1) { WINDOWPLACEMENT placement = { sizeof(placement) }; BYTE alpha; ULONG flags; ULONG i; ULONG id; // State GetWindowPlacement(windows[0]->WindowHandle, &placement); if (placement.showCmd == SW_MINIMIZE) PhSetFlagsEMenuItem(menu, ID_WINDOW_MINIMIZE, PH_EMENU_DISABLED, PH_EMENU_DISABLED); else if (placement.showCmd == SW_MAXIMIZE) PhSetFlagsEMenuItem(menu, ID_WINDOW_MAXIMIZE, PH_EMENU_DISABLED, PH_EMENU_DISABLED); else if (placement.showCmd == SW_NORMAL) PhSetFlagsEMenuItem(menu, ID_WINDOW_RESTORE, PH_EMENU_DISABLED, PH_EMENU_DISABLED); // Visible PhSetFlagsEMenuItem(menu, ID_WINDOW_VISIBLE, PH_EMENU_CHECKED, (GetWindowLong(windows[0]->WindowHandle, GWL_STYLE) & WS_VISIBLE) ? PH_EMENU_CHECKED : 0); // Enabled PhSetFlagsEMenuItem(menu, ID_WINDOW_ENABLED, PH_EMENU_CHECKED, !(GetWindowLong(windows[0]->WindowHandle, GWL_STYLE) & WS_DISABLED) ? PH_EMENU_CHECKED : 0); // Always on Top PhSetFlagsEMenuItem(menu, ID_WINDOW_ALWAYSONTOP, PH_EMENU_CHECKED, (GetWindowLong(windows[0]->WindowHandle, GWL_EXSTYLE) & WS_EX_TOPMOST) ? PH_EMENU_CHECKED : 0); // Opacity if (GetLayeredWindowAttributes(windows[0]->WindowHandle, NULL, &alpha, &flags)) { if (!(flags & LWA_ALPHA)) alpha = 255; } else { alpha = 255; } if (alpha == 255) { id = ID_OPACITY_OPAQUE; } else { id = 0; // Due to integer division, we cannot use simple arithmetic to calculate which menu item to check. for (i = 0; i < 10; i++) { if (alpha == (BYTE)(255 * (i + 1) / 10)) { id = ID_OPACITY_10 + i; break; } } } if (id != 0) { PhSetFlagsEMenuItem(menu, id, PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); } } else { PhSetFlagsAllEMenuItems(menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); PhSetFlagsEMenuItem(menu, ID_WINDOW_COPY, PH_EMENU_DISABLED, 0); } PhShowEMenu(menu, hwndDlg, PH_EMENU_SHOW_SEND_COMMAND | PH_EMENU_SHOW_LEFTRIGHT, PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y); PhDestroyEMenu(menu); } } break; case ID_WINDOW_BRINGTOFRONT: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { WINDOWPLACEMENT placement = { sizeof(placement) }; GetWindowPlacement(selectedNode->WindowHandle, &placement); if (placement.showCmd == SW_MINIMIZE) ShowWindowAsync(selectedNode->WindowHandle, SW_RESTORE); else SetForegroundWindow(selectedNode->WindowHandle); } } break; case ID_WINDOW_RESTORE: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { ShowWindowAsync(selectedNode->WindowHandle, SW_RESTORE); } } break; case ID_WINDOW_MINIMIZE: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { ShowWindowAsync(selectedNode->WindowHandle, SW_MINIMIZE); } } break; case ID_WINDOW_MAXIMIZE: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { ShowWindowAsync(selectedNode->WindowHandle, SW_MAXIMIZE); } } break; case ID_WINDOW_CLOSE: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { PostMessage(selectedNode->WindowHandle, WM_CLOSE, 0, 0); } } break; case ID_WINDOW_VISIBLE: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { if (IsWindowVisible(selectedNode->WindowHandle)) { selectedNode->WindowVisible = FALSE; ShowWindowAsync(selectedNode->WindowHandle, SW_HIDE); } else { selectedNode->WindowVisible = TRUE; ShowWindowAsync(selectedNode->WindowHandle, SW_SHOW); } PhInvalidateTreeNewNode(&selectedNode->Node, TN_CACHE_COLOR); TreeNew_InvalidateNode(context->TreeNewHandle, &selectedNode->Node); } } break; case ID_WINDOW_ENABLED: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { EnableWindow(selectedNode->WindowHandle, !IsWindowEnabled(selectedNode->WindowHandle)); } } break; case ID_WINDOW_ALWAYSONTOP: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { LOGICAL topMost; topMost = GetWindowLong(selectedNode->WindowHandle, GWL_EXSTYLE) & WS_EX_TOPMOST; SetWindowPos(selectedNode->WindowHandle, topMost ? HWND_NOTOPMOST : HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } } break; 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: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { ULONG opacity; opacity = ((ULONG)LOWORD(wParam) - ID_OPACITY_10) + 1; if (opacity == 10) { // Remove the WS_EX_LAYERED bit since it is not needed. PhSetWindowExStyle(selectedNode->WindowHandle, WS_EX_LAYERED, 0); RedrawWindow(selectedNode->WindowHandle, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); } else { // Add the WS_EX_LAYERED bit so opacity will work. PhSetWindowExStyle(selectedNode->WindowHandle, WS_EX_LAYERED, WS_EX_LAYERED); SetLayeredWindowAttributes(selectedNode->WindowHandle, 0, (BYTE)(255 * opacity / 10), LWA_ALPHA); } } } break; case ID_WINDOW_HIGHLIGHT: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { if (context->HighlightingWindow) { if (context->HighlightingWindowCount & 1) WeInvertWindowBorder(context->HighlightingWindow); } context->HighlightingWindow = selectedNode->WindowHandle; context->HighlightingWindowCount = 10; SetTimer(hwndDlg, 9, 100, NULL); } } break; case ID_WINDOW_GOTOTHREAD: { PWE_WINDOW_NODE selectedNode; PPH_PROCESS_ITEM processItem; PPH_PROCESS_PROPCONTEXT propContext; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) { if (processItem = PhReferenceProcessItem(selectedNode->ClientId.UniqueProcess)) { if (propContext = PhCreateProcessPropContext(WE_PhMainWndHandle, processItem)) { PhSetSelectThreadIdProcessPropContext(propContext, selectedNode->ClientId.UniqueThread); PhShowProcessProperties(propContext); PhDereferenceObject(propContext); } PhDereferenceObject(processItem); } else { PhShowError(hwndDlg, L"The process does not exist."); } } } break; case ID_WINDOW_PROPERTIES: { PWE_WINDOW_NODE selectedNode; if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) WeShowWindowProperties(hwndDlg, selectedNode->WindowHandle); } break; case ID_WINDOW_COPY: { PPH_STRING text; text = PhGetTreeNewText(context->TreeNewHandle, 0); PhSetClipboardString(hwndDlg, &text->sr); PhDereferenceObject(text); } break; } } break; case WM_TIMER: { switch (wParam) { case 9: { WeInvertWindowBorder(context->HighlightingWindow); if (--context->HighlightingWindowCount == 0) KillTimer(hwndDlg, 9); } break; } } break; case WM_SIZE: { PhLayoutManagerLayout(&context->LayoutManager); } break; case WM_SIZING: { PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); } break; case WM_WE_PLUSMINUS: { PWE_WINDOW_NODE node = (PWE_WINDOW_NODE)lParam; if (!node->Opened) { TreeNew_SetRedraw(context->TreeNewHandle, FALSE); WepAddChildWindows(context, node, node->WindowHandle, NULL, NULL); node->Opened = TRUE; TreeNew_SetRedraw(context->TreeNewHandle, TRUE); } } break; } return FALSE; }