xref: /reactos/win32ss/user/user32/controls/ghost.c (revision 2ea03b5b)
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
IntCreate32BppBitmap(INT cx,INT cy)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
IntGetWindowBitmap(HWND hwnd,INT cx,INT cy)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
IntMakeGhostImage(HBITMAP hbm)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 *
Ghost_GetData(HWND hwnd)109 Ghost_GetData(HWND hwnd)
110 {
111     return (GHOST_DATA *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
112 }
113 
114 static HWND
Ghost_GetTarget(HWND 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
Ghost_GetText(HWND hwndTarget,INT * pcchTextW,INT cchExtra)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
Ghost_OnCreate(HWND hwnd,CREATESTRUCTW * lpcs)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
Ghost_Unenchant(HWND hwnd,BOOL bDestroyTarget)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
Ghost_OnDraw(HWND hwnd,HDC hdc)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
Ghost_OnNCPaint(HWND hwnd,HRGN hrgn,BOOL bUnicode)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
Ghost_OnPaint(HWND hwnd)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
Ghost_OnMove(HWND hwnd,int x,int y)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
Ghost_OnDestroy(HWND hwnd)376 Ghost_OnDestroy(HWND hwnd)
377 {
378     KillTimer(hwnd, GHOST_TIMER_ID);
379 }
380 
381 static void
Ghost_DestroyTarget(GHOST_DATA * pData)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
Ghost_OnNCDestroy(HWND hwnd)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
Ghost_OnClose(HWND hwnd)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
Ghost_OnTimer(HWND hwnd,UINT id)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
Ghost_GetIcon(HWND hwnd,INT fType)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
GhostWndProc_common(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL unicode)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
GhostWndProcA(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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
GhostWndProcW(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)610 GhostWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
611 {
612     return GhostWndProc_common(hwnd, uMsg, wParam, lParam, TRUE);
613 }
614