VirtualBox

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

Last change on this file since 106945 was 106466, checked in by vboxsync, 7 weeks ago

Additions/VBoxTray: More cleanup (renaming). ​bugref:10763

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