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