VBoxTray.cpp revision cdd495aa1ca91a3325fbc66d67c120fd14f1dfff
/* $Id$ */
/** @file
* VBoxTray - Guest Additions Tray Application
*/
/*
* Copyright (C) 2006-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "VBoxTray.h"
#include "VBoxTrayMsg.h"
#include "VBoxHelpers.h"
#include "VBoxSeamless.h"
#include "VBoxClipboard.h"
#include "VBoxDisplay.h"
#include "VBoxRestore.h"
#include "VBoxVRDP.h"
#include "VBoxHostVersion.h"
#include "VBoxSharedFolders.h"
#include "VBoxIPC.h"
#include "VBoxLA.h"
#include "VBoxMMR.h"
#include <VBoxHook.h>
#include "resource.h"
#include <malloc.h>
#include <VBoxGuestInternal.h>
#include <sddl.h>
#include <iprt/buildconfig.h>
/* Default desktop state tracking */
#include <Wtsapi32.h>
#ifdef DEBUG_misha
#define WARN(_m) do { \
Assert(0); \
Log(_m); \
} while (0)
#else
#define WARN(_m) do { \
Log(_m); \
} while (0)
#endif
/*
* St (session [state] tracking) functionality API
*
* !!!NOTE: this API is NOT thread-safe!!!
* it is supposed to be called & used from within the window message handler thread
* of the window passed to vboxStInit */
static int vboxStInit(HWND hWnd);
static void vboxStTerm();
/* @returns true on "IsActiveConsole" state change */
static BOOL vboxStHandleEvent(WPARAM EventID, LPARAM SessionID);
static BOOL vboxStIsActiveConsole();
/*
* Dt (desktop [state] tracking) functionality API
*
* !!!NOTE: this API is NOT thread-safe!!!
* */
static int vboxDtInit();
static void vboxDtTerm();
/* @returns true on "IsInputDesktop" state change */
static BOOL vboxDtHandleEvent();
/* @returns true iff the application (VBoxTray) desktop is input */
static BOOL vboxDtIsInputDesktop();
static HANDLE vboxDtGetNotifyEvent();
/* caps API */
#define VBOXCAPS_ENTRY_IDX_SEAMLESS 0
#define VBOXCAPS_ENTRY_IDX_GRAPHICS 1
#define VBOXCAPS_ENTRY_IDX_COUNT 2
typedef enum VBOXCAPS_ENTRY_FUNCSTATE
{
/* the cap is unsupported */
VBOXCAPS_ENTRY_FUNCSTATE_UNSUPPORTED = 0,
/* the cap is supported */
VBOXCAPS_ENTRY_FUNCSTATE_SUPPORTED,
/* the cap functionality is started, it can be disabled however if its AcState is not ACQUIRED */
VBOXCAPS_ENTRY_FUNCSTATE_STARTED,
} VBOXCAPS_ENTRY_FUNCSTATE;
static void VBoxCapsEntryFuncStateSet(uint32_t iCup, VBOXCAPS_ENTRY_FUNCSTATE enmFuncState);
static int VBoxCapsInit();
static int VBoxCapsReleaseAll();
static void VBoxCapsTerm();
static BOOL VBoxCapsEntryIsAcquired(uint32_t iCap);
static BOOL VBoxCapsEntryIsEnabled(uint32_t iCap);
static BOOL VBoxCapsCheckTimer(WPARAM wParam);
static int VBoxCapsEntryRelease(uint32_t iCap);
static int VBoxCapsEntryAcquire(uint32_t iCap);
static int VBoxCapsAcquireAllSupported();
/* console-related caps API */
static BOOL VBoxConsoleIsAllowed();
static void VBoxConsoleEnable(BOOL fEnable);
static void VBoxConsoleCapSetSupported(uint32_t iCap, BOOL fSupported);
static void VBoxGrapicsSetSupported(BOOL fSupported);
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int vboxTrayCreateTrayIcon(void);
static LRESULT CALLBACK vboxToolWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
/* Global message handler prototypes. */
static int vboxTrayGlMsgTaskbarCreated(WPARAM lParam, LPARAM wParam);
/*static int vboxTrayGlMsgShowBalloonMsg(WPARAM lParam, LPARAM wParam);*/
/*******************************************************************************
* Global Variables *
*******************************************************************************/
HANDLE ghVBoxDriver;
HANDLE ghStopSem;
HANDLE ghSeamlessWtNotifyEvent = 0;
SERVICE_STATUS gVBoxServiceStatus;
SERVICE_STATUS_HANDLE gVBoxServiceStatusHandle;
HINSTANCE ghInstance;
HWND ghwndToolWindow;
NOTIFYICONDATA gNotifyIconData;
DWORD gMajorVersion;
/* The service table. */
static VBOXSERVICEINFO vboxServiceTable[] =
{
{
"Display",
VBoxDisplayInit,
VBoxDisplayThread,
VBoxDisplayDestroy
},
{
"Shared Clipboard",
VBoxClipboardInit,
VBoxClipboardThread,
VBoxClipboardDestroy
},
{
"Seamless Windows",
VBoxSeamlessInit,
VBoxSeamlessThread,
VBoxSeamlessDestroy
},
#ifdef VBOX_WITH_VRDP_SESSION_HANDLING
{
"Restore",
VBoxRestoreInit,
VBoxRestoreThread,
VBoxRestoreDestroy
},
#endif
{
"VRDP",
VBoxVRDPInit,
VBoxVRDPThread,
VBoxVRDPDestroy
},
{
"IPC",
VBoxIPCInit,
VBoxIPCThread,
VBoxIPCDestroy
},
{
"Location Awareness",
VBoxLAInit,
VBoxLAThread,
VBoxLADestroy
},
#ifdef VBOX_WITH_MMR
{
"Multimedia Redirection",
VBoxMMRInit,
VBoxMMRThread,
VBoxMMRDestroy
},
#endif
{
NULL
}
};
/* The global message table. */
static VBOXGLOBALMESSAGE s_vboxGlobalMessageTable[] =
{
/* Windows specific stuff. */
{
"TaskbarCreated",
vboxTrayGlMsgTaskbarCreated
},
/* VBoxTray specific stuff. */
/** @todo Add new messages here! */
{
NULL
}
};
/**
* Gets called whenever the Windows main taskbar
* get (re-)created. Nice to install our tray icon.
*
* @return IPRT status code.
* @param wParam
* @param lParam
*/
static int vboxTrayGlMsgTaskbarCreated(WPARAM wParam, LPARAM lParam)
{
return vboxTrayCreateTrayIcon();
}
static int vboxTrayCreateTrayIcon(void)
{
HICON hIcon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_VIRTUALBOX));
if (hIcon == NULL)
{
DWORD dwErr = GetLastError();
Log(("VBoxTray: Could not load tray icon, error %08X\n", dwErr));
return RTErrConvertFromWin32(dwErr);
}
/* Prepare the system tray icon. */
RT_ZERO(gNotifyIconData);
gNotifyIconData.cbSize = NOTIFYICONDATA_V1_SIZE; // sizeof(NOTIFYICONDATA);
gNotifyIconData.hWnd = ghwndToolWindow;
gNotifyIconData.uID = ID_TRAYICON;
gNotifyIconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
gNotifyIconData.uCallbackMessage = WM_VBOXTRAY_TRAY_ICON;
gNotifyIconData.hIcon = hIcon;
sprintf(gNotifyIconData.szTip, "%s Guest Additions %d.%d.%dr%d",
VBOX_PRODUCT, VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR, VBOX_VERSION_BUILD, VBOX_SVN_REV);
int rc = VINF_SUCCESS;
if (!Shell_NotifyIcon(NIM_ADD, &gNotifyIconData))
{
DWORD dwErr = GetLastError();
Log(("VBoxTray: Could not create tray icon, error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
RT_ZERO(gNotifyIconData);
}
if (hIcon)
DestroyIcon(hIcon);
return rc;
}
static void vboxTrayRemoveTrayIcon()
{
if (gNotifyIconData.cbSize > 0)
{
/* Remove the system tray icon and refresh system tray. */
Shell_NotifyIcon(NIM_DELETE, &gNotifyIconData);
HWND hTrayWnd = FindWindow("Shell_TrayWnd", NULL); /* We assume we only have one tray atm. */
if (hTrayWnd)
{
HWND hTrayNotifyWnd = FindWindowEx(hTrayWnd, 0, "TrayNotifyWnd", NULL);
if (hTrayNotifyWnd)
SendMessage(hTrayNotifyWnd, WM_PAINT, 0, NULL);
}
RT_ZERO(gNotifyIconData);
}
}
static int vboxTrayStartServices(VBOXSERVICEENV *pEnv, VBOXSERVICEINFO *pTable)
{
Log(("VBoxTray: Starting services ...\n"));
pEnv->hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!pEnv->hStopEvent)
{
/* Could not create event. */
return VERR_NOT_SUPPORTED;
}
while (pTable->pszName)
{
Log(("VBoxTray: Starting %s ...\n", pTable->pszName));
int rc = VINF_SUCCESS;
bool fStartThread = false;
pTable->hThread = (HANDLE)0;
pTable->pInstance = NULL;
pTable->fStarted = false;
if (pTable->pfnInit)
rc = pTable->pfnInit (pEnv, &pTable->pInstance, &fStartThread);
if (RT_FAILURE(rc))
{
Log(("VBoxTray: Failed to initialize rc = %Rrc\n", rc));
}
else
{
if (pTable->pfnThread && fStartThread)
{
unsigned threadid;
pTable->hThread = (HANDLE)_beginthreadex(NULL, /* security */
0, /* stacksize */
pTable->pfnThread,
pTable->pInstance,
0, /* initflag */
&threadid);
if (pTable->hThread == (HANDLE)(0))
rc = VERR_NOT_SUPPORTED;
}
if (RT_SUCCESS(rc))
pTable->fStarted = true;
else
{
Log(("VBoxTray: Failed to start the thread\n"));
if (pTable->pfnDestroy)
pTable->pfnDestroy(pEnv, pTable->pInstance);
}
}
/* Advance to next table element. */
pTable++;
}
return VINF_SUCCESS;
}
static void vboxTrayStopServices(VBOXSERVICEENV *pEnv, VBOXSERVICEINFO *pTable)
{
if (!pEnv->hStopEvent)
return;
/* Signal to all threads. */
SetEvent(pEnv->hStopEvent);
while (pTable->pszName)
{
if (pTable->fStarted)
{
if (pTable->pfnThread)
{
/* There is a thread, wait for termination. */
WaitForSingleObject(pTable->hThread, INFINITE);
CloseHandle(pTable->hThread);
pTable->hThread = 0;
}
if (pTable->pfnDestroy)
pTable->pfnDestroy (pEnv, pTable->pInstance);
pTable->fStarted = false;
}
/* Advance to next table element. */
pTable++;
}
CloseHandle(pEnv->hStopEvent);
}
static int vboxTrayRegisterGlobalMessages(PVBOXGLOBALMESSAGE pTable)
{
int rc = VINF_SUCCESS;
if (pTable == NULL) /* No table to register? Skip. */
return rc;
while ( pTable->pszName
&& RT_SUCCESS(rc))
{
/* Register global accessible window messages. */
pTable->uMsgID = RegisterWindowMessage(TEXT(pTable->pszName));
if (!pTable->uMsgID)
{
DWORD dwErr = GetLastError();
Log(("VBoxTray: Registering global message \"%s\" failed, error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
/* Advance to next table element. */
pTable++;
}
return rc;
}
static bool vboxTrayHandleGlobalMessages(PVBOXGLOBALMESSAGE pTable, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
if (pTable == NULL)
return false;
while (pTable && pTable->pszName)
{
if (pTable->uMsgID == uMsg)
{
if (pTable->pfnHandler)
pTable->pfnHandler(wParam, lParam);
return true;
}
/* Advance to next table element. */
pTable++;
}
return false;
}
static int vboxTrayOpenBaseDriver(void)
{
/* Open VBox guest driver. */
DWORD dwErr = ERROR_SUCCESS;
ghVBoxDriver = CreateFile(VBOXGUEST_DEVICE_NAME,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (ghVBoxDriver == INVALID_HANDLE_VALUE)
{
dwErr = GetLastError();
LogRel(("VBoxTray: Could not open VirtualBox Guest Additions driver! Please install / start it first! Error = %08X\n", dwErr));
}
return RTErrConvertFromWin32(dwErr);
}
static void vboxTrayCloseBaseDriver(void)
{
if (ghVBoxDriver)
{
CloseHandle(ghVBoxDriver);
ghVBoxDriver = NULL;
}
}
static void vboxTrayDestroyToolWindow(void)
{
if (ghwndToolWindow)
{
Log(("VBoxTray: Destroying tool window ...\n"));
/* Destroy the tool window. */
DestroyWindow(ghwndToolWindow);
ghwndToolWindow = NULL;
UnregisterClass("VBoxTrayToolWndClass", ghInstance);
}
}
static int vboxTrayCreateToolWindow(void)
{
DWORD dwErr = ERROR_SUCCESS;
/* Create a custom window class. */
WNDCLASS windowClass = {0};
windowClass.style = CS_NOCLOSE;
windowClass.lpfnWndProc = (WNDPROC)vboxToolWndProc;
windowClass.hInstance = ghInstance;
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
windowClass.lpszClassName = "VBoxTrayToolWndClass";
if (!RegisterClass(&windowClass))
{
dwErr = GetLastError();
Log(("VBoxTray: Registering invisible tool window failed, error = %08X\n", dwErr));
}
else
{
/*
* Create our (invisible) tool window.
* Note: The window name ("VBoxTrayToolWnd") and class ("VBoxTrayToolWndClass") is
* needed for posting globally registered messages to VBoxTray and must not be
* changed! Otherwise things get broken!
*
*/
ghwndToolWindow = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
"VBoxTrayToolWndClass", "VBoxTrayToolWnd",
WS_POPUPWINDOW,
-200, -200, 100, 100, NULL, NULL, ghInstance, NULL);
if (!ghwndToolWindow)
{
dwErr = GetLastError();
Log(("VBoxTray: Creating invisible tool window failed, error = %08X\n", dwErr));
}
else
{
/* Reload the cursor(s). */
hlpReloadCursor();
Log(("VBoxTray: Invisible tool window handle = %p\n", ghwndToolWindow));
}
}
if (dwErr != ERROR_SUCCESS)
vboxTrayDestroyToolWindow();
return RTErrConvertFromWin32(dwErr);
}
static int vboxTraySetupSeamless(void)
{
OSVERSIONINFO info;
gMajorVersion = 5; /* Default to Windows XP. */
info.dwOSVersionInfoSize = sizeof(info);
if (GetVersionEx(&info))
{
Log(("VBoxTray: Windows version %ld.%ld\n", info.dwMajorVersion, info.dwMinorVersion));
gMajorVersion = info.dwMajorVersion;
}
/* We need to setup a security descriptor to allow other processes modify access to the seamless notification event semaphore. */
SECURITY_ATTRIBUTES SecAttr;
DWORD dwErr = ERROR_SUCCESS;
char secDesc[SECURITY_DESCRIPTOR_MIN_LENGTH];
BOOL fRC;
SecAttr.nLength = sizeof(SecAttr);
SecAttr.bInheritHandle = FALSE;
SecAttr.lpSecurityDescriptor = &secDesc;
InitializeSecurityDescriptor(SecAttr.lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
fRC = SetSecurityDescriptorDacl(SecAttr.lpSecurityDescriptor, TRUE, 0, FALSE);
if (!fRC)
{
dwErr = GetLastError();
Log(("VBoxTray: SetSecurityDescriptorDacl failed with last error = %08X\n", dwErr));
}
else
{
/* For Vista and up we need to change the integrity of the security descriptor, too. */
if (gMajorVersion >= 6)
{
BOOL (WINAPI * pfnConvertStringSecurityDescriptorToSecurityDescriptorA)(LPCSTR StringSecurityDescriptor, DWORD StringSDRevision, PSECURITY_DESCRIPTOR *SecurityDescriptor, PULONG SecurityDescriptorSize);
HMODULE hModule = LoadLibrary("ADVAPI32.DLL");
if (!hModule)
{
dwErr = GetLastError();
Log(("VBoxTray: Loading module ADVAPI32.DLL failed with last error = %08X\n", dwErr));
}
else
{
PSECURITY_DESCRIPTOR pSD;
PACL pSacl = NULL;
BOOL fSaclPresent = FALSE;
BOOL fSaclDefaulted = FALSE;
*(uintptr_t *)&pfnConvertStringSecurityDescriptorToSecurityDescriptorA = (uintptr_t)GetProcAddress(hModule, "ConvertStringSecurityDescriptorToSecurityDescriptorA");
Log(("VBoxTray: pfnConvertStringSecurityDescriptorToSecurityDescriptorA = %x\n", pfnConvertStringSecurityDescriptorToSecurityDescriptorA));
if (pfnConvertStringSecurityDescriptorToSecurityDescriptorA)
{
fRC = pfnConvertStringSecurityDescriptorToSecurityDescriptorA("S:(ML;;NW;;;LW)", /* this means "low integrity" */
SDDL_REVISION_1, &pSD, NULL);
if (!fRC)
{
dwErr = GetLastError();
Log(("VBoxTray: ConvertStringSecurityDescriptorToSecurityDescriptorA failed with last error = %08X\n", dwErr));
}
else
{
fRC = GetSecurityDescriptorSacl(pSD, &fSaclPresent, &pSacl, &fSaclDefaulted);
if (!fRC)
{
dwErr = GetLastError();
Log(("VBoxTray: GetSecurityDescriptorSacl failed with last error = %08X\n", dwErr));
}
else
{
fRC = SetSecurityDescriptorSacl(SecAttr.lpSecurityDescriptor, TRUE, pSacl, FALSE);
if (!fRC)
{
dwErr = GetLastError();
Log(("VBoxTray: SetSecurityDescriptorSacl failed with last error = %08X\n", dwErr));
}
}
}
}
}
}
if ( dwErr == ERROR_SUCCESS
&& gMajorVersion >= 5) /* Only for W2K and up ... */
{
ghSeamlessWtNotifyEvent = CreateEvent(&SecAttr, FALSE, FALSE, VBOXHOOK_GLOBAL_WT_EVENT_NAME);
if (ghSeamlessWtNotifyEvent == NULL)
{
dwErr = GetLastError();
Log(("VBoxTray: CreateEvent for Seamless failed, last error = %08X\n", dwErr));
}
}
}
return RTErrConvertFromWin32(dwErr);
}
static void vboxTrayShutdownSeamless(void)
{
if (ghSeamlessWtNotifyEvent)
{
CloseHandle(ghSeamlessWtNotifyEvent);
ghSeamlessWtNotifyEvent = NULL;
}
}
static int vboxTrayServiceMain(void)
{
int rc = VINF_SUCCESS;
Log(("VBoxTray: Entering vboxTrayServiceMain\n"));
ghStopSem = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ghStopSem == NULL)
{
rc = RTErrConvertFromWin32(GetLastError());
Log(("VBoxTray: CreateEvent for stopping VBoxTray failed, rc=%Rrc\n", rc));
}
else
{
/*
* Start services listed in the vboxServiceTable.
*/
VBOXSERVICEENV svcEnv;
svcEnv.hInstance = ghInstance;
svcEnv.hDriver = ghVBoxDriver;
/* Initializes disp-if to default (XPDM) mode. */
VBoxDispIfInit(&svcEnv.dispIf); /* Cannot fail atm. */
#ifdef VBOX_WITH_WDDM
/*
* For now the display mode will be adjusted to WDDM mode if needed
* on display service initialization when it detects the display driver type.
*/
#endif
/* Finally start all the built-in services! */
rc = vboxTrayStartServices(&svcEnv, vboxServiceTable);
if (RT_FAILURE(rc))
{
/* Terminate service if something went wrong. */
vboxTrayStopServices(&svcEnv, vboxServiceTable);
}
else
{
rc = vboxTrayCreateTrayIcon();
if ( RT_SUCCESS(rc)
&& gMajorVersion >= 5) /* Only for W2K and up ... */
{
/* We're ready to create the tooltip balloon.
Check in 10 seconds (@todo make seconds configurable) ... */
SetTimer(ghwndToolWindow,
TIMERID_VBOXTRAY_CHECK_HOSTVERSION,
10 * 1000, /* 10 seconds */
NULL /* No timerproc */);
}
if (RT_SUCCESS(rc))
{
/* Do the Shared Folders auto-mounting stuff. */
rc = VBoxSharedFoldersAutoMount();
if (RT_SUCCESS(rc))
{
/* Report the host that we're up and running! */
hlpReportStatus(VBoxGuestFacilityStatus_Active);
}
}
if (RT_SUCCESS(rc))
{
/* Boost thread priority to make sure we wake up early for seamless window notifications
* (not sure if it actually makes any difference though). */
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
/*
* Main execution loop
* Wait for the stop semaphore to be posted or a window event to arrive
*/
HANDLE hWaitEvent[3] = {0};
DWORD dwEventCount = 0;
hWaitEvent[dwEventCount++] = ghStopSem;
/* Check if seamless mode is not active and add seamless event to the list */
if (0 != ghSeamlessWtNotifyEvent)
{
hWaitEvent[dwEventCount++] = ghSeamlessWtNotifyEvent;
}
if (0 != vboxDtGetNotifyEvent())
{
hWaitEvent[dwEventCount++] = vboxDtGetNotifyEvent();
}
Log(("VBoxTray: Number of events to wait in main loop: %ld\n", dwEventCount));
while (true)
{
DWORD waitResult = MsgWaitForMultipleObjectsEx(dwEventCount, hWaitEvent, 500, QS_ALLINPUT, 0);
waitResult = waitResult - WAIT_OBJECT_0;
/* Only enable for message debugging, lots of traffic! */
//Log(("VBoxTray: Wait result = %ld\n", waitResult));
if (waitResult == 0)
{
Log(("VBoxTray: Event 'Exit' triggered\n"));
/* exit */
break;
}
else
{
BOOL fHandled = FALSE;
if (waitResult < RT_ELEMENTS(hWaitEvent))
{
if (hWaitEvent[waitResult])
{
if (hWaitEvent[waitResult] == ghSeamlessWtNotifyEvent)
{
Log(("VBoxTray: Event 'Seamless' triggered\n"));
/* seamless window notification */
VBoxSeamlessCheckWindows();
fHandled = TRUE;
}
else if (hWaitEvent[waitResult] == vboxDtGetNotifyEvent())
{
Log(("VBoxTray: Event 'Dt' triggered\n"));
BOOL fOldAllowedState = VBoxConsoleIsAllowed();
if (vboxDtHandleEvent())
{
if (!VBoxConsoleIsAllowed() != !fOldAllowedState)
VBoxConsoleEnable(!fOldAllowedState);
}
fHandled = TRUE;
}
}
}
if (!fHandled)
{
/* timeout or a window message, handle it */
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
Log(("VBoxTray: msg %p\n", msg.message));
if (msg.message == WM_QUIT)
{
Log(("VBoxTray: WM_QUIT!\n"));
SetEvent(ghStopSem);
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
Log(("VBoxTray: Returned from main loop, exiting ...\n"));
}
Log(("VBoxTray: Waiting for services to stop ...\n"));
vboxTrayStopServices(&svcEnv, vboxServiceTable);
} /* Services started */
CloseHandle(ghStopSem);
} /* Stop event created */
vboxTrayRemoveTrayIcon();
Log(("VBoxTray: Leaving vboxTrayServiceMain with rc=%Rrc\n", rc));
return rc;
}
/**
* Main function
*/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
/* Do not use a global namespace ("Global\\") for mutex name here, will blow up NT4 compatibility! */
HANDLE hMutexAppRunning = CreateMutex(NULL, FALSE, "VBoxTray");
if ( hMutexAppRunning != NULL
&& GetLastError() == ERROR_ALREADY_EXISTS)
{
/* Close the mutex for this application instance. */
CloseHandle (hMutexAppRunning);
hMutexAppRunning = NULL;
return 0;
}
LogRel(("VBoxTray: %s r%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()));
int rc = RTR3InitExeNoArguments(0);
if (RT_SUCCESS(rc))
{
rc = VbglR3Init();
if (RT_SUCCESS(rc))
rc = vboxTrayOpenBaseDriver();
}
if (RT_SUCCESS(rc))
{
/* Save instance handle. */
ghInstance = hInstance;
hlpReportStatus(VBoxGuestFacilityStatus_Init);
rc = vboxTrayCreateToolWindow();
if (RT_SUCCESS(rc))
{
VBoxCapsInit();
rc = vboxStInit(ghwndToolWindow);
if (!RT_SUCCESS(rc))
{
WARN(("VBoxTray: vboxStInit failed, rc %d"));
/* ignore the St Init failure. this can happen for < XP win that do not support WTS API
* in that case the session is treated as active connected to the physical console
* (i.e. fallback to the old behavior that was before introduction of VBoxSt) */
Assert(vboxStIsActiveConsole());
}
rc = vboxDtInit();
if (!RT_SUCCESS(rc))
{
WARN(("VBoxTray: vboxDtInit failed, rc %d"));
/* ignore the Dt Init failure. this can happen for < XP win that do not support WTS API
* in that case the session is treated as active connected to the physical console
* (i.e. fallback to the old behavior that was before introduction of VBoxSt) */
Assert(vboxDtIsInputDesktop());
}
rc = vboxTraySetupSeamless();
if (RT_SUCCESS(rc))
{
Log(("VBoxTray: Init successful\n"));
rc = vboxTrayServiceMain();
if (RT_SUCCESS(rc))
hlpReportStatus(VBoxGuestFacilityStatus_Terminating);
vboxTrayShutdownSeamless();
}
/* it should be safe to call vboxDtTerm even if vboxStInit above failed */
vboxDtTerm();
/* it should be safe to call vboxStTerm even if vboxStInit above failed */
vboxStTerm();
VBoxCapsTerm();
vboxTrayDestroyToolWindow();
}
if (RT_SUCCESS(rc))
hlpReportStatus(VBoxGuestFacilityStatus_Terminated);
}
if (RT_FAILURE(rc))
{
LogRel(("VBoxTray: Error while starting, rc=%Rrc\n", rc));
hlpReportStatus(VBoxGuestFacilityStatus_Failed);
}
LogRel(("VBoxTray: Ended\n"));
vboxTrayCloseBaseDriver();
/* Release instance mutex. */
if (hMutexAppRunning != NULL)
{
CloseHandle(hMutexAppRunning);
hMutexAppRunning = NULL;
}
VbglR3Term();
return RT_SUCCESS(rc) ? 0 : 1;
}
/**
* Window procedure for our tool window
*/
static LRESULT CALLBACK vboxToolWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
{
Log(("VBoxTray: Tool window created\n"));
int rc = vboxTrayRegisterGlobalMessages(&s_vboxGlobalMessageTable[0]);
if (RT_FAILURE(rc))
Log(("VBoxTray: Error registering global window messages, rc=%Rrc\n", rc));
return 0;
}
case WM_CLOSE:
return 0;
case WM_DESTROY:
Log(("VBoxTray: Tool window destroyed\n"));
KillTimer(ghwndToolWindow, TIMERID_VBOXTRAY_CHECK_HOSTVERSION);
return 0;
case WM_TIMER:
if (VBoxCapsCheckTimer(wParam))
return 0;
switch (wParam)
{
case TIMERID_VBOXTRAY_CHECK_HOSTVERSION:
if (RT_SUCCESS(VBoxCheckHostVersion()))
{
/* After successful run we don't need to check again. */
KillTimer(ghwndToolWindow, TIMERID_VBOXTRAY_CHECK_HOSTVERSION);
}
return 0;
default:
break;
}
break; /* Make sure other timers get processed the usual way! */
case WM_VBOXTRAY_TRAY_ICON:
switch (lParam)
{
case WM_LBUTTONDBLCLK:
break;
case WM_RBUTTONDOWN:
break;
}
return 0;
case WM_VBOX_SEAMLESS_ENABLE:
VBoxCapsEntryFuncStateSet(VBOXCAPS_ENTRY_IDX_SEAMLESS, VBOXCAPS_ENTRY_FUNCSTATE_STARTED);
return 0;
case WM_VBOX_SEAMLESS_DISABLE:
VBoxCapsEntryFuncStateSet(VBOXCAPS_ENTRY_IDX_SEAMLESS, VBOXCAPS_ENTRY_FUNCSTATE_SUPPORTED);
return 0;
case WM_VBOX_SEAMLESS_UPDATE:
if (VBoxCapsEntryIsEnabled(VBOXCAPS_ENTRY_IDX_SEAMLESS))
VBoxSeamlessCheckWindows();
return 0;
case WM_VBOX_GRAPHICS_SUPPORTED:
VBoxGrapicsSetSupported(TRUE);
return 0;
case WM_VBOX_GRAPHICS_UNSUPPORTED:
VBoxGrapicsSetSupported(FALSE);
return 0;
case WM_VBOXTRAY_VM_RESTORED:
VBoxRestoreSession();
return 0;
case WM_VBOXTRAY_VRDP_CHECK:
VBoxRestoreCheckVRDP();
return 0;
case WM_WTSSESSION_CHANGE:
{
BOOL fOldAllowedState = VBoxConsoleIsAllowed();
if (vboxStHandleEvent(wParam, lParam))
{
if (!VBoxConsoleIsAllowed() != !fOldAllowedState)
VBoxConsoleEnable(!fOldAllowedState);
}
return 0;
}
default:
/* Handle all globally registered window messages. */
if (vboxTrayHandleGlobalMessages(&s_vboxGlobalMessageTable[0], uMsg,
wParam, lParam))
{
return 0; /* We handled the message. @todo Add return value!*/
}
break; /* We did not handle the message, dispatch to DefWndProc. */
}
/* Only if message was *not* handled by our switch above, dispatch
* to DefWindowProc. */
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
/* St (session [state] tracking) functionality API impl */
typedef struct VBOXST
{
HWND hWTSAPIWnd;
HMODULE hWTSAPI32;
BOOL fIsConsole;
WTS_CONNECTSTATE_CLASS enmConnectState;
BOOL (WINAPI * pfnWTSRegisterSessionNotification)(HWND hWnd, DWORD dwFlags);
BOOL (WINAPI * pfnWTSUnRegisterSessionNotification)(HWND hWnd);
BOOL (WINAPI * pfnWTSQuerySessionInformationA)(HANDLE hServer, DWORD SessionId, WTS_INFO_CLASS WTSInfoClass, LPTSTR *ppBuffer, DWORD *pBytesReturned);
} VBOXST;
static VBOXST gVBoxSt;
static int vboxStCheckState()
{
int rc = VINF_SUCCESS;
WTS_CONNECTSTATE_CLASS *penmConnectState = NULL;
USHORT *pProtocolType = NULL;
DWORD cbBuf = 0;
Assert(0);
if (gVBoxSt.pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSConnectState, (LPTSTR *)&penmConnectState, &cbBuf))
{
if (gVBoxSt.pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSClientProtocolType, (LPTSTR *)&pProtocolType, &cbBuf))
{
gVBoxSt.fIsConsole = (*pProtocolType == 0);
gVBoxSt.enmConnectState = *penmConnectState;
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: WTSQuerySessionInformationA WTSClientProtocolType failed, error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: WTSQuerySessionInformationA WTSConnectState failed, error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
return rc;
}
static int vboxStInit(HWND hWnd)
{
int rc = VINF_SUCCESS;
memset(&gVBoxSt, 0, sizeof (gVBoxSt));
gVBoxSt.hWTSAPI32 = LoadLibrary("WTSAPI32.DLL");
if (gVBoxSt.hWTSAPI32)
{
*(uintptr_t *)&gVBoxSt.pfnWTSRegisterSessionNotification = (uintptr_t)GetProcAddress(gVBoxSt.hWTSAPI32, "WTSRegisterSessionNotification");
if (!gVBoxSt.pfnWTSRegisterSessionNotification)
{
WARN(("VBoxTray: WTSRegisterSessionNotification not found\n"));
rc = VERR_NOT_SUPPORTED;
}
*(uintptr_t *)&gVBoxSt.pfnWTSUnRegisterSessionNotification = (uintptr_t)GetProcAddress(gVBoxSt.hWTSAPI32, "WTSUnRegisterSessionNotification");
if (!gVBoxSt.pfnWTSUnRegisterSessionNotification)
{
WARN(("VBoxTray: WTSUnRegisterSessionNotification not found\n"));
rc = VERR_NOT_SUPPORTED;
}
*(uintptr_t *)&gVBoxSt.pfnWTSQuerySessionInformationA = (uintptr_t)GetProcAddress(gVBoxSt.hWTSAPI32, "WTSQuerySessionInformationA");
if (!gVBoxSt.pfnWTSQuerySessionInformationA)
{
WARN(("VBoxTray: WTSQuerySessionInformationA not found\n"));
rc = VERR_NOT_SUPPORTED;
}
Assert(0);
if (rc == VINF_SUCCESS)
{
gVBoxSt.hWTSAPIWnd = hWnd;
if (gVBoxSt.pfnWTSRegisterSessionNotification(gVBoxSt.hWTSAPIWnd, NOTIFY_FOR_THIS_SESSION))
{
vboxStCheckState();
return VINF_SUCCESS;
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: WTSRegisterSessionNotification failed, error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
}
FreeLibrary(gVBoxSt.hWTSAPI32);
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: WTSAPI32 load failed, error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
memset(&gVBoxSt, 0, sizeof (gVBoxSt));
gVBoxSt.fIsConsole = TRUE;
gVBoxSt.enmConnectState = WTSActive;
return rc;
}
static void vboxStTerm()
{
if (gVBoxSt.hWTSAPIWnd)
{
WARN(("VBoxTray: vboxStTerm called for non-initialized St\n"));
return;
}
if (!gVBoxSt.pfnWTSUnRegisterSessionNotification(gVBoxSt.hWTSAPIWnd))
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: WTSAPI32 load failed, error = %08X\n", dwErr));
}
FreeLibrary(gVBoxSt.hWTSAPI32);
memset(&gVBoxSt, 0, sizeof (gVBoxSt));
}
#define VBOXST_DBG_MAKECASE(_val) case _val: return #_val;
static const char* vboxStDbgGetString(DWORD val)
{
switch (val)
{
VBOXST_DBG_MAKECASE(WTS_CONSOLE_CONNECT);
VBOXST_DBG_MAKECASE(WTS_CONSOLE_DISCONNECT);
VBOXST_DBG_MAKECASE(WTS_REMOTE_CONNECT);
VBOXST_DBG_MAKECASE(WTS_REMOTE_DISCONNECT);
VBOXST_DBG_MAKECASE(WTS_SESSION_LOGON);
VBOXST_DBG_MAKECASE(WTS_SESSION_LOGOFF);
VBOXST_DBG_MAKECASE(WTS_SESSION_LOCK);
VBOXST_DBG_MAKECASE(WTS_SESSION_UNLOCK);
VBOXST_DBG_MAKECASE(WTS_SESSION_REMOTE_CONTROL);
default:
WARN(("VBoxTray: invalid WTS state %d", val));
return "Unknown";
}
}
static BOOL vboxStHandleEvent(WPARAM wEvent, LPARAM SessionID)
{
WARN(("VBoxTray: WTS Event: %s\n", vboxStDbgGetString(wEvent)));
BOOL fOldIsActiveConsole = vboxStIsActiveConsole();
vboxStCheckState();
return !vboxStIsActiveConsole() != !fOldIsActiveConsole;
}
static BOOL vboxStIsActiveConsole()
{
return (gVBoxSt.enmConnectState == WTSActive && gVBoxSt.fIsConsole);
}
/*
* Dt (desktop [state] tracking) functionality API impl
*
* !!!NOTE: this API is NOT thread-safe!!!
* */
typedef struct VBOXDT
{
HANDLE hNotifyEvent;
BOOL fIsInputDesktop;
HMODULE hHookModule;
BOOL (* pfnVBoxHookInstallActiveDesktopTracker)(HMODULE hDll);
BOOL (* pfnVBoxHookRemoveActiveDesktopTracker)();
HMODULE hUSER32;
HDESK (WINAPI * pfnGetThreadDesktop)(DWORD dwThreadId);
HDESK (WINAPI * pfnOpenInputDesktop)(DWORD dwFlags, BOOL fInherit, ACCESS_MASK dwDesiredAccess);
BOOL (WINAPI * pfnCloseDesktop)(HDESK hDesktop);
} VBOXDT;
static VBOXDT gVBoxDt;
static BOOL vboxDtCalculateIsInputDesktop()
{
BOOL fIsInputDt = FALSE;
HDESK hInput = gVBoxDt.pfnOpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW);
if (hInput)
{
// DWORD dwThreadId = GetCurrentThreadId();
// HDESK hThreadDt = gVBoxDt.pfnGetThreadDesktop(dwThreadId);
// if (hThreadDt)
// {
fIsInputDt = TRUE;
// }
// else
// {
// DWORD dwErr = GetLastError();
// WARN(("VBoxTray: pfnGetThreadDesktop for Seamless failed, last error = %08X\n", dwErr));
// }
gVBoxDt.pfnCloseDesktop(hInput);
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: pfnOpenInputDesktop for Seamless failed, last error = %08X\n", dwErr));
}
return fIsInputDt;
}
static int vboxDtInit()
{
int rc = VINF_SUCCESS;
OSVERSIONINFO info;
gMajorVersion = 5; /* Default to Windows XP. */
info.dwOSVersionInfoSize = sizeof(info);
if (GetVersionEx(&info))
{
WARN(("VBoxTray: Windows version %ld.%ld\n", info.dwMajorVersion, info.dwMinorVersion));
gMajorVersion = info.dwMajorVersion;
}
memset(&gVBoxDt, 0, sizeof (gVBoxDt));
/* For Vista and up we need to change the integrity of the security descriptor, too. */
if (gMajorVersion >= 6)
{
gVBoxDt.hNotifyEvent = CreateEvent(NULL, FALSE, FALSE, VBOXHOOK_GLOBAL_DT_EVENT_NAME);
if (gVBoxDt.hNotifyEvent != NULL)
{
gVBoxDt.hHookModule = LoadLibrary(VBOXHOOK_DLL_NAME);
if (gVBoxDt.hHookModule)
{
*(uintptr_t *)&gVBoxDt.pfnVBoxHookInstallActiveDesktopTracker = (uintptr_t)GetProcAddress(gVBoxDt.hHookModule, "VBoxHookInstallActiveDesktopTracker");
if (!gVBoxDt.pfnVBoxHookInstallActiveDesktopTracker)
{
WARN(("VBoxTray: VBoxHookInstallActiveDesktopTracker not found\n"));
rc = VERR_NOT_SUPPORTED;
}
*(uintptr_t *)&gVBoxDt.pfnVBoxHookRemoveActiveDesktopTracker = (uintptr_t)GetProcAddress(gVBoxDt.hHookModule, "VBoxHookInstallActiveDesktopTracker");
if (!gVBoxDt.pfnVBoxHookRemoveActiveDesktopTracker)
{
WARN(("VBoxTray: VBoxHookRemoveActiveDesktopTracker not found\n"));
rc = VERR_NOT_SUPPORTED;
}
if (VINF_SUCCESS == rc)
{
gVBoxDt.hUSER32 = LoadLibrary("User32.dll");
if (gVBoxDt.hUSER32)
{
*(uintptr_t *)&gVBoxDt.pfnGetThreadDesktop = (uintptr_t)GetProcAddress(gVBoxDt.hUSER32, "GetThreadDesktop");
if (!gVBoxDt.pfnGetThreadDesktop)
{
WARN(("VBoxTray: GetThreadDesktop not found\n"));
rc = VERR_NOT_SUPPORTED;
}
*(uintptr_t *)&gVBoxDt.pfnOpenInputDesktop = (uintptr_t)GetProcAddress(gVBoxDt.hUSER32, "OpenInputDesktop");
if (!gVBoxDt.pfnOpenInputDesktop)
{
WARN(("VBoxTray: OpenInputDesktop not found\n"));
rc = VERR_NOT_SUPPORTED;
}
*(uintptr_t *)&gVBoxDt.pfnCloseDesktop = (uintptr_t)GetProcAddress(gVBoxDt.hUSER32, "CloseDesktop");
if (!gVBoxDt.pfnCloseDesktop)
{
WARN(("VBoxTray: CloseDesktop not found\n"));
rc = VERR_NOT_SUPPORTED;
}
if (VINF_SUCCESS == rc)
{
gVBoxDt.pfnVBoxHookInstallActiveDesktopTracker(gVBoxDt.hHookModule);
gVBoxDt.fIsInputDesktop = vboxDtCalculateIsInputDesktop();
return VINF_SUCCESS;
}
FreeLibrary(gVBoxDt.hUSER32);
}
}
FreeLibrary(gVBoxDt.hHookModule);
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: CreateEvent for Seamless failed, last error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
CloseHandle(gVBoxDt.hNotifyEvent);
}
else
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: CreateEvent for Seamless failed, last error = %08X\n", dwErr));
rc = RTErrConvertFromWin32(dwErr);
}
}
memset(&gVBoxDt, 0, sizeof (gVBoxDt));
gVBoxDt.fIsInputDesktop = TRUE;
return rc;
}
static void vboxDtTerm()
{
if (!gVBoxDt.hHookModule)
return;
gVBoxDt.pfnVBoxHookRemoveActiveDesktopTracker();
FreeLibrary(gVBoxDt.hUSER32);
FreeLibrary(gVBoxDt.hHookModule);
CloseHandle(gVBoxDt.hNotifyEvent);
memset(&gVBoxDt, 0, sizeof (gVBoxDt));
}
/* @returns true on "IsInputDesktop" state change */
static BOOL vboxDtHandleEvent()
{
BOOL fIsInputDesktop = gVBoxDt.fIsInputDesktop;
gVBoxDt.fIsInputDesktop = vboxDtCalculateIsInputDesktop();
return !fIsInputDesktop != !gVBoxDt.fIsInputDesktop;
}
static HANDLE vboxDtGetNotifyEvent()
{
return gVBoxDt.hNotifyEvent;
}
/* @returns true iff the application (VBoxTray) desktop is input */
static BOOL vboxDtIsInputDesktop()
{
return gVBoxDt.fIsInputDesktop;
}
/* we need to perform Acquire/Release using the file handled we use for rewuesting events from VBoxGuest
* otherwise Acquisition mechanism will treat us as different client and will not propagate necessary requests
* */
static int VBoxAcquireGuestCaps(uint32_t fOr, uint32_t fNot)
{
DWORD cbReturned = 0;
VBoxGuestCapsAquire Info;
Info.rc = VERR_NOT_IMPLEMENTED;
Info.u32OrMask = fOr;
Info.u32NotMask = fNot;
if (!DeviceIoControl(ghVBoxDriver, VBOXGUEST_IOCTL_GUEST_CAPS_ACQUIRE, &Info, sizeof(Info), &Info, sizeof(Info), &cbReturned, NULL))
{
DWORD LastErr = GetLastError();
WARN(("DeviceIoControl VBOXGUEST_IOCTL_GUEST_CAPS_ACQUIRE failed LastErr %d", LastErr));
return RTErrConvertFromWin32(LastErr);
}
int rc = Info.rc;
if (!RT_SUCCESS(rc))
{
WARN(("VBOXGUEST_IOCTL_GUEST_CAPS_ACQUIRE failed rc %d", rc));
return rc;
}
return rc;
}
typedef enum VBOXCAPS_ENTRY_ACSTATE
{
/* the given cap is released */
VBOXCAPS_ENTRY_ACSTATE_RELEASED = 0,
/* the given cap acquisition is in progress */
VBOXCAPS_ENTRY_ACSTATE_ACQUIRING,
/* the given cap is acquired */
VBOXCAPS_ENTRY_ACSTATE_ACQUIRED
} VBOXCAPS_ENTRY_ACSTATE;
struct VBOXCAPS_ENTRY;
struct VBOXCAPS;
typedef DECLCALLBACKPTR(void, PFNVBOXCAPS_ENTRY_ON_ENABLE)(struct VBOXCAPS *pConsole, struct VBOXCAPS_ENTRY *pCap, BOOL fEnabled);
typedef struct VBOXCAPS_ENTRY
{
uint32_t fCap;
uint32_t iCap;
VBOXCAPS_ENTRY_FUNCSTATE enmFuncState;
VBOXCAPS_ENTRY_ACSTATE enmAcState;
PFNVBOXCAPS_ENTRY_ON_ENABLE pfnOnEnable;
} VBOXCAPS_ENTRY;
typedef struct VBOXCAPS
{
UINT_PTR idTimer;
VBOXCAPS_ENTRY aCaps[VBOXCAPS_ENTRY_IDX_COUNT];
} VBOXCAPS;
static VBOXCAPS gVBoxCaps;
static DECLCALLBACK(void) vboxCapsOnEnableSeamles(struct VBOXCAPS *pConsole, struct VBOXCAPS_ENTRY *pCap, BOOL fEnabled)
{
if (fEnabled)
{
Assert(pCap->enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED);
Assert(pCap->enmFuncState == VBOXCAPS_ENTRY_FUNCSTATE_STARTED);
VBoxSeamlessInstallHook();
}
else
{
Assert(pCap->enmAcState != VBOXCAPS_ENTRY_ACSTATE_ACQUIRED || pCap->enmFuncState != VBOXCAPS_ENTRY_FUNCSTATE_STARTED);
VBoxSeamlessRemoveHook();
}
}
static void vboxCapsEntryAcStateSet(VBOXCAPS_ENTRY *pCap, VBOXCAPS_ENTRY_ACSTATE enmAcState)
{
VBOXCAPS *pConsole = &gVBoxCaps;
if (pCap->enmAcState == enmAcState)
return;
VBOXCAPS_ENTRY_ACSTATE enmOldAcState = pCap->enmAcState;
pCap->enmAcState = enmAcState;
if (enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED)
{
if (pCap->enmFuncState == VBOXCAPS_ENTRY_FUNCSTATE_STARTED)
{
if (pCap->pfnOnEnable)
pCap->pfnOnEnable(pConsole, pCap, TRUE);
}
}
else if (enmOldAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED && pCap->enmFuncState == VBOXCAPS_ENTRY_FUNCSTATE_STARTED)
{
if (pCap->pfnOnEnable)
pCap->pfnOnEnable(pConsole, pCap, FALSE);
}
}
static void vboxCapsEntryFuncStateSet(VBOXCAPS_ENTRY *pCap, VBOXCAPS_ENTRY_FUNCSTATE enmFuncState)
{
VBOXCAPS *pConsole = &gVBoxCaps;
if (pCap->enmFuncState == enmFuncState)
return;
VBOXCAPS_ENTRY_FUNCSTATE enmOldFuncState = pCap->enmFuncState;
pCap->enmFuncState = enmFuncState;
if (enmFuncState == VBOXCAPS_ENTRY_FUNCSTATE_STARTED)
{
Assert(enmOldFuncState == VBOXCAPS_ENTRY_FUNCSTATE_SUPPORTED);
if (pCap->enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED)
{
if (pCap->pfnOnEnable)
pCap->pfnOnEnable(pConsole, pCap, TRUE);
}
}
else if (pCap->enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED && enmOldFuncState == VBOXCAPS_ENTRY_FUNCSTATE_STARTED)
{
if (pCap->pfnOnEnable)
pCap->pfnOnEnable(pConsole, pCap, FALSE);
}
}
static void VBoxCapsEntryFuncStateSet(uint32_t iCup, VBOXCAPS_ENTRY_FUNCSTATE enmFuncState)
{
VBOXCAPS *pConsole = &gVBoxCaps;
VBOXCAPS_ENTRY *pCap = &pConsole->aCaps[iCup];
vboxCapsEntryFuncStateSet(pCap, enmFuncState);
}
static int VBoxCapsInit()
{
VBOXCAPS *pConsole = &gVBoxCaps;
memset(pConsole, 0, sizeof (*pConsole));
pConsole->aCaps[VBOXCAPS_ENTRY_IDX_SEAMLESS].fCap = VMMDEV_GUEST_SUPPORTS_SEAMLESS;
pConsole->aCaps[VBOXCAPS_ENTRY_IDX_SEAMLESS].iCap = VBOXCAPS_ENTRY_IDX_SEAMLESS;
pConsole->aCaps[VBOXCAPS_ENTRY_IDX_SEAMLESS].pfnOnEnable = vboxCapsOnEnableSeamles;
pConsole->aCaps[VBOXCAPS_ENTRY_IDX_GRAPHICS].fCap = VMMDEV_GUEST_SUPPORTS_GRAPHICS;
pConsole->aCaps[VBOXCAPS_ENTRY_IDX_GRAPHICS].iCap = VBOXCAPS_ENTRY_IDX_GRAPHICS;
return VINF_SUCCESS;
}
static int VBoxCapsReleaseAll()
{
VBOXCAPS *pConsole = &gVBoxCaps;
int rc = VBoxAcquireGuestCaps(0, VMMDEV_GUEST_SUPPORTS_SEAMLESS | VMMDEV_GUEST_SUPPORTS_GRAPHICS);
if (!RT_SUCCESS(rc))
{
WARN(("VBoxTray: vboxCapsEntryReleaseAll VBoxAcquireGuestCaps failed rc %d\n", rc));
return rc;
}
if (pConsole->idTimer)
{
KillTimer(ghwndToolWindow, pConsole->idTimer);
pConsole->idTimer = 0;
}
for (int i = 0; i < RT_ELEMENTS(pConsole->aCaps); ++i)
{
vboxCapsEntryAcStateSet(&pConsole->aCaps[i], VBOXCAPS_ENTRY_ACSTATE_RELEASED);
}
return rc;
}
static void VBoxCapsTerm()
{
VBOXCAPS *pConsole = &gVBoxCaps;
VBoxCapsReleaseAll();
memset(pConsole, 0, sizeof (*pConsole));
}
static BOOL VBoxCapsEntryIsAcquired(uint32_t iCap)
{
VBOXCAPS *pConsole = &gVBoxCaps;
return pConsole->aCaps[iCap].enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED;
}
static BOOL VBoxCapsEntryIsEnabled(uint32_t iCap)
{
VBOXCAPS *pConsole = &gVBoxCaps;
return pConsole->aCaps[iCap].enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED
&& pConsole->aCaps[iCap].enmFuncState == VBOXCAPS_ENTRY_FUNCSTATE_STARTED;
}
static BOOL VBoxCapsCheckTimer(WPARAM wParam)
{
VBOXCAPS *pConsole = &gVBoxCaps;
if (wParam != pConsole->idTimer)
return FALSE;
uint32_t u32AcquiredCaps = 0;
BOOL fNeedNewTimer = FALSE;
for (int i = 0; i < RT_ELEMENTS(pConsole->aCaps); ++i)
{
VBOXCAPS_ENTRY *pCap = &pConsole->aCaps[i];
if (pCap->enmAcState != VBOXCAPS_ENTRY_ACSTATE_ACQUIRING)
continue;
int rc = VBoxAcquireGuestCaps(pCap->fCap, 0);
if (RT_SUCCESS(rc))
{
vboxCapsEntryAcStateSet(&pConsole->aCaps[i], VBOXCAPS_ENTRY_ACSTATE_ACQUIRED);
u32AcquiredCaps |= pCap->fCap;
}
else
{
Assert(rc == VERR_RESOURCE_BUSY);
fNeedNewTimer = TRUE;
}
}
if (!fNeedNewTimer)
{
KillTimer(ghwndToolWindow, pConsole->idTimer);
/* cleanup timer data */
pConsole->idTimer = 0;
}
return TRUE;
}
static int VBoxCapsEntryRelease(uint32_t iCap)
{
VBOXCAPS *pConsole = &gVBoxCaps;
VBOXCAPS_ENTRY *pCap = &pConsole->aCaps[iCap];
if (pCap->enmAcState == VBOXCAPS_ENTRY_ACSTATE_RELEASED)
{
WARN(("invalid cap[%d] state[%d] on release\n", iCap, pCap->enmAcState));
return VERR_INVALID_STATE;
}
if (pCap->enmAcState == VBOXCAPS_ENTRY_ACSTATE_ACQUIRED)
{
int rc = VBoxAcquireGuestCaps(0, pCap->fCap);
AssertRC(rc);
}
vboxCapsEntryAcStateSet(pCap, VBOXCAPS_ENTRY_ACSTATE_RELEASED);
return VINF_SUCCESS;
}
static int VBoxCapsEntryAcquire(uint32_t iCap)
{
VBOXCAPS *pConsole = &gVBoxCaps;
Assert(VBoxConsoleIsAllowed());
VBOXCAPS_ENTRY *pCap = &pConsole->aCaps[iCap];
if (pCap->enmAcState != VBOXCAPS_ENTRY_ACSTATE_RELEASED)
{
WARN(("invalid cap[%d] state[%d] on acquire\n", iCap, pCap->enmAcState));
return VERR_INVALID_STATE;
}
vboxCapsEntryAcStateSet(pCap, VBOXCAPS_ENTRY_ACSTATE_ACQUIRING);
int rc = VBoxAcquireGuestCaps(pCap->fCap, 0);
if (RT_SUCCESS(rc))
{
vboxCapsEntryAcStateSet(pCap, VBOXCAPS_ENTRY_ACSTATE_ACQUIRED);
return VINF_SUCCESS;
}
if (rc != VERR_RESOURCE_BUSY)
{
WARN(("VBoxTray: vboxCapsEntryReleaseAll VBoxAcquireGuestCaps failed rc %d\n", rc));
return rc;
}
WARN(("VBoxTray: iCap %d is busy!\n", iCap));
/* the cap was busy, most likely it is still used by other VBoxTray instance running in another session,
* queue the retry timer */
if (!pConsole->idTimer)
{
pConsole->idTimer = SetTimer(ghwndToolWindow, TIMERID_VBOXTRAY_CAPS_TIMER, 100, (TIMERPROC)NULL);
if (!pConsole->idTimer)
{
DWORD dwErr = GetLastError();
WARN(("VBoxTray: SetTimer error %08X\n", dwErr));
return RTErrConvertFromWin32(dwErr);
}
}
return rc;
}
static int VBoxCapsAcquireAllSupported()
{
VBOXCAPS *pConsole = &gVBoxCaps;
for (int i = 0; i < RT_ELEMENTS(pConsole->aCaps); ++i)
{
if (pConsole->aCaps[i].enmFuncState >= VBOXCAPS_ENTRY_FUNCSTATE_SUPPORTED)
VBoxCapsEntryAcquire(i);
}
return VINF_SUCCESS;
}
static BOOL VBoxConsoleIsAllowed()
{
return vboxDtIsInputDesktop() && vboxStIsActiveConsole();
}
static void VBoxConsoleEnable(BOOL fEnable)
{
if (fEnable)
VBoxCapsAcquireAllSupported();
else
VBoxCapsReleaseAll();
}
static void VBoxConsoleCapSetSupported(uint32_t iCap, BOOL fSupported)
{
if (fSupported)
{
VBoxCapsEntryFuncStateSet(iCap, VBOXCAPS_ENTRY_FUNCSTATE_SUPPORTED);
if (VBoxConsoleIsAllowed())
VBoxCapsEntryAcquire(iCap);
}
else
{
VBoxCapsEntryFuncStateSet(iCap, VBOXCAPS_ENTRY_FUNCSTATE_UNSUPPORTED);
VBoxCapsEntryRelease(iCap);
}
}
void VBoxSeamlessSetSupported(BOOL fSupported)
{
VBoxConsoleCapSetSupported(VBOXCAPS_ENTRY_IDX_SEAMLESS, fSupported);
}
static void VBoxGrapicsSetSupported(BOOL fSupported)
{
VBoxConsoleCapSetSupported(VBOXCAPS_ENTRY_IDX_GRAPHICS, fSupported);
}