VirtualBox

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

Last change on this file since 79506 was 79183, checked in by vboxsync, 5 years ago

Forward ported r131352 (6.0: Additions/VBoxTray: Don't make VBoxTray fail completely if seamless mode is not available for whatever reason (optional)).

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