VirtualBox

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

Last change on this file since 95966 was 95966, checked in by vboxsync, 22 months ago

Additions/VBoxTray: More cleanup: Moved the capabilities and console API into own modules, also the desktop tracking stuff. All lacks documentation, hard to find out what this all does, and why.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use