[SHELL32] SHChangeNotify: Add drive, remove drive (#6782)
[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 #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 behaivour
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 }