VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxSeamless.cpp@ 95961

Last change on this file since 95961 was 95961, checked in by vboxsync, 2 years ago

Additions/VBoxTray: Got rid of VBoxDisplay.h (renamed to VBoxTrayInternal.h, more stuff added later), as I also tripped over this several times in the past already, log include fixes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 18.6 KB
Line 
1/* $Id: VBoxSeamless.cpp 95961 2022-08-01 14:08:20Z vboxsync $ */
2/** @file
3 * VBoxSeamless - Seamless windows
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/assert.h>
23#include <iprt/ldr.h>
24#include <iprt/log.h>
25#include <iprt/mem.h>
26#include <iprt/system.h>
27
28#define _WIN32_WINNT 0x0500
29#include <iprt/win/windows.h>
30
31#include <VBoxDisplay.h> /** @todo r=bird: Presumably the ../include/VBoxDisplay.h file rather than ./VBoxDisplay.h. WTF??? */
32#include <VBoxHook.h> /* from ../include/ */
33
34#include "VBoxTray.h"
35#include "VBoxHelpers.h"
36#include "VBoxSeamless.h"
37
38
39
40/*********************************************************************************************************************************
41* Structures and Typedefs *
42*********************************************************************************************************************************/
43typedef struct _VBOXSEAMLESSCONTEXT
44{
45 const VBOXSERVICEENV *pEnv;
46
47 RTLDRMOD hModHook;
48
49 BOOL (* pfnVBoxHookInstallWindowTracker)(HMODULE hDll);
50 BOOL (* pfnVBoxHookRemoveWindowTracker)();
51
52 PVBOXDISPIFESCAPE lpEscapeData;
53} VBOXSEAMLESSCONTEXT, *PVBOXSEAMLESSCONTEXT;
54
55typedef struct
56{
57 HDC hdc;
58 HRGN hrgn;
59} VBOX_ENUM_PARAM, *PVBOX_ENUM_PARAM;
60
61
62/*********************************************************************************************************************************
63* Global Variables *
64*********************************************************************************************************************************/
65static VBOXSEAMLESSCONTEXT g_Ctx = { 0 };
66
67
68/*********************************************************************************************************************************
69* Internal Functions *
70*********************************************************************************************************************************/
71void VBoxLogString(HANDLE hDriver, char *pszStr);
72
73
74
75static DECLCALLBACK(int) VBoxSeamlessInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
76{
77 LogFlowFuncEnter();
78
79 PVBOXSEAMLESSCONTEXT pCtx = &g_Ctx; /* Only one instance at the moment. */
80 AssertPtr(pCtx);
81
82 pCtx->pEnv = pEnv;
83 pCtx->hModHook = NIL_RTLDRMOD;
84
85 int rc;
86
87 /* We have to jump out here when using NT4, otherwise it complains about
88 a missing API function "UnhookWinEvent" used by the dynamically loaded VBoxHook.dll below */
89 uint64_t const uNtVersion = RTSystemGetNtVersion();
90 if (uNtVersion < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0)) /* Windows NT 4.0 or older */
91 {
92 LogRel(("Seamless: Windows NT 4.0 or older not supported!\n"));
93 rc = VERR_NOT_SUPPORTED;
94 }
95 else
96 {
97 /* Will fail if SetWinEventHook is not present (version < NT4 SP6 apparently) */
98 rc = RTLdrLoadAppPriv(VBOXHOOK_DLL_NAME, &pCtx->hModHook);
99 if (RT_SUCCESS(rc))
100 {
101 *(PFNRT *)&pCtx->pfnVBoxHookInstallWindowTracker = RTLdrGetFunction(pCtx->hModHook, "VBoxHookInstallWindowTracker");
102 *(PFNRT *)&pCtx->pfnVBoxHookRemoveWindowTracker = RTLdrGetFunction(pCtx->hModHook, "VBoxHookRemoveWindowTracker");
103
104 if ( pCtx->pfnVBoxHookInstallWindowTracker
105 && pCtx->pfnVBoxHookRemoveWindowTracker)
106 {
107 VBoxSeamlessSetSupported(TRUE);
108
109 *ppInstance = pCtx;
110 }
111 else
112 {
113 LogRel(("Seamless: Not supported, skipping\n"));
114 rc = VERR_NOT_SUPPORTED;
115 }
116 }
117 else
118 {
119 LogRel(("Seamless: Could not load %s (%Rrc), skipping\n", VBOXHOOK_DLL_NAME, rc));
120 rc = VERR_NOT_SUPPORTED;
121 }
122 }
123
124 LogFlowFuncLeaveRC(rc);
125 return rc;
126}
127
128static DECLCALLBACK(void) VBoxSeamlessDestroy(void *pInstance)
129{
130 LogFlowFuncEnter();
131
132 if (!pInstance)
133 return;
134
135 PVBOXSEAMLESSCONTEXT pCtx = (PVBOXSEAMLESSCONTEXT)pInstance;
136 AssertPtr(pCtx);
137
138 VBoxSeamlessSetSupported(FALSE);
139
140 /* Inform the host that we no longer support the seamless window mode. */
141 if (pCtx->pfnVBoxHookRemoveWindowTracker)
142 pCtx->pfnVBoxHookRemoveWindowTracker();
143 if (pCtx->hModHook != NIL_RTLDRMOD)
144 {
145 RTLdrClose(pCtx->hModHook);
146 pCtx->hModHook = NIL_RTLDRMOD;
147 }
148 return;
149}
150
151static void VBoxSeamlessInstallHook(void)
152{
153 PVBOXSEAMLESSCONTEXT pCtx = &g_Ctx; /** @todo r=andy Use instance data via service lookup (add void *pInstance). */
154 AssertPtr(pCtx);
155
156 if (pCtx->pfnVBoxHookInstallWindowTracker)
157 {
158 /* Check current visible region state */
159 VBoxSeamlessCheckWindows(true);
160
161 HMODULE hMod = (HMODULE)RTLdrGetNativeHandle(pCtx->hModHook);
162 Assert(hMod != (HMODULE)~(uintptr_t)0);
163 pCtx->pfnVBoxHookInstallWindowTracker(hMod);
164 }
165}
166
167static void VBoxSeamlessRemoveHook(void)
168{
169 PVBOXSEAMLESSCONTEXT pCtx = &g_Ctx; /** @todo r=andy Use instance data via service lookup (add void *pInstance). */
170 AssertPtr(pCtx);
171
172 if (pCtx->pfnVBoxHookRemoveWindowTracker)
173 pCtx->pfnVBoxHookRemoveWindowTracker();
174
175 if (pCtx->lpEscapeData)
176 {
177 RTMemFree(pCtx->lpEscapeData);
178 pCtx->lpEscapeData = NULL;
179 }
180}
181
182extern HANDLE g_hSeamlessKmNotifyEvent;
183
184static VBOXDISPIF_SEAMLESS gVBoxDispIfSeamless; /** @todo r=andy Move this into VBOXSEAMLESSCONTEXT? */
185
186
187void VBoxSeamlessEnable(void)
188{
189 PVBOXSEAMLESSCONTEXT pCtx = &g_Ctx; /** @todo r=andy Use instance data via service lookup (add void *pInstance). */
190 AssertPtr(pCtx);
191
192 Assert(g_hSeamlessKmNotifyEvent);
193
194 VBoxDispIfSeamlessCreate(&pCtx->pEnv->dispIf, &gVBoxDispIfSeamless, g_hSeamlessKmNotifyEvent);
195
196 VBoxSeamlessInstallHook();
197}
198
199void VBoxSeamlessDisable(void)
200{
201 PVBOXSEAMLESSCONTEXT pCtx = &g_Ctx; /** @todo r=andy Use instance data via service lookup (add void *pInstance). */
202 AssertPtr(pCtx);
203 NOREF(pCtx);
204
205 VBoxSeamlessRemoveHook();
206
207 VBoxDispIfSeamlessTerm(&gVBoxDispIfSeamless);
208}
209
210BOOL CALLBACK VBoxEnumFunc(HWND hwnd, LPARAM lParam) RT_NOTHROW_DEF
211{
212 PVBOX_ENUM_PARAM lpParam = (PVBOX_ENUM_PARAM)lParam;
213 DWORD dwStyle, dwExStyle;
214 RECT rectWindow, rectVisible;
215
216 dwStyle = GetWindowLong(hwnd, GWL_STYLE);
217 dwExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
218
219 if ( !(dwStyle & WS_VISIBLE) || (dwStyle & WS_CHILD))
220 return TRUE;
221
222 LogFlow(("VBoxTray: VBoxEnumFunc %x\n", hwnd));
223 /* Only visible windows that are present on the desktop are interesting here */
224 if (!GetWindowRect(hwnd, &rectWindow))
225 {
226 return TRUE;
227 }
228
229 char szWindowText[256];
230 char szWindowClass[256];
231 HWND hStart = NULL;
232
233 szWindowText[0] = 0;
234 szWindowClass[0] = 0;
235
236 GetWindowText(hwnd, szWindowText, sizeof(szWindowText));
237 GetClassName(hwnd, szWindowClass, sizeof(szWindowClass));
238
239 uint64_t const uNtVersion = RTSystemGetNtVersion();
240 if (uNtVersion >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
241 {
242 hStart = ::FindWindowEx(GetDesktopWindow(), NULL, "Button", "Start");
243
244 if ( hwnd == hStart && !strcmp(szWindowText, "Start") )
245 {
246 /* for vista and above. To solve the issue of small bar above
247 * the Start button when mouse is hovered over the start button in seamless mode.
248 * Difference of 7 is observed in Win 7 platform between the dimensions of rectangle with Start title and its shadow.
249 */
250 rectWindow.top += 7;
251 rectWindow.bottom -=7;
252 }
253 }
254
255 rectVisible = rectWindow;
256
257 /* Filter out Windows XP shadow windows */
258 /** @todo still shows inside the guest */
259 if ( szWindowText[0] == 0 &&
260 (dwStyle == (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS)
261 && dwExStyle == (WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST))
262 || (dwStyle == (WS_POPUP | WS_VISIBLE | WS_DISABLED | WS_CLIPSIBLINGS | WS_CLIPCHILDREN)
263 && dwExStyle == (WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE))
264 || (dwStyle == (WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN)
265 && dwExStyle == (WS_EX_TOOLWINDOW)) )
266 {
267 Log(("VBoxTray: Filter out shadow window style=%x exstyle=%x\n", dwStyle, dwExStyle));
268 Log(("VBoxTray: Enum hwnd=%x rect (%d,%d) (%d,%d) (filtered)\n", hwnd, rectWindow.left, rectWindow.top, rectWindow.right, rectWindow.bottom));
269 Log(("VBoxTray: title=%s style=%x exStyle=%x\n", szWindowText, dwStyle, dwExStyle));
270 return TRUE;
271 }
272
273 /** Such a windows covers the whole screen making desktop background*/
274 if (strcmp(szWindowText, "Program Manager") && strcmp(szWindowClass, "ApplicationFrameWindow"))
275 {
276 Log(("VBoxTray: Enum hwnd=%x rect (%d,%d)-(%d,%d) [%d x %d](applying)\n", hwnd,
277 rectWindow.left, rectWindow.top, rectWindow.right, rectWindow.bottom,
278 rectWindow.left - rectWindow.right, rectWindow.bottom - rectWindow.top));
279 Log(("VBoxTray: title=%s style=%x exStyle=%x\n", szWindowText, dwStyle, dwExStyle));
280
281 HRGN hrgn = CreateRectRgn(0, 0, 0, 0);
282
283 int ret = GetWindowRgn(hwnd, hrgn);
284
285 if (ret == ERROR)
286 {
287 Log(("VBoxTray: GetWindowRgn failed with rc=%d, adding antire rect\n", GetLastError()));
288 SetRectRgn(hrgn, rectVisible.left, rectVisible.top, rectVisible.right, rectVisible.bottom);
289 }
290 else
291 {
292 /* this region is relative to the window origin instead of the desktop origin */
293 OffsetRgn(hrgn, rectWindow.left, rectWindow.top);
294 }
295
296 if (lpParam->hrgn)
297 {
298 /* create a union of the current visible region and the visible rectangle of this window. */
299 CombineRgn(lpParam->hrgn, lpParam->hrgn, hrgn, RGN_OR);
300 DeleteObject(hrgn);
301 }
302 else
303 lpParam->hrgn = hrgn;
304 }
305 else
306 {
307 Log(("VBoxTray: Enum hwnd=%x rect (%d,%d)-(%d,%d) [%d x %d](ignored)\n", hwnd,
308 rectWindow.left, rectWindow.top, rectWindow.right, rectWindow.bottom,
309 rectWindow.left - rectWindow.right, rectWindow.bottom - rectWindow.top));
310 Log(("VBoxTray: title=%s style=%x exStyle=%x\n", szWindowText, dwStyle, dwExStyle));
311 }
312
313 return TRUE; /* continue enumeration */
314}
315
316void VBoxSeamlessCheckWindows(bool fForce)
317{
318 PVBOXSEAMLESSCONTEXT pCtx = &g_Ctx; /** @todo r=andy Use instance data via service lookup (add void *pInstance). */
319 AssertPtr(pCtx);
320
321 if (!VBoxDispIfSeamlesIsValid(&gVBoxDispIfSeamless))
322 return;
323
324 VBOX_ENUM_PARAM param;
325
326 param.hdc = GetDC(HWND_DESKTOP);
327 param.hrgn = 0;
328
329 EnumWindows(VBoxEnumFunc, (LPARAM)&param);
330
331 if (param.hrgn)
332 {
333 DWORD cbSize = GetRegionData(param.hrgn, 0, NULL);
334 if (cbSize)
335 {
336 PVBOXDISPIFESCAPE lpEscapeData = (PVBOXDISPIFESCAPE)RTMemAllocZ(VBOXDISPIFESCAPE_SIZE(cbSize));
337 if (lpEscapeData)
338 {
339 lpEscapeData->escapeCode = VBOXESC_SETVISIBLEREGION;
340 LPRGNDATA lpRgnData = VBOXDISPIFESCAPE_DATA(lpEscapeData, RGNDATA);
341
342 cbSize = GetRegionData(param.hrgn, cbSize, lpRgnData);
343 if (cbSize)
344 {
345#ifdef LOG_ENABLED
346 RECT *paRects = (RECT *)&lpRgnData->Buffer[0];
347 Log(("VBoxTray: New visible region: \n"));
348 for (DWORD i = 0; i < lpRgnData->rdh.nCount; i++)
349 Log(("VBoxTray: visible rect (%d,%d)(%d,%d)\n",
350 paRects[i].left, paRects[i].top, paRects[i].right, paRects[i].bottom));
351#endif
352
353 LPRGNDATA lpCtxRgnData = VBOXDISPIFESCAPE_DATA(pCtx->lpEscapeData, RGNDATA);
354
355 if ( fForce
356 || !pCtx->lpEscapeData
357 || (lpCtxRgnData->rdh.dwSize + lpCtxRgnData->rdh.nRgnSize != cbSize)
358 || memcmp(lpCtxRgnData, lpRgnData, cbSize))
359 {
360 /* send to display driver */
361 VBoxDispIfSeamlessSubmit(&gVBoxDispIfSeamless, lpEscapeData, cbSize);
362
363 if (pCtx->lpEscapeData)
364 RTMemFree(pCtx->lpEscapeData);
365 pCtx->lpEscapeData = lpEscapeData;
366 }
367 else
368 Log(("VBoxTray: Visible rectangles haven't changed; ignore\n"));
369 }
370 if (lpEscapeData != pCtx->lpEscapeData)
371 RTMemFree(lpEscapeData);
372 }
373 }
374
375 DeleteObject(param.hrgn);
376 }
377
378 ReleaseDC(HWND_DESKTOP, param.hdc);
379}
380
381/**
382 * Thread function to wait for and process seamless mode change
383 * requests
384 */
385static DECLCALLBACK(int) VBoxSeamlessWorker(void *pvInstance, bool volatile *pfShutdown)
386{
387 AssertPtrReturn(pvInstance, VERR_INVALID_POINTER);
388 LogFlowFunc(("pvInstance=%p\n", pvInstance));
389
390 /*
391 * Tell the control thread that it can continue spawning services.
392 */
393 RTThreadUserSignal(RTThreadSelf());
394
395 int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 0 /*fNot*/);
396 if (RT_FAILURE(rc))
397 {
398 LogRel(("Seamless: VbglR3CtlFilterMask(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST,0) failed with %Rrc, exiting ...\n", rc));
399 return rc;
400 }
401
402 BOOL fWasScreenSaverActive = FALSE;
403 for (;;)
404 {
405 /*
406 * Wait for a seamless change event, check for shutdown both before and after.
407 */
408 if (*pfShutdown)
409 {
410 rc = VINF_SUCCESS;
411 break;
412 }
413
414 /** @todo r=andy We do duplicate code here (see VbglR3SeamlessWaitEvent()). */
415 uint32_t fEvent = 0;
416 rc = VbglR3WaitEvent(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 5000 /*ms*/, &fEvent);
417
418 if (*pfShutdown)
419 {
420 rc = VINF_SUCCESS;
421 break;
422 }
423
424 if (RT_SUCCESS(rc))
425 {
426 /* did we get the right event? */
427 if (fEvent & VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST)
428 {
429 /*
430 * We got at least one event. Read the requested resolution
431 * and try to set it until success. New events will not be seen
432 * but a new resolution will be read in this poll loop.
433 */
434 for (;;)
435 {
436 /* get the seamless change request */
437 VMMDevSeamlessMode enmMode = (VMMDevSeamlessMode)-1;
438 rc = VbglR3SeamlessGetLastEvent(&enmMode);
439 if (RT_SUCCESS(rc))
440 {
441 LogFlowFunc(("Mode changed to %d\n", enmMode));
442
443 BOOL fRet;
444 switch (enmMode)
445 {
446 case VMMDev_Seamless_Disabled:
447 if (fWasScreenSaverActive)
448 {
449 LogRel(("Seamless: Re-enabling the screensaver\n"));
450 fRet = SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, NULL, 0);
451 if (!fRet)
452 LogRel(("Seamless: SystemParametersInfo SPI_SETSCREENSAVEACTIVE failed with %ld\n", GetLastError()));
453 }
454 PostMessage(g_hwndToolWindow, WM_VBOX_SEAMLESS_DISABLE, 0, 0);
455 break;
456
457 case VMMDev_Seamless_Visible_Region:
458 fRet = SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &fWasScreenSaverActive, 0);
459 if (!fRet)
460 LogRel(("Seamless: SystemParametersInfo SPI_GETSCREENSAVEACTIVE failed with %ld\n", GetLastError()));
461
462 if (fWasScreenSaverActive)
463 LogRel(("Seamless: Disabling the screensaver\n"));
464
465 fRet = SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);
466 if (!fRet)
467 LogRel(("Seamless: SystemParametersInfo SPI_SETSCREENSAVEACTIVE failed with %ld\n", GetLastError()));
468 PostMessage(g_hwndToolWindow, WM_VBOX_SEAMLESS_ENABLE, 0, 0);
469 break;
470
471 case VMMDev_Seamless_Host_Window:
472 break;
473
474 default:
475 AssertFailed();
476 break;
477 }
478 break;
479 }
480
481 LogRel(("Seamless: VbglR3SeamlessGetLastEvent() failed with %Rrc\n", rc));
482
483 if (*pfShutdown)
484 break;
485
486 /* sleep a bit to not eat too much CPU while retrying */
487 RTThreadSleep(10);
488 }
489 }
490 }
491 /* sleep a bit to not eat too much CPU in case the above call always fails */
492 else if (rc != VERR_TIMEOUT)
493 RTThreadSleep(10);
494 }
495
496 int rc2 = VbglR3CtlFilterMask(0 /*fOk*/, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST);
497 if (RT_FAILURE(rc2))
498 LogRel(("Seamless: VbglR3CtlFilterMask(0, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST) failed with %Rrc\n", rc));
499
500 LogFlowFuncLeaveRC(rc);
501 return rc;
502}
503
504/**
505 * The service description.
506 */
507VBOXSERVICEDESC g_SvcDescSeamless =
508{
509 /* pszName. */
510 "seamless",
511 /* pszDescription. */
512 "Seamless Windows",
513 /* methods */
514 VBoxSeamlessInit,
515 VBoxSeamlessWorker,
516 NULL /* pfnStop */,
517 VBoxSeamlessDestroy
518};
519
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette