1 /* 2 * PROJECT: ReactOS user32.dll 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Ghost window class 5 * COPYRIGHT: Copyright 2018 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 6 */ 7 8 #include <user32.h> 9 #include <strsafe.h> 10 #include "ghostwnd.h" 11 12 WINE_DEFAULT_DEBUG_CHANNEL(ghost); 13 14 #define GHOST_TIMER_ID 0xFACEDEAD 15 #define GHOST_INTERVAL 1000 // one second 16 17 const struct builtin_class_descr GHOST_builtin_class = 18 { 19 L"Ghost", /* name */ 20 0, /* style */ 21 GhostWndProcA, /* procA */ 22 GhostWndProcW, /* procW */ 23 0, /* extra */ 24 IDC_WAIT, /* cursor */ 25 NULL /* brush */ 26 }; 27 28 static HBITMAP 29 IntCreate32BppBitmap(INT cx, INT cy) 30 { 31 HBITMAP hbm = NULL; 32 BITMAPINFO bi; 33 HDC hdc; 34 LPVOID pvBits; 35 36 ZeroMemory(&bi, sizeof(bi)); 37 bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 38 bi.bmiHeader.biWidth = cx; 39 bi.bmiHeader.biHeight = cy; 40 bi.bmiHeader.biPlanes = 1; 41 bi.bmiHeader.biBitCount = 32; 42 43 hdc = CreateCompatibleDC(NULL); 44 if (hdc) 45 { 46 hbm = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0); 47 DeleteDC(hdc); 48 } 49 return hbm; 50 } 51 52 static HBITMAP 53 IntGetWindowBitmap(HWND hwnd, INT cx, INT cy) 54 { 55 HBITMAP hbm = NULL; 56 HDC hdc, hdcMem; 57 HGDIOBJ hbmOld; 58 59 hdc = GetWindowDC(hwnd); 60 if (!hdc) 61 return NULL; 62 63 hdcMem = CreateCompatibleDC(hdc); 64 if (!hdcMem) 65 goto earth; 66 67 hbm = IntCreate32BppBitmap(cx, cy); 68 if (hbm) 69 { 70 hbmOld = SelectObject(hdcMem, hbm); 71 BitBlt(hdcMem, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY | CAPTUREBLT); 72 SelectObject(hdcMem, hbmOld); 73 } 74 75 DeleteDC(hdcMem); 76 77 earth: 78 ReleaseDC(hwnd, hdc); 79 80 return hbm; 81 } 82 83 static VOID 84 IntMakeGhostImage(HBITMAP hbm) 85 { 86 BITMAP bm; 87 DWORD i, *pdw; 88 89 GetObject(hbm, sizeof(bm), &bm); 90 91 if (bm.bmBitsPixel != 32 || !bm.bmBits) 92 { 93 ERR("bm.bmBitsPixel == %d, bm.bmBits == %p\n", 94 bm.bmBitsPixel, bm.bmBits); 95 return; 96 } 97 98 pdw = bm.bmBits; 99 for (i = 0; i < bm.bmWidth * bm.bmHeight; ++i) 100 { 101 *pdw = *pdw | 0x00C0C0C0; // bitwise-OR with ARGB #C0C0C0 102 ++pdw; 103 } 104 } 105 106 /****************************************************************************/ 107 108 static GHOST_DATA * 109 Ghost_GetData(HWND hwnd) 110 { 111 return (GHOST_DATA *)GetWindowLongPtrW(hwnd, GWLP_USERDATA); 112 } 113 114 static HWND 115 Ghost_GetTarget(HWND hwnd) 116 { 117 GHOST_DATA *pData = Ghost_GetData(hwnd); 118 if (!pData) 119 return NULL; 120 return pData->hwndTarget; 121 } 122 123 static LPWSTR 124 Ghost_GetText(HWND hwndTarget, INT *pcchTextW, INT cchExtra) 125 { 126 LPWSTR pszTextW = NULL, pszTextNewW; 127 INT cchNonExtra, cchTextW = *pcchTextW; 128 129 pszTextNewW = HeapAlloc(GetProcessHeap(), 0, cchTextW * sizeof(WCHAR)); 130 for (;;) 131 { 132 if (!pszTextNewW) 133 { 134 ERR("HeapAlloc failed\n"); 135 if (pszTextW) 136 HeapFree(GetProcessHeap(), 0, pszTextW); 137 return NULL; 138 } 139 pszTextW = pszTextNewW; 140 141 cchNonExtra = cchTextW - cchExtra; 142 if (InternalGetWindowText(hwndTarget, pszTextW, 143 cchNonExtra) < cchNonExtra - 1) 144 { 145 break; 146 } 147 148 cchTextW *= 2; 149 pszTextNewW = HeapReAlloc(GetProcessHeap(), 0, pszTextW, 150 cchTextW * sizeof(WCHAR)); 151 } 152 153 *pcchTextW = cchTextW; 154 return pszTextW; 155 } 156 157 static BOOL 158 Ghost_OnCreate(HWND hwnd, CREATESTRUCTW *lpcs) 159 { 160 HBITMAP hbm32bpp; 161 HWND hwndTarget, hwndPrev; 162 GHOST_DATA *pData; 163 RECT rc; 164 DWORD style, exstyle; 165 WCHAR szTextW[320], szNotRespondingW[32]; 166 LPWSTR pszTextW; 167 INT cchTextW, cchExtraW, cchNonExtraW; 168 PWND pWnd = ValidateHwnd(hwnd); 169 if (pWnd) 170 { 171 if (!pWnd->fnid) 172 { 173 NtUserSetWindowFNID(hwnd, FNID_GHOST); 174 } 175 else if (pWnd->fnid != FNID_GHOST) 176 { 177 ERR("Wrong window class for Ghost! fnId 0x%x\n", pWnd->fnid); 178 return FALSE; 179 } 180 } 181 182 // get the target 183 hwndTarget = (HWND)lpcs->lpCreateParams; 184 if (!IsWindowVisible(hwndTarget) || // invisible? 185 (GetWindowLongPtrW(hwndTarget, GWL_STYLE) & WS_CHILD) || // child? 186 !IsHungAppWindow(hwndTarget)) // not hung? 187 { 188 return FALSE; 189 } 190 191 // check prop 192 if (GetPropW(hwndTarget, GHOST_PROP)) 193 return FALSE; 194 195 // set prop 196 SetPropW(hwndTarget, GHOST_PROP, hwnd); 197 198 // create user data 199 pData = HeapAlloc(GetProcessHeap(), 0, sizeof(GHOST_DATA)); 200 if (!pData) 201 { 202 ERR("HeapAlloc failed\n"); 203 return FALSE; 204 } 205 206 // get window image 207 GetWindowRect(hwndTarget, &rc); 208 hbm32bpp = IntGetWindowBitmap(hwndTarget, 209 rc.right - rc.left, 210 rc.bottom - rc.top); 211 if (!hbm32bpp) 212 { 213 ERR("hbm32bpp was NULL\n"); 214 HeapFree(GetProcessHeap(), 0, pData); 215 return FALSE; 216 } 217 // make a ghost image 218 IntMakeGhostImage(hbm32bpp); 219 220 // set user data 221 pData->hwndTarget = hwndTarget; 222 pData->hbm32bpp = hbm32bpp; 223 pData->bDestroyTarget = FALSE; 224 SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pData); 225 226 // get style 227 style = GetWindowLongPtrW(hwndTarget, GWL_STYLE); 228 exstyle = GetWindowLongPtrW(hwndTarget, GWL_EXSTYLE); 229 230 // get text 231 cchTextW = ARRAYSIZE(szTextW); 232 cchExtraW = ARRAYSIZE(szNotRespondingW); 233 cchNonExtraW = cchTextW - cchExtraW; 234 if (InternalGetWindowText(hwndTarget, szTextW, 235 cchNonExtraW) < cchNonExtraW - 1) 236 { 237 pszTextW = szTextW; 238 } 239 else 240 { 241 cchTextW *= 2; 242 pszTextW = Ghost_GetText(hwndTarget, &cchTextW, cchExtraW); 243 if (!pszTextW) 244 { 245 ERR("Ghost_GetText failed\n"); 246 DeleteObject(hbm32bpp); 247 HeapFree(GetProcessHeap(), 0, pData); 248 return FALSE; 249 } 250 } 251 252 // don't use scrollbars. 253 style &= ~(WS_HSCROLL | WS_VSCROLL | WS_VISIBLE); 254 255 // set style 256 SetWindowLongPtrW(hwnd, GWL_STYLE, style); 257 SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exstyle); 258 259 // set text with " (Not Responding)" 260 LoadStringW(User32Instance, IDS_NOT_RESPONDING, 261 szNotRespondingW, ARRAYSIZE(szNotRespondingW)); 262 StringCchCatW(pszTextW, cchTextW, szNotRespondingW); 263 SetWindowTextW(hwnd, pszTextW); 264 265 // free the text buffer 266 if (szTextW != pszTextW) 267 HeapFree(GetProcessHeap(), 0, pszTextW); 268 269 // get previous window of target 270 hwndPrev = GetWindow(hwndTarget, GW_HWNDPREV); 271 272 // hide target 273 ShowWindowAsync(hwndTarget, SW_HIDE); 274 275 // shrink the ghost to zero size and insert. 276 // this will avoid effects. 277 SetWindowPos(hwnd, hwndPrev, 0, 0, 0, 0, 278 SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | 279 SWP_DRAWFRAME); 280 281 // resume the position and size of ghost 282 MoveWindow(hwnd, rc.left, rc.top, 283 rc.right - rc.left, rc.bottom - rc.top, TRUE); 284 285 // make ghost visible 286 SetWindowLongPtrW(hwnd, GWL_STYLE, style | WS_VISIBLE); 287 288 // redraw 289 InvalidateRect(hwnd, NULL, TRUE); 290 291 // start timer 292 SetTimer(hwnd, GHOST_TIMER_ID, GHOST_INTERVAL, NULL); 293 294 return TRUE; 295 } 296 297 static void 298 Ghost_Unenchant(HWND hwnd, BOOL bDestroyTarget) 299 { 300 GHOST_DATA *pData = Ghost_GetData(hwnd); 301 if (!pData) 302 return; 303 304 pData->bDestroyTarget |= bDestroyTarget; 305 DestroyWindow(hwnd); 306 } 307 308 static void 309 Ghost_OnDraw(HWND hwnd, HDC hdc) 310 { 311 BITMAP bm; 312 HDC hdcMem; 313 GHOST_DATA *pData = Ghost_GetData(hwnd); 314 315 if (!pData || !GetObject(pData->hbm32bpp, sizeof(bm), &bm)) 316 return; 317 318 hdcMem = CreateCompatibleDC(hdc); 319 if (hdcMem) 320 { 321 HGDIOBJ hbmOld = SelectObject(hdcMem, pData->hbm32bpp); 322 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, 323 hdcMem, 0, 0, SRCCOPY | CAPTUREBLT); 324 SelectObject(hdcMem, hbmOld); 325 DeleteDC(hdcMem); 326 } 327 } 328 329 static void 330 Ghost_OnNCPaint(HWND hwnd, HRGN hrgn, BOOL bUnicode) 331 { 332 HDC hdc; 333 334 // do the default behaviour 335 if (bUnicode) 336 DefWindowProcW(hwnd, WM_NCPAINT, (WPARAM)hrgn, 0); 337 else 338 DefWindowProcA(hwnd, WM_NCPAINT, (WPARAM)hrgn, 0); 339 340 // draw the ghost image 341 hdc = GetWindowDC(hwnd); 342 if (hdc) 343 { 344 Ghost_OnDraw(hwnd, hdc); 345 ReleaseDC(hwnd, hdc); 346 } 347 } 348 349 static void 350 Ghost_OnPaint(HWND hwnd) 351 { 352 PAINTSTRUCT ps; 353 HDC hdc = BeginPaint(hwnd, &ps); 354 if (hdc) 355 { 356 // don't draw at here 357 EndPaint(hwnd, &ps); 358 } 359 } 360 361 static void 362 Ghost_OnMove(HWND hwnd, int x, int y) 363 { 364 RECT rc; 365 HWND hwndTarget = Ghost_GetTarget(hwnd); 366 367 GetWindowRect(hwnd, &rc); 368 369 // move the target 370 SetWindowPos(hwndTarget, NULL, rc.left, rc.top, 0, 0, 371 SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | 372 SWP_NOSENDCHANGING); 373 } 374 375 static void 376 Ghost_OnDestroy(HWND hwnd) 377 { 378 KillTimer(hwnd, GHOST_TIMER_ID); 379 } 380 381 static void 382 Ghost_DestroyTarget(GHOST_DATA *pData) 383 { 384 HWND hwndTarget = pData->hwndTarget; 385 DWORD pid; 386 HANDLE hProcess; 387 388 pData->hwndTarget = NULL; 389 GetWindowThreadProcessId(hwndTarget, &pid); 390 391 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); 392 if (hProcess) 393 { 394 TerminateProcess(hProcess, -1); 395 CloseHandle(hProcess); 396 } 397 398 DestroyWindow(hwndTarget); 399 } 400 401 static void 402 Ghost_OnNCDestroy(HWND hwnd) 403 { 404 // delete the user data 405 GHOST_DATA *pData = Ghost_GetData(hwnd); 406 if (pData) 407 { 408 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); 409 410 // delete image 411 DeleteObject(pData->hbm32bpp); 412 pData->hbm32bpp = NULL; 413 414 // remove prop 415 RemovePropW(pData->hwndTarget, GHOST_PROP); 416 417 // show target 418 ShowWindowAsync(pData->hwndTarget, SW_SHOWNOACTIVATE); 419 420 // destroy target if necessary 421 if (pData->bDestroyTarget) 422 { 423 Ghost_DestroyTarget(pData); 424 } 425 426 HeapFree(GetProcessHeap(), 0, pData); 427 } 428 429 NtUserSetWindowFNID(hwnd, FNID_DESTROY); 430 431 PostQuitMessage(0); 432 } 433 434 static void 435 Ghost_OnClose(HWND hwnd) 436 { 437 INT id; 438 WCHAR szAskTerminate[128]; 439 WCHAR szHungUpTitle[128]; 440 441 // stop timer 442 KillTimer(hwnd, GHOST_TIMER_ID); 443 444 LoadStringW(User32Instance, IDS_ASK_TERMINATE, 445 szAskTerminate, ARRAYSIZE(szAskTerminate)); 446 LoadStringW(User32Instance, IDS_HUNG_UP_TITLE, 447 szHungUpTitle, ARRAYSIZE(szHungUpTitle)); 448 449 id = MessageBoxW(hwnd, szAskTerminate, szHungUpTitle, 450 MB_ICONINFORMATION | MB_YESNO); 451 if (id == IDYES) 452 { 453 // destroy the target 454 Ghost_Unenchant(hwnd, TRUE); 455 return; 456 } 457 458 // restart timer 459 SetTimer(hwnd, GHOST_TIMER_ID, GHOST_INTERVAL, NULL); 460 } 461 462 static void 463 Ghost_OnTimer(HWND hwnd, UINT id) 464 { 465 HWND hwndTarget; 466 GHOST_DATA *pData = Ghost_GetData(hwnd); 467 468 if (id != GHOST_TIMER_ID || !pData) 469 return; 470 471 // stop the timer 472 KillTimer(hwnd, id); 473 474 hwndTarget = pData->hwndTarget; 475 if (!IsWindow(hwndTarget) || !IsHungAppWindow(hwndTarget)) 476 { 477 // resume if window is destroyed or responding 478 Ghost_Unenchant(hwnd, FALSE); 479 return; 480 } 481 482 // restart the timer 483 SetTimer(hwnd, GHOST_TIMER_ID, GHOST_INTERVAL, NULL); 484 } 485 486 static HICON 487 Ghost_GetIcon(HWND hwnd, INT fType) 488 { 489 GHOST_DATA *pData = Ghost_GetData(hwnd); 490 HICON hIcon = NULL; 491 492 if (!pData) 493 return NULL; 494 495 // same as the original icon 496 switch (fType) 497 { 498 case ICON_BIG: 499 { 500 hIcon = (HICON)GetClassLongPtrW(pData->hwndTarget, GCLP_HICON); 501 break; 502 } 503 504 case ICON_SMALL: 505 { 506 hIcon = (HICON)GetClassLongPtrW(pData->hwndTarget, GCLP_HICONSM); 507 break; 508 } 509 } 510 511 return hIcon; 512 } 513 514 LRESULT WINAPI 515 GhostWndProc_common(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 516 BOOL unicode) 517 { 518 switch (uMsg) 519 { 520 case WM_CREATE: 521 if (!Ghost_OnCreate(hwnd, (CREATESTRUCTW *)lParam)) 522 return -1; 523 break; 524 525 case WM_NCPAINT: 526 Ghost_OnNCPaint(hwnd, (HRGN)wParam, unicode); 527 return 0; 528 529 case WM_ERASEBKGND: 530 Ghost_OnDraw(hwnd, (HDC)wParam); 531 return TRUE; 532 533 case WM_PAINT: 534 Ghost_OnPaint(hwnd); 535 break; 536 537 case WM_MOVE: 538 Ghost_OnMove(hwnd, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam)); 539 break; 540 541 case WM_SIZE: 542 break; 543 544 case WM_SIZING: 545 return TRUE; 546 547 case WM_SYSCOMMAND: 548 switch ((UINT)wParam) 549 { 550 case SC_MAXIMIZE: 551 case SC_SIZE: 552 // sizing-related 553 return 0; 554 } 555 if (unicode) 556 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 557 else 558 return DefWindowProcA(hwnd, uMsg, wParam, lParam); 559 560 case WM_CLOSE: 561 Ghost_OnClose(hwnd); 562 break; 563 564 case WM_TIMER: 565 Ghost_OnTimer(hwnd, (UINT)wParam); 566 break; 567 568 case WM_NCMOUSEMOVE: 569 if (unicode) 570 DefWindowProcW(hwnd, uMsg, wParam, lParam); 571 else 572 DefWindowProcA(hwnd, uMsg, wParam, lParam); 573 SetCursor(LoadCursor(NULL, IDC_WAIT)); 574 return 0; 575 576 case WM_GETICON: 577 return (LRESULT)Ghost_GetIcon(hwnd, (INT)wParam); 578 579 case WM_COMMAND: 580 if (LOWORD(wParam) == 3333) 581 Ghost_Unenchant(hwnd, FALSE); 582 break; 583 584 case WM_DESTROY: 585 Ghost_OnDestroy(hwnd); 586 break; 587 588 case WM_NCDESTROY: 589 Ghost_OnNCDestroy(hwnd); 590 break; 591 592 default: 593 { 594 if (unicode) 595 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 596 else 597 return DefWindowProcA(hwnd, uMsg, wParam, lParam); 598 } 599 } 600 return 0; 601 } 602 603 LRESULT CALLBACK 604 GhostWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 605 { 606 return GhostWndProc_common(hwnd, uMsg, wParam, lParam, FALSE); 607 } 608 609 LRESULT CALLBACK 610 GhostWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 611 { 612 return GhostWndProc_common(hwnd, uMsg, wParam, lParam, TRUE); 613 } 614