diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Frontends/VBoxSDL | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Frontends/VBoxSDL')
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/.scm-settings | 32 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/Framebuffer-darwin.m | 51 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/Framebuffer.cpp | 959 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/Framebuffer.h | 266 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/Helper.cpp | 179 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/Helper.h | 67 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/Makefile.kmk | 167 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/VBoxSDL.cpp | 4798 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/VBoxSDL.h | 103 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/VBoxSDLHardened.cpp | 35 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.h | 12 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.m | 386 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/VBoxSDLTest.cpp | 478 | ||||
-rw-r--r-- | src/VBox/Frontends/VBoxSDL/ico64x01.pnm | bin | 0 -> 12320 bytes |
14 files changed, 7533 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VBoxSDL/.scm-settings b/src/VBox/Frontends/VBoxSDL/.scm-settings new file mode 100644 index 00000000..69a06a7f --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/.scm-settings @@ -0,0 +1,32 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for VBoxSDL. +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +/VBoxSDLMain-darwin.h: --external-copyright --no-fix-header-guards +/VBoxSDLMain-darwin.m: --external-copyright + +--filter-out-files /diff_SDL13_svn + diff --git a/src/VBox/Frontends/VBoxSDL/Framebuffer-darwin.m b/src/VBox/Frontends/VBoxSDL/Framebuffer-darwin.m new file mode 100644 index 00000000..76aca2cb --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/Framebuffer-darwin.m @@ -0,0 +1,51 @@ +/* $Id: Framebuffer-darwin.m $ */ +/** @file + * VBoxSDL - Darwin Cocoa helper functions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define NO_SDL_H +#import "VBoxSDL.h" +#import <Cocoa/Cocoa.h> + +void *VBoxSDLGetDarwinWindowId(void) +{ + NSView *pView = nil; + NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; + { + NSApplication *pApp = NSApp; + NSWindow *pMainWnd; + pMainWnd = [pApp mainWindow]; + if (!pMainWnd) + pMainWnd = pApp->_mainWindow; /* UGLY!! but mApp->_AppFlags._active = 0, so mainWindow() fails. */ + pView = [pMainWnd contentView]; + } + [pPool release]; + return pView; +} + diff --git a/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp b/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp new file mode 100644 index 00000000..16dc2826 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/Framebuffer.cpp @@ -0,0 +1,959 @@ +/* $Id: Framebuffer.cpp $ */ +/** @file + * VBoxSDL - Implementation of VBoxSDLFB (SDL framebuffer) class + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <VBox/com/com.h> +#include <VBox/com/array.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/VirtualBox.h> + +#include <iprt/stream.h> +#include <iprt/env.h> + +#ifdef RT_OS_OS2 +# undef RT_MAX +// from <iprt/cdefs.h> +# define RT_MAX(Value1, Value2) ((Value1) >= (Value2) ? (Value1) : (Value2)) +#endif + +using namespace com; + +#define LOG_GROUP LOG_GROUP_GUI +#include <iprt/errcore.h> +#include <VBox/log.h> + +#include "VBoxSDL.h" +#include "Framebuffer.h" +#include "Ico64x01.h" + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4121) /* warning C4121: 'SDL_SysWMmsg' : alignment of a member was sensitive to packing*/ +# endif +# include <SDL_syswm.h> /* for SDL_GetWMInfo() */ +# ifdef _MSC_VER +# pragma warning(pop) +# endif +#endif + +#if defined(VBOX_WITH_XPCOM) +NS_IMPL_THREADSAFE_ISUPPORTS1_CI(VBoxSDLFB, IFramebuffer) +NS_DECL_CLASSINFO(VBoxSDLFB) +NS_IMPL_THREADSAFE_ISUPPORTS2_CI(VBoxSDLFBOverlay, IFramebufferOverlay, IFramebuffer) +NS_DECL_CLASSINFO(VBoxSDLFBOverlay) +#endif + +static bool gfSdlInitialized = false; /**< if SDL was initialized */ +static SDL_Surface *gWMIcon = NULL; /**< the application icon */ +static RTNATIVETHREAD gSdlNativeThread = NIL_RTNATIVETHREAD; /**< the SDL thread */ + +// +// Constructor / destructor +// + +VBoxSDLFB::VBoxSDLFB() +{ +} + +HRESULT VBoxSDLFB::FinalConstruct() +{ + return 0; +} + +void VBoxSDLFB::FinalRelease() +{ + return; +} + +/** + * SDL framebuffer constructor. It is called from the main + * (i.e. SDL) thread. Therefore it is safe to use SDL calls + * here. + * @param fFullscreen flag whether we start in fullscreen mode + * @param fResizable flag whether the SDL window should be resizable + * @param fShowSDLConfig flag whether we print out SDL settings + * @param fKeepHostRes flag whether we switch the host screen resolution + * when switching to fullscreen or not + * @param iFixedWidth fixed SDL width (-1 means not set) + * @param iFixedHeight fixed SDL height (-1 means not set) + */ +HRESULT VBoxSDLFB::init(uint32_t uScreenId, + bool fFullscreen, bool fResizable, bool fShowSDLConfig, + bool fKeepHostRes, uint32_t u32FixedWidth, + uint32_t u32FixedHeight, uint32_t u32FixedBPP, + bool fUpdateImage) +{ + LogFlow(("VBoxSDLFB::VBoxSDLFB\n")); + + mScreenId = uScreenId; + mfUpdateImage = fUpdateImage; + mpWindow = NULL; + mpTexture = NULL; + mpRenderer = NULL; + mSurfVRAM = NULL; + mfInitialized = false; + mfFullscreen = fFullscreen; + mfKeepHostRes = fKeepHostRes; + mTopOffset = 0; + mfResizable = fResizable; + mfShowSDLConfig = fShowSDLConfig; + mFixedSDLWidth = u32FixedWidth; + mFixedSDLHeight = u32FixedHeight; + mFixedSDLBPP = u32FixedBPP; + mCenterXOffset = 0; + mCenterYOffset = 0; + /* Start with standard screen dimensions. */ + mGuestXRes = 640; + mGuestYRes = 480; + mPtrVRAM = NULL; + mBitsPerPixel = 0; + mBytesPerLine = 0; + mfSameSizeRequested = false; + mfUpdates = false; + + int rc = RTCritSectInit(&mUpdateLock); + AssertMsg(rc == VINF_SUCCESS, ("Error from RTCritSectInit!\n")); + + resizeGuest(); + mfInitialized = true; +#ifdef RT_OS_WINDOWS + HRESULT hr = CoCreateFreeThreadedMarshaler(this, m_pUnkMarshaler.asOutParam()); + Log(("CoCreateFreeThreadedMarshaler hr %08X\n", hr)); NOREF(hr); +#endif + + rc = SDL_GetRendererInfo(mpRenderer, &mRenderInfo); + if (RT_SUCCESS(rc)) + { + if (fShowSDLConfig) + RTPrintf("Render info:\n" + " Name: %s\n" + " Render flags: 0x%x\n" + " SDL video driver: %s\n", + mRenderInfo.name, + mRenderInfo.flags, + RTEnvGet("SDL_VIDEODRIVER")); + } + + return rc; +} + +VBoxSDLFB::~VBoxSDLFB() +{ + LogFlow(("VBoxSDLFB::~VBoxSDLFB\n")); + if (mSurfVRAM) + { + SDL_FreeSurface(mSurfVRAM); + mSurfVRAM = NULL; + } + RTCritSectDelete(&mUpdateLock); +} + +/* static */ +bool VBoxSDLFB::init(bool fShowSDLConfig) +{ + LogFlow(("VBoxSDLFB::init\n")); + + /* memorize the thread that inited us, that's the SDL thread */ + gSdlNativeThread = RTThreadNativeSelf(); + +#ifdef RT_OS_WINDOWS + /* default to DirectX if nothing else set */ + if (!RTEnvExist("SDL_VIDEODRIVER")) + { + RTEnvSet("SDL_VIDEODRIVER", "directx"); + } +#endif +#ifdef VBOXSDL_WITH_X11 + /* On some X servers the mouse is stuck inside the bottom right corner. + * See http://wiki.clug.org.za/wiki/QEMU_mouse_not_working */ + RTEnvSet("SDL_VIDEO_X11_DGAMOUSE", "0"); +#endif + + int rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE); + if (rc != 0) + { + RTPrintf("SDL Error: '%s'\n", SDL_GetError()); + return false; + } + gfSdlInitialized = true; + + RT_NOREF(fShowSDLConfig); + return true; +} + +/** + * Terminate SDL + * + * @remarks must be called from the SDL thread! + */ +void VBoxSDLFB::uninit() +{ + if (gfSdlInitialized) + { + AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n")); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } +} + +/** + * Returns the current framebuffer width in pixels. + * + * @returns COM status code + * @param width Address of result buffer. + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(Width)(ULONG *width) +{ + LogFlow(("VBoxSDLFB::GetWidth\n")); + if (!width) + return E_INVALIDARG; + *width = mGuestXRes; + return S_OK; +} + +/** + * Returns the current framebuffer height in pixels. + * + * @returns COM status code + * @param height Address of result buffer. + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(Height)(ULONG *height) +{ + LogFlow(("VBoxSDLFB::GetHeight\n")); + if (!height) + return E_INVALIDARG; + *height = mGuestYRes; + return S_OK; +} + +/** + * Return the current framebuffer color depth. + * + * @returns COM status code + * @param bitsPerPixel Address of result variable + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(BitsPerPixel)(ULONG *bitsPerPixel) +{ + LogFlow(("VBoxSDLFB::GetBitsPerPixel\n")); + if (!bitsPerPixel) + return E_INVALIDARG; + /* get the information directly from the surface in use */ + Assert(mSurfVRAM); + *bitsPerPixel = (ULONG)(mSurfVRAM ? mSurfVRAM->format->BitsPerPixel : 0); + return S_OK; +} + +/** + * Return the current framebuffer line size in bytes. + * + * @returns COM status code. + * @param lineSize Address of result variable. + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(BytesPerLine)(ULONG *bytesPerLine) +{ + LogFlow(("VBoxSDLFB::GetBytesPerLine\n")); + if (!bytesPerLine) + return E_INVALIDARG; + /* get the information directly from the surface */ + Assert(mSurfVRAM); + *bytesPerLine = (ULONG)(mSurfVRAM ? mSurfVRAM->pitch : 0); + return S_OK; +} + +STDMETHODIMP VBoxSDLFB::COMGETTER(PixelFormat) (BitmapFormat_T *pixelFormat) +{ + if (!pixelFormat) + return E_POINTER; + *pixelFormat = BitmapFormat_BGR; + return S_OK; +} + +/** + * Returns by how many pixels the guest should shrink its + * video mode height values. + * + * @returns COM status code. + * @param heightReduction Address of result variable. + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(HeightReduction)(ULONG *heightReduction) +{ + if (!heightReduction) + return E_POINTER; + *heightReduction = 0; + return S_OK; +} + +/** + * Returns a pointer to an alpha-blended overlay used for displaying status + * icons above the framebuffer. + * + * @returns COM status code. + * @param aOverlay The overlay framebuffer. + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(Overlay)(IFramebufferOverlay **aOverlay) +{ + if (!aOverlay) + return E_POINTER; + /* Not yet implemented */ + *aOverlay = 0; + return S_OK; +} + +/** + * Returns handle of window where framebuffer context is being drawn + * + * @returns COM status code. + * @param winId Handle of associated window. + */ +STDMETHODIMP VBoxSDLFB::COMGETTER(WinId)(LONG64 *winId) +{ + if (!winId) + return E_POINTER; +#ifdef RT_OS_DARWIN + if (mWinId == NULL) /* (In case it failed the first time.) */ + mWinId = (intptr_t)VBoxSDLGetDarwinWindowId(); +#endif + *winId = mWinId; + return S_OK; +} + +STDMETHODIMP VBoxSDLFB::COMGETTER(Capabilities)(ComSafeArrayOut(FramebufferCapabilities_T, aCapabilities)) +{ + if (ComSafeArrayOutIsNull(aCapabilities)) + return E_POINTER; + + com::SafeArray<FramebufferCapabilities_T> caps; + + if (mfUpdateImage) + { + caps.resize(2); + caps[0] = FramebufferCapabilities_UpdateImage; + caps[1] = FramebufferCapabilities_RenderCursor; + } + else + { + caps.resize(1); + caps[0] = FramebufferCapabilities_RenderCursor; + } + + caps.detachTo(ComSafeArrayOutArg(aCapabilities)); + return S_OK; +} + +/** + * Notify framebuffer of an update. + * + * @returns COM status code + * @param x Update region upper left corner x value. + * @param y Update region upper left corner y value. + * @param w Update region width in pixels. + * @param h Update region height in pixels. + * @param finished Address of output flag whether the update + * could be fully processed in this call (which + * has to return immediately) or VBox should wait + * for a call to the update complete API before + * continuing with display updates. + */ +STDMETHODIMP VBoxSDLFB::NotifyUpdate(ULONG x, ULONG y, + ULONG w, ULONG h) +{ + /* + * The input values are in guest screen coordinates. + */ + LogFlow(("VBoxSDLFB::NotifyUpdate: x = %d, y = %d, w = %d, h = %d\n", + x, y, w, h)); + +#ifdef VBOXSDL_WITH_X11 + /* + * SDL does not allow us to make this call from any other thread than + * the main SDL thread (which initialized the video mode). So we have + * to send an event to the main SDL thread and process it there. For + * sake of simplicity, we encode all information in the event parameters. + */ + SDL_Event event; + event.type = SDL_USEREVENT; + event.user.code = mScreenId; + event.user.type = SDL_USER_EVENT_UPDATERECT; + // 16 bit is enough for coordinates + event.user.data1 = (void*)(uintptr_t)(x << 16 | y); + event.user.data2 = (void*)(uintptr_t)(w << 16 | h); + PushNotifyUpdateEvent(&event); +#else /* !VBOXSDL_WITH_X11 */ + update(x, y, w, h, true /* fGuestRelative */); +#endif /* !VBOXSDL_WITH_X11 */ + + return S_OK; +} + +STDMETHODIMP VBoxSDLFB::NotifyUpdateImage(ULONG aX, + ULONG aY, + ULONG aWidth, + ULONG aHeight, + ComSafeArrayIn(BYTE, aImage)) +{ + LogFlow(("NotifyUpdateImage: %d,%d %dx%d\n", aX, aY, aWidth, aHeight)); + + com::SafeArray<BYTE> image(ComSafeArrayInArg(aImage)); + + /* Copy to mSurfVRAM. */ + SDL_Rect srcRect; + SDL_Rect dstRect; + srcRect.x = 0; + srcRect.y = 0; + srcRect.w = (uint16_t)aWidth; + srcRect.h = (uint16_t)aHeight; + dstRect.x = (int16_t)aX; + dstRect.y = (int16_t)aY; + dstRect.w = (uint16_t)aWidth; + dstRect.h = (uint16_t)aHeight; + + const uint32_t Rmask = 0x00FF0000, Gmask = 0x0000FF00, Bmask = 0x000000FF, Amask = 0; + SDL_Surface *surfSrc = SDL_CreateRGBSurfaceFrom(image.raw(), aWidth, aHeight, 32, aWidth * 4, + Rmask, Gmask, Bmask, Amask); + if (surfSrc) + { + RTCritSectEnter(&mUpdateLock); + if (mfUpdates) + SDL_BlitSurface(surfSrc, &srcRect, mSurfVRAM, &dstRect); + RTCritSectLeave(&mUpdateLock); + + SDL_FreeSurface(surfSrc); + } + + return NotifyUpdate(aX, aY, aWidth, aHeight); +} + +extern ComPtr<IDisplay> gpDisplay; + +STDMETHODIMP VBoxSDLFB::NotifyChange(ULONG aScreenId, + ULONG aXOrigin, + ULONG aYOrigin, + ULONG aWidth, + ULONG aHeight) +{ + LogRel(("NotifyChange: %d %d,%d %dx%d\n", + aScreenId, aXOrigin, aYOrigin, aWidth, aHeight)); + + ComPtr<IDisplaySourceBitmap> pSourceBitmap; + if (!mfUpdateImage) + gpDisplay->QuerySourceBitmap(aScreenId, pSourceBitmap.asOutParam()); + + RTCritSectEnter(&mUpdateLock); + + /* Disable screen updates. */ + mfUpdates = false; + + if (mfUpdateImage) + { + mGuestXRes = aWidth; + mGuestYRes = aHeight; + mPtrVRAM = NULL; + mBitsPerPixel = 0; + mBytesPerLine = 0; + } + else + { + /* Save the new bitmap. */ + mpPendingSourceBitmap = pSourceBitmap; + } + + RTCritSectLeave(&mUpdateLock); + + SDL_Event event; + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_NOTIFYCHANGE; + event.user.code = mScreenId; + + PushSDLEventForSure(&event); + + RTThreadYield(); + + return S_OK; +} + +/** + * Returns whether we like the given video mode. + * + * @returns COM status code + * @param width video mode width in pixels + * @param height video mode height in pixels + * @param bpp video mode bit depth in bits per pixel + * @param supported pointer to result variable + */ +STDMETHODIMP VBoxSDLFB::VideoModeSupported(ULONG width, ULONG height, ULONG bpp, BOOL *supported) +{ + RT_NOREF(bpp); + + if (!supported) + return E_POINTER; + + /* are constraints set? */ + if ( ( (mMaxScreenWidth != ~(uint32_t)0) + && (width > mMaxScreenWidth)) + || ( (mMaxScreenHeight != ~(uint32_t)0) + && (height > mMaxScreenHeight))) + { + /* nope, we don't want that (but still don't freak out if it is set) */ +#ifdef DEBUG + RTPrintf("VBoxSDL::VideoModeSupported: we refused mode %dx%dx%d\n", width, height, bpp); +#endif + *supported = false; + } + else + { + /* anything will do */ + *supported = true; + } + return S_OK; +} + +STDMETHODIMP VBoxSDLFB::GetVisibleRegion(BYTE *aRectangles, ULONG aCount, + ULONG *aCountCopied) +{ + PRTRECT rects = (PRTRECT)aRectangles; + + if (!rects) + return E_POINTER; + + /// @todo + + NOREF(aCount); + NOREF(aCountCopied); + + return S_OK; +} + +STDMETHODIMP VBoxSDLFB::SetVisibleRegion(BYTE *aRectangles, ULONG aCount) +{ + PRTRECT rects = (PRTRECT)aRectangles; + + if (!rects) + return E_POINTER; + + /// @todo + + NOREF(aCount); + + return S_OK; +} + +STDMETHODIMP VBoxSDLFB::ProcessVHWACommand(BYTE *pCommand, LONG enmCmd, BOOL fGuestCmd) +{ + RT_NOREF(pCommand, enmCmd, fGuestCmd); + return E_NOTIMPL; +} + +STDMETHODIMP VBoxSDLFB::Notify3DEvent(ULONG uType, ComSafeArrayIn(BYTE, aData)) +{ + RT_NOREF(uType); ComSafeArrayNoRef(aData); + return E_NOTIMPL; +} + +// +// Internal public methods +// + +/* This method runs on the main SDL thread. */ +void VBoxSDLFB::notifyChange(ULONG aScreenId) +{ + /* Disable screen updates. */ + RTCritSectEnter(&mUpdateLock); + + if (!mfUpdateImage && mpPendingSourceBitmap.isNull()) + { + /* Do nothing. Change event already processed. */ + RTCritSectLeave(&mUpdateLock); + return; + } + + /* Release the current bitmap and keep the pending one. */ + mpSourceBitmap = mpPendingSourceBitmap; + mpPendingSourceBitmap.setNull(); + + RTCritSectLeave(&mUpdateLock); + + if (mpSourceBitmap.isNull()) + { + mPtrVRAM = NULL; + mBitsPerPixel = 32; + mBytesPerLine = mGuestXRes * 4; + } + else + { + BYTE *pAddress = NULL; + ULONG ulWidth = 0; + ULONG ulHeight = 0; + ULONG ulBitsPerPixel = 0; + ULONG ulBytesPerLine = 0; + BitmapFormat_T bitmapFormat = BitmapFormat_Opaque; + + mpSourceBitmap->QueryBitmapInfo(&pAddress, + &ulWidth, + &ulHeight, + &ulBitsPerPixel, + &ulBytesPerLine, + &bitmapFormat); + + if ( mGuestXRes == ulWidth + && mGuestYRes == ulHeight + && mBitsPerPixel == ulBitsPerPixel + && mBytesPerLine == ulBytesPerLine + && mPtrVRAM == pAddress + ) + { + mfSameSizeRequested = true; + } + else + { + mfSameSizeRequested = false; + } + + mGuestXRes = ulWidth; + mGuestYRes = ulHeight; + mPtrVRAM = pAddress; + mBitsPerPixel = ulBitsPerPixel; + mBytesPerLine = ulBytesPerLine; + } + + resizeGuest(); + + gpDisplay->InvalidateAndUpdateScreen(aScreenId); +} + +/** + * Method that does the actual resize of the guest framebuffer and + * then changes the SDL framebuffer setup. + */ +void VBoxSDLFB::resizeGuest() +{ + LogFlowFunc (("mGuestXRes: %d, mGuestYRes: %d\n", mGuestXRes, mGuestYRes)); + AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), + ("Wrong thread! SDL is not threadsafe!\n")); + + RTCritSectEnter(&mUpdateLock); + + const uint32_t Rmask = 0x00FF0000, Gmask = 0x0000FF00, Bmask = 0x000000FF, Amask = 0; + + /* first free the current surface */ + if (mSurfVRAM) + { + SDL_FreeSurface(mSurfVRAM); + mSurfVRAM = NULL; + } + + if (mPtrVRAM) + { + /* Create a source surface from the source bitmap. */ + mSurfVRAM = SDL_CreateRGBSurfaceFrom(mPtrVRAM, mGuestXRes, mGuestYRes, mBitsPerPixel, + mBytesPerLine, Rmask, Gmask, Bmask, Amask); + LogFlow(("VBoxSDL:: using the source bitmap\n")); + } + else + { + mSurfVRAM = SDL_CreateRGBSurface(SDL_SWSURFACE, mGuestXRes, mGuestYRes, 32, + Rmask, Gmask, Bmask, Amask); + LogFlow(("VBoxSDL:: using SDL_SWSURFACE\n")); + } + LogFlow(("VBoxSDL:: created VRAM surface %p\n", mSurfVRAM)); + + if (mfSameSizeRequested) + { + mfSameSizeRequested = false; + LogFlow(("VBoxSDL:: the same resolution requested, skipping the resize.\n")); + } + else + { + /* now adjust the SDL resolution */ + resizeSDL(); + } + + /* Enable screen updates. */ + mfUpdates = true; + + RTCritSectLeave(&mUpdateLock); + + repaint(); +} + +/** + * Sets SDL video mode. This is independent from guest video + * mode changes. + * + * @remarks Must be called from the SDL thread! + */ +void VBoxSDLFB::resizeSDL(void) +{ + LogFlow(("VBoxSDL:resizeSDL\n")); + + const int cDisplays = SDL_GetNumVideoDisplays(); + if (cDisplays > 0) + { + for (int d = 0; d < cDisplays; d++) + { + const int cDisplayModes = SDL_GetNumDisplayModes(d); + for (int m = 0; m < cDisplayModes; m++) + { + SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; + if (SDL_GetDisplayMode(d, m, &mode) != 0) + { + RTPrintf("Display #%d, mode %d:\t\t%i bpp\t%i x %i", + SDL_BITSPERPIXEL(mode.format), mode.w, mode.h); + } + + if (m == 0) + { + /* + * according to the SDL documentation, the API guarantees that + * the modes are sorted from larger to smaller, so we just + * take the first entry as the maximum. + */ + mMaxScreenWidth = mode.w; + mMaxScreenHeight = mode.h; + } + + /* Keep going. */ + } + } + } + else + AssertFailed(); /** @todo */ + + uint32_t newWidth; + uint32_t newHeight; + + /* reset the centering offsets */ + mCenterXOffset = 0; + mCenterYOffset = 0; + + /* we either have a fixed SDL resolution or we take the guest's */ + if (mFixedSDLWidth != ~(uint32_t)0) + { + newWidth = mFixedSDLWidth; + newHeight = mFixedSDLHeight; + } + else + { + newWidth = RT_MIN(mGuestXRes, mMaxScreenWidth); + newHeight = RT_MIN(mGuestYRes, mMaxScreenHeight); + } + + /* we don't have any extra space by default */ + mTopOffset = 0; + + int sdlWindowFlags = SDL_WINDOW_SHOWN; + if (mfResizable) + sdlWindowFlags |= SDL_WINDOW_RESIZABLE; + if (!mpWindow) + { + SDL_DisplayMode desktop_mode; + int x = 40 + mScreenId * 20; + int y = 40 + mScreenId * 15; + + SDL_GetDesktopDisplayMode(mScreenId, &desktop_mode); + /* create new window */ + + char szTitle[64]; + RTStrPrintf(szTitle, sizeof(szTitle), "SDL window %d", mScreenId); + mpWindow = SDL_CreateWindow(szTitle, x, y, + newWidth, newHeight, sdlWindowFlags); + mpRenderer = SDL_CreateRenderer(mpWindow, -1, 0 /* SDL_RendererFlags */); + if (mpRenderer) + { + SDL_GetRendererInfo(mpRenderer, &mRenderInfo); + + mpTexture = SDL_CreateTexture(mpRenderer, desktop_mode.format, + SDL_TEXTUREACCESS_STREAMING, newWidth, newHeight); + if (!mpTexture) + AssertReleaseFailed(); + } + else + AssertReleaseFailed(); + + if (12320 == g_cbIco64x01) + { + gWMIcon = SDL_CreateRGBSurface(0 /* Flags, must be 0 */, 64, 64, 24, 0xff, 0xff00, 0xff0000, 0); + /** @todo make it as simple as possible. No PNM interpreter here... */ + if (gWMIcon) + { + memcpy(gWMIcon->pixels, g_abIco64x01+32, g_cbIco64x01-32); + SDL_SetWindowIcon(mpWindow, gWMIcon); + } + } + } + else + { + int w, h; + uint32_t format; + int access; + + /* resize current window */ + SDL_GetWindowSize(mpWindow, &w, &h); + + if (w != (int)newWidth || h != (int)newHeight) + SDL_SetWindowSize(mpWindow, newWidth, newHeight); + + SDL_QueryTexture(mpTexture, &format, &access, &w, &h); + SDL_DestroyTexture(mpTexture); + mpTexture = SDL_CreateTexture(mpRenderer, format, access, newWidth, newHeight); + if (!mpTexture) + AssertReleaseFailed(); + } +} + +/** + * Update specified framebuffer area. The coordinates can either be + * relative to the guest framebuffer or relative to the screen. + * + * @remarks Must be called from the SDL thread on Linux! + * @param x left column + * @param y top row + * @param w width in pixels + * @param h height in pixels + * @param fGuestRelative flag whether the above values are guest relative or screen relative; + */ +void VBoxSDLFB::update(int x, int y, int w, int h, bool fGuestRelative) +{ +#ifdef VBOXSDL_WITH_X11 + AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n")); +#endif + RTCritSectEnter(&mUpdateLock); + Log(("Updates %d, %d,%d %dx%d\n", mfUpdates, x, y, w, h)); + // printf("Updates %d, %d,%d %dx%d\n", mfUpdates, x, y, w, h); + if (!mfUpdates) + { + RTCritSectLeave(&mUpdateLock); + return; + } + + Assert(mSurfVRAM); + if (!mSurfVRAM) + { + RTCritSectLeave(&mUpdateLock); + return; + } + + /* the source and destination rectangles */ + SDL_Rect srcRect; + SDL_Rect dstRect; + + /* this is how many pixels we have to cut off from the height for this specific blit */ + int yCutoffGuest = 0; + /** + * If we get a SDL window relative update, we + * just perform a full screen update to keep things simple. + * + * @todo improve + */ + if (!fGuestRelative) + { + x = 0; + w = mGuestXRes; + y = 0; + h = mGuestYRes; + } + + srcRect.x = x; + srcRect.y = y + yCutoffGuest; + srcRect.w = w; + srcRect.h = RT_MAX(0, h - yCutoffGuest); + + /* + * Destination rectangle is just offset by the label height. + * There are two cases though: label height is added to the + * guest resolution (mTopOffset == mLabelHeight; yCutoffGuest == 0) + * or the label cuts off a portion of the guest screen (mTopOffset == 0; + * yCutoffGuest >= 0) + */ + dstRect.x = x + mCenterXOffset; + dstRect.y = y + yCutoffGuest + mTopOffset + mCenterYOffset; + dstRect.w = w; + dstRect.h = RT_MAX(0, h - yCutoffGuest); + + SDL_Texture *pNewTexture = SDL_CreateTextureFromSurface(mpRenderer, mSurfVRAM); + /** @todo Do we need to update the dirty rect for the texture for SDL2 here as well? */ + // SDL_RenderClear(mpRenderer); + //SDL_UpdateTexture(mpTexture, &dstRect, mSurfVRAM->pixels, mSurfVRAM->pitch); + // SDL_RenderCopy(mpRenderer, mpTexture, NULL, NULL); + SDL_RenderCopy(mpRenderer, pNewTexture, &srcRect, &dstRect); + SDL_RenderPresent(mpRenderer); + SDL_DestroyTexture(pNewTexture); + RTCritSectLeave(&mUpdateLock); +} + +/** + * Repaint the whole framebuffer + * + * @remarks Must be called from the SDL thread! + */ +void VBoxSDLFB::repaint() +{ + AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n")); + LogFlow(("VBoxSDLFB::repaint\n")); + int w, h; + uint32_t format; + int access; + SDL_QueryTexture(mpTexture, &format, &access, &w, &h); + update(0, 0, w, h, false /* fGuestRelative */); +} + +/** + * Toggle fullscreen mode + * + * @remarks Must be called from the SDL thread! + */ +void VBoxSDLFB::setFullscreen(bool fFullscreen) +{ + AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n")); + LogFlow(("VBoxSDLFB::SetFullscreen: fullscreen: %d\n", fFullscreen)); + mfFullscreen = fFullscreen; + /* only change the SDL resolution, do not touch the guest framebuffer */ + resizeSDL(); + repaint(); +} + +/** + * Return the geometry of the host. This isn't very well tested but it seems + * to work at least on Linux hosts. + */ +void VBoxSDLFB::getFullscreenGeometry(uint32_t *width, uint32_t *height) +{ + SDL_DisplayMode dm; + int rc = SDL_GetDesktopDisplayMode(0, &dm); /** @BUGBUG Handle multi monitor setups! */ + if (rc == 0) + { + *width = dm.w; + *height = dm.w; + } +} + +int VBoxSDLFB::setWindowTitle(const char *pcszTitle) +{ + SDL_SetWindowTitle(mpWindow, pcszTitle); + + return VINF_SUCCESS; +} diff --git a/src/VBox/Frontends/VBoxSDL/Framebuffer.h b/src/VBox/Frontends/VBoxSDL/Framebuffer.h new file mode 100644 index 00000000..3bb1737c --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/Framebuffer.h @@ -0,0 +1,266 @@ +/* $Id: Framebuffer.h $ */ +/** @file + * + * VBox frontends: VBoxSDL (simple frontend based on SDL): + * Declaration of VBoxSDLFB (SDL framebuffer) class + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxSDL_Framebuffer_h +#define VBOX_INCLUDED_SRC_VBoxSDL_Framebuffer_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxSDL.h" +#include <iprt/thread.h> + +#include <iprt/critsect.h> + +class VBoxSDLFBOverlay; + +class ATL_NO_VTABLE VBoxSDLFB : + public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>, + VBOX_SCRIPTABLE_IMPL(IFramebuffer) +{ +public: + VBoxSDLFB(); + virtual ~VBoxSDLFB(); + + HRESULT init(uint32_t uScreenId, + bool fFullscreen, bool fResizable, bool fShowSDLConfig, + bool fKeepHostRes, uint32_t u32FixedWidth, + uint32_t u32FixedHeight, uint32_t u32FixedBPP, + bool fUpdateImage); + + static bool init(bool fShowSDLConfig); + static void uninit(); + + DECLARE_NOT_AGGREGATABLE(VBoxSDLFB) + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(VBoxSDLFB) + COM_INTERFACE_ENTRY(IFramebuffer) + COM_INTERFACE_ENTRY2(IDispatch,IFramebuffer) + COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.m_p) + END_COM_MAP() + + HRESULT FinalConstruct(); + void FinalRelease(); + + STDMETHOD(COMGETTER(Width))(ULONG *width); + STDMETHOD(COMGETTER(Height))(ULONG *height); + STDMETHOD(COMGETTER(BitsPerPixel))(ULONG *bitsPerPixel); + STDMETHOD(COMGETTER(BytesPerLine))(ULONG *bytesPerLine); + STDMETHOD(COMGETTER(PixelFormat))(BitmapFormat_T *pixelFormat); + STDMETHOD(COMGETTER(HeightReduction))(ULONG *heightReduction); + STDMETHOD(COMGETTER(Overlay))(IFramebufferOverlay **aOverlay); + STDMETHOD(COMGETTER(WinId))(LONG64 *winId); + STDMETHOD(COMGETTER(Capabilities))(ComSafeArrayOut(FramebufferCapabilities_T, aCapabilities)); + + STDMETHOD(NotifyUpdate)(ULONG x, ULONG y, ULONG w, ULONG h); + STDMETHOD(NotifyUpdateImage)(ULONG x, ULONG y, ULONG w, ULONG h, ComSafeArrayIn(BYTE, aImage)); + STDMETHOD(NotifyChange)(ULONG aScreenId, + ULONG aXOrigin, + ULONG aYOrigin, + ULONG aWidth, + ULONG aHeight); + STDMETHOD(VideoModeSupported)(ULONG width, ULONG height, ULONG bpp, BOOL *supported); + + STDMETHOD(GetVisibleRegion)(BYTE *aRectangles, ULONG aCount, ULONG *aCountCopied); + STDMETHOD(SetVisibleRegion)(BYTE *aRectangles, ULONG aCount); + + STDMETHOD(ProcessVHWACommand)(BYTE *pCommand, LONG enmCmd, BOOL fGuestCmd); + + STDMETHOD(Notify3DEvent)(ULONG uType, ComSafeArrayIn(BYTE, aData)); + + // internal public methods + bool initialized() { return mfInitialized; } + void notifyChange(ULONG aScreenId); + void resizeGuest(); + void resizeSDL(); + void update(int x, int y, int w, int h, bool fGuestRelative); + void repaint(); + void setFullscreen(bool fFullscreen); + void getFullscreenGeometry(uint32_t *width, uint32_t *height); + uint32_t getScreenId() { return mScreenId; } + uint32_t getGuestXRes() { return mGuestXRes; } + uint32_t getGuestYRes() { return mGuestYRes; } + int32_t getOriginX() { return mOriginX; } + int32_t getOriginY() { return mOriginY; } + int32_t getXOffset() { return mCenterXOffset; } + int32_t getYOffset() { return mCenterYOffset; } + SDL_Window *getWindow() { return mpWindow; } + bool hasWindow(uint32_t id) { return SDL_GetWindowID(mpWindow) == id; } + int setWindowTitle(const char *pcszTitle); + void setWinId(int64_t winId) { mWinId = winId; } + void setOrigin(int32_t axOrigin, int32_t ayOrigin) { mOriginX = axOrigin; mOriginY = ayOrigin; } + bool getFullscreen() { return mfFullscreen; } + +private: + + /** the SDL window */ + SDL_Window *mpWindow; + /** the texture */ + SDL_Texture *mpTexture; + /** renderer */ + SDL_Renderer *mpRenderer; + /** render info */ + SDL_RendererInfo mRenderInfo; + /** false if constructor failed */ + bool mfInitialized; + /** the screen number of this framebuffer */ + uint32_t mScreenId; + /** use NotifyUpdateImage */ + bool mfUpdateImage; + /** maximum possible screen width in pixels (~0 = no restriction) */ + uint32_t mMaxScreenWidth; + /** maximum possible screen height in pixels (~0 = no restriction) */ + uint32_t mMaxScreenHeight; + /** current guest screen width in pixels */ + ULONG mGuestXRes; + /** current guest screen height in pixels */ + ULONG mGuestYRes; + int32_t mOriginX; + int32_t mOriginY; + /** fixed SDL screen width (~0 = not set) */ + uint32_t mFixedSDLWidth; + /** fixed SDL screen height (~0 = not set) */ + uint32_t mFixedSDLHeight; + /** fixed SDL bits per pixel (~0 = not set) */ + uint32_t mFixedSDLBPP; + /** Y offset in pixels, i.e. guest-nondrawable area at the top */ + uint32_t mTopOffset; + /** X offset for guest screen centering */ + uint32_t mCenterXOffset; + /** Y offset for guest screen centering */ + uint32_t mCenterYOffset; + /** flag whether we're in fullscreen mode */ + bool mfFullscreen; + /** flag whether we keep the host screen resolution when switching to + * fullscreen or not */ + bool mfKeepHostRes; + /** framebuffer update semaphore */ + RTCRITSECT mUpdateLock; + /** flag whether the SDL window should be resizable */ + bool mfResizable; + /** flag whether we print out SDL information */ + bool mfShowSDLConfig; + /** handle to window where framebuffer context is being drawn*/ + int64_t mWinId; + SDL_Surface *mSurfVRAM; + + BYTE *mPtrVRAM; + ULONG mBitsPerPixel; + ULONG mBytesPerLine; + BOOL mfSameSizeRequested; + + ComPtr<IDisplaySourceBitmap> mpSourceBitmap; + ComPtr<IDisplaySourceBitmap> mpPendingSourceBitmap; + bool mfUpdates; + +#ifdef RT_OS_WINDOWS + ComPtr<IUnknown> m_pUnkMarshaler; +#endif +}; + +class VBoxSDLFBOverlay : + public IFramebufferOverlay +{ +public: + VBoxSDLFBOverlay(ULONG x, ULONG y, ULONG width, ULONG height, BOOL visible, + VBoxSDLFB *aParent); + virtual ~VBoxSDLFBOverlay(); + +#ifdef RT_OS_WINDOWS + STDMETHOD_(ULONG, AddRef)() + { + return ::InterlockedIncrement(&refcnt); + } + STDMETHOD_(ULONG, Release)() + { + long cnt = ::InterlockedDecrement(&refcnt); + if (cnt == 0) + delete this; + return cnt; + } +#endif + VBOX_SCRIPTABLE_DISPATCH_IMPL(IFramebuffer) + + NS_DECL_ISUPPORTS + + STDMETHOD(COMGETTER(X))(ULONG *x); + STDMETHOD(COMGETTER(Y))(ULONG *y); + STDMETHOD(COMGETTER(Width))(ULONG *width); + STDMETHOD(COMGETTER(Height))(ULONG *height); + STDMETHOD(COMGETTER(Visible))(BOOL *visible); + STDMETHOD(COMSETTER(Visible))(BOOL visible); + STDMETHOD(COMGETTER(Alpha))(ULONG *alpha); + STDMETHOD(COMSETTER(Alpha))(ULONG alpha); + STDMETHOD(COMGETTER(BytesPerLine))(ULONG *bytesPerLine); + + /* These are not used, or return standard values. */ + STDMETHOD(COMGETTER(BitsPerPixel))(ULONG *bitsPerPixel); + STDMETHOD(COMGETTER(PixelFormat))(ULONG *pixelFormat); + STDMETHOD(COMGETTER(UsesGuestVRAM))(BOOL *usesGuestVRAM); + STDMETHOD(COMGETTER(HeightReduction))(ULONG *heightReduction); + STDMETHOD(COMGETTER(Overlay))(IFramebufferOverlay **aOverlay); + STDMETHOD(COMGETTER(WinId))(LONG64 *winId); + + STDMETHOD(Lock)(); + STDMETHOD(Unlock)(); + STDMETHOD(Move)(ULONG x, ULONG y); + STDMETHOD(NotifyUpdate)(ULONG x, ULONG y, ULONG w, ULONG h); + STDMETHOD(RequestResize)(ULONG aScreenId, ULONG pixelFormat, ULONG vram, + ULONG bitsPerPixel, ULONG bytesPerLine, + ULONG w, ULONG h, BOOL *finished); + STDMETHOD(VideoModeSupported)(ULONG width, ULONG height, ULONG bpp, BOOL *supported); + + // internal public methods + HRESULT init(); + +private: + /** Overlay X offset */ + ULONG mOverlayX; + /** Overlay Y offset */ + ULONG mOverlayY; + /** Overlay width */ + ULONG mOverlayWidth; + /** Overlay height */ + ULONG mOverlayHeight; + /** Whether the overlay is currently active */ + BOOL mOverlayVisible; + /** The parent IFramebuffer */ + VBoxSDLFB *mParent; + /** SDL surface containing the actual framebuffer bits */ + SDL_Surface *mOverlayBits; + /** Additional SDL surface used for combining the framebuffer and the overlay */ + SDL_Surface *mBlendedBits; +#ifdef RT_OS_WINDOWS + long refcnt; +#endif +}; + +#endif /* !VBOX_INCLUDED_SRC_VBoxSDL_Framebuffer_h */ diff --git a/src/VBox/Frontends/VBoxSDL/Helper.cpp b/src/VBox/Frontends/VBoxSDL/Helper.cpp new file mode 100644 index 00000000..3e61251d --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/Helper.cpp @@ -0,0 +1,179 @@ +/* $Id: Helper.cpp $ */ +/** @file + * + * VBox frontends: VBoxSDL (simple frontend based on SDL): + * Miscellaneous helpers + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_GUI +#include <iprt/errcore.h> +#include <VBox/log.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include "VBoxSDL.h" +#include "Helper.h" + + +/** + * Globals + */ + + +#ifdef USE_XPCOM_QUEUE_THREAD + +/** global flag indicating that the event queue thread should terminate */ +static bool volatile g_fTerminateXPCOMQueueThread = false; + +/** How many XPCOM user events are on air. Only allow one pending event to + * prevent an overflow of the SDL event queue. */ +static volatile int32_t g_s32XPCOMEventsPending; + +/** Semaphore the XPCOM event thread will sleep on while it waits for the main thread to process pending requests. */ +RTSEMEVENT g_EventSemXPCOMQueueThread = NULL; + +/** + * Thread method to wait for XPCOM events and notify the SDL thread. + * + * @returns Error code + * @param thread Thread ID + * @param pvUser User specific parameter, the file descriptor + * of the event queue socket + */ +DECLCALLBACK(int) xpcomEventThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + int eqFD = (intptr_t)pvUser; + unsigned cErrors = 0; + int rc; + + /* Wait with the processing till the main thread needs it. */ + RTSemEventWait(g_EventSemXPCOMQueueThread, 2500); + + do + { + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(eqFD, &fdset); + int n = select(eqFD + 1, &fdset, NULL, NULL, NULL); + + /* are there any events to process? */ + if ((n > 0) && !g_fTerminateXPCOMQueueThread) + { + /* + * Wait until all XPCOM events are processed. 1s just for sanity. + */ + int iWait = 1000; + /* + * Don't post an event if there is a pending XPCOM event to prevent an + * overflow of the SDL event queue. + */ + if (g_s32XPCOMEventsPending < 1) + { + /* + * Post the event and wait for it to be processed. If we don't wait, + * we'll flood the queue on SMP systems and when the main thread is busy. + * In the event of a push error, we'll yield the timeslice and retry. + */ + SDL_Event event = {0}; + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_XPCOM_EVENTQUEUE; + rc = SDL_PushEvent(&event); + if (!rc) + { + /* success */ + ASMAtomicIncS32(&g_s32XPCOMEventsPending); + cErrors = 0; + } + else + { + /* failure */ + cErrors++; + if (!RTThreadYield()) + RTThreadSleep(2); + iWait = (cErrors >= 10) ? RT_MIN(cErrors - 8, 50) : 0; + } + } + else + Log2(("not enqueueing SDL XPCOM event (%d)\n", g_s32XPCOMEventsPending)); + + if (iWait) + RTSemEventWait(g_EventSemXPCOMQueueThread, iWait); + } + } while (!g_fTerminateXPCOMQueueThread); + return VINF_SUCCESS; +} + +/** + * Creates the XPCOM event thread + * + * @returns VBOX status code + * @param eqFD XPCOM event queue file descriptor + */ +int startXPCOMEventQueueThread(int eqFD) +{ + int rc = RTSemEventCreate(&g_EventSemXPCOMQueueThread); + if (RT_SUCCESS(rc)) + { + RTTHREAD Thread; + rc = RTThreadCreate(&Thread, xpcomEventThread, (void *)(intptr_t)eqFD, + 0, RTTHREADTYPE_MSG_PUMP, 0, "XPCOMEvent"); + } + AssertRC(rc); + return rc; +} + +/** + * Notify the XPCOM thread that we consumed an XPCOM event. + */ +void consumedXPCOMUserEvent(void) +{ + ASMAtomicDecS32(&g_s32XPCOMEventsPending); +} + +/** + * Signal to the XPCOM even queue thread that it should select for more events. + */ +void signalXPCOMEventQueueThread(void) +{ + int rc = RTSemEventSignal(g_EventSemXPCOMQueueThread); + AssertRC(rc); +} + +/** + * Indicates to the XPCOM thread that it should terminate now. + */ +void terminateXPCOMQueueThread(void) +{ + g_fTerminateXPCOMQueueThread = true; + if (g_EventSemXPCOMQueueThread) + { + RTSemEventSignal(g_EventSemXPCOMQueueThread); + RTThreadYield(); + } +} + +#endif /* USE_XPCOM_QUEUE_THREAD */ diff --git a/src/VBox/Frontends/VBoxSDL/Helper.h b/src/VBox/Frontends/VBoxSDL/Helper.h new file mode 100644 index 00000000..b0b33bd7 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/Helper.h @@ -0,0 +1,67 @@ +/* $Id: Helper.h $ */ +/** @file + * + * VBox frontends: VBoxSDL (simple frontend based on SDL): + * Miscellaneous helpers header + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxSDL_Helper_h +#define VBOX_INCLUDED_SRC_VBoxSDL_Helper_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2) + +/** Indicates that the XPCOM queue thread is needed for this platform. */ +# define USE_XPCOM_QUEUE_THREAD 1 + +/** + * Creates the XPCOM event thread + * + * @returns VBOX status code + * @param eqFD XPCOM event queue file descriptor + */ +int startXPCOMEventQueueThread(int eqFD); + +/* + * Notify the XPCOM thread that we consumed an XPCOM event + */ +void consumedXPCOMUserEvent(void); + +/** + * Signal to the XPCOM even queue thread that it should select for more events. + */ +void signalXPCOMEventQueueThread(void); + +/** + * Indicates to the XPCOM thread that it should terminate now. + */ +void terminateXPCOMQueueThread(void); + +#endif /* defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2) */ + +#endif /* !VBOX_INCLUDED_SRC_VBoxSDL_Helper_h */ + diff --git a/src/VBox/Frontends/VBoxSDL/Makefile.kmk b/src/VBox/Frontends/VBoxSDL/Makefile.kmk new file mode 100644 index 00000000..da431533 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/Makefile.kmk @@ -0,0 +1,167 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxSDL (a simple frontend based on SDL). +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk +if !defined(VBOX_WITH_HARDENING) || "$(KBUILD_TARGET)" != "darwin" # No hardened VBoxSDL on darwin. + + + ifdef VBOX_WITH_HARDENING + # + # Hardened VBoxSDL + # + PROGRAMS += VBoxSDLHardened + VBoxSDLHardened_TEMPLATE = VBoxR3HardenedExe + VBoxSDLHardened_SOURCES = VBoxSDLHardened.cpp + VBoxSDLHardened_NAME = VBoxSDL + $(call VBOX_SET_VER_INFO_EXE,VBoxSDLHardened,VirtualBox Pure SDL Frontend,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. + endif + + + # + # VBoxSDL + # + ifdef VBOX_WITH_HARDENING + DLLS += VBoxSDL + else + PROGRAMS += VBoxSDL + endif + VBoxSDL_TEMPLATE := $(if $(VBOX_WITH_HARDENING),VBoxMainClientDll,VBoxMainClientExe) + VBoxSDL_SDKS = LIBSDL2 + VBoxSDL_SOURCES = \ + VBoxSDL.cpp \ + Framebuffer.cpp \ + Helper.cpp \ + ../Common/PasswordInput.cpp + VBoxSDL_SOURCES.darwin = \ + VBoxSDLMain-darwin.m \ + Framebuffer-darwin.m + + VBoxSDL_DEFS = + VBoxSDL_DEFS.freebsd = VBOXSDL_WITH_X11 + VBoxSDL_DEFS.linux = _GNU_SOURCE VBOXSDL_WITH_X11 + VBoxSDL_DEFS.solaris = VBOXSDL_WITH_X11 + ifdef VBOX_OPENGL + #VBoxSDL_DEFS.linux += VBOX_OPENGL + endif + VBoxSDL_DEFS.win.x86 = _WIN32_WINNT=0x0500 + VBoxSDL_DEFS.win.amd64 = _WIN32_WINNT=0x0510 + + VBoxSDL_INCS = \ + $(VBoxSDL_0_OUTDIR) \ + $(VBOX_GRAPHICS_INCS) \ + ../Common + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) # X11 + VBoxSDL_INCS += \ + $(VBOX_XCURSOR_INCS) + endif + ifn1of ($(KBUILD_TARGET), solaris) # Probably wrong with SDL2 + VBoxSDL_LIBS = \ + $(LIB_SDK_LIBSDL2_SDLMAIN) + endif + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) # X11 + VBoxSDL_LIBS += \ + $(PATH_STAGE_DLL)/VBoxKeyboard$(VBOX_SUFF_DLL) \ + $(VBOX_XCURSOR_LIBS) \ + X11 + VBoxSDL_LIBPATH = \ + $(VBOX_LIBPATH_X11) + endif + ifdef VBOX_OPENGL + #VBoxSDL_LIBS.linux += GL + endif + + VBoxSDL_LDFLAGS.darwin = \ + -framework Foundation -framework AppKit + VBoxSDL_LDFLAGS.win = -SubSystem:Windows + + VBoxSDL_CLEAN = $(VBoxSDL_0_OUTDIR)/Ico64x01.h + VBoxSDL_INTERMEDIATES = $(VBoxSDL_0_OUTDIR)/Ico64x01.h + + + # Convert the pnm-file to a byte array. + $$(VBoxSDL_0_OUTDIR)/Ico64x01.h: $(PATH_ROOT)/src/VBox/Frontends/VBoxSDL/ico64x01.pnm $(VBOX_BIN2C) | $$(dir $$@) + $(call MSG_TOOL,bin2c,VBoxSDL,$<,$@) + $(QUIET)$(VBOX_BIN2C) Ico64x01 $< $@ + + ifdef VBOX_WITH_HARDENING + $(call VBOX_SET_VER_INFO_DLL,VBoxSDL,VirtualBox Pure SDL Frontend,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. + else + $(call VBOX_SET_VER_INFO_EXE,VBoxSDL,VirtualBox Pure SDL Frontend,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. + endif + + + # + # tstSDL + # + if 0 + PROGRAMS += tstSDL + tstSDL_TEMPLATE = VBoxR3TstExe + tstSDL_SDKS = LIBSDL2 + tstSDL_INST = $(INST_TESTCASE) + tstSDL_SOURCES = \ + VBoxSDLTest.cpp + tstSDL_SOURCES.darwin = \ + VBoxSDLMain-darwin.m + tstSDL_DEFS = IN_RING3 IN_RT_R3 _GNU_SOURCE + tstSDL_DEFS.win.x86 = _WIN32_WINNT=0x0500 + ifdef VBOX_OPENGL + tstSDL_DEFS.linux = VBOX_OPENGL + endif + + + tstSDL_LIBS = \ + $(LIB_RUNTIME) + ifn1of ($(KBUILD_TARGET), solaris) + tstSDL_LIBS += \ + $(LIB_SDK_LIBSDL2_SDLMAIN) + endif + + ifdef VBOX_OPENGL + tstSDL_LIBS.linux += GL + endif + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) # X11 + tstSDL_LIBPATH = \ + $(VBOX_LIBPATH_X11) + endif + + tstSDL_LDFLAGS.darwin = \ + -framework Foundation -framework AppKit + endif + ## @todo What was this stuff doing here? The exception config is saying two different things, and why just -O for release builds? + #tstSDL_CXXFLAGS.win = \ + # -EHsc + #tstSDL_CXXFLAGS.linux = \ + # -DNDEBUG -DTRIMMED -O -Wall -fno-rtti -fno-exceptions \ + # -Wno-non-virtual-dtor -Wno-long-long -fshort-wchar -pthread -pipe + # Is this what's intended? Why -fshort-wchar? + tstSDL_DEFS.linux = NDEBUG TRIMMED + tstSDL_CXXFLAGS.linux = -O -Wall -Wno-non-virtual-dtor -Wno-long-long -fshort-wchar + + +endif # !VBOX_WITH_HARDENING || "$(KBUILD_TARGET)" != "darwin" +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Frontends/VBoxSDL/VBoxSDL.cpp b/src/VBox/Frontends/VBoxSDL/VBoxSDL.cpp new file mode 100644 index 00000000..065c391c --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/VBoxSDL.cpp @@ -0,0 +1,4798 @@ +/* $Id: VBoxSDL.cpp $ */ +/** @file + * VBox frontends: VBoxSDL (simple frontend based on SDL): + * Main code + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUI + +#include <iprt/stream.h> + +#include <VBox/com/com.h> +#include <VBox/com/string.h> +#include <VBox/com/Guid.h> +#include <VBox/com/array.h> +#include <VBox/com/ErrorInfo.h> +#include <VBox/com/errorprint.h> + +#include <VBox/com/NativeEventQueue.h> +#include <VBox/com/VirtualBox.h> + +using namespace com; + +#if defined(VBOXSDL_WITH_X11) +# include <VBox/VBoxKeyboard.h> + +# include <X11/Xlib.h> +# include <X11/cursorfont.h> /* for XC_left_ptr */ +# if !defined(VBOX_WITHOUT_XCURSOR) +# include <X11/Xcursor/Xcursor.h> +# endif +# include <unistd.h> +#endif + +#include "VBoxSDL.h" + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4121) /* warning C4121: 'SDL_SysWMmsg' : alignment of a member was sensitive to packing*/ +#endif +#ifndef RT_OS_DARWIN +# include <SDL_syswm.h> /* for SDL_GetWMInfo() */ +#endif +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#include "Framebuffer.h" +#include "Helper.h" + +#include <VBox/types.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/log.h> +#include <VBox/version.h> +#include <VBoxVideo.h> +#include <VBox/com/listeners.h> + +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/ldr.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/uuid.h> + +#include <signal.h> + +#include <vector> +#include <list> + +#include "PasswordInput.h" + +/* Xlib would re-define our enums */ +#undef True +#undef False + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Enables the rawr[0|3], patm, and casm options. */ +#define VBOXSDL_ADVANCED_OPTIONS + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer shape change event data structure */ +struct PointerShapeChangeData +{ + PointerShapeChangeData(BOOL aVisible, BOOL aAlpha, ULONG aXHot, ULONG aYHot, + ULONG aWidth, ULONG aHeight, ComSafeArrayIn(BYTE,pShape)) + : visible(aVisible), alpha(aAlpha), xHot(aXHot), yHot(aYHot), + width(aWidth), height(aHeight) + { + // make a copy of the shape + com::SafeArray<BYTE> aShape(ComSafeArrayInArg(pShape)); + size_t cbShapeSize = aShape.size(); + if (cbShapeSize > 0) + { + shape.resize(cbShapeSize); + ::memcpy(shape.raw(), aShape.raw(), cbShapeSize); + } + } + + ~PointerShapeChangeData() + { + } + + const BOOL visible; + const BOOL alpha; + const ULONG xHot; + const ULONG yHot; + const ULONG width; + const ULONG height; + com::SafeArray<BYTE> shape; +}; + +enum TitlebarMode +{ + TITLEBAR_NORMAL = 1, + TITLEBAR_STARTUP = 2, + TITLEBAR_SAVE = 3, + TITLEBAR_SNAPSHOT = 4 +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static bool UseAbsoluteMouse(void); +static void ResetKeys(void); +static void ProcessKey(SDL_KeyboardEvent *ev); +static void InputGrabStart(void); +static void InputGrabEnd(void); +static void SendMouseEvent(VBoxSDLFB *fb, int dz, int button, int down); +static void UpdateTitlebar(TitlebarMode mode, uint32_t u32User = 0); +static void SetPointerShape(const PointerShapeChangeData *data); +static void HandleGuestCapsChanged(void); +static int HandleHostKey(const SDL_KeyboardEvent *pEv); +static Uint32 StartupTimer(Uint32 interval, void *param) RT_NOTHROW_PROTO; +static Uint32 ResizeTimer(Uint32 interval, void *param) RT_NOTHROW_PROTO; +static Uint32 QuitTimer(Uint32 interval, void *param) RT_NOTHROW_PROTO; +static int WaitSDLEvent(SDL_Event *event); +static void SetFullscreen(bool enable); +static VBoxSDLFB *getFbFromWinId(Uint32 id); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static int gHostKeyMod = KMOD_RCTRL; +static int gHostKeySym1 = SDLK_RCTRL; +static int gHostKeySym2 = SDLK_UNKNOWN; +static const char *gHostKeyDisabledCombinations = ""; +static const char *gpszPidFile; +static BOOL gfGrabbed = FALSE; +static BOOL gfGrabOnMouseClick = TRUE; +static BOOL gfFullscreenResize = FALSE; +static BOOL gfIgnoreNextResize = FALSE; +static BOOL gfAllowFullscreenToggle = TRUE; +static BOOL gfAbsoluteMouseHost = FALSE; +static BOOL gfAbsoluteMouseGuest = FALSE; +static BOOL gfRelativeMouseGuest = TRUE; +static BOOL gfGuestNeedsHostCursor = FALSE; +static BOOL gfOffCursorActive = FALSE; +static BOOL gfGuestNumLockPressed = FALSE; +static BOOL gfGuestCapsLockPressed = FALSE; +static BOOL gfGuestScrollLockPressed = FALSE; +static BOOL gfACPITerm = FALSE; +static BOOL gfXCursorEnabled = FALSE; +static int gcGuestNumLockAdaptions = 2; +static int gcGuestCapsLockAdaptions = 2; +static uint32_t gmGuestNormalXRes; +static uint32_t gmGuestNormalYRes; + +/** modifier keypress status (scancode as index) */ +static uint8_t gaModifiersState[256]; + +static ComPtr<IMachine> gpMachine; +static ComPtr<IConsole> gpConsole; +static ComPtr<IMachineDebugger> gpMachineDebugger; +static ComPtr<IKeyboard> gpKeyboard; +static ComPtr<IMouse> gpMouse; +ComPtr<IDisplay> gpDisplay; +static ComPtr<IVRDEServer> gpVRDEServer; +static ComPtr<IProgress> gpProgress; + +static ULONG gcMonitors = 1; +static ComObjPtr<VBoxSDLFB> gpFramebuffer[64]; +static Bstr gaFramebufferId[64]; +static SDL_Cursor *gpDefaultCursor = NULL; +static SDL_Cursor *gpOffCursor = NULL; +static SDL_TimerID gSdlResizeTimer = 0; +static SDL_TimerID gSdlQuitTimer = 0; + +static RTSEMEVENT g_EventSemSDLEvents; +static volatile int32_t g_cNotifyUpdateEventsPending; + +/** + * Event handler for VirtualBoxClient events + */ +class VBoxSDLClientEventListener +{ +public: + VBoxSDLClientEventListener() + { + } + + virtual ~VBoxSDLClientEventListener() + { + } + + HRESULT init() + { + return S_OK; + } + + void uninit() + { + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent) + { + switch (aType) + { + case VBoxEventType_OnVBoxSVCAvailabilityChanged: + { + ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent; + Assert(pVSACEv); + BOOL fAvailable = FALSE; + pVSACEv->COMGETTER(Available)(&fAvailable); + if (!fAvailable) + { + LogRel(("VBoxSDL: VBoxSVC became unavailable, exiting.\n")); + RTPrintf("VBoxSVC became unavailable, exiting.\n"); + /* Send QUIT event to terminate the VM as cleanly as possible + * given that VBoxSVC is no longer present. */ + SDL_Event event = {0}; + event.type = SDL_QUIT; + PushSDLEventForSure(&event); + } + break; + } + + default: + AssertFailed(); + } + + return S_OK; + } +}; + +/** + * Event handler for VirtualBox (server) events + */ +class VBoxSDLEventListener +{ +public: + VBoxSDLEventListener() + { + } + + virtual ~VBoxSDLEventListener() + { + } + + HRESULT init() + { + return S_OK; + } + + void uninit() + { + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent) + { + RT_NOREF(aEvent); + switch (aType) + { + case VBoxEventType_OnExtraDataChanged: + break; + default: + AssertFailed(); + } + + return S_OK; + } +}; + +/** + * Event handler for Console events + */ +class VBoxSDLConsoleEventListener +{ +public: + VBoxSDLConsoleEventListener() : m_fIgnorePowerOffEvents(false) + { + } + + virtual ~VBoxSDLConsoleEventListener() + { + } + + HRESULT init() + { + return S_OK; + } + + void uninit() + { + } + + STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent) + { + // likely all this double copy is now excessive, and we can just use existing event object + /// @todo eliminate it + switch (aType) + { + case VBoxEventType_OnMousePointerShapeChanged: + { + ComPtr<IMousePointerShapeChangedEvent> pMPSCEv = aEvent; + Assert(pMPSCEv); + PointerShapeChangeData *data; + BOOL visible, alpha; + ULONG xHot, yHot, width, height; + com::SafeArray<BYTE> shape; + + pMPSCEv->COMGETTER(Visible)(&visible); + pMPSCEv->COMGETTER(Alpha)(&alpha); + pMPSCEv->COMGETTER(Xhot)(&xHot); + pMPSCEv->COMGETTER(Yhot)(&yHot); + pMPSCEv->COMGETTER(Width)(&width); + pMPSCEv->COMGETTER(Height)(&height); + pMPSCEv->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape)); + data = new PointerShapeChangeData(visible, alpha, xHot, yHot, width, height, + ComSafeArrayAsInParam(shape)); + Assert(data); + if (!data) + break; + + SDL_Event event = {0}; + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_POINTER_CHANGE; + event.user.data1 = data; + + int rc = PushSDLEventForSure(&event); + if (rc) + delete data; + + break; + } + case VBoxEventType_OnMouseCapabilityChanged: + { + ComPtr<IMouseCapabilityChangedEvent> pMCCEv = aEvent; + Assert(pMCCEv); + pMCCEv->COMGETTER(SupportsAbsolute)(&gfAbsoluteMouseGuest); + pMCCEv->COMGETTER(SupportsRelative)(&gfRelativeMouseGuest); + pMCCEv->COMGETTER(NeedsHostCursor)(&gfGuestNeedsHostCursor); + SDL_Event event = {0}; + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_GUEST_CAP_CHANGED; + + PushSDLEventForSure(&event); + break; + } + case VBoxEventType_OnKeyboardLedsChanged: + { + ComPtr<IKeyboardLedsChangedEvent> pCLCEv = aEvent; + Assert(pCLCEv); + BOOL fNumLock, fCapsLock, fScrollLock; + pCLCEv->COMGETTER(NumLock)(&fNumLock); + pCLCEv->COMGETTER(CapsLock)(&fCapsLock); + pCLCEv->COMGETTER(ScrollLock)(&fScrollLock); + /* Don't bother the guest with NumLock scancodes if he doesn't set the NumLock LED */ + if (gfGuestNumLockPressed != fNumLock) + gcGuestNumLockAdaptions = 2; + if (gfGuestCapsLockPressed != fCapsLock) + gcGuestCapsLockAdaptions = 2; + gfGuestNumLockPressed = fNumLock; + gfGuestCapsLockPressed = fCapsLock; + gfGuestScrollLockPressed = fScrollLock; + break; + } + + case VBoxEventType_OnStateChanged: + { + ComPtr<IStateChangedEvent> pSCEv = aEvent; + Assert(pSCEv); + MachineState_T machineState; + pSCEv->COMGETTER(State)(&machineState); + LogFlow(("OnStateChange: machineState = %d (%s)\n", machineState, GetStateName(machineState))); + SDL_Event event = {0}; + + if ( machineState == MachineState_Aborted + || machineState == MachineState_Teleported + || (machineState == MachineState_Saved && !m_fIgnorePowerOffEvents) + || (machineState == MachineState_AbortedSaved && !m_fIgnorePowerOffEvents) + || (machineState == MachineState_PoweredOff && !m_fIgnorePowerOffEvents) + ) + { + /* + * We have to inform the SDL thread that the application has be terminated + */ + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_TERMINATE; + event.user.code = machineState == MachineState_Aborted + ? VBOXSDL_TERM_ABEND + : VBOXSDL_TERM_NORMAL; + } + else + { + /* + * Inform the SDL thread to refresh the titlebar + */ + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_UPDATE_TITLEBAR; + } + + PushSDLEventForSure(&event); + break; + } + + case VBoxEventType_OnRuntimeError: + { + ComPtr<IRuntimeErrorEvent> pRTEEv = aEvent; + Assert(pRTEEv); + BOOL fFatal; + + pRTEEv->COMGETTER(Fatal)(&fFatal); + MachineState_T machineState; + gpMachine->COMGETTER(State)(&machineState); + const char *pszType; + bool fPaused = machineState == MachineState_Paused; + if (fFatal) + pszType = "FATAL ERROR"; + else if (machineState == MachineState_Paused) + pszType = "Non-fatal ERROR"; + else + pszType = "WARNING"; + Bstr bstrId, bstrMessage; + pRTEEv->COMGETTER(Id)(bstrId.asOutParam()); + pRTEEv->COMGETTER(Message)(bstrMessage.asOutParam()); + RTPrintf("\n%s: ** %ls **\n%ls\n%s\n", pszType, bstrId.raw(), bstrMessage.raw(), + fPaused ? "The VM was paused. Continue with HostKey + P after you solved the problem.\n" : ""); + break; + } + + case VBoxEventType_OnCanShowWindow: + { + ComPtr<ICanShowWindowEvent> pCSWEv = aEvent; + Assert(pCSWEv); +#ifdef RT_OS_DARWIN + /* SDL feature not available on Quartz */ +#else + bool fCanShow = false; + Uint32 winId = 0; + VBoxSDLFB *fb = getFbFromWinId(winId); + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if (SDL_GetWindowWMInfo(fb->getWindow(), &info)) + fCanShow = true; + if (fCanShow) + pCSWEv->AddApproval(NULL); + else + pCSWEv->AddVeto(NULL); +#endif + break; + } + + case VBoxEventType_OnShowWindow: + { + ComPtr<IShowWindowEvent> pSWEv = aEvent; + Assert(pSWEv); + LONG64 winId = 0; + pSWEv->COMGETTER(WinId)(&winId); + if (winId != 0) + break; /* WinId already set by some other listener. */ +#ifndef RT_OS_DARWIN + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + VBoxSDLFB *fb = getFbFromWinId(winId); + if (SDL_GetWindowWMInfo(fb->getWindow(), &info)) + { +# if defined(VBOXSDL_WITH_X11) + pSWEv->COMSETTER(WinId)((LONG64)info.info.x11.window); +# elif defined(RT_OS_WINDOWS) + pSWEv->COMSETTER(WinId)((intptr_t)info.info.win.window); +# else /* !RT_OS_WINDOWS */ + AssertFailed(); +# endif + } +#endif /* !RT_OS_DARWIN */ + break; + } + + default: + AssertFailed(); + } + return S_OK; + } + + static const char *GetStateName(MachineState_T machineState) + { + switch (machineState) + { + case MachineState_Null: return "<null>"; + case MachineState_PoweredOff: return "PoweredOff"; + case MachineState_Saved: return "Saved"; + case MachineState_Teleported: return "Teleported"; + case MachineState_Aborted: return "Aborted"; + case MachineState_AbortedSaved: return "Aborted-Saved"; + case MachineState_Running: return "Running"; + case MachineState_Teleporting: return "Teleporting"; + case MachineState_LiveSnapshotting: return "LiveSnapshotting"; + case MachineState_Paused: return "Paused"; + case MachineState_Stuck: return "GuruMeditation"; + case MachineState_Starting: return "Starting"; + case MachineState_Stopping: return "Stopping"; + case MachineState_Saving: return "Saving"; + case MachineState_Restoring: return "Restoring"; + case MachineState_TeleportingPausedVM: return "TeleportingPausedVM"; + case MachineState_TeleportingIn: return "TeleportingIn"; + case MachineState_RestoringSnapshot: return "RestoringSnapshot"; + case MachineState_DeletingSnapshot: return "DeletingSnapshot"; + case MachineState_SettingUp: return "SettingUp"; + default: return "no idea"; + } + } + + void ignorePowerOffEvents(bool fIgnore) + { + m_fIgnorePowerOffEvents = fIgnore; + } + +private: + bool m_fIgnorePowerOffEvents; +}; + +typedef ListenerImpl<VBoxSDLClientEventListener> VBoxSDLClientEventListenerImpl; +typedef ListenerImpl<VBoxSDLEventListener> VBoxSDLEventListenerImpl; +typedef ListenerImpl<VBoxSDLConsoleEventListener> VBoxSDLConsoleEventListenerImpl; + +static void show_usage() +{ + RTPrintf("Usage:\n" + " --startvm <uuid|name> Virtual machine to start, either UUID or name\n" + " --separate Run a separate VM process or attach to a running VM\n" + " --hda <file> Set temporary first hard disk to file\n" + " --fda <file> Set temporary first floppy disk to file\n" + " --cdrom <file> Set temporary CDROM/DVD to file/device ('none' to unmount)\n" + " --boot <a|c|d|n> Set temporary boot device (a = floppy, c = 1st HD, d = DVD, n = network)\n" + " --memory <size> Set temporary memory size in megabytes\n" + " --vram <size> Set temporary size of video memory in megabytes\n" + " --fullscreen Start VM in fullscreen mode\n" + " --fullscreenresize Resize the guest on fullscreen\n" + " --fixedmode <w> <h> <bpp> Use a fixed SDL video mode with given width, height and bits per pixel\n" + " --nofstoggle Forbid switching to/from fullscreen mode\n" + " --noresize Make the SDL frame non resizable\n" + " --nohostkey Disable all hostkey combinations\n" + " --nohostkeys ... Disable specific hostkey combinations, see below for valid keys\n" + " --nograbonclick Disable mouse/keyboard grabbing on mouse click w/o additions\n" + " --detecthostkey Get the hostkey identifier and modifier state\n" + " --hostkey <key> {<key2>} <mod> Set the host key to the values obtained using --detecthostkey\n" + " --termacpi Send an ACPI power button event when closing the window\n" + " --vrdp <ports> Listen for VRDP connections on one of specified ports (default if not specified)\n" + " --discardstate Discard saved state (if present) and revert to last snapshot (if present)\n" + " --settingspw <pw> Specify the settings password\n" + " --settingspwfile <file> Specify a file containing the settings password\n" +#ifdef VBOXSDL_ADVANCED_OPTIONS + " --warpdrive <pct> Sets the warp driver rate in percent (100 = normal)\n" +#endif + "\n" + "Key bindings:\n" + " <hostkey> + f Switch to full screen / restore to previous view\n" + " h Press ACPI power button\n" + " n Take a snapshot and continue execution\n" + " p Pause / resume execution\n" + " q Power off\n" + " r VM reset\n" + " s Save state and power off\n" + " <del> Send <ctrl><alt><del>\n" + " <F1>...<F12> Send <ctrl><alt><Fx>\n" +#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS) + "\n" + "Further key bindings useful for debugging:\n" + " LCtrl + Alt + F12 Reset statistics counter\n" + " LCtrl + Alt + F11 Dump statistics to logfile\n" + " Alt + F8 Toggle single step mode\n" + " LCtrl/RCtrl + F12 Toggle logger\n" + " F12 Write log marker to logfile\n" +#endif + "\n"); +} + +static void PrintError(const char *pszName, CBSTR pwszDescr, CBSTR pwszComponent=NULL) +{ + const char *pszFile, *pszFunc, *pszStat; + char pszBuffer[1024]; + com::ErrorInfo info; + + RTStrPrintf(pszBuffer, sizeof(pszBuffer), "%ls", pwszDescr); + + RTPrintf("\n%s! Error info:\n", pszName); + if ( (pszFile = strstr(pszBuffer, "At '")) + && (pszFunc = strstr(pszBuffer, ") in ")) + && (pszStat = strstr(pszBuffer, "VBox status code: "))) + RTPrintf(" %.*s %.*s\n In%.*s %s", + pszFile-pszBuffer, pszBuffer, + pszFunc-pszFile+1, pszFile, + pszStat-pszFunc-4, pszFunc+4, + pszStat); + else + RTPrintf("%s\n", pszBuffer); + + if (pwszComponent) + RTPrintf("(component %ls).\n", pwszComponent); + + RTPrintf("\n"); +} + +#ifdef VBOXSDL_WITH_X11 +/** + * Custom signal handler. Currently it is only used to release modifier + * keys when receiving the USR1 signal. When switching VTs, we might not + * get release events for Ctrl-Alt and in case a savestate is performed + * on the new VT, the VM will be saved with modifier keys stuck. This is + * annoying enough for introducing this hack. + */ +void signal_handler_SIGUSR1(int sig, siginfo_t *info, void *secret) +{ + RT_NOREF(info, secret); + + /* only SIGUSR1 is interesting */ + if (sig == SIGUSR1) + { + /* just release the modifiers */ + ResetKeys(); + } +} + +/** + * Custom signal handler for catching exit events. + */ +void signal_handler_SIGINT(int sig) +{ + if (gpszPidFile) + RTFileDelete(gpszPidFile); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGSEGV, SIG_DFL); + kill(getpid(), sig); +} +#endif /* VBOXSDL_WITH_X11 */ + +/** + * Returns a stringified version of a keyboard modifier. + * + * @returns Stringified version of the keyboard modifier. + * @param mod Modifier code to return a stringified version for. + */ +static const char *keyModToStr(unsigned mod) +{ + switch (mod) + { + RT_CASE_RET_STR(KMOD_NONE); + RT_CASE_RET_STR(KMOD_LSHIFT); + RT_CASE_RET_STR(KMOD_RSHIFT); + RT_CASE_RET_STR(KMOD_LCTRL); + RT_CASE_RET_STR(KMOD_RCTRL); + RT_CASE_RET_STR(KMOD_LALT); + RT_CASE_RET_STR(KMOD_RALT); + RT_CASE_RET_STR(KMOD_LGUI); + RT_CASE_RET_STR(KMOD_RGUI); + RT_CASE_RET_STR(KMOD_NUM); + RT_CASE_RET_STR(KMOD_CAPS); + RT_CASE_RET_STR(KMOD_MODE); + RT_CASE_RET_STR(KMOD_SCROLL); + default: + break; + } + + return "<Unknown>"; +} + +/** + * Handles detecting a host key by printing its values to stdout. + * + * @returns RTEXITCODE + */ +static RTEXITCODE handleDetectHostKey(void) +{ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + int rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER); + if (rc == 0) + { + /* We need a window, otherwise we won't get any keypress events. */ + SDL_Window *pWnd = SDL_CreateWindow("VBoxSDL", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN); + RTPrintf("Please hit one or two function key(s) to get the --hostkey value. ..\n"); + RTPrintf("Press CTRL+C to quit.\n"); + SDL_Event e1; + while (SDL_WaitEvent(&e1)) + { + if ( e1.key.keysym.sym == SDLK_c + && (e1.key.keysym.mod & KMOD_CTRL) != 0) + break; + if (e1.type == SDL_QUIT) + break; + if (e1.type == SDL_KEYDOWN) + { + unsigned const mod = SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED); + RTPrintf("--hostkey %d", e1.key.keysym.sym); + if (mod) + RTPrintf(" %d\n", mod); + else + RTPrintf("\n"); + + if (mod) + RTPrintf("Host key is '%s' + '%s'\n", keyModToStr(mod), SDL_GetKeyName(e1.key.keysym.sym)); + else + RTPrintf("Host key is '%s'\n", SDL_GetKeyName(e1.key.keysym.sym)); + } + } + SDL_DestroyWindow(pWnd); + SDL_Quit(); + } + else + { + RTPrintf("Error: SDL_InitSubSystem failed with message '%s'\n", SDL_GetError()); + rcExit = RTEXITCODE_FAILURE; + } + + return rcExit; +} + +/** entry point */ +extern "C" +DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) +{ + RT_NOREF(envp); +#ifdef RT_OS_WINDOWS + /* As we run with the WINDOWS subsystem, we need to either attach to or create an own console + * to get any stdout / stderr output. */ + bool fAllocConsole = IsDebuggerPresent(); + if (!fAllocConsole) + { + if (!AttachConsole(ATTACH_PARENT_PROCESS)) + fAllocConsole = true; + } + + if (fAllocConsole) + { + if (!AllocConsole()) + MessageBox(GetDesktopWindow(), L"Unable to attach to or allocate a console!", L"VBoxSDL", MB_OK | MB_ICONERROR); + /* Continue running. */ + } + + RTFILE hStdIn; + RTFileFromNative(&hStdIn, (RTHCINTPTR)GetStdHandle(STD_INPUT_HANDLE)); + /** @todo Closing of standard handles not support via IPRT (yet). */ + RTStrmOpenFileHandle(hStdIn, "r", 0, &g_pStdIn); + + RTFILE hStdOut; + RTFileFromNative(&hStdOut, (RTHCINTPTR)GetStdHandle(STD_OUTPUT_HANDLE)); + /** @todo Closing of standard handles not support via IPRT (yet). */ + RTStrmOpenFileHandle(hStdOut, "wt", 0, &g_pStdOut); + + RTFILE hStdErr; + RTFileFromNative(&hStdErr, (RTHCINTPTR)GetStdHandle(STD_ERROR_HANDLE)); + RTStrmOpenFileHandle(hStdErr, "wt", 0, &g_pStdErr); + + if (!fAllocConsole) /* When attaching to the parent console, make sure we start on a fresh line. */ + RTPrintf("\n"); + + ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */ +#endif /* RT_OS_WINDOWS */ + +#ifdef Q_WS_X11 + if (!XInitThreads()) + return 1; +#endif +#ifdef VBOXSDL_WITH_X11 + /* + * Lock keys on SDL behave different from normal keys: A KeyPress event is generated + * if the lock mode gets active and a keyRelease event is generated if the lock mode + * gets inactive, that is KeyPress and KeyRelease are sent when pressing the lock key + * to change the mode. The current lock mode is reflected in SDL_GetModState(). + * + * Debian patched libSDL to make the lock keys behave like normal keys + * generating a KeyPress/KeyRelease event if the lock key was + * pressed/released. With the new behaviour, the lock status is not + * reflected in the mod status anymore, but the user can request the old + * behaviour by setting an environment variable. To confuse matters further + * version 1.2.14 (fortunately including the Debian packaged versions) + * adopted the Debian behaviour officially, but inverted the meaning of the + * environment variable to select the new behaviour, keeping the old as the + * default. We disable the new behaviour to ensure a defined environment + * and work around the missing KeyPress/KeyRelease events in ProcessKeys(). + */ + { +#if 0 + const SDL_version *pVersion = SDL_Linked_Version(); + if ( SDL_VERSIONNUM(pVersion->major, pVersion->minor, pVersion->patch) + < SDL_VERSIONNUM(1, 2, 14)) + RTEnvSet("SDL_DISABLE_LOCK_KEYS", "1"); +#endif + } +#endif + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + + /* + * the hostkey detection mode is unrelated to VM processing, so handle it before + * we initialize anything COM related + */ + if (argc == 2 && ( !strcmp(argv[1], "-detecthostkey") + || !strcmp(argv[1], "--detecthostkey"))) + { + rcExit = handleDetectHostKey(); +#ifdef RT_OS_WINDOWS + FreeConsole(); /* Detach or destroy (from) console. */ +#endif + return rcExit; + } + + /** @todo r=andy This function is waaaaaay to long, uses goto's and leaks stuff. Use RTGetOpt handling. */ + + HRESULT hrc; + int vrc; + Guid uuidVM; + char *vmName = NULL; + bool fSeparate = false; + DeviceType_T bootDevice = DeviceType_Null; + uint32_t memorySize = 0; + uint32_t vramSize = 0; + ComPtr<IEventListener> pVBoxClientListener; + ComPtr<IEventListener> pVBoxListener; + ComObjPtr<VBoxSDLConsoleEventListenerImpl> pConsoleListener; + + bool fFullscreen = false; + bool fResizable = true; +#ifdef USE_XPCOM_QUEUE_THREAD + bool fXPCOMEventThreadSignaled = false; +#endif + const char *pcszHdaFile = NULL; + const char *pcszCdromFile = NULL; + const char *pcszFdaFile = NULL; + const char *pszPortVRDP = NULL; + bool fDiscardState = false; + const char *pcszSettingsPw = NULL; + const char *pcszSettingsPwFile = NULL; +#ifdef VBOXSDL_ADVANCED_OPTIONS + uint32_t u32WarpDrive = 0; +#endif +#ifdef VBOX_WIN32_UI + bool fWin32UI = true; + int64_t winId = 0; +#endif + bool fShowSDLConfig = false; + uint32_t fixedWidth = ~(uint32_t)0; + uint32_t fixedHeight = ~(uint32_t)0; + uint32_t fixedBPP = ~(uint32_t)0; + uint32_t uResizeWidth = ~(uint32_t)0; + uint32_t uResizeHeight = ~(uint32_t)0; + + /* The damned GOTOs forces this to be up here - totally out of place. */ + /* + * Host key handling. + * + * The golden rule is that host-key combinations should not be seen + * by the guest. For instance a CAD should not have any extra RCtrl down + * and RCtrl up around itself. Nor should a resume be followed by a Ctrl-P + * that could encourage applications to start printing. + * + * We must not confuse the hostkey processing into any release sequences + * either, the host key is supposed to be explicitly pressing one key. + * + * Quick state diagram: + * + * host key down alone + * (Normal) --------------- + * ^ ^ | + * | | v host combination key down + * | | (Host key down) ---------------- + * | | host key up v | | + * | |-------------- | other key down v host combination key down + * | | (host key used) ------------- + * | | | ^ | + * | (not host key)-- | |--------------- + * | | | | | + * | | ---- other | + * | modifiers = 0 v v + * ----------------------------------------------- + */ + enum HKEYSTATE + { + /** The initial and most common state, pass keystrokes to the guest. + * Next state: HKEYSTATE_DOWN + * Prev state: Any */ + HKEYSTATE_NORMAL = 1, + /** The first host key was pressed down + */ + HKEYSTATE_DOWN_1ST, + /** The second host key was pressed down (if gHostKeySym2 != SDLK_UNKNOWN) + */ + HKEYSTATE_DOWN_2ND, + /** The host key has been pressed down. + * Prev state: HKEYSTATE_NORMAL + * Next state: HKEYSTATE_NORMAL - host key up, capture toggle. + * Next state: HKEYSTATE_USED - host key combination down. + * Next state: HKEYSTATE_NOT_IT - non-host key combination down. + */ + HKEYSTATE_DOWN, + /** A host key combination was pressed. + * Prev state: HKEYSTATE_DOWN + * Next state: HKEYSTATE_NORMAL - when modifiers are all 0 + */ + HKEYSTATE_USED, + /** A non-host key combination was attempted. Send hostkey down to the + * guest and continue until all modifiers have been released. + * Prev state: HKEYSTATE_DOWN + * Next state: HKEYSTATE_NORMAL - when modifiers are all 0 + */ + HKEYSTATE_NOT_IT + } enmHKeyState = HKEYSTATE_NORMAL; + /** The host key down event which we have been hiding from the guest. + * Used when going from HKEYSTATE_DOWN to HKEYSTATE_NOT_IT. */ + SDL_Event EvHKeyDown1; + SDL_Event EvHKeyDown2; + + LogFlow(("SDL GUI started\n")); + RTPrintf(VBOX_PRODUCT " SDL GUI version %s\n" + "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n" + VBOX_VERSION_STRING); + + // less than one parameter is not possible + if (argc < 2) + { + show_usage(); + return 1; + } + + // command line argument parsing stuff + for (int curArg = 1; curArg < argc; curArg++) + { + if ( !strcmp(argv[curArg], "--vm") + || !strcmp(argv[curArg], "-vm") + || !strcmp(argv[curArg], "--startvm") + || !strcmp(argv[curArg], "-startvm") + || !strcmp(argv[curArg], "-s") + ) + { + if (++curArg >= argc) + { + RTPrintf("Error: VM not specified (UUID or name)!\n"); + return 1; + } + // first check if a UUID was supplied + uuidVM = argv[curArg]; + + if (!uuidVM.isValid()) + { + LogFlow(("invalid UUID format, assuming it's a VM name\n")); + vmName = argv[curArg]; + } + else if (uuidVM.isZero()) + { + RTPrintf("Error: UUID argument is zero!\n"); + return 1; + } + } + else if ( !strcmp(argv[curArg], "--separate") + || !strcmp(argv[curArg], "-separate")) + { + fSeparate = true; + } + else if ( !strcmp(argv[curArg], "--comment") + || !strcmp(argv[curArg], "-comment")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing argument for comment!\n"); + return 1; + } + } + else if ( !strcmp(argv[curArg], "--boot") + || !strcmp(argv[curArg], "-boot")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing argument for boot drive!\n"); + return 1; + } + switch (argv[curArg][0]) + { + case 'a': + { + bootDevice = DeviceType_Floppy; + break; + } + + case 'c': + { + bootDevice = DeviceType_HardDisk; + break; + } + + case 'd': + { + bootDevice = DeviceType_DVD; + break; + } + + case 'n': + { + bootDevice = DeviceType_Network; + break; + } + + default: + { + RTPrintf("Error: wrong argument for boot drive!\n"); + return 1; + } + } + } + else if ( !strcmp(argv[curArg], "--detecthostkey") + || !strcmp(argv[curArg], "-detecthostkey")) + { + RTPrintf("Error: please specify \"%s\" without any additional parameters!\n", + argv[curArg]); + return 1; + } + else if ( !strcmp(argv[curArg], "--memory") + || !strcmp(argv[curArg], "-memory") + || !strcmp(argv[curArg], "-m")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing argument for memory size!\n"); + return 1; + } + memorySize = atoi(argv[curArg]); + } + else if ( !strcmp(argv[curArg], "--vram") + || !strcmp(argv[curArg], "-vram")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing argument for vram size!\n"); + return 1; + } + vramSize = atoi(argv[curArg]); + } + else if ( !strcmp(argv[curArg], "--fullscreen") + || !strcmp(argv[curArg], "-fullscreen")) + { + fFullscreen = true; + } + else if ( !strcmp(argv[curArg], "--fullscreenresize") + || !strcmp(argv[curArg], "-fullscreenresize")) + { + gfFullscreenResize = true; +#ifdef VBOXSDL_WITH_X11 + RTEnvSet("SDL_VIDEO_X11_VIDMODE", "0"); +#endif + } + else if ( !strcmp(argv[curArg], "--fixedmode") + || !strcmp(argv[curArg], "-fixedmode")) + { + /* three parameters follow */ + if (curArg + 3 >= argc) + { + RTPrintf("Error: missing arguments for fixed video mode!\n"); + return 1; + } + fixedWidth = atoi(argv[++curArg]); + fixedHeight = atoi(argv[++curArg]); + fixedBPP = atoi(argv[++curArg]); + } + else if ( !strcmp(argv[curArg], "--nofstoggle") + || !strcmp(argv[curArg], "-nofstoggle")) + { + gfAllowFullscreenToggle = FALSE; + } + else if ( !strcmp(argv[curArg], "--noresize") + || !strcmp(argv[curArg], "-noresize")) + { + fResizable = false; + } + else if ( !strcmp(argv[curArg], "--nohostkey") + || !strcmp(argv[curArg], "-nohostkey")) + { + gHostKeyMod = 0; + gHostKeySym1 = 0; + } + else if ( !strcmp(argv[curArg], "--nohostkeys") + || !strcmp(argv[curArg], "-nohostkeys")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing a string of disabled hostkey combinations\n"); + return 1; + } + gHostKeyDisabledCombinations = argv[curArg]; + size_t cch = strlen(gHostKeyDisabledCombinations); + for (size_t i = 0; i < cch; i++) + { + if (!strchr("fhnpqrs", gHostKeyDisabledCombinations[i])) + { + RTPrintf("Error: <hostkey> + '%c' is not a valid combination\n", + gHostKeyDisabledCombinations[i]); + return 1; + } + } + } + else if ( !strcmp(argv[curArg], "--nograbonclick") + || !strcmp(argv[curArg], "-nograbonclick")) + { + gfGrabOnMouseClick = FALSE; + } + else if ( !strcmp(argv[curArg], "--termacpi") + || !strcmp(argv[curArg], "-termacpi")) + { + gfACPITerm = TRUE; + } + else if ( !strcmp(argv[curArg], "--pidfile") + || !strcmp(argv[curArg], "-pidfile")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing file name for --pidfile!\n"); + return 1; + } + gpszPidFile = argv[curArg]; + } + else if ( !strcmp(argv[curArg], "--hda") + || !strcmp(argv[curArg], "-hda")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing file name for first hard disk!\n"); + return 1; + } + /* resolve it. */ + if (RTPathExists(argv[curArg])) + pcszHdaFile = RTPathRealDup(argv[curArg]); + if (!pcszHdaFile) + { + RTPrintf("Error: The path to the specified harddisk, '%s', could not be resolved.\n", argv[curArg]); + return 1; + } + } + else if ( !strcmp(argv[curArg], "--fda") + || !strcmp(argv[curArg], "-fda")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing file/device name for first floppy disk!\n"); + return 1; + } + /* resolve it. */ + if (RTPathExists(argv[curArg])) + pcszFdaFile = RTPathRealDup(argv[curArg]); + if (!pcszFdaFile) + { + RTPrintf("Error: The path to the specified floppy disk, '%s', could not be resolved.\n", argv[curArg]); + return 1; + } + } + else if ( !strcmp(argv[curArg], "--cdrom") + || !strcmp(argv[curArg], "-cdrom")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing file/device name for cdrom!\n"); + return 1; + } + /* resolve it. */ + if (RTPathExists(argv[curArg])) + pcszCdromFile = RTPathRealDup(argv[curArg]); + if (!pcszCdromFile) + { + RTPrintf("Error: The path to the specified cdrom, '%s', could not be resolved.\n", argv[curArg]); + return 1; + } + } + else if ( !strcmp(argv[curArg], "--vrdp") + || !strcmp(argv[curArg], "-vrdp")) + { + // start with the standard VRDP port + pszPortVRDP = "0"; + + // is there another argument + if (argc > (curArg + 1)) + { + curArg++; + pszPortVRDP = argv[curArg]; + LogFlow(("Using non standard VRDP port %s\n", pszPortVRDP)); + } + } + else if ( !strcmp(argv[curArg], "--discardstate") + || !strcmp(argv[curArg], "-discardstate")) + { + fDiscardState = true; + } + else if (!strcmp(argv[curArg], "--settingspw")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing password"); + return 1; + } + pcszSettingsPw = argv[curArg]; + } + else if (!strcmp(argv[curArg], "--settingspwfile")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing password file\n"); + return 1; + } + pcszSettingsPwFile = argv[curArg]; + } +#ifdef VBOXSDL_ADVANCED_OPTIONS + else if ( !strcmp(argv[curArg], "--warpdrive") + || !strcmp(argv[curArg], "-warpdrive")) + { + if (++curArg >= argc) + { + RTPrintf("Error: missing the rate value for the --warpdrive option!\n"); + return 1; + } + u32WarpDrive = RTStrToUInt32(argv[curArg]); + if (u32WarpDrive < 2 || u32WarpDrive > 20000) + { + RTPrintf("Error: the warp drive rate is restricted to [2..20000]. (%d)\n", u32WarpDrive); + return 1; + } + } +#endif /* VBOXSDL_ADVANCED_OPTIONS */ +#ifdef VBOX_WIN32_UI + else if ( !strcmp(argv[curArg], "--win32ui") + || !strcmp(argv[curArg], "-win32ui")) + fWin32UI = true; +#endif + else if ( !strcmp(argv[curArg], "--showsdlconfig") + || !strcmp(argv[curArg], "-showsdlconfig")) + fShowSDLConfig = true; + else if ( !strcmp(argv[curArg], "--hostkey") + || !strcmp(argv[curArg], "-hostkey")) + { + if (++curArg + 1 >= argc) + { + RTPrintf("Error: not enough arguments for host keys!\n"); + return 1; + } + gHostKeySym1 = atoi(argv[curArg++]); + if (curArg + 1 < argc && (argv[curArg+1][0] == '0' || atoi(argv[curArg+1]) > 0)) + { + /* two-key sequence as host key specified */ + gHostKeySym2 = atoi(argv[curArg++]); + } + gHostKeyMod = atoi(argv[curArg]); + } + /* just show the help screen */ + else + { + if ( strcmp(argv[curArg], "-h") + && strcmp(argv[curArg], "-help") + && strcmp(argv[curArg], "--help")) + RTPrintf("Error: unrecognized switch '%s'\n", argv[curArg]); + show_usage(); + return 1; + } + } + + hrc = com::Initialize(); +#ifdef VBOX_WITH_XPCOM + if (hrc == NS_ERROR_FILE_ACCESS_DENIED) + { + char szHome[RTPATH_MAX] = ""; + com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome)); + RTPrintf("Failed to initialize COM because the global settings directory '%s' is not accessible!\n", szHome); + return 1; + } +#endif + if (FAILED(hrc)) + { + RTPrintf("Error: COM initialization failed (rc=%Rhrc)!\n", hrc); + return 1; + } + + /* NOTE: do not convert the following scope to a "do {} while (0);", as + * this would make it all too tempting to use "break;" incorrectly - it + * would skip over the cleanup. */ + { + // scopes all the stuff till shutdown + //////////////////////////////////////////////////////////////////////////// + + ComPtr<IVirtualBoxClient> pVirtualBoxClient; + ComPtr<IVirtualBox> pVirtualBox; + ComPtr<ISession> pSession; + bool sessionOpened = false; + NativeEventQueue* eventQ = com::NativeEventQueue::getMainEventQueue(); + + ComPtr<IMachine> pMachine; + ComPtr<IGraphicsAdapter> pGraphicsAdapter; + + hrc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); + if (FAILED(hrc)) + { + com::ErrorInfo info; + if (info.isFullAvailable()) + PrintError("Failed to create VirtualBoxClient object", + info.getText().raw(), info.getComponent().raw()); + else + RTPrintf("Failed to create VirtualBoxClient object! No error information available (rc=%Rhrc).\n", hrc); + goto leave; + } + + hrc = pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam()); + if (FAILED(hrc)) + { + RTPrintf("Failed to get VirtualBox object (rc=%Rhrc)!\n", hrc); + goto leave; + } + hrc = pVirtualBoxClient->COMGETTER(Session)(pSession.asOutParam()); + if (FAILED(hrc)) + { + RTPrintf("Failed to get session object (rc=%Rhrc)!\n", hrc); + goto leave; + } + + if (pcszSettingsPw) + { + CHECK_ERROR(pVirtualBox, SetSettingsSecret(Bstr(pcszSettingsPw).raw())); + if (FAILED(hrc)) + goto leave; + } + else if (pcszSettingsPwFile) + { + rcExit = settingsPasswordFile(pVirtualBox, pcszSettingsPwFile); + if (rcExit != RTEXITCODE_SUCCESS) + goto leave; + } + + /* + * Do we have a UUID? + */ + if (uuidVM.isValid()) + { + hrc = pVirtualBox->FindMachine(uuidVM.toUtf16().raw(), pMachine.asOutParam()); + if (FAILED(hrc) || !pMachine) + { + RTPrintf("Error: machine with the given ID not found!\n"); + goto leave; + } + } + else if (vmName) + { + /* + * Do we have a name but no UUID? + */ + hrc = pVirtualBox->FindMachine(Bstr(vmName).raw(), pMachine.asOutParam()); + if ((hrc == S_OK) && pMachine) + { + Bstr bstrId; + pMachine->COMGETTER(Id)(bstrId.asOutParam()); + uuidVM = Guid(bstrId); + } + else + { + RTPrintf("Error: machine with the given name not found!\n"); + RTPrintf("Check if this VM has been corrupted and is now inaccessible."); + goto leave; + } + } + + /* create SDL event semaphore */ + vrc = RTSemEventCreate(&g_EventSemSDLEvents); + AssertReleaseRC(vrc); + + hrc = pVirtualBoxClient->CheckMachineError(pMachine); + if (FAILED(hrc)) + { + com::ErrorInfo info; + if (info.isFullAvailable()) + PrintError("The VM has errors", + info.getText().raw(), info.getComponent().raw()); + else + RTPrintf("Failed to check for VM errors! No error information available (rc=%Rhrc).\n", hrc); + goto leave; + } + + if (fSeparate) + { + MachineState_T machineState = MachineState_Null; + pMachine->COMGETTER(State)(&machineState); + if ( machineState == MachineState_Running + || machineState == MachineState_Teleporting + || machineState == MachineState_LiveSnapshotting + || machineState == MachineState_Paused + || machineState == MachineState_TeleportingPausedVM + ) + { + RTPrintf("VM is already running.\n"); + } + else + { + ComPtr<IProgress> progress; + hrc = pMachine->LaunchVMProcess(pSession, Bstr("headless").raw(), ComSafeArrayNullInParam(), progress.asOutParam()); + if (SUCCEEDED(hrc) && !progress.isNull()) + { + RTPrintf("Waiting for VM to power on...\n"); + hrc = progress->WaitForCompletion(-1); + if (SUCCEEDED(hrc)) + { + BOOL completed = true; + hrc = progress->COMGETTER(Completed)(&completed); + if (SUCCEEDED(hrc)) + { + LONG iRc; + hrc = progress->COMGETTER(ResultCode)(&iRc); + if (SUCCEEDED(hrc)) + { + if (FAILED(iRc)) + { + ProgressErrorInfo info(progress); + com::GluePrintErrorInfo(info); + } + else + { + RTPrintf("VM has been successfully started.\n"); + /* LaunchVMProcess obtains a shared lock on the machine. + * Unlock it here, because the lock will be obtained below + * in the common code path as for already running VM. + */ + pSession->UnlockMachine(); + } + } + } + } + } + } + if (FAILED(hrc)) + { + RTPrintf("Error: failed to power up VM! No error text available.\n"); + goto leave; + } + + hrc = pMachine->LockMachine(pSession, LockType_Shared); + } + else + { + pSession->COMSETTER(Name)(Bstr("GUI/SDL").raw()); + hrc = pMachine->LockMachine(pSession, LockType_VM); + } + + if (FAILED(hrc)) + { + com::ErrorInfo info; + if (info.isFullAvailable()) + PrintError("Could not open VirtualBox session", + info.getText().raw(), info.getComponent().raw()); + goto leave; + } + if (!pSession) + { + RTPrintf("Could not open VirtualBox session!\n"); + goto leave; + } + sessionOpened = true; + // get the mutable VM we're dealing with + pSession->COMGETTER(Machine)(gpMachine.asOutParam()); + if (!gpMachine) + { + com::ErrorInfo info; + if (info.isFullAvailable()) + PrintError("Cannot start VM!", + info.getText().raw(), info.getComponent().raw()); + else + RTPrintf("Error: given machine not found!\n"); + goto leave; + } + + // get the VM console + pSession->COMGETTER(Console)(gpConsole.asOutParam()); + if (!gpConsole) + { + RTPrintf("Given console not found!\n"); + goto leave; + } + + /* + * Are we supposed to use a different hard disk file? + */ + if (pcszHdaFile) + { + ComPtr<IMedium> pMedium; + + /* + * Strategy: if any registered hard disk points to the same file, + * assign it. If not, register a new image and assign it to the VM. + */ + Bstr bstrHdaFile(pcszHdaFile); + pVirtualBox->OpenMedium(bstrHdaFile.raw(), DeviceType_HardDisk, + AccessMode_ReadWrite, FALSE /* fForceNewUuid */, + pMedium.asOutParam()); + if (!pMedium) + { + /* we've not found the image */ + RTPrintf("Adding hard disk '%s'...\n", pcszHdaFile); + pVirtualBox->OpenMedium(bstrHdaFile.raw(), DeviceType_HardDisk, + AccessMode_ReadWrite, FALSE /* fForceNewUuid */, + pMedium.asOutParam()); + } + /* do we have the right image now? */ + if (pMedium) + { + Bstr bstrSCName; + + /* get the first IDE controller to attach the harddisk to + * and if there is none, add one temporarily */ + { + ComPtr<IStorageController> pStorageCtl; + com::SafeIfaceArray<IStorageController> aStorageControllers; + CHECK_ERROR(gpMachine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(aStorageControllers))); + for (size_t i = 0; i < aStorageControllers.size(); ++ i) + { + StorageBus_T storageBus = StorageBus_Null; + + CHECK_ERROR(aStorageControllers[i], COMGETTER(Bus)(&storageBus)); + if (storageBus == StorageBus_IDE) + { + pStorageCtl = aStorageControllers[i]; + break; + } + } + + if (pStorageCtl) + { + CHECK_ERROR(pStorageCtl, COMGETTER(Name)(bstrSCName.asOutParam())); + gpMachine->DetachDevice(bstrSCName.raw(), 0, 0); + } + else + { + bstrSCName = "IDE Controller"; + CHECK_ERROR(gpMachine, AddStorageController(bstrSCName.raw(), + StorageBus_IDE, + pStorageCtl.asOutParam())); + } + } + + CHECK_ERROR(gpMachine, AttachDevice(bstrSCName.raw(), 0, 0, + DeviceType_HardDisk, pMedium)); + /// @todo why is this attachment saved? + } + else + { + RTPrintf("Error: failed to mount the specified hard disk image!\n"); + goto leave; + } + } + + /* + * Mount a floppy if requested. + */ + if (pcszFdaFile) + do + { + ComPtr<IMedium> pMedium; + + /* unmount? */ + if (!strcmp(pcszFdaFile, "none")) + { + /* nothing to do, NULL object will cause unmount */ + } + else + { + Bstr bstrFdaFile(pcszFdaFile); + + /* Assume it's a host drive name */ + ComPtr<IHost> pHost; + CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(Host)(pHost.asOutParam())); + hrc = pHost->FindHostFloppyDrive(bstrFdaFile.raw(), + pMedium.asOutParam()); + if (FAILED(hrc)) + { + /* try to find an existing one */ + hrc = pVirtualBox->OpenMedium(bstrFdaFile.raw(), + DeviceType_Floppy, + AccessMode_ReadWrite, + FALSE /* fForceNewUuid */, + pMedium.asOutParam()); + if (FAILED(hrc)) + { + /* try to add to the list */ + RTPrintf("Adding floppy image '%s'...\n", pcszFdaFile); + CHECK_ERROR_BREAK(pVirtualBox, + OpenMedium(bstrFdaFile.raw(), + DeviceType_Floppy, + AccessMode_ReadWrite, + FALSE /* fForceNewUuid */, + pMedium.asOutParam())); + } + } + } + + Bstr bstrSCName; + + /* get the first floppy controller to attach the floppy to + * and if there is none, add one temporarily */ + { + ComPtr<IStorageController> pStorageCtl; + com::SafeIfaceArray<IStorageController> aStorageControllers; + CHECK_ERROR(gpMachine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(aStorageControllers))); + for (size_t i = 0; i < aStorageControllers.size(); ++ i) + { + StorageBus_T storageBus = StorageBus_Null; + + CHECK_ERROR(aStorageControllers[i], COMGETTER(Bus)(&storageBus)); + if (storageBus == StorageBus_Floppy) + { + pStorageCtl = aStorageControllers[i]; + break; + } + } + + if (pStorageCtl) + { + CHECK_ERROR(pStorageCtl, COMGETTER(Name)(bstrSCName.asOutParam())); + gpMachine->DetachDevice(bstrSCName.raw(), 0, 0); + } + else + { + bstrSCName = "Floppy Controller"; + CHECK_ERROR(gpMachine, AddStorageController(bstrSCName.raw(), + StorageBus_Floppy, + pStorageCtl.asOutParam())); + } + } + + CHECK_ERROR(gpMachine, AttachDevice(bstrSCName.raw(), 0, 0, + DeviceType_Floppy, pMedium)); + } + while (0); + if (FAILED(hrc)) + goto leave; + + /* + * Mount a CD-ROM if requested. + */ + if (pcszCdromFile) + do + { + ComPtr<IMedium> pMedium; + + /* unmount? */ + if (!strcmp(pcszCdromFile, "none")) + { + /* nothing to do, NULL object will cause unmount */ + } + else + { + Bstr bstrCdromFile(pcszCdromFile); + + /* Assume it's a host drive name */ + ComPtr<IHost> pHost; + CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(Host)(pHost.asOutParam())); + hrc = pHost->FindHostDVDDrive(bstrCdromFile.raw(), pMedium.asOutParam()); + if (FAILED(hrc)) + { + /* try to find an existing one */ + hrc = pVirtualBox->OpenMedium(bstrCdromFile.raw(), + DeviceType_DVD, + AccessMode_ReadWrite, + FALSE /* fForceNewUuid */, + pMedium.asOutParam()); + if (FAILED(hrc)) + { + /* try to add to the list */ + RTPrintf("Adding ISO image '%s'...\n", pcszCdromFile); + CHECK_ERROR_BREAK(pVirtualBox, + OpenMedium(bstrCdromFile.raw(), + DeviceType_DVD, + AccessMode_ReadWrite, + FALSE /* fForceNewUuid */, + pMedium.asOutParam())); + } + } + } + + Bstr bstrSCName; + + /* get the first IDE controller to attach the DVD drive to + * and if there is none, add one temporarily */ + { + ComPtr<IStorageController> pStorageCtl; + com::SafeIfaceArray<IStorageController> aStorageControllers; + CHECK_ERROR(gpMachine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(aStorageControllers))); + for (size_t i = 0; i < aStorageControllers.size(); ++ i) + { + StorageBus_T storageBus = StorageBus_Null; + + CHECK_ERROR(aStorageControllers[i], COMGETTER(Bus)(&storageBus)); + if (storageBus == StorageBus_IDE) + { + pStorageCtl = aStorageControllers[i]; + break; + } + } + + if (pStorageCtl) + { + CHECK_ERROR(pStorageCtl, COMGETTER(Name)(bstrSCName.asOutParam())); + gpMachine->DetachDevice(bstrSCName.raw(), 1, 0); + } + else + { + bstrSCName = "IDE Controller"; + CHECK_ERROR(gpMachine, AddStorageController(bstrSCName.raw(), + StorageBus_IDE, + pStorageCtl.asOutParam())); + } + } + + CHECK_ERROR(gpMachine, AttachDevice(bstrSCName.raw(), 1, 0, + DeviceType_DVD, pMedium)); + } + while (0); + if (FAILED(hrc)) + goto leave; + + if (fDiscardState) + { + /* + * If the machine is currently saved, + * discard the saved state first. + */ + MachineState_T machineState; + gpMachine->COMGETTER(State)(&machineState); + if (machineState == MachineState_Saved || machineState == MachineState_AbortedSaved) + { + CHECK_ERROR(gpMachine, DiscardSavedState(true /* fDeleteFile */)); + } + /* + * If there are snapshots, discard the current state, + * i.e. revert to the last snapshot. + */ + ULONG cSnapshots; + gpMachine->COMGETTER(SnapshotCount)(&cSnapshots); + if (cSnapshots) + { + gpProgress = NULL; + + ComPtr<ISnapshot> pCurrentSnapshot; + CHECK_ERROR(gpMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam())); + if (FAILED(hrc)) + goto leave; + + CHECK_ERROR(gpMachine, RestoreSnapshot(pCurrentSnapshot, gpProgress.asOutParam())); + hrc = gpProgress->WaitForCompletion(-1); + } + } + + // get the machine debugger (does not have to be there) + gpConsole->COMGETTER(Debugger)(gpMachineDebugger.asOutParam()); + if (gpMachineDebugger) + { + Log(("Machine debugger available!\n")); + } + gpConsole->COMGETTER(Display)(gpDisplay.asOutParam()); + if (!gpDisplay) + { + RTPrintf("Error: could not get display object!\n"); + goto leave; + } + + // set the boot drive + if (bootDevice != DeviceType_Null) + { + hrc = gpMachine->SetBootOrder(1, bootDevice); + if (hrc != S_OK) + { + RTPrintf("Error: could not set boot device, using default.\n"); + } + } + + // set the memory size if not default + if (memorySize) + { + hrc = gpMachine->COMSETTER(MemorySize)(memorySize); + if (hrc != S_OK) + { + ULONG ramSize = 0; + gpMachine->COMGETTER(MemorySize)(&ramSize); + RTPrintf("Error: could not set memory size, using current setting of %d MBytes\n", ramSize); + } + } + + hrc = gpMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam()); + if (hrc != S_OK) + { + RTPrintf("Error: could not get graphics adapter object\n"); + goto leave; + } + + if (vramSize) + { + hrc = pGraphicsAdapter->COMSETTER(VRAMSize)(vramSize); + if (hrc != S_OK) + { + pGraphicsAdapter->COMGETTER(VRAMSize)((ULONG*)&vramSize); + RTPrintf("Error: could not set VRAM size, using current setting of %d MBytes\n", vramSize); + } + } + + // we're always able to process absolute mouse events and we prefer that + gfAbsoluteMouseHost = TRUE; + +#ifdef VBOX_WIN32_UI + if (fWin32UI) + { + /* initialize the Win32 user interface inside which SDL will be embedded */ + if (initUI(fResizable, winId)) + return 1; + } +#endif + + /* static initialization of the SDL stuff */ + if (!VBoxSDLFB::init(fShowSDLConfig)) + goto leave; + + pGraphicsAdapter->COMGETTER(MonitorCount)(&gcMonitors); + if (gcMonitors > 64) + gcMonitors = 64; + + for (unsigned i = 0; i < gcMonitors; i++) + { + // create our SDL framebuffer instance + gpFramebuffer[i].createObject(); + hrc = gpFramebuffer[i]->init(i, fFullscreen, fResizable, fShowSDLConfig, false, + fixedWidth, fixedHeight, fixedBPP, fSeparate); + if (FAILED(hrc)) + { + RTPrintf("Error: could not create framebuffer object!\n"); + goto leave; + } + } + +#ifdef VBOX_WIN32_UI + gpFramebuffer[0]->setWinId(winId); +#endif + + for (unsigned i = 0; i < gcMonitors; i++) + { + if (!gpFramebuffer[i]->initialized()) + goto leave; + gpFramebuffer[i]->AddRef(); + if (fFullscreen) + SetFullscreen(true); + } + +#ifdef VBOXSDL_WITH_X11 + /* NOTE1: We still want Ctrl-C to work, so we undo the SDL redirections. + * NOTE2: We have to remove the PidFile if this file exists. */ + signal(SIGINT, signal_handler_SIGINT); + signal(SIGQUIT, signal_handler_SIGINT); + signal(SIGSEGV, signal_handler_SIGINT); +#endif + + + for (ULONG i = 0; i < gcMonitors; i++) + { + // register our framebuffer + hrc = gpDisplay->AttachFramebuffer(i, gpFramebuffer[i], gaFramebufferId[i].asOutParam()); + if (FAILED(hrc)) + { + RTPrintf("Error: could not register framebuffer object!\n"); + goto leave; + } + ULONG dummy; + LONG xOrigin, yOrigin; + GuestMonitorStatus_T monitorStatus; + hrc = gpDisplay->GetScreenResolution(i, &dummy, &dummy, &dummy, &xOrigin, &yOrigin, &monitorStatus); + gpFramebuffer[i]->setOrigin(xOrigin, yOrigin); + } + + { + // register listener for VirtualBoxClient events + ComPtr<IEventSource> pES; + CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam())); + ComObjPtr<VBoxSDLClientEventListenerImpl> listener; + listener.createObject(); + listener->init(new VBoxSDLClientEventListener()); + pVBoxClientListener = listener; + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged); + CHECK_ERROR(pES, RegisterListener(pVBoxClientListener, ComSafeArrayAsInParam(eventTypes), true)); + } + + { + // register listener for VirtualBox (server) events + ComPtr<IEventSource> pES; + CHECK_ERROR(pVirtualBox, COMGETTER(EventSource)(pES.asOutParam())); + ComObjPtr<VBoxSDLEventListenerImpl> listener; + listener.createObject(); + listener->init(new VBoxSDLEventListener()); + pVBoxListener = listener; + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnExtraDataChanged); + CHECK_ERROR(pES, RegisterListener(pVBoxListener, ComSafeArrayAsInParam(eventTypes), true)); + } + + { + // register listener for Console events + ComPtr<IEventSource> pES; + CHECK_ERROR(gpConsole, COMGETTER(EventSource)(pES.asOutParam())); + pConsoleListener.createObject(); + pConsoleListener->init(new VBoxSDLConsoleEventListener()); + com::SafeArray<VBoxEventType_T> eventTypes; + eventTypes.push_back(VBoxEventType_OnMousePointerShapeChanged); + eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged); + eventTypes.push_back(VBoxEventType_OnKeyboardLedsChanged); + eventTypes.push_back(VBoxEventType_OnStateChanged); + eventTypes.push_back(VBoxEventType_OnRuntimeError); + eventTypes.push_back(VBoxEventType_OnCanShowWindow); + eventTypes.push_back(VBoxEventType_OnShowWindow); + CHECK_ERROR(pES, RegisterListener(pConsoleListener, ComSafeArrayAsInParam(eventTypes), true)); + // until we've tried to to start the VM, ignore power off events + pConsoleListener->getWrapped()->ignorePowerOffEvents(true); + } + + if (pszPortVRDP) + { + hrc = gpMachine->COMGETTER(VRDEServer)(gpVRDEServer.asOutParam()); + AssertMsg((hrc == S_OK) && gpVRDEServer, ("Could not get VRDP Server! rc = 0x%x\n", hrc)); + if (gpVRDEServer) + { + // has a non standard VRDP port been requested? + if (strcmp(pszPortVRDP, "0")) + { + hrc = gpVRDEServer->SetVRDEProperty(Bstr("TCP/Ports").raw(), Bstr(pszPortVRDP).raw()); + if (hrc != S_OK) + { + RTPrintf("Error: could not set VRDP port! rc = 0x%x\n", hrc); + goto leave; + } + } + // now enable VRDP + hrc = gpVRDEServer->COMSETTER(Enabled)(TRUE); + if (hrc != S_OK) + { + RTPrintf("Error: could not enable VRDP server! rc = 0x%x\n", hrc); + goto leave; + } + } + } + + hrc = E_FAIL; +#ifdef VBOXSDL_ADVANCED_OPTIONS + if (u32WarpDrive != 0) + { + if (!gpMachineDebugger) + { + RTPrintf("Error: No debugger object; --warpdrive %d cannot be executed!\n", u32WarpDrive); + goto leave; + } + gpMachineDebugger->COMSETTER(VirtualTimeRate)(u32WarpDrive); + } +#endif /* VBOXSDL_ADVANCED_OPTIONS */ + + /* start with something in the titlebar */ + UpdateTitlebar(TITLEBAR_NORMAL); + + /* memorize the default cursor */ + gpDefaultCursor = SDL_GetCursor(); + /* + * Register our user signal handler. + */ +#ifdef VBOXSDL_WITH_X11 + struct sigaction sa; + sa.sa_sigaction = signal_handler_SIGUSR1; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_SIGINFO; + sigaction(SIGUSR1, &sa, NULL); +#endif /* VBOXSDL_WITH_X11 */ + + /* + * Start the VM execution thread. This has to be done + * asynchronously as powering up can take some time + * (accessing devices such as the host DVD drive). In + * the meantime, we have to service the SDL event loop. + */ + SDL_Event event; + + if (!fSeparate) + { + LogFlow(("Powering up the VM...\n")); + hrc = gpConsole->PowerUp(gpProgress.asOutParam()); + if (hrc != S_OK) + { + com::ErrorInfo info(gpConsole, COM_IIDOF(IConsole)); + if (info.isBasicAvailable()) + PrintError("Failed to power up VM", info.getText().raw()); + else + RTPrintf("Error: failed to power up VM! No error text available.\n"); + goto leave; + } + } + +#ifdef USE_XPCOM_QUEUE_THREAD + /* + * Before we starting to do stuff, we have to launch the XPCOM + * event queue thread. It will wait for events and send messages + * to the SDL thread. After having done this, we should fairly + * quickly start to process the SDL event queue as an XPCOM + * event storm might arrive. Stupid SDL has a ridiculously small + * event queue buffer! + */ + startXPCOMEventQueueThread(eventQ->getSelectFD()); +#endif /* USE_XPCOM_QUEUE_THREAD */ + + /* termination flag */ + bool fTerminateDuringStartup; + fTerminateDuringStartup = false; + + LogRel(("VBoxSDL: NUM lock initially %s, CAPS lock initially %s\n", + !!(SDL_GetModState() & KMOD_NUM) ? "ON" : "OFF", + !!(SDL_GetModState() & KMOD_CAPS) ? "ON" : "OFF")); + + /* start regular timer so we don't starve in the event loop */ + SDL_TimerID sdlTimer; + sdlTimer = SDL_AddTimer(100, StartupTimer, NULL); + + /* loop until the powerup processing is done */ + MachineState_T machineState; + do + { + hrc = gpMachine->COMGETTER(State)(&machineState); + if ( hrc == S_OK + && ( machineState == MachineState_Starting + || machineState == MachineState_Restoring + || machineState == MachineState_TeleportingIn + ) + ) + { + /* + * wait for the next event. This is uncritical as + * power up guarantees to change the machine state + * to either running or aborted and a machine state + * change will send us an event. However, we have to + * service the XPCOM event queue! + */ +#ifdef USE_XPCOM_QUEUE_THREAD + if (!fXPCOMEventThreadSignaled) + { + signalXPCOMEventQueueThread(); + fXPCOMEventThreadSignaled = true; + } +#endif + /* + * Wait for SDL events. + */ + if (WaitSDLEvent(&event)) + { + switch (event.type) + { + /* + * Timer event. Used to have the titlebar updated. + */ + case SDL_USER_EVENT_TIMER: + { + /* + * Update the title bar. + */ + UpdateTitlebar(TITLEBAR_STARTUP); + break; + } + + /* + * User specific framebuffer change event. + */ + case SDL_USER_EVENT_NOTIFYCHANGE: + { + LogFlow(("SDL_USER_EVENT_NOTIFYCHANGE\n")); + LONG xOrigin, yOrigin; + gpFramebuffer[event.user.code]->notifyChange(event.user.code); + /* update xOrigin, yOrigin -> mouse */ + ULONG dummy; + GuestMonitorStatus_T monitorStatus; + hrc = gpDisplay->GetScreenResolution(event.user.code, &dummy, &dummy, &dummy, &xOrigin, &yOrigin, &monitorStatus); + gpFramebuffer[event.user.code]->setOrigin(xOrigin, yOrigin); + break; + } + +#ifdef USE_XPCOM_QUEUE_THREAD + /* + * User specific XPCOM event queue event + */ + case SDL_USER_EVENT_XPCOM_EVENTQUEUE: + { + LogFlow(("SDL_USER_EVENT_XPCOM_EVENTQUEUE: processing XPCOM event queue...\n")); + eventQ->processEventQueue(0); + signalXPCOMEventQueueThread(); + break; + } +#endif /* USE_XPCOM_QUEUE_THREAD */ + + /* + * Termination event from the on state change callback. + */ + case SDL_USER_EVENT_TERMINATE: + { + if (event.user.code != VBOXSDL_TERM_NORMAL) + { + com::ProgressErrorInfo info(gpProgress); + if (info.isBasicAvailable()) + PrintError("Failed to power up VM", info.getText().raw()); + else + RTPrintf("Error: failed to power up VM! No error text available.\n"); + } + fTerminateDuringStartup = true; + break; + } + + default: + { + Log8(("VBoxSDL: Unknown SDL event %d (pre)\n", event.type)); + break; + } + } + + } + } + eventQ->processEventQueue(0); + } while ( hrc == S_OK + && ( machineState == MachineState_Starting + || machineState == MachineState_Restoring + || machineState == MachineState_TeleportingIn + ) + ); + + /* kill the timer again */ + SDL_RemoveTimer(sdlTimer); + sdlTimer = 0; + + /* are we supposed to terminate the process? */ + if (fTerminateDuringStartup) + goto leave; + + /* did the power up succeed? */ + if (machineState != MachineState_Running) + { + com::ProgressErrorInfo info(gpProgress); + if (info.isBasicAvailable()) + PrintError("Failed to power up VM", info.getText().raw()); + else + RTPrintf("Error: failed to power up VM! No error text available (rc = 0x%x state = %d)\n", hrc, machineState); + goto leave; + } + + // accept power off events from now on because we're running + // note that there's a possible race condition here... + pConsoleListener->getWrapped()->ignorePowerOffEvents(false); + + hrc = gpConsole->COMGETTER(Keyboard)(gpKeyboard.asOutParam()); + if (!gpKeyboard) + { + RTPrintf("Error: could not get keyboard object!\n"); + goto leave; + } + gpConsole->COMGETTER(Mouse)(gpMouse.asOutParam()); + if (!gpMouse) + { + RTPrintf("Error: could not get mouse object!\n"); + goto leave; + } + + if (fSeparate && gpMouse) + { + LogFlow(("Fetching mouse caps\n")); + + /* Fetch current mouse status, etc */ + gpMouse->COMGETTER(AbsoluteSupported)(&gfAbsoluteMouseGuest); + gpMouse->COMGETTER(RelativeSupported)(&gfRelativeMouseGuest); + gpMouse->COMGETTER(NeedsHostCursor)(&gfGuestNeedsHostCursor); + + HandleGuestCapsChanged(); + + ComPtr<IMousePointerShape> mps; + gpMouse->COMGETTER(PointerShape)(mps.asOutParam()); + if (!mps.isNull()) + { + BOOL visible, alpha; + ULONG hotX, hotY, width, height; + com::SafeArray <BYTE> shape; + + mps->COMGETTER(Visible)(&visible); + mps->COMGETTER(Alpha)(&alpha); + mps->COMGETTER(HotX)(&hotX); + mps->COMGETTER(HotY)(&hotY); + mps->COMGETTER(Width)(&width); + mps->COMGETTER(Height)(&height); + mps->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape)); + + if (shape.size() > 0) + { + PointerShapeChangeData data(visible, alpha, hotX, hotY, width, height, + ComSafeArrayAsInParam(shape)); + SetPointerShape(&data); + } + } + } + + UpdateTitlebar(TITLEBAR_NORMAL); + + /* + * Create PID file. + */ + if (gpszPidFile) + { + char szBuf[32]; + const char *pcszLf = "\n"; + RTFILE PidFile; + RTFileOpen(&PidFile, gpszPidFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); + RTStrFormatNumber(szBuf, RTProcSelf(), 10, 0, 0, 0); + RTFileWrite(PidFile, szBuf, strlen(szBuf), NULL); + RTFileWrite(PidFile, pcszLf, strlen(pcszLf), NULL); + RTFileClose(PidFile); + } + + /* + * Main event loop + */ +#ifdef USE_XPCOM_QUEUE_THREAD + if (!fXPCOMEventThreadSignaled) + { + signalXPCOMEventQueueThread(); + } +#endif + LogFlow(("VBoxSDL: Entering big event loop\n")); + while (WaitSDLEvent(&event)) + { + switch (event.type) + { + /* + * The screen needs to be repainted. + */ + case SDL_WINDOWEVENT: + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_EXPOSED: + { + VBoxSDLFB *fb = getFbFromWinId(event.window.windowID); + if (fb) + fb->repaint(); + break; + } + case SDL_WINDOWEVENT_FOCUS_GAINED: + { + break; + } + case SDL_WINDOWEVENT_FOCUS_LOST: + { + break; + } + case SDL_WINDOWEVENT_RESIZED: + { + if (gpDisplay) + { + if (gfIgnoreNextResize) + { + gfIgnoreNextResize = FALSE; + break; + } + uResizeWidth = event.window.data1; + uResizeHeight = event.window.data2; + if (gSdlResizeTimer) + SDL_RemoveTimer(gSdlResizeTimer); + gSdlResizeTimer = SDL_AddTimer(300, ResizeTimer, NULL); + } + break; + } + default: + break; + } + break; + } + + /* + * Keyboard events. + */ + case SDL_KEYDOWN: + case SDL_KEYUP: + { + SDL_Keycode ksym = event.key.keysym.sym; + switch (enmHKeyState) + { + case HKEYSTATE_NORMAL: + { + if ( event.type == SDL_KEYDOWN + && ksym != SDLK_UNKNOWN + && (ksym == gHostKeySym1 || ksym == gHostKeySym2)) + { + EvHKeyDown1 = event; + enmHKeyState = ksym == gHostKeySym1 ? HKEYSTATE_DOWN_1ST + : HKEYSTATE_DOWN_2ND; + break; + } + ProcessKey(&event.key); + break; + } + + case HKEYSTATE_DOWN_1ST: + case HKEYSTATE_DOWN_2ND: + { + if (gHostKeySym2 != SDLK_UNKNOWN) + { + if ( event.type == SDL_KEYDOWN + && ksym != SDLK_UNKNOWN + && ( (enmHKeyState == HKEYSTATE_DOWN_1ST && ksym == gHostKeySym2) + || (enmHKeyState == HKEYSTATE_DOWN_2ND && ksym == gHostKeySym1))) + { + EvHKeyDown2 = event; + enmHKeyState = HKEYSTATE_DOWN; + break; + } + enmHKeyState = event.type == SDL_KEYUP ? HKEYSTATE_NORMAL + : HKEYSTATE_NOT_IT; + ProcessKey(&EvHKeyDown1.key); + /* ugly hack: Some guests (e.g. mstsc.exe on Windows XP) + * expect a small delay between two key events. 5ms work + * reliable here so use 10ms to be on the safe side. A + * better but more complicated fix would be to introduce + * a new state and don't wait here. */ + RTThreadSleep(10); + ProcessKey(&event.key); + break; + } + } + RT_FALL_THRU(); + + case HKEYSTATE_DOWN: + { + if (event.type == SDL_KEYDOWN) + { + /* potential host key combination, try execute it */ + int irc = HandleHostKey(&event.key); + if (irc == VINF_SUCCESS) + { + enmHKeyState = HKEYSTATE_USED; + break; + } + if (RT_SUCCESS(irc)) + goto leave; + } + else /* SDL_KEYUP */ + { + if ( ksym != SDLK_UNKNOWN + && (ksym == gHostKeySym1 || ksym == gHostKeySym2)) + { + /* toggle grabbing state */ + if (!gfGrabbed) + InputGrabStart(); + else + InputGrabEnd(); + + /* SDL doesn't always reset the keystates, correct it */ + ResetKeys(); + enmHKeyState = HKEYSTATE_NORMAL; + break; + } + } + + /* not host key */ + enmHKeyState = HKEYSTATE_NOT_IT; + ProcessKey(&EvHKeyDown1.key); + /* see the comment for the 2-key case above */ + RTThreadSleep(10); + if (gHostKeySym2 != SDLK_UNKNOWN) + { + ProcessKey(&EvHKeyDown2.key); + /* see the comment for the 2-key case above */ + RTThreadSleep(10); + } + ProcessKey(&event.key); + break; + } + + case HKEYSTATE_USED: + { + if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == 0) + enmHKeyState = HKEYSTATE_NORMAL; + if (event.type == SDL_KEYDOWN) + { + int irc = HandleHostKey(&event.key); + if (RT_SUCCESS(irc) && irc != VINF_SUCCESS) + goto leave; + } + break; + } + + default: + AssertMsgFailed(("enmHKeyState=%d\n", enmHKeyState)); + RT_FALL_THRU(); + case HKEYSTATE_NOT_IT: + { + if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == 0) + enmHKeyState = HKEYSTATE_NORMAL; + ProcessKey(&event.key); + break; + } + } /* state switch */ + break; + } + + /* + * The window was closed. + */ + case SDL_QUIT: + { + if (!gfACPITerm || gSdlQuitTimer) + goto leave; + if (gpConsole) + gpConsole->PowerButton(); + gSdlQuitTimer = SDL_AddTimer(1000, QuitTimer, NULL); + break; + } + + /* + * The mouse has moved + */ + case SDL_MOUSEMOTION: + { + if (gfGrabbed || UseAbsoluteMouse()) + { + VBoxSDLFB *fb; + fb = getFbFromWinId(event.motion.windowID); + AssertPtrBreak(fb); + SendMouseEvent(fb, 0, 0, 0); + } + break; + } + + case SDL_MOUSEWHEEL: + { + VBoxSDLFB *fb; + fb = getFbFromWinId(event.button.windowID); + AssertPtrBreak(fb); + SendMouseEvent(fb, -1 * event.wheel.y, 0, 0); + break; + } + /* + * A mouse button has been clicked or released. + */ + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + SDL_MouseButtonEvent *bev = &event.button; + /* don't grab on mouse click if we have guest additions */ + if (!gfGrabbed && !UseAbsoluteMouse() && gfGrabOnMouseClick) + { + if (event.type == SDL_MOUSEBUTTONDOWN && (bev->state & SDL_BUTTON_LMASK)) + { + /* start grabbing all events */ + InputGrabStart(); + } + } + else if (gfGrabbed || UseAbsoluteMouse()) + { + /* end host key combination (CTRL+MouseButton) */ + switch (enmHKeyState) + { + case HKEYSTATE_DOWN_1ST: + case HKEYSTATE_DOWN_2ND: + enmHKeyState = HKEYSTATE_NOT_IT; + ProcessKey(&EvHKeyDown1.key); + /* ugly hack: small delay to ensure that the key event is + * actually handled _prior_ to the mouse click event */ + RTThreadSleep(20); + break; + case HKEYSTATE_DOWN: + enmHKeyState = HKEYSTATE_NOT_IT; + ProcessKey(&EvHKeyDown1.key); + if (gHostKeySym2 != SDLK_UNKNOWN) + ProcessKey(&EvHKeyDown2.key); + /* ugly hack: small delay to ensure that the key event is + * actually handled _prior_ to the mouse click event */ + RTThreadSleep(20); + break; + default: + break; + } + + VBoxSDLFB *fb; + fb = getFbFromWinId(event.button.windowID); + AssertPtrBreak(fb); + SendMouseEvent(fb, 0 /*wheel vertical movement*/, event.type == SDL_MOUSEBUTTONDOWN, bev->button); + } + break; + } + +#if 0 + /* + * The window has gained or lost focus. + */ + case SDL_ACTIVEEVENT: /** @todo Needs to be also fixed with SDL2? Check! */ + { + /* + * There is a strange behaviour in SDL when running without a window + * manager: When SDL_WM_GrabInput(SDL_GRAB_ON) is called we receive two + * consecutive events SDL_ACTIVEEVENTs (input lost, input gained). + * Asking SDL_GetAppState() seems the better choice. + */ + if (gfGrabbed && (SDL_GetAppState() & SDL_APPINPUTFOCUS) == 0) + { + /* + * another window has stolen the (keyboard) input focus + */ + InputGrabEnd(); + } + break; + } + + /* + * The SDL window was resized. + * For SDL2 this is done in SDL_WINDOWEVENT. + */ + case SDL_VIDEORESIZE: + { + if (gpDisplay) + { + if (gfIgnoreNextResize) + { + gfIgnoreNextResize = FALSE; + break; + } + uResizeWidth = event.resize.w; + uResizeHeight = event.resize.h; + if (gSdlResizeTimer) + SDL_RemoveTimer(gSdlResizeTimer); + gSdlResizeTimer = SDL_AddTimer(300, ResizeTimer, NULL); + } + break; + } +#endif + /* + * User specific update event. + */ + /** @todo use a common user event handler so that SDL_PeepEvents() won't + * possibly remove other events in the queue! + */ + case SDL_USER_EVENT_UPDATERECT: + { + /* + * Decode event parameters. + */ + ASMAtomicDecS32(&g_cNotifyUpdateEventsPending); + #define DECODEX(event) (int)((intptr_t)(event).user.data1 >> 16) + #define DECODEY(event) (int)((intptr_t)(event).user.data1 & 0xFFFF) + #define DECODEW(event) (int)((intptr_t)(event).user.data2 >> 16) + #define DECODEH(event) (int)((intptr_t)(event).user.data2 & 0xFFFF) + int x = DECODEX(event); + int y = DECODEY(event); + int w = DECODEW(event); + int h = DECODEH(event); + LogFlow(("SDL_USER_EVENT_UPDATERECT: x = %d, y = %d, w = %d, h = %d\n", + x, y, w, h)); + + Assert(gpFramebuffer[event.user.code]); + gpFramebuffer[event.user.code]->update(x, y, w, h, true /* fGuestRelative */); + + #undef DECODEX + #undef DECODEY + #undef DECODEW + #undef DECODEH + break; + } + + /* + * User event: Window resize done + */ + case SDL_USER_EVENT_WINDOW_RESIZE_DONE: + { + /** + * @todo This is a workaround for synchronization problems between EMT and the + * SDL main thread. It can happen that the SDL thread already starts a + * new resize operation while the EMT is still busy with the old one + * leading to a deadlock. Therefore we call SetVideoModeHint only once + * when the mouse button was released. + */ + /* communicate the resize event to the guest */ + gpDisplay->SetVideoModeHint(0 /*=display*/, true /*=enabled*/, false /*=changeOrigin*/, + 0 /*=originX*/, 0 /*=originY*/, + uResizeWidth, uResizeHeight, 0 /*=don't change bpp*/, true /*=notify*/); + break; + + } + + /* + * User specific framebuffer change event. + */ + case SDL_USER_EVENT_NOTIFYCHANGE: + { + LogFlow(("SDL_USER_EVENT_NOTIFYCHANGE\n")); + LONG xOrigin, yOrigin; + gpFramebuffer[event.user.code]->notifyChange(event.user.code); + /* update xOrigin, yOrigin -> mouse */ + ULONG dummy; + GuestMonitorStatus_T monitorStatus; + hrc = gpDisplay->GetScreenResolution(event.user.code, &dummy, &dummy, &dummy, &xOrigin, &yOrigin, &monitorStatus); + gpFramebuffer[event.user.code]->setOrigin(xOrigin, yOrigin); + break; + } + +#ifdef USE_XPCOM_QUEUE_THREAD + /* + * User specific XPCOM event queue event + */ + case SDL_USER_EVENT_XPCOM_EVENTQUEUE: + { + LogFlow(("SDL_USER_EVENT_XPCOM_EVENTQUEUE: processing XPCOM event queue...\n")); + eventQ->processEventQueue(0); + signalXPCOMEventQueueThread(); + break; + } +#endif /* USE_XPCOM_QUEUE_THREAD */ + + /* + * User specific update title bar notification event + */ + case SDL_USER_EVENT_UPDATE_TITLEBAR: + { + UpdateTitlebar(TITLEBAR_NORMAL); + break; + } + + /* + * User specific termination event + */ + case SDL_USER_EVENT_TERMINATE: + { + if (event.user.code != VBOXSDL_TERM_NORMAL) + RTPrintf("Error: VM terminated abnormally!\n"); + goto leave; + } + /* + * User specific pointer shape change event + */ + case SDL_USER_EVENT_POINTER_CHANGE: + { + PointerShapeChangeData *data = (PointerShapeChangeData *)event.user.data1; + SetPointerShape (data); + delete data; + break; + } + + /* + * User specific guest capabilities changed + */ + case SDL_USER_EVENT_GUEST_CAP_CHANGED: + { + HandleGuestCapsChanged(); + break; + } + + default: + { + Log8(("unknown SDL event %d\n", event.type)); + break; + } + } + } + +leave: + if (gpszPidFile) + RTFileDelete(gpszPidFile); + + LogFlow(("leaving...\n")); +#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2) + /* make sure the XPCOM event queue thread doesn't do anything harmful */ + terminateXPCOMQueueThread(); +#endif /* VBOX_WITH_XPCOM */ + + if (gpVRDEServer) + hrc = gpVRDEServer->COMSETTER(Enabled)(FALSE); + + /* + * Get the machine state. + */ + if (gpMachine) + gpMachine->COMGETTER(State)(&machineState); + else + machineState = MachineState_Aborted; + + if (!fSeparate) + { + /* + * Turn off the VM if it's running + */ + if ( gpConsole + && ( machineState == MachineState_Running + || machineState == MachineState_Teleporting + || machineState == MachineState_LiveSnapshotting + /** @todo power off paused VMs too? */ + ) + ) + do + { + pConsoleListener->getWrapped()->ignorePowerOffEvents(true); + ComPtr<IProgress> pProgress; + CHECK_ERROR_BREAK(gpConsole, PowerDown(pProgress.asOutParam())); + CHECK_ERROR_BREAK(pProgress, WaitForCompletion(-1)); + BOOL completed; + CHECK_ERROR_BREAK(pProgress, COMGETTER(Completed)(&completed)); + ASSERT(completed); + LONG hrc2; + CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&hrc2)); + if (FAILED(hrc2)) + { + com::ErrorInfo info; + if (info.isFullAvailable()) + PrintError("Failed to power down VM", + info.getText().raw(), info.getComponent().raw()); + else + RTPrintf("Failed to power down virtual machine! No error information available (rc=%Rhrc).\n", hrc2); + break; + } + } while (0); + } + + /* unregister Console listener */ + if (pConsoleListener) + { + ComPtr<IEventSource> pES; + CHECK_ERROR(gpConsole, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR(pES, UnregisterListener(pConsoleListener)); + pConsoleListener.setNull(); + } + + /* + * Now we discard all settings so that our changes will + * not be flushed to the permanent configuration + */ + if ( gpMachine + && machineState != MachineState_Saved + && machineState != MachineState_AbortedSaved) + { + hrc = gpMachine->DiscardSettings(); + AssertMsg(SUCCEEDED(hrc), ("DiscardSettings %Rhrc, machineState %d\n", hrc, machineState)); + } + + /* close the session */ + if (sessionOpened) + { + hrc = pSession->UnlockMachine(); + AssertComRC(hrc); + } + + LogFlow(("Releasing mouse, keyboard, remote desktop server, display, console...\n")); + if (gpDisplay) + { + for (unsigned i = 0; i < gcMonitors; i++) + gpDisplay->DetachFramebuffer(i, gaFramebufferId[i].raw()); + } + + gpMouse = NULL; + gpKeyboard = NULL; + gpVRDEServer = NULL; + gpDisplay = NULL; + gpConsole = NULL; + gpMachineDebugger = NULL; + gpProgress = NULL; + // we can only uninitialize SDL here because it is not threadsafe + + for (unsigned i = 0; i < gcMonitors; i++) + { + if (gpFramebuffer[i]) + { + LogFlow(("Releasing framebuffer...\n")); + gpFramebuffer[i]->Release(); + gpFramebuffer[i] = NULL; + } + } + + VBoxSDLFB::uninit(); + + /* VirtualBox (server) listener unregistration. */ + if (pVBoxListener) + { + ComPtr<IEventSource> pES; + CHECK_ERROR(pVirtualBox, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR(pES, UnregisterListener(pVBoxListener)); + pVBoxListener.setNull(); + } + + /* VirtualBoxClient listener unregistration. */ + if (pVBoxClientListener) + { + ComPtr<IEventSource> pES; + CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam())); + if (!pES.isNull()) + CHECK_ERROR(pES, UnregisterListener(pVBoxClientListener)); + pVBoxClientListener.setNull(); + } + + LogFlow(("Releasing machine, session...\n")); + gpMachine = NULL; + pSession = NULL; + LogFlow(("Releasing VirtualBox object...\n")); + pVirtualBox = NULL; + LogFlow(("Releasing VirtualBoxClient object...\n")); + pVirtualBoxClient = NULL; + + // end "all-stuff" scope + //////////////////////////////////////////////////////////////////////////// + } + + /* Must be before com::Shutdown() */ + LogFlow(("Uninitializing COM...\n")); + com::Shutdown(); + + LogFlow(("Returning from main()!\n")); + RTLogFlush(NULL); + +#ifdef RT_OS_WINDOWS + FreeConsole(); /* Detach or destroy (from) console. */ +#endif + + return FAILED(hrc) ? 1 : 0; +} + +#ifndef VBOX_WITH_HARDENING +/** + * Main entry point + */ +int main(int argc, char **argv) +{ +#ifdef Q_WS_X11 + if (!XInitThreads()) + return 1; +#endif + /* + * Before we do *anything*, we initialize the runtime. + */ + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + return TrustedMain(argc, argv, NULL); +} +#endif /* !VBOX_WITH_HARDENING */ + + +/** + * Returns whether the absolute mouse is in use, i.e. both host + * and guest have opted to enable it. + * + * @returns bool Flag whether the absolute mouse is in use + */ +static bool UseAbsoluteMouse(void) +{ + return (gfAbsoluteMouseHost && gfAbsoluteMouseGuest); +} + +#if defined(RT_OS_DARWIN) || defined(RT_OS_OS2) +/** + * Fallback keycode conversion using SDL symbols. + * + * This is used to catch keycodes that's missing from the translation table. + * + * @returns XT scancode + * @param ev SDL scancode + */ +static uint16_t Keyevent2KeycodeFallback(const SDL_KeyboardEvent *ev) +{ + const SDLKey sym = ev->keysym.sym; + Log(("SDL key event: sym=%d scancode=%#x unicode=%#x\n", + sym, ev->keysym.scancode, ev->keysym.unicode)); + switch (sym) + { /* set 1 scan code */ + case SDLK_ESCAPE: return 0x01; + case SDLK_EXCLAIM: + case SDLK_1: return 0x02; + case SDLK_AT: + case SDLK_2: return 0x03; + case SDLK_HASH: + case SDLK_3: return 0x04; + case SDLK_DOLLAR: + case SDLK_4: return 0x05; + /* % */ + case SDLK_5: return 0x06; + case SDLK_CARET: + case SDLK_6: return 0x07; + case SDLK_AMPERSAND: + case SDLK_7: return 0x08; + case SDLK_ASTERISK: + case SDLK_8: return 0x09; + case SDLK_LEFTPAREN: + case SDLK_9: return 0x0a; + case SDLK_RIGHTPAREN: + case SDLK_0: return 0x0b; + case SDLK_UNDERSCORE: + case SDLK_MINUS: return 0x0c; + case SDLK_EQUALS: + case SDLK_PLUS: return 0x0d; + case SDLK_BACKSPACE: return 0x0e; + case SDLK_TAB: return 0x0f; + case SDLK_q: return 0x10; + case SDLK_w: return 0x11; + case SDLK_e: return 0x12; + case SDLK_r: return 0x13; + case SDLK_t: return 0x14; + case SDLK_y: return 0x15; + case SDLK_u: return 0x16; + case SDLK_i: return 0x17; + case SDLK_o: return 0x18; + case SDLK_p: return 0x19; + case SDLK_LEFTBRACKET: return 0x1a; + case SDLK_RIGHTBRACKET: return 0x1b; + case SDLK_RETURN: return 0x1c; + case SDLK_KP_ENTER: return 0x1c | 0x100; + case SDLK_LCTRL: return 0x1d; + case SDLK_RCTRL: return 0x1d | 0x100; + case SDLK_a: return 0x1e; + case SDLK_s: return 0x1f; + case SDLK_d: return 0x20; + case SDLK_f: return 0x21; + case SDLK_g: return 0x22; + case SDLK_h: return 0x23; + case SDLK_j: return 0x24; + case SDLK_k: return 0x25; + case SDLK_l: return 0x26; + case SDLK_COLON: + case SDLK_SEMICOLON: return 0x27; + case SDLK_QUOTEDBL: + case SDLK_QUOTE: return 0x28; + case SDLK_BACKQUOTE: return 0x29; + case SDLK_LSHIFT: return 0x2a; + case SDLK_BACKSLASH: return 0x2b; + case SDLK_z: return 0x2c; + case SDLK_x: return 0x2d; + case SDLK_c: return 0x2e; + case SDLK_v: return 0x2f; + case SDLK_b: return 0x30; + case SDLK_n: return 0x31; + case SDLK_m: return 0x32; + case SDLK_LESS: + case SDLK_COMMA: return 0x33; + case SDLK_GREATER: + case SDLK_PERIOD: return 0x34; + case SDLK_KP_DIVIDE: /*??*/ + case SDLK_QUESTION: + case SDLK_SLASH: return 0x35; + case SDLK_RSHIFT: return 0x36; + case SDLK_KP_MULTIPLY: + case SDLK_PRINT: return 0x37; /* fixme */ + case SDLK_LALT: return 0x38; + case SDLK_MODE: /* alt gr*/ + case SDLK_RALT: return 0x38 | 0x100; + case SDLK_SPACE: return 0x39; + case SDLK_CAPSLOCK: return 0x3a; + case SDLK_F1: return 0x3b; + case SDLK_F2: return 0x3c; + case SDLK_F3: return 0x3d; + case SDLK_F4: return 0x3e; + case SDLK_F5: return 0x3f; + case SDLK_F6: return 0x40; + case SDLK_F7: return 0x41; + case SDLK_F8: return 0x42; + case SDLK_F9: return 0x43; + case SDLK_F10: return 0x44; + case SDLK_PAUSE: return 0x45; /* not right */ + case SDLK_NUMLOCK: return 0x45; + case SDLK_SCROLLOCK: return 0x46; + case SDLK_KP7: return 0x47; + case SDLK_HOME: return 0x47 | 0x100; + case SDLK_KP8: return 0x48; + case SDLK_UP: return 0x48 | 0x100; + case SDLK_KP9: return 0x49; + case SDLK_PAGEUP: return 0x49 | 0x100; + case SDLK_KP_MINUS: return 0x4a; + case SDLK_KP4: return 0x4b; + case SDLK_LEFT: return 0x4b | 0x100; + case SDLK_KP5: return 0x4c; + case SDLK_KP6: return 0x4d; + case SDLK_RIGHT: return 0x4d | 0x100; + case SDLK_KP_PLUS: return 0x4e; + case SDLK_KP1: return 0x4f; + case SDLK_END: return 0x4f | 0x100; + case SDLK_KP2: return 0x50; + case SDLK_DOWN: return 0x50 | 0x100; + case SDLK_KP3: return 0x51; + case SDLK_PAGEDOWN: return 0x51 | 0x100; + case SDLK_KP0: return 0x52; + case SDLK_INSERT: return 0x52 | 0x100; + case SDLK_KP_PERIOD: return 0x53; + case SDLK_DELETE: return 0x53 | 0x100; + case SDLK_SYSREQ: return 0x54; + case SDLK_F11: return 0x57; + case SDLK_F12: return 0x58; + case SDLK_F13: return 0x5b; + case SDLK_LMETA: + case SDLK_LSUPER: return 0x5b | 0x100; + case SDLK_F14: return 0x5c; + case SDLK_RMETA: + case SDLK_RSUPER: return 0x5c | 0x100; + case SDLK_F15: return 0x5d; + case SDLK_MENU: return 0x5d | 0x100; +#if 0 + case SDLK_CLEAR: return 0x; + case SDLK_KP_EQUALS: return 0x; + case SDLK_COMPOSE: return 0x; + case SDLK_HELP: return 0x; + case SDLK_BREAK: return 0x; + case SDLK_POWER: return 0x; + case SDLK_EURO: return 0x; + case SDLK_UNDO: return 0x; +#endif + default: + Log(("Unhandled sdl key event: sym=%d scancode=%#x unicode=%#x\n", + ev->keysym.sym, ev->keysym.scancode, ev->keysym.unicode)); + return 0; + } +} +#endif /* RT_OS_DARWIN */ + +/** + * Converts an SDL keyboard eventcode to a XT scancode. + * + * @returns XT scancode + * @param ev SDL scancode + */ +static uint16_t Keyevent2Keycode(const SDL_KeyboardEvent *ev) +{ + // start with the scancode determined by SDL + int keycode = ev->keysym.scancode; + +#ifdef VBOXSDL_WITH_X11 + switch (ev->keysym.sym) + { + case SDLK_ESCAPE: return 0x01; + case SDLK_EXCLAIM: + case SDLK_1: return 0x02; + case SDLK_AT: + case SDLK_2: return 0x03; + case SDLK_HASH: + case SDLK_3: return 0x04; + case SDLK_DOLLAR: + case SDLK_4: return 0x05; + /* % */ + case SDLK_5: return 0x06; + case SDLK_CARET: + case SDLK_6: return 0x07; + case SDLK_AMPERSAND: + case SDLK_7: return 0x08; + case SDLK_ASTERISK: + case SDLK_8: return 0x09; + case SDLK_LEFTPAREN: + case SDLK_9: return 0x0a; + case SDLK_RIGHTPAREN: + case SDLK_0: return 0x0b; + case SDLK_UNDERSCORE: + case SDLK_MINUS: return 0x0c; + case SDLK_PLUS: return 0x0d; + case SDLK_BACKSPACE: return 0x0e; + case SDLK_TAB: return 0x0f; + case SDLK_q: return 0x10; + case SDLK_w: return 0x11; + case SDLK_e: return 0x12; + case SDLK_r: return 0x13; + case SDLK_t: return 0x14; + case SDLK_y: return 0x15; + case SDLK_u: return 0x16; + case SDLK_i: return 0x17; + case SDLK_o: return 0x18; + case SDLK_p: return 0x19; + case SDLK_RETURN: return 0x1c; + case SDLK_KP_ENTER: return 0x1c | 0x100; + case SDLK_LCTRL: return 0x1d; + case SDLK_RCTRL: return 0x1d | 0x100; + case SDLK_a: return 0x1e; + case SDLK_s: return 0x1f; + case SDLK_d: return 0x20; + case SDLK_f: return 0x21; + case SDLK_g: return 0x22; + case SDLK_h: return 0x23; + case SDLK_j: return 0x24; + case SDLK_k: return 0x25; + case SDLK_l: return 0x26; + case SDLK_COLON: return 0x27; + case SDLK_QUOTEDBL: + case SDLK_QUOTE: return 0x28; + case SDLK_BACKQUOTE: return 0x29; + case SDLK_LSHIFT: return 0x2a; + case SDLK_z: return 0x2c; + case SDLK_x: return 0x2d; + case SDLK_c: return 0x2e; + case SDLK_v: return 0x2f; + case SDLK_b: return 0x30; + case SDLK_n: return 0x31; + case SDLK_m: return 0x32; + case SDLK_LESS: return 0x33; + case SDLK_GREATER: return 0x34; + case SDLK_KP_DIVIDE: /*??*/ + case SDLK_QUESTION: return 0x35; + case SDLK_RSHIFT: return 0x36; + case SDLK_KP_MULTIPLY: + //case SDLK_PRINT: return 0x37; /* fixme */ + case SDLK_LALT: return 0x38; + case SDLK_MODE: /* alt gr*/ + case SDLK_RALT: return 0x38 | 0x100; + case SDLK_SPACE: return 0x39; + case SDLK_CAPSLOCK: return 0x3a; + case SDLK_F1: return 0x3b; + case SDLK_F2: return 0x3c; + case SDLK_F3: return 0x3d; + case SDLK_F4: return 0x3e; + case SDLK_F5: return 0x3f; + case SDLK_F6: return 0x40; + case SDLK_F7: return 0x41; + case SDLK_F8: return 0x42; + case SDLK_F9: return 0x43; + case SDLK_F10: return 0x44; + case SDLK_PAUSE: return 0x45; /* not right */ + //case SDLK_NUMLOCK: return 0x45; + //case SDLK_SCROLLOCK: return 0x46; + //case SDLK_KP7: return 0x47; + case SDLK_HOME: return 0x47 | 0x100; + //case SDLK_KP8: return 0x48; + case SDLK_UP: return 0x48 | 0x100; + //case SDLK_KP9: return 0x49; + case SDLK_PAGEUP: return 0x49 | 0x100; + case SDLK_KP_MINUS: return 0x4a; + //case SDLK_KP4: return 0x4b; + case SDLK_LEFT: return 0x4b | 0x100; + //case SDLK_KP5: return 0x4c; + //case SDLK_KP6: return 0x4d; + case SDLK_RIGHT: return 0x4d | 0x100; + case SDLK_KP_PLUS: return 0x4e; + //case SDLK_KP1: return 0x4f; + case SDLK_END: return 0x4f | 0x100; + //case SDLK_KP2: return 0x50; + case SDLK_DOWN: return 0x50 | 0x100; + //case SDLK_KP3: return 0x51; + case SDLK_PAGEDOWN: return 0x51 | 0x100; + //case SDLK_KP0: return 0x52; + case SDLK_INSERT: return 0x52 | 0x100; + case SDLK_KP_PERIOD: return 0x53; + case SDLK_DELETE: return 0x53 | 0x100; + case SDLK_SYSREQ: return 0x54; + case SDLK_F11: return 0x57; + case SDLK_F12: return 0x58; + case SDLK_F13: return 0x5b; + case SDLK_F14: return 0x5c; + case SDLK_F15: return 0x5d; + case SDLK_MENU: return 0x5d | 0x100; + default: + return 0; + } +#elif defined(RT_OS_DARWIN) + /* This is derived partially from SDL_QuartzKeys.h and partially from testing. */ + static const uint16_t s_aMacToSet1[] = + { + /* set-1 SDL_QuartzKeys.h */ + 0x1e, /* QZ_a 0x00 */ + 0x1f, /* QZ_s 0x01 */ + 0x20, /* QZ_d 0x02 */ + 0x21, /* QZ_f 0x03 */ + 0x23, /* QZ_h 0x04 */ + 0x22, /* QZ_g 0x05 */ + 0x2c, /* QZ_z 0x06 */ + 0x2d, /* QZ_x 0x07 */ + 0x2e, /* QZ_c 0x08 */ + 0x2f, /* QZ_v 0x09 */ + 0x56, /* between lshift and z. 'INT 1'? */ + 0x30, /* QZ_b 0x0B */ + 0x10, /* QZ_q 0x0C */ + 0x11, /* QZ_w 0x0D */ + 0x12, /* QZ_e 0x0E */ + 0x13, /* QZ_r 0x0F */ + 0x15, /* QZ_y 0x10 */ + 0x14, /* QZ_t 0x11 */ + 0x02, /* QZ_1 0x12 */ + 0x03, /* QZ_2 0x13 */ + 0x04, /* QZ_3 0x14 */ + 0x05, /* QZ_4 0x15 */ + 0x07, /* QZ_6 0x16 */ + 0x06, /* QZ_5 0x17 */ + 0x0d, /* QZ_EQUALS 0x18 */ + 0x0a, /* QZ_9 0x19 */ + 0x08, /* QZ_7 0x1A */ + 0x0c, /* QZ_MINUS 0x1B */ + 0x09, /* QZ_8 0x1C */ + 0x0b, /* QZ_0 0x1D */ + 0x1b, /* QZ_RIGHTBRACKET 0x1E */ + 0x18, /* QZ_o 0x1F */ + 0x16, /* QZ_u 0x20 */ + 0x1a, /* QZ_LEFTBRACKET 0x21 */ + 0x17, /* QZ_i 0x22 */ + 0x19, /* QZ_p 0x23 */ + 0x1c, /* QZ_RETURN 0x24 */ + 0x26, /* QZ_l 0x25 */ + 0x24, /* QZ_j 0x26 */ + 0x28, /* QZ_QUOTE 0x27 */ + 0x25, /* QZ_k 0x28 */ + 0x27, /* QZ_SEMICOLON 0x29 */ + 0x2b, /* QZ_BACKSLASH 0x2A */ + 0x33, /* QZ_COMMA 0x2B */ + 0x35, /* QZ_SLASH 0x2C */ + 0x31, /* QZ_n 0x2D */ + 0x32, /* QZ_m 0x2E */ + 0x34, /* QZ_PERIOD 0x2F */ + 0x0f, /* QZ_TAB 0x30 */ + 0x39, /* QZ_SPACE 0x31 */ + 0x29, /* QZ_BACKQUOTE 0x32 */ + 0x0e, /* QZ_BACKSPACE 0x33 */ + 0x9c, /* QZ_IBOOK_ENTER 0x34 */ + 0x01, /* QZ_ESCAPE 0x35 */ + 0x5c|0x100, /* QZ_RMETA 0x36 */ + 0x5b|0x100, /* QZ_LMETA 0x37 */ + 0x2a, /* QZ_LSHIFT 0x38 */ + 0x3a, /* QZ_CAPSLOCK 0x39 */ + 0x38, /* QZ_LALT 0x3A */ + 0x1d, /* QZ_LCTRL 0x3B */ + 0x36, /* QZ_RSHIFT 0x3C */ + 0x38|0x100, /* QZ_RALT 0x3D */ + 0x1d|0x100, /* QZ_RCTRL 0x3E */ + 0, /* */ + 0, /* */ + 0x53, /* QZ_KP_PERIOD 0x41 */ + 0, /* */ + 0x37, /* QZ_KP_MULTIPLY 0x43 */ + 0, /* */ + 0x4e, /* QZ_KP_PLUS 0x45 */ + 0, /* */ + 0x45, /* QZ_NUMLOCK 0x47 */ + 0, /* */ + 0, /* */ + 0, /* */ + 0x35|0x100, /* QZ_KP_DIVIDE 0x4B */ + 0x1c|0x100, /* QZ_KP_ENTER 0x4C */ + 0, /* */ + 0x4a, /* QZ_KP_MINUS 0x4E */ + 0, /* */ + 0, /* */ + 0x0d/*?*/, /* QZ_KP_EQUALS 0x51 */ + 0x52, /* QZ_KP0 0x52 */ + 0x4f, /* QZ_KP1 0x53 */ + 0x50, /* QZ_KP2 0x54 */ + 0x51, /* QZ_KP3 0x55 */ + 0x4b, /* QZ_KP4 0x56 */ + 0x4c, /* QZ_KP5 0x57 */ + 0x4d, /* QZ_KP6 0x58 */ + 0x47, /* QZ_KP7 0x59 */ + 0, /* */ + 0x48, /* QZ_KP8 0x5B */ + 0x49, /* QZ_KP9 0x5C */ + 0, /* */ + 0, /* */ + 0, /* */ + 0x3f, /* QZ_F5 0x60 */ + 0x40, /* QZ_F6 0x61 */ + 0x41, /* QZ_F7 0x62 */ + 0x3d, /* QZ_F3 0x63 */ + 0x42, /* QZ_F8 0x64 */ + 0x43, /* QZ_F9 0x65 */ + 0, /* */ + 0x57, /* QZ_F11 0x67 */ + 0, /* */ + 0x37|0x100, /* QZ_PRINT / F13 0x69 */ + 0x63, /* QZ_F16 0x6A */ + 0x46, /* QZ_SCROLLOCK 0x6B */ + 0, /* */ + 0x44, /* QZ_F10 0x6D */ + 0x5d|0x100, /* */ + 0x58, /* QZ_F12 0x6F */ + 0, /* */ + 0/* 0xe1,0x1d,0x45*/, /* QZ_PAUSE 0x71 */ + 0x52|0x100, /* QZ_INSERT / HELP 0x72 */ + 0x47|0x100, /* QZ_HOME 0x73 */ + 0x49|0x100, /* QZ_PAGEUP 0x74 */ + 0x53|0x100, /* QZ_DELETE 0x75 */ + 0x3e, /* QZ_F4 0x76 */ + 0x4f|0x100, /* QZ_END 0x77 */ + 0x3c, /* QZ_F2 0x78 */ + 0x51|0x100, /* QZ_PAGEDOWN 0x79 */ + 0x3b, /* QZ_F1 0x7A */ + 0x4b|0x100, /* QZ_LEFT 0x7B */ + 0x4d|0x100, /* QZ_RIGHT 0x7C */ + 0x50|0x100, /* QZ_DOWN 0x7D */ + 0x48|0x100, /* QZ_UP 0x7E */ + 0x5e|0x100, /* QZ_POWER 0x7F */ /* have different break key! */ + }; + + if (keycode == 0) + { + /* This could be a modifier or it could be 'a'. */ + switch (ev->keysym.sym) + { + case SDLK_LSHIFT: keycode = 0x2a; break; + case SDLK_RSHIFT: keycode = 0x36; break; + case SDLK_LCTRL: keycode = 0x1d; break; + case SDLK_RCTRL: keycode = 0x1d | 0x100; break; + case SDLK_LALT: keycode = 0x38; break; + case SDLK_MODE: /* alt gr */ + case SDLK_RALT: keycode = 0x38 | 0x100; break; + case SDLK_RMETA: + case SDLK_RSUPER: keycode = 0x5c | 0x100; break; + case SDLK_LMETA: + case SDLK_LSUPER: keycode = 0x5b | 0x100; break; + /* Assumes normal key. */ + default: keycode = s_aMacToSet1[keycode]; break; + } + } + else + { + if ((unsigned)keycode < RT_ELEMENTS(s_aMacToSet1)) + keycode = s_aMacToSet1[keycode]; + else + keycode = 0; + if (!keycode) + { +# ifdef DEBUG_bird + RTPrintf("Untranslated: keycode=%#x (%d)\n", keycode, keycode); +# endif + keycode = Keyevent2KeycodeFallback(ev); + } + } +# ifdef DEBUG_bird + RTPrintf("scancode=%#x -> %#x\n", ev->keysym.scancode, keycode); +# endif + +#elif defined(RT_OS_OS2) + keycode = Keyevent2KeycodeFallback(ev); +#endif /* RT_OS_DARWIN */ + return keycode; +} + +/** + * Releases any modifier keys that are currently in pressed state. + */ +static void ResetKeys(void) +{ + int i; + + if (!gpKeyboard) + return; + + for(i = 0; i < 256; i++) + { + if (gaModifiersState[i]) + { + if (i & 0x80) + gpKeyboard->PutScancode(0xe0); + gpKeyboard->PutScancode(i | 0x80); + gaModifiersState[i] = 0; + } + } +} + +/** + * Keyboard event handler. + * + * @param ev SDL keyboard event. + */ +static void ProcessKey(SDL_KeyboardEvent *ev) +{ +#if 0 //(defined(DEBUG) || defined(VBOX_WITH_STATISTICS)) && !defined(VBOX_WITH_SDL2) + if (gpMachineDebugger && ev->type == SDL_KEYDOWN) + { + // first handle the debugger hotkeys + uint8_t *keystate = SDL_GetKeyState(NULL); +#if 0 + // CTRL+ALT+Fn is not free on Linux hosts with Xorg .. + if (keystate[SDLK_LALT] && !keystate[SDLK_LCTRL]) +#else + if (keystate[SDLK_LALT] && keystate[SDLK_LCTRL]) +#endif + { + switch (ev->keysym.sym) + { + // pressing CTRL+ALT+F11 dumps the statistics counter + case SDLK_F12: + RTPrintf("ResetStats\n"); /* Visual feedback in console window */ + gpMachineDebugger->ResetStats(NULL); + break; + // pressing CTRL+ALT+F12 resets all statistics counter + case SDLK_F11: + gpMachineDebugger->DumpStats(NULL); + RTPrintf("DumpStats\n"); /* Vistual feedback in console window */ + break; + default: + break; + } + } +#if 1 + else if (keystate[SDLK_LALT] && !keystate[SDLK_LCTRL]) + { + switch (ev->keysym.sym) + { + // pressing Alt-F8 toggles singlestepping mode + case SDLK_F8: + { + BOOL singlestepEnabled; + gpMachineDebugger->COMGETTER(SingleStep)(&singlestepEnabled); + gpMachineDebugger->COMSETTER(SingleStep)(!singlestepEnabled); + break; + } + default: + break; + } + } +#endif + // pressing Ctrl-F12 toggles the logger + else if ((keystate[SDLK_RCTRL] || keystate[SDLK_LCTRL]) && ev->keysym.sym == SDLK_F12) + { + BOOL logEnabled = TRUE; + gpMachineDebugger->COMGETTER(LogEnabled)(&logEnabled); + gpMachineDebugger->COMSETTER(LogEnabled)(!logEnabled); +#ifdef DEBUG_bird + return; +#endif + } + // pressing F12 sets a logmark + else if (ev->keysym.sym == SDLK_F12) + { + RTLogPrintf("****** LOGGING MARK ******\n"); + RTLogFlush(NULL); + } + // now update the titlebar flags + UpdateTitlebar(TITLEBAR_NORMAL); + } +#endif // DEBUG || VBOX_WITH_STATISTICS + + // the pause key is the weirdest, needs special handling + if (ev->keysym.sym == SDLK_PAUSE) + { + int v = 0; + if (ev->type == SDL_KEYUP) + v |= 0x80; + gpKeyboard->PutScancode(0xe1); + gpKeyboard->PutScancode(0x1d | v); + gpKeyboard->PutScancode(0x45 | v); + return; + } + + /* + * Perform SDL key event to scancode conversion + */ + int keycode = Keyevent2Keycode(ev); + + switch(keycode) + { + case 0x00: + { + /* sent when leaving window: reset the modifiers state */ + ResetKeys(); + return; + } + + case 0x2a: /* Left Shift */ + case 0x36: /* Right Shift */ + case 0x1d: /* Left CTRL */ + case 0x1d|0x100: /* Right CTRL */ + case 0x38: /* Left ALT */ + case 0x38|0x100: /* Right ALT */ + { + if (ev->type == SDL_KEYUP) + gaModifiersState[keycode & ~0x100] = 0; + else + gaModifiersState[keycode & ~0x100] = 1; + break; + } + + case 0x45: /* Num Lock */ + case 0x3a: /* Caps Lock */ + { + /* + * SDL generates a KEYDOWN event if the lock key is active and a KEYUP event + * if the lock key is inactive. See SDL_DISABLE_LOCK_KEYS. + */ + if (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) + { + gpKeyboard->PutScancode(keycode); + gpKeyboard->PutScancode(keycode | 0x80); + } + return; + } + } + + if (ev->type != SDL_KEYDOWN) + { + /* + * Some keyboards (e.g. the one of mine T60) don't send a NumLock scan code on every + * press of the key. Both the guest and the host should agree on the NumLock state. + * If they differ, we try to alter the guest NumLock state by sending the NumLock key + * scancode. We will get a feedback through the KBD_CMD_SET_LEDS command if the guest + * tries to set/clear the NumLock LED. If a (silly) guest doesn't change the LED, don't + * bother him with NumLock scancodes. At least our BIOS, Linux and Windows handle the + * NumLock LED well. + */ + if ( gcGuestNumLockAdaptions + && (gfGuestNumLockPressed ^ !!(SDL_GetModState() & KMOD_NUM))) + { + gcGuestNumLockAdaptions--; + gpKeyboard->PutScancode(0x45); + gpKeyboard->PutScancode(0x45 | 0x80); + } + if ( gcGuestCapsLockAdaptions + && (gfGuestCapsLockPressed ^ !!(SDL_GetModState() & KMOD_CAPS))) + { + gcGuestCapsLockAdaptions--; + gpKeyboard->PutScancode(0x3a); + gpKeyboard->PutScancode(0x3a | 0x80); + } + } + + /* + * Now we send the event. Apply extended and release prefixes. + */ + if (keycode & 0x100) + gpKeyboard->PutScancode(0xe0); + + gpKeyboard->PutScancode(ev->type == SDL_KEYUP ? (keycode & 0x7f) | 0x80 + : (keycode & 0x7f)); +} + +#ifdef RT_OS_DARWIN +#include <Carbon/Carbon.h> +RT_C_DECLS_BEGIN +/* Private interface in 10.3 and later. */ +typedef int CGSConnection; +typedef enum +{ + kCGSGlobalHotKeyEnable = 0, + kCGSGlobalHotKeyDisable, + kCGSGlobalHotKeyInvalid = -1 /* bird */ +} CGSGlobalHotKeyOperatingMode; +extern CGSConnection _CGSDefaultConnection(void); +extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode *enmMode); +extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode enmMode); +RT_C_DECLS_END + +/** Keeping track of whether we disabled the hotkeys or not. */ +static bool g_fHotKeysDisabled = false; +/** Whether we've connected or not. */ +static bool g_fConnectedToCGS = false; +/** Cached connection. */ +static CGSConnection g_CGSConnection; + +/** + * Disables or enabled global hot keys. + */ +static void DisableGlobalHotKeys(bool fDisable) +{ + if (!g_fConnectedToCGS) + { + g_CGSConnection = _CGSDefaultConnection(); + g_fConnectedToCGS = true; + } + + /* get current mode. */ + CGSGlobalHotKeyOperatingMode enmMode = kCGSGlobalHotKeyInvalid; + CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmMode); + + /* calc new mode. */ + if (fDisable) + { + if (enmMode != kCGSGlobalHotKeyEnable) + return; + enmMode = kCGSGlobalHotKeyDisable; + } + else + { + if ( enmMode != kCGSGlobalHotKeyDisable + /*|| !g_fHotKeysDisabled*/) + return; + enmMode = kCGSGlobalHotKeyEnable; + } + + /* try set it and check the actual result. */ + CGSSetGlobalHotKeyOperatingMode(g_CGSConnection, enmMode); + CGSGlobalHotKeyOperatingMode enmNewMode = kCGSGlobalHotKeyInvalid; + CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmNewMode); + if (enmNewMode == enmMode) + g_fHotKeysDisabled = enmMode == kCGSGlobalHotKeyDisable; +} +#endif /* RT_OS_DARWIN */ + +/** + * Start grabbing the mouse. + */ +static void InputGrabStart(void) +{ +#ifdef RT_OS_DARWIN + DisableGlobalHotKeys(true); +#endif + if (!gfGuestNeedsHostCursor && gfRelativeMouseGuest) + SDL_ShowCursor(SDL_DISABLE); + SDL_SetRelativeMouseMode(SDL_TRUE); + gfGrabbed = TRUE; + UpdateTitlebar(TITLEBAR_NORMAL); +} + +/** + * End mouse grabbing. + */ +static void InputGrabEnd(void) +{ + SDL_SetRelativeMouseMode(SDL_FALSE); + if (!gfGuestNeedsHostCursor && gfRelativeMouseGuest) + SDL_ShowCursor(SDL_ENABLE); +#ifdef RT_OS_DARWIN + DisableGlobalHotKeys(false); +#endif + gfGrabbed = FALSE; + UpdateTitlebar(TITLEBAR_NORMAL); +} + +/** + * Query mouse position and button state from SDL and send to the VM + * + * @param dz Relative mouse wheel movement + */ +static void SendMouseEvent(VBoxSDLFB *fb, int dz, int down, int button) +{ + int x, y, state, buttons; + bool abs; + + if (!fb) + { + SDL_GetMouseState(&x, &y); + RTPrintf("MouseEvent: Cannot find fb mouse = %d,%d\n", x, y); + return; + } + + /* + * If supported and we're not in grabbed mode, we'll use the absolute mouse. + * If we are in grabbed mode and the guest is not able to draw the mouse cursor + * itself, or can't handle relative reporting, we have to use absolute + * coordinates, otherwise the host cursor and + * the coordinates the guest thinks the mouse is at could get out-of-sync. From + * the SDL mailing list: + * + * "The event processing is usually asynchronous and so somewhat delayed, and + * SDL_GetMouseState is returning the immediate mouse state. So at the time you + * call SDL_GetMouseState, the "button" is already up." + */ + abs = (UseAbsoluteMouse() && !gfGrabbed) + || gfGuestNeedsHostCursor + || !gfRelativeMouseGuest; + + /* only used if abs == TRUE */ + int xOrigin = fb->getOriginX(); + int yOrigin = fb->getOriginY(); + int xMin = fb->getXOffset() + xOrigin; + int yMin = fb->getYOffset() + yOrigin; + int xMax = xMin + (int)fb->getGuestXRes(); + int yMax = yMin + (int)fb->getGuestYRes(); + + state = abs ? SDL_GetMouseState(&x, &y) + : SDL_GetRelativeMouseState(&x, &y); + + /* + * process buttons + */ + buttons = 0; + if (state & SDL_BUTTON(SDL_BUTTON_LEFT)) + buttons |= MouseButtonState_LeftButton; + if (state & SDL_BUTTON(SDL_BUTTON_RIGHT)) + buttons |= MouseButtonState_RightButton; + if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) + buttons |= MouseButtonState_MiddleButton; + + if (abs) + { + x += xOrigin; + y += yOrigin; + + /* + * Check if the mouse event is inside the guest area. This solves the + * following problem: Some guests switch off the VBox hardware mouse + * cursor and draw the mouse cursor itself instead. Moving the mouse + * outside the guest area then leads to annoying mouse hangs if we + * don't pass mouse motion events into the guest. + */ + if (x < xMin || y < yMin || x > xMax || y > yMax) + { + /* + * Cursor outside of valid guest area (outside window or in secure + * label area. Don't allow any mouse button press. + */ + button = 0; + + /* + * Release any pressed button. + */ +#if 0 + /* disabled on customers request */ + buttons &= ~(MouseButtonState_LeftButton | + MouseButtonState_MiddleButton | + MouseButtonState_RightButton); +#endif + + /* + * Prevent negative coordinates. + */ + if (x < xMin) x = xMin; + if (x > xMax) x = xMax; + if (y < yMin) y = yMin; + if (y > yMax) y = yMax; + + if (!gpOffCursor) + { + gpOffCursor = SDL_GetCursor(); /* Cursor image */ + gfOffCursorActive = SDL_ShowCursor(-1); /* enabled / disabled */ + SDL_SetCursor(gpDefaultCursor); + SDL_ShowCursor(SDL_ENABLE); + } + } + else + { + if (gpOffCursor) + { + /* + * We just entered the valid guest area. Restore the guest mouse + * cursor. + */ + SDL_SetCursor(gpOffCursor); + SDL_ShowCursor(gfOffCursorActive ? SDL_ENABLE : SDL_DISABLE); + gpOffCursor = NULL; + } + } + } + + /* + * Button was pressed but that press is not reflected in the button state? + */ + if (down && !(state & SDL_BUTTON(button))) + { + /* + * It can happen that a mouse up event follows a mouse down event immediately + * and we see the events when the bit in the button state is already cleared + * again. In that case we simulate the mouse down event. + */ + int tmp_button = 0; + switch (button) + { + case SDL_BUTTON_LEFT: tmp_button = MouseButtonState_LeftButton; break; + case SDL_BUTTON_MIDDLE: tmp_button = MouseButtonState_MiddleButton; break; + case SDL_BUTTON_RIGHT: tmp_button = MouseButtonState_RightButton; break; + } + + if (abs) + { + /** + * @todo + * PutMouseEventAbsolute() expects x and y starting from 1,1. + * should we do the increment internally in PutMouseEventAbsolute() + * or state it in PutMouseEventAbsolute() docs? + */ + gpMouse->PutMouseEventAbsolute(x + 1 - xMin + xOrigin, + y + 1 - yMin + yOrigin, + dz, 0 /* horizontal scroll wheel */, + buttons | tmp_button); + } + else + { + gpMouse->PutMouseEvent(0, 0, dz, + 0 /* horizontal scroll wheel */, + buttons | tmp_button); + } + } + + // now send the mouse event + if (abs) + { + /** + * @todo + * PutMouseEventAbsolute() expects x and y starting from 1,1. + * should we do the increment internally in PutMouseEventAbsolute() + * or state it in PutMouseEventAbsolute() docs? + */ + gpMouse->PutMouseEventAbsolute(x + 1 - xMin + xOrigin, + y + 1 - yMin + yOrigin, + dz, 0 /* Horizontal wheel */, buttons); + } + else + { + gpMouse->PutMouseEvent(x, y, dz, 0 /* Horizontal wheel */, buttons); + } +} + +/** + * Resets the VM + */ +void ResetVM(void) +{ + if (gpConsole) + gpConsole->Reset(); +} + +/** + * Initiates a saved state and updates the titlebar with progress information + */ +void SaveState(void) +{ + ResetKeys(); + RTThreadYield(); + if (gfGrabbed) + InputGrabEnd(); + RTThreadYield(); + UpdateTitlebar(TITLEBAR_SAVE); + gpProgress = NULL; + HRESULT hrc = gpMachine->SaveState(gpProgress.asOutParam()); + if (FAILED(hrc)) + { + RTPrintf("Error saving state! rc=%Rhrc\n", hrc); + return; + } + Assert(gpProgress); + + /* + * Wait for the operation to be completed and work + * the title bar in the mean while. + */ + ULONG cPercent = 0; +#ifndef RT_OS_DARWIN /* don't break the other guys yet. */ + for (;;) + { + BOOL fCompleted = false; + hrc = gpProgress->COMGETTER(Completed)(&fCompleted); + if (FAILED(hrc) || fCompleted) + break; + ULONG cPercentNow; + hrc = gpProgress->COMGETTER(Percent)(&cPercentNow); + if (FAILED(hrc)) + break; + if (cPercentNow != cPercent) + { + UpdateTitlebar(TITLEBAR_SAVE, cPercent); + cPercent = cPercentNow; + } + + /* wait */ + hrc = gpProgress->WaitForCompletion(100); + if (FAILED(hrc)) + break; + /// @todo process gui events. + } + +#else /* new loop which processes GUI events while saving. */ + + /* start regular timer so we don't starve in the event loop */ + SDL_TimerID sdlTimer; + sdlTimer = SDL_AddTimer(100, StartupTimer, NULL); + + for (;;) + { + /* + * Check for completion. + */ + BOOL fCompleted = false; + hrc = gpProgress->COMGETTER(Completed)(&fCompleted); + if (FAILED(hrc) || fCompleted) + break; + ULONG cPercentNow; + hrc = gpProgress->COMGETTER(Percent)(&cPercentNow); + if (FAILED(hrc)) + break; + if (cPercentNow != cPercent) + { + UpdateTitlebar(TITLEBAR_SAVE, cPercent); + cPercent = cPercentNow; + } + + /* + * Wait for and process GUI a event. + * This is necessary for XPCOM IPC and for updating the + * title bar on the Mac. + */ + SDL_Event event; + if (WaitSDLEvent(&event)) + { + switch (event.type) + { + /* + * Timer event preventing us from getting stuck. + */ + case SDL_USER_EVENT_TIMER: + break; + +#ifdef USE_XPCOM_QUEUE_THREAD + /* + * User specific XPCOM event queue event + */ + case SDL_USER_EVENT_XPCOM_EVENTQUEUE: + { + LogFlow(("SDL_USER_EVENT_XPCOM_EVENTQUEUE: processing XPCOM event queue...\n")); + eventQ->ProcessPendingEvents(); + signalXPCOMEventQueueThread(); + break; + } +#endif /* USE_XPCOM_QUEUE_THREAD */ + + + /* + * Ignore all other events. + */ + case SDL_USER_EVENT_NOTIFYCHANGE: + case SDL_USER_EVENT_TERMINATE: + default: + break; + } + } + } + + /* kill the timer */ + SDL_RemoveTimer(sdlTimer); + sdlTimer = 0; + +#endif /* RT_OS_DARWIN */ + + /* + * What's the result of the operation? + */ + LONG lrc; + hrc = gpProgress->COMGETTER(ResultCode)(&lrc); + if (FAILED(hrc)) + lrc = ~0; + if (!lrc) + { + UpdateTitlebar(TITLEBAR_SAVE, 100); + RTThreadYield(); + RTPrintf("Saved the state successfully.\n"); + } + else + RTPrintf("Error saving state, lrc=%d (%#x)\n", lrc, lrc); +} + +/** + * Build the titlebar string + */ +static void UpdateTitlebar(TitlebarMode mode, uint32_t u32User) +{ + static char szTitle[1024] = {0}; + + /* back up current title */ + char szPrevTitle[1024]; + strcpy(szPrevTitle, szTitle); + + Bstr bstrName; + gpMachine->COMGETTER(Name)(bstrName.asOutParam()); + + RTStrPrintf(szTitle, sizeof(szTitle), "%s - " VBOX_PRODUCT, + !bstrName.isEmpty() ? Utf8Str(bstrName).c_str() : "<noname>"); + + /* which mode are we in? */ + switch (mode) + { + case TITLEBAR_NORMAL: + { + MachineState_T machineState; + gpMachine->COMGETTER(State)(&machineState); + if (machineState == MachineState_Paused) + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), " - [Paused]"); + + if (gfGrabbed) + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), " - [Input captured]"); + +#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS) + // do we have a debugger interface + if (gpMachineDebugger) + { + // query the machine state + BOOL singlestepEnabled = FALSE; + BOOL logEnabled = FALSE; + VMExecutionEngine_T enmExecEngine = VMExecutionEngine_NotSet; + ULONG virtualTimeRate = 100; + gpMachineDebugger->COMGETTER(LogEnabled)(&logEnabled); + gpMachineDebugger->COMGETTER(SingleStep)(&singlestepEnabled); + gpMachineDebugger->COMGETTER(ExecutionEngine)(&enmExecEngine); + gpMachineDebugger->COMGETTER(VirtualTimeRate)(&virtualTimeRate); + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " [STEP=%d LOG=%d EXEC=%s", + singlestepEnabled == TRUE, logEnabled == TRUE, + enmExecEngine == VMExecutionEngine_NotSet ? "NotSet" + : enmExecEngine == VMExecutionEngine_Emulated ? "IEM" + : enmExecEngine == VMExecutionEngine_HwVirt ? "HM" + : enmExecEngine == VMExecutionEngine_NativeApi ? "NEM" : "UNK"); + char *psz = strchr(szTitle, '\0'); + if (virtualTimeRate != 100) + RTStrPrintf(psz, &szTitle[sizeof(szTitle)] - psz, " WD=%d%%]", virtualTimeRate); + else + RTStrPrintf(psz, &szTitle[sizeof(szTitle)] - psz, "]"); + } +#endif /* DEBUG || VBOX_WITH_STATISTICS */ + break; + } + + case TITLEBAR_STARTUP: + { + /* + * Format it. + */ + MachineState_T machineState; + gpMachine->COMGETTER(State)(&machineState); + if (machineState == MachineState_Starting) + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Starting..."); + else if (machineState == MachineState_Restoring) + { + ULONG cPercentNow; + HRESULT hrc = gpProgress->COMGETTER(Percent)(&cPercentNow); + if (SUCCEEDED(hrc)) + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Restoring %d%%...", (int)cPercentNow); + else + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Restoring..."); + } + else if (machineState == MachineState_TeleportingIn) + { + ULONG cPercentNow; + HRESULT hrc = gpProgress->COMGETTER(Percent)(&cPercentNow); + if (SUCCEEDED(hrc)) + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Teleporting %d%%...", (int)cPercentNow); + else + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Teleporting..."); + } + /* ignore other states, we could already be in running or aborted state */ + break; + } + + case TITLEBAR_SAVE: + { + AssertMsg(u32User <= 100, ("%d\n", u32User)); + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Saving %d%%...", u32User); + break; + } + + case TITLEBAR_SNAPSHOT: + { + AssertMsg(u32User <= 100, ("%d\n", u32User)); + RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), + " - Taking snapshot %d%%...", u32User); + break; + } + + default: + RTPrintf("Error: Invalid title bar mode %d!\n", mode); + return; + } + + /* + * Don't update if it didn't change. + */ + if (!strcmp(szTitle, szPrevTitle)) + return; + + /* + * Set the new title + */ +#ifdef VBOX_WIN32_UI + setUITitle(szTitle); +#else + for (unsigned i = 0; i < gcMonitors; i++) + gpFramebuffer[i]->setWindowTitle(szTitle); +#endif +} + +#if 0 +static void vbox_show_shape(unsigned short w, unsigned short h, + uint32_t bg, const uint8_t *image) +{ + size_t x, y; + unsigned short pitch; + const uint32_t *color; + const uint8_t *mask; + size_t size_mask; + + mask = image; + pitch = (w + 7) / 8; + size_mask = (pitch * h + 3) & ~3; + + color = (const uint32_t *)(image + size_mask); + + printf("show_shape %dx%d pitch %d size mask %d\n", + w, h, pitch, size_mask); + for (y = 0; y < h; ++y, mask += pitch, color += w) + { + for (x = 0; x < w; ++x) { + if (mask[x / 8] & (1 << (7 - (x % 8)))) + printf(" "); + else + { + uint32_t c = color[x]; + if (c == bg) + printf("Y"); + else + printf("X"); + } + } + printf("\n"); + } +} +#endif + +/** + * Sets the pointer shape according to parameters. + * Must be called only from the main SDL thread. + */ +static void SetPointerShape(const PointerShapeChangeData *data) +{ + /* + * don't allow to change the pointer shape if we are outside the valid + * guest area. In that case set standard mouse pointer is set and should + * not get overridden. + */ + if (gpOffCursor) + return; + + if (data->shape.size() > 0) + { + bool ok = false; + + uint32_t andMaskSize = (data->width + 7) / 8 * data->height; + uint32_t srcShapePtrScan = data->width * 4; + + const uint8_t* shape = data->shape.raw(); + const uint8_t *srcAndMaskPtr = shape; + const uint8_t *srcShapePtr = shape + ((andMaskSize + 3) & ~3); + +#if 0 + /* pointer debugging code */ + // vbox_show_shape(data->width, data->height, 0, data->shape); + uint32_t shapeSize = ((((data->width + 7) / 8) * data->height + 3) & ~3) + data->width * 4 * data->height; + printf("visible: %d\n", data->visible); + printf("width = %d\n", data->width); + printf("height = %d\n", data->height); + printf("alpha = %d\n", data->alpha); + printf("xhot = %d\n", data->xHot); + printf("yhot = %d\n", data->yHot); + printf("uint8_t pointerdata[] = { "); + for (uint32_t i = 0; i < shapeSize; i++) + { + printf("0x%x, ", data->shape[i]); + } + printf("};\n"); +#endif + +#if defined(RT_OS_WINDOWS) + + BITMAPV5HEADER bi; + HBITMAP hBitmap; + void *lpBits; + + ::ZeroMemory(&bi, sizeof(BITMAPV5HEADER)); + bi.bV5Size = sizeof(BITMAPV5HEADER); + bi.bV5Width = data->width; + bi.bV5Height = -(LONG)data->height; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + // specify a supported 32 BPP alpha format for Windows XP + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + if (data->alpha) + bi.bV5AlphaMask = 0xFF000000; + else + bi.bV5AlphaMask = 0; + + HDC hdc = ::GetDC(NULL); + + // create the DIB section with an alpha channel + hBitmap = ::CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, + (void **)&lpBits, NULL, (DWORD)0); + + ::ReleaseDC(NULL, hdc); + + HBITMAP hMonoBitmap = NULL; + if (data->alpha) + { + // create an empty mask bitmap + hMonoBitmap = ::CreateBitmap(data->width, data->height, 1, 1, NULL); + } + else + { + /* Word aligned AND mask. Will be allocated and created if necessary. */ + uint8_t *pu8AndMaskWordAligned = NULL; + + /* Width in bytes of the original AND mask scan line. */ + uint32_t cbAndMaskScan = (data->width + 7) / 8; + + if (cbAndMaskScan & 1) + { + /* Original AND mask is not word aligned. */ + + /* Allocate memory for aligned AND mask. */ + pu8AndMaskWordAligned = (uint8_t *)RTMemTmpAllocZ((cbAndMaskScan + 1) * data->height); + + Assert(pu8AndMaskWordAligned); + + if (pu8AndMaskWordAligned) + { + /* According to MSDN the padding bits must be 0. + * Compute the bit mask to set padding bits to 0 in the last byte of original AND mask. + */ + uint32_t u32PaddingBits = cbAndMaskScan * 8 - data->width; + Assert(u32PaddingBits < 8); + uint8_t u8LastBytesPaddingMask = (uint8_t)(0xFF << u32PaddingBits); + + Log(("u8LastBytesPaddingMask = %02X, aligned w = %d, width = %d, cbAndMaskScan = %d\n", + u8LastBytesPaddingMask, (cbAndMaskScan + 1) * 8, data->width, cbAndMaskScan)); + + uint8_t *src = (uint8_t *)srcAndMaskPtr; + uint8_t *dst = pu8AndMaskWordAligned; + + unsigned i; + for (i = 0; i < data->height; i++) + { + memcpy(dst, src, cbAndMaskScan); + + dst[cbAndMaskScan - 1] &= u8LastBytesPaddingMask; + + src += cbAndMaskScan; + dst += cbAndMaskScan + 1; + } + } + } + + // create the AND mask bitmap + hMonoBitmap = ::CreateBitmap(data->width, data->height, 1, 1, + pu8AndMaskWordAligned? pu8AndMaskWordAligned: srcAndMaskPtr); + + if (pu8AndMaskWordAligned) + { + RTMemTmpFree(pu8AndMaskWordAligned); + } + } + + Assert(hBitmap); + Assert(hMonoBitmap); + if (hBitmap && hMonoBitmap) + { + DWORD *dstShapePtr = (DWORD *)lpBits; + + for (uint32_t y = 0; y < data->height; y ++) + { + memcpy(dstShapePtr, srcShapePtr, srcShapePtrScan); + srcShapePtr += srcShapePtrScan; + dstShapePtr += data->width; + } + } + + if (hMonoBitmap) + ::DeleteObject(hMonoBitmap); + if (hBitmap) + ::DeleteObject(hBitmap); + +#elif defined(VBOXSDL_WITH_X11) && !defined(VBOX_WITHOUT_XCURSOR) + + if (gfXCursorEnabled) + { + XcursorImage *img = XcursorImageCreate(data->width, data->height); + Assert(img); + if (img) + { + img->xhot = data->xHot; + img->yhot = data->yHot; + + XcursorPixel *dstShapePtr = img->pixels; + + for (uint32_t y = 0; y < data->height; y ++) + { + memcpy(dstShapePtr, srcShapePtr, srcShapePtrScan); + + if (!data->alpha) + { + // convert AND mask to the alpha channel + uint8_t byte = 0; + for (uint32_t x = 0; x < data->width; x ++) + { + if (!(x % 8)) + byte = *(srcAndMaskPtr ++); + else + byte <<= 1; + + if (byte & 0x80) + { + // Linux doesn't support inverted pixels (XOR ops, + // to be exact) in cursor shapes, so we detect such + // pixels and always replace them with black ones to + // make them visible at least over light colors + if (dstShapePtr [x] & 0x00FFFFFF) + dstShapePtr [x] = 0xFF000000; + else + dstShapePtr [x] = 0x00000000; + } + else + dstShapePtr [x] |= 0xFF000000; + } + } + + srcShapePtr += srcShapePtrScan; + dstShapePtr += data->width; + } + } + XcursorImageDestroy(img); + } + +#endif /* VBOXSDL_WITH_X11 && !VBOX_WITHOUT_XCURSOR */ + + if (!ok) + { + SDL_SetCursor(gpDefaultCursor); + SDL_ShowCursor(SDL_ENABLE); + } + } + else + { + if (data->visible) + SDL_ShowCursor(SDL_ENABLE); + else if (gfAbsoluteMouseGuest) + /* Don't disable the cursor if the guest additions are not active (anymore) */ + SDL_ShowCursor(SDL_DISABLE); + } +} + +/** + * Handle changed mouse capabilities + */ +static void HandleGuestCapsChanged(void) +{ + if (!gfAbsoluteMouseGuest) + { + // Cursor could be overwritten by the guest tools + SDL_SetCursor(gpDefaultCursor); + SDL_ShowCursor(SDL_ENABLE); + gpOffCursor = NULL; + } + if (gpMouse && UseAbsoluteMouse()) + { + // Actually switch to absolute coordinates + if (gfGrabbed) + InputGrabEnd(); + gpMouse->PutMouseEventAbsolute(-1, -1, 0, 0, 0); + } +} + +/** + * Handles a host key down event + */ +static int HandleHostKey(const SDL_KeyboardEvent *pEv) +{ + /* + * Revalidate the host key modifier + */ + if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) != gHostKeyMod) + return VERR_NOT_SUPPORTED; + + /* + * What was pressed? + */ + switch (pEv->keysym.sym) + { + /* Control-Alt-Delete */ + case SDLK_DELETE: + { + gpKeyboard->PutCAD(); + break; + } + + /* + * Fullscreen / Windowed toggle. + */ + case SDLK_f: + { + if ( strchr(gHostKeyDisabledCombinations, 'f') + || !gfAllowFullscreenToggle) + return VERR_NOT_SUPPORTED; + + /* + * We have to pause/resume the machine during this + * process because there might be a short moment + * without a valid framebuffer + */ + MachineState_T machineState; + gpMachine->COMGETTER(State)(&machineState); + bool fPauseIt = machineState == MachineState_Running + || machineState == MachineState_Teleporting + || machineState == MachineState_LiveSnapshotting; + if (fPauseIt) + gpConsole->Pause(); + SetFullscreen(!gpFramebuffer[0]->getFullscreen()); + if (fPauseIt) + gpConsole->Resume(); + + /* + * We have switched from/to fullscreen, so request a full + * screen repaint, just to be sure. + */ + gpDisplay->InvalidateAndUpdate(); + break; + } + + /* + * Pause / Resume toggle. + */ + case SDLK_p: + { + if (strchr(gHostKeyDisabledCombinations, 'p')) + return VERR_NOT_SUPPORTED; + + MachineState_T machineState; + gpMachine->COMGETTER(State)(&machineState); + if ( machineState == MachineState_Running + || machineState == MachineState_Teleporting + || machineState == MachineState_LiveSnapshotting + ) + { + if (gfGrabbed) + InputGrabEnd(); + gpConsole->Pause(); + } + else if (machineState == MachineState_Paused) + { + gpConsole->Resume(); + } + UpdateTitlebar(TITLEBAR_NORMAL); + break; + } + + /* + * Reset the VM + */ + case SDLK_r: + { + if (strchr(gHostKeyDisabledCombinations, 'r')) + return VERR_NOT_SUPPORTED; + + ResetVM(); + break; + } + + /* + * Terminate the VM + */ + case SDLK_q: + { + if (strchr(gHostKeyDisabledCombinations, 'q')) + return VERR_NOT_SUPPORTED; + + return VINF_EM_TERMINATE; + } + + /* + * Save the machine's state and exit + */ + case SDLK_s: + { + if (strchr(gHostKeyDisabledCombinations, 's')) + return VERR_NOT_SUPPORTED; + + SaveState(); + return VINF_EM_TERMINATE; + } + + case SDLK_h: + { + if (strchr(gHostKeyDisabledCombinations, 'h')) + return VERR_NOT_SUPPORTED; + + if (gpConsole) + gpConsole->PowerButton(); + break; + } + + /* + * Perform an online snapshot. Continue operation. + */ + case SDLK_n: + { + if (strchr(gHostKeyDisabledCombinations, 'n')) + return VERR_NOT_SUPPORTED; + + RTThreadYield(); + ULONG cSnapshots = 0; + gpMachine->COMGETTER(SnapshotCount)(&cSnapshots); + char pszSnapshotName[20]; + RTStrPrintf(pszSnapshotName, sizeof(pszSnapshotName), "Snapshot %d", cSnapshots + 1); + gpProgress = NULL; + HRESULT hrc; + Bstr snapId; + CHECK_ERROR(gpMachine, TakeSnapshot(Bstr(pszSnapshotName).raw(), + Bstr("Taken by VBoxSDL").raw(), + TRUE, snapId.asOutParam(), + gpProgress.asOutParam())); + if (FAILED(hrc)) + { + RTPrintf("Error taking snapshot! rc=%Rhrc\n", hrc); + /* continue operation */ + return VINF_SUCCESS; + } + /* + * Wait for the operation to be completed and work + * the title bar in the mean while. + */ + ULONG cPercent = 0; + for (;;) + { + BOOL fCompleted = false; + hrc = gpProgress->COMGETTER(Completed)(&fCompleted); + if (FAILED(hrc) || fCompleted) + break; + ULONG cPercentNow; + hrc = gpProgress->COMGETTER(Percent)(&cPercentNow); + if (FAILED(hrc)) + break; + if (cPercentNow != cPercent) + { + UpdateTitlebar(TITLEBAR_SNAPSHOT, cPercent); + cPercent = cPercentNow; + } + + /* wait */ + hrc = gpProgress->WaitForCompletion(100); + if (FAILED(hrc)) + break; + /// @todo process gui events. + } + + /* continue operation */ + return VINF_SUCCESS; + } + + case SDLK_F1: case SDLK_F2: case SDLK_F3: + case SDLK_F4: case SDLK_F5: case SDLK_F6: + case SDLK_F7: case SDLK_F8: case SDLK_F9: + case SDLK_F10: case SDLK_F11: case SDLK_F12: + { + // /* send Ctrl-Alt-Fx to guest */ + com::SafeArray<LONG> keys(6); + + keys[0] = 0x1d; // Ctrl down + keys[1] = 0x38; // Alt down + keys[2] = Keyevent2Keycode(pEv); // Fx down + keys[3] = keys[2] + 0x80; // Fx up + keys[4] = 0xb8; // Alt up + keys[5] = 0x9d; // Ctrl up + + gpKeyboard->PutScancodes(ComSafeArrayAsInParam(keys), NULL); + return VINF_SUCCESS; + } + + /* + * Not a host key combination. + * Indicate this by returning false. + */ + default: + return VERR_NOT_SUPPORTED; + } + + return VINF_SUCCESS; +} + +/** + * Timer callback function for startup processing + */ +static Uint32 StartupTimer(Uint32 interval, void *param) RT_NOTHROW_DEF +{ + RT_NOREF(param); + + /* post message so we can do something in the startup loop */ + SDL_Event event = {0}; + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_TIMER; + SDL_PushEvent(&event); + RTSemEventSignal(g_EventSemSDLEvents); + return interval; +} + +/** + * Timer callback function to check if resizing is finished + */ +static Uint32 ResizeTimer(Uint32 interval, void *param) RT_NOTHROW_DEF +{ + RT_NOREF(interval, param); + + /* post message so the window is actually resized */ + SDL_Event event = {0}; + event.type = SDL_USEREVENT; + event.user.type = SDL_USER_EVENT_WINDOW_RESIZE_DONE; + PushSDLEventForSure(&event); + /* one-shot */ + return 0; +} + +/** + * Timer callback function to check if an ACPI power button event was handled by the guest. + */ +static Uint32 QuitTimer(Uint32 interval, void *param) RT_NOTHROW_DEF +{ + RT_NOREF(interval, param); + + BOOL fHandled = FALSE; + + gSdlQuitTimer = 0; + if (gpConsole) + { + int rc = gpConsole->GetPowerButtonHandled(&fHandled); + LogRel(("QuitTimer: rc=%d handled=%d\n", rc, fHandled)); + if (RT_FAILURE(rc) || !fHandled) + { + /* event was not handled, power down the guest */ + gfACPITerm = FALSE; + SDL_Event event = {0}; + event.type = SDL_QUIT; + PushSDLEventForSure(&event); + } + } + /* one-shot */ + return 0; +} + +/** + * Wait for the next SDL event. Don't use SDL_WaitEvent since this function + * calls SDL_Delay(10) if the event queue is empty. + */ +static int WaitSDLEvent(SDL_Event *event) +{ + for (;;) + { + int rc = SDL_PollEvent(event); + if (rc == 1) + { +#ifdef USE_XPCOM_QUEUE_THREAD + if (event->type == SDL_USER_EVENT_XPCOM_EVENTQUEUE) + consumedXPCOMUserEvent(); +#endif + return 1; + } + /* Immediately wake up if new SDL events are available. This does not + * work for internal SDL events. Don't wait more than 10ms. */ + RTSemEventWait(g_EventSemSDLEvents, 10); + } +} + +/** + * Ensure that an SDL event is really enqueued. Try multiple times if necessary. + */ +int PushSDLEventForSure(SDL_Event *event) +{ + int ntries = 10; + for (; ntries > 0; ntries--) + { + int rc = SDL_PushEvent(event); + RTSemEventSignal(g_EventSemSDLEvents); + if (rc == 1) + return 0; + Log(("PushSDLEventForSure: waiting for 2ms (rc = %d)\n", rc)); + RTThreadSleep(2); + } + LogRel(("WARNING: Failed to enqueue SDL event %d.%d!\n", + event->type, event->type == SDL_USEREVENT ? event->user.type : 0)); + return -1; +} + +#ifdef VBOXSDL_WITH_X11 +/** + * Special SDL_PushEvent function for NotifyUpdate events. These events may occur in bursts + * so make sure they don't flood the SDL event queue. + */ +void PushNotifyUpdateEvent(SDL_Event *event) +{ + int rc = SDL_PushEvent(event); + bool fSuccess = (rc == 1); + + RTSemEventSignal(g_EventSemSDLEvents); + AssertMsg(fSuccess, ("SDL_PushEvent returned SDL error\n")); + /* A global counter is faster than SDL_PeepEvents() */ + if (fSuccess) + ASMAtomicIncS32(&g_cNotifyUpdateEventsPending); + /* In order to not flood the SDL event queue, yield the CPU or (if there are already many + * events queued) even sleep */ + if (g_cNotifyUpdateEventsPending > 96) + { + /* Too many NotifyUpdate events, sleep for a small amount to give the main thread time + * to handle these events. The SDL queue can hold up to 128 events. */ + Log(("PushNotifyUpdateEvent: Sleep 1ms\n")); + RTThreadSleep(1); + } + else + RTThreadYield(); +} +#endif /* VBOXSDL_WITH_X11 */ + +/** + * + */ +static void SetFullscreen(bool enable) +{ + if (enable == gpFramebuffer[0]->getFullscreen()) + return; + + if (!gfFullscreenResize) + { + /* + * The old/default way: SDL will resize the host to fit the guest screen resolution. + */ + gpFramebuffer[0]->setFullscreen(enable); + } + else + { + /* + * The alternate way: Switch to fullscreen with the host screen resolution and adapt + * the guest screen resolution to the host window geometry. + */ + uint32_t NewWidth = 0, NewHeight = 0; + if (enable) + { + /* switch to fullscreen */ + gmGuestNormalXRes = gpFramebuffer[0]->getGuestXRes(); + gmGuestNormalYRes = gpFramebuffer[0]->getGuestYRes(); + gpFramebuffer[0]->getFullscreenGeometry(&NewWidth, &NewHeight); + } + else + { + /* switch back to saved geometry */ + NewWidth = gmGuestNormalXRes; + NewHeight = gmGuestNormalYRes; + } + if (NewWidth != 0 && NewHeight != 0) + { + gpFramebuffer[0]->setFullscreen(enable); + gfIgnoreNextResize = TRUE; + gpDisplay->SetVideoModeHint(0 /*=display*/, true /*=enabled*/, + false /*=changeOrigin*/, 0 /*=originX*/, 0 /*=originY*/, + NewWidth, NewHeight, 0 /*don't change bpp*/, true /*=notify*/); + } + } +} + +static VBoxSDLFB *getFbFromWinId(Uint32 id) +{ + for (unsigned i = 0; i < gcMonitors; i++) + if (gpFramebuffer[i]->hasWindow(id)) + return gpFramebuffer[i]; + + return NULL; +} diff --git a/src/VBox/Frontends/VBoxSDL/VBoxSDL.h b/src/VBox/Frontends/VBoxSDL/VBoxSDL.h new file mode 100644 index 00000000..dde548f8 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/VBoxSDL.h @@ -0,0 +1,103 @@ +/* $Id: VBoxSDL.h $ */ +/** @file + * + * VBox frontends: VBoxSDL (simple frontend based on SDL): + * Main header + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxSDL_VBoxSDL_h +#define VBOX_INCLUDED_SRC_VBoxSDL_VBoxSDL_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#ifdef RT_OS_WINDOWS /** @todo check why we need to do this on windows. */ +/* convince SDL to not overload main() */ +# define _SDL_main_h +#endif + +/* include this first so Windows.h get's in before our stuff. */ +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4121) /* warning C4121: 'SDL_SysWMmsg' : alignment of a member was sensitive to packing*/ +# pragma warning(disable: 4668) /* warning C4668: '__GNUC__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#endif +#include <SDL.h> +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +/** custom SDL event for display update handling */ +#define SDL_USER_EVENT_UPDATERECT (SDL_USEREVENT + 4) +/** custom SDL event for changing the guest resolution */ +#define SDL_USER_EVENT_NOTIFYCHANGE (SDL_USEREVENT + 5) +/** custom SDL for XPCOM event queue processing */ +#define SDL_USER_EVENT_XPCOM_EVENTQUEUE (SDL_USEREVENT + 6) +/** custom SDL event for updating the titlebar */ +#define SDL_USER_EVENT_UPDATE_TITLEBAR (SDL_USEREVENT + 7) +/** custom SDL user event for terminating the session */ +#define SDL_USER_EVENT_TERMINATE (SDL_USEREVENT + 8) +/** custom SDL user event for pointer shape change request */ +#define SDL_USER_EVENT_POINTER_CHANGE (SDL_USEREVENT + 9) +/** custom SDL user event for a regular timer */ +#define SDL_USER_EVENT_TIMER (SDL_USEREVENT + 10) +/** custom SDL user event for resetting mouse cursor */ +#define SDL_USER_EVENT_GUEST_CAP_CHANGED (SDL_USEREVENT + 11) +/** custom SDL user event for window resize done */ +#define SDL_USER_EVENT_WINDOW_RESIZE_DONE (SDL_USEREVENT + 12) + + +/** The user.code field of the SDL_USER_EVENT_TERMINATE event. + * @{ + */ +/** Normal termination. */ +#define VBOXSDL_TERM_NORMAL 0 +/** Abnormal termination. */ +#define VBOXSDL_TERM_ABEND 1 +/** @} */ + +void ResetVM(void); +void SaveState(void); + +#ifdef VBOX_WIN32_UI +int initUI(bool fResizable, int64_t &winId); +int uninitUI(void); +int resizeUI(uint16_t width, uint16_t height); +int setUITitle(char *title); +#endif /* VBOX_WIN32_UI */ + +#ifdef VBOXSDL_WITH_X11 +void PushNotifyUpdateEvent(SDL_Event *event); +#endif +int PushSDLEventForSure(SDL_Event *event); + +#ifdef RT_OS_DARWIN +RT_C_DECLS_BEGIN +void *VBoxSDLGetDarwinWindowId(void); +RT_C_DECLS_END +#endif + +#endif /* !VBOX_INCLUDED_SRC_VBoxSDL_VBoxSDL_h */ diff --git a/src/VBox/Frontends/VBoxSDL/VBoxSDLHardened.cpp b/src/VBox/Frontends/VBoxSDL/VBoxSDLHardened.cpp new file mode 100644 index 00000000..b2573586 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/VBoxSDLHardened.cpp @@ -0,0 +1,35 @@ +/* $Id: VBoxSDLHardened.cpp $ */ +/** @file + * VBoxSDL - Hardened main(). + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <VBox/sup.h> + + +int main(int argc, char **argv, char **envp) +{ + return SUPR3HardenedMain("VBoxSDL", 0, argc, argv, envp); +} + diff --git a/src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.h b/src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.h new file mode 100644 index 00000000..6d4177f9 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.h @@ -0,0 +1,12 @@ +/* $Id: VBoxSDLMain-darwin.h $ */ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser <dwaliss1@purdue.edu> + Non-NIB-Code & other changes: Max Horn <max@quendi.de> + + Feel free to customize this file to suit your needs +*/ + +#import <Cocoa/Cocoa.h> + +@interface SDLMain : NSObject +@end diff --git a/src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.m b/src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.m new file mode 100644 index 00000000..36a1e25b --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.m @@ -0,0 +1,386 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser <dwaliss1@purdue.edu> + Non-NIB-Code & other changes: Max Horn <max@quendi.de> + + Feel free to customize this file to suit your needs +*/ + +#import "SDL.h" +#import "VBoxSDLMain-darwin.h" +#import <sys/param.h> /* for MAXPATHLEN */ +#import <unistd.h> +#import <iprt/assert.h> + +/* For some reason, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface SDLApplication : NSApplication +@end + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) { + int rc = chdir(parentdir); /* chdir to the binary app's parent */ + Assert(rc == 0); + } + CFRelease(url); + CFRelease(url2); + } + +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } + [ aMenu sizeToFit ]; +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [SDLApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [SDLApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } else { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + [SDLApplication poseAsClass:[NSApplication class]]; + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} + diff --git a/src/VBox/Frontends/VBoxSDL/VBoxSDLTest.cpp b/src/VBox/Frontends/VBoxSDL/VBoxSDLTest.cpp new file mode 100644 index 00000000..ac8c2d77 --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/VBoxSDLTest.cpp @@ -0,0 +1,478 @@ +/* $Id: VBoxSDLTest.cpp $ */ +/** @file + * + * VBox frontends: VBoxSDL (simple frontend based on SDL): + * VBoxSDL testcases + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4121) +#endif +#if defined(RT_OS_WINDOWS) /// @todo someone please explain why we don't follow the book! +# define _SDL_main_h +#endif +#include <SDL.h> +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#include <iprt/assert.h> +#include <iprt/env.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> + +#include <stdlib.h> +#include <signal.h> + +#ifdef VBOX_OPENGL +#include "SDL_opengl.h" +#endif + +#ifdef RT_OS_WINDOWS +#define ESC_NORM +#define ESC_BOLD +#else +#define ESC_NORM "\033[m" +#define ESC_BOLD "\033[1m" +#endif + +static SDL_Surface *gSurfVRAM; /* SDL virtual framebuffer surface */ +static void *gPtrVRAM; /* allocated virtual framebuffer */ +static SDL_Surface *gScreen; /* SDL screen surface */ +static unsigned long guGuestXRes; /* virtual framebuffer width */ +static unsigned long guGuestYRes; /* virtual framebuffer height */ +static unsigned long guGuestBpp; /* virtual framebuffer bits per pixel */ +static unsigned long guMaxScreenWidth; /* max screen width SDL allows */ +static unsigned long guMaxScreenHeight; /* max screen height SDL allows */ +static int gfResizable = 1; /* SDL window is resizable */ +static int gfFullscreen = 0; /* use fullscreen mode */ +#ifdef VBOX_OPENGL +static unsigned long guTextureWidth; /* width of OpenGL texture */ +static unsigned long guTextureHeight; /* height of OpenGL texture */ +static unsigned int gTexture; +static int gfOpenGL; /* use OpenGL as backend */ +#endif +static unsigned int guLoop = 1000; /* Number of frame redrawings for each test */ + +static void bench(unsigned long w, unsigned long h, unsigned long bpp); +static void benchExecute(void); +static int checkSDL(const char *fn, int rc); +static void checkEvents(void); + +int +main(int argc, char **argv) +{ + int rc; + RTR3InitExe(argc, &argv, 0); + + for (int i = 1; i < argc; i++) + { +#ifdef VBOX_OPENGL + if (strcmp(argv[i], "-gl") == 0) + { + gfOpenGL = 1; + continue; + } +#endif + if (strcmp(argv[i], "-loop") == 0 && ++i < argc) + { + guLoop = atoi(argv[i]); + continue; + } + RTPrintf("Unrecognized option '%s'\n", argv[i]); + return -1; + } + +#ifdef RT_OS_WINDOWS + /* Default to DirectX if nothing else set. "windib" would be possible. */ + if (!RTEnvExist("SDL_VIDEODRIVER")) + { + _putenv("SDL_VIDEODRIVER=directx"); + } +#endif + +#ifdef RT_OS_WINDOWS + _putenv("SDL_VIDEO_WINDOW_POS=0,0"); +#else + RTEnvSet("SDL_VIDEO_WINDOW_POS", "0,0"); +#endif + + rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE); + if (rc != 0) + { + RTPrintf("Error: SDL_InitSubSystem failed with message '%s'\n", SDL_GetError()); + return -1; + } + + /* output what SDL is capable of */ + const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo(); + + if (!videoInfo) + { + RTPrintf("No SDL video info available!\n"); + return -1; + } + + RTPrintf("SDL capabilities:\n"); + RTPrintf(" Hardware surface support: %s\n", videoInfo->hw_available ? "yes" : "no"); + RTPrintf(" Window manager available: %s\n", videoInfo->wm_available ? "yes" : "no"); + RTPrintf(" Screen to screen blits accelerated: %s\n", videoInfo->blit_hw ? "yes" : "no"); + RTPrintf(" Screen to screen colorkey blits accelerated: %s\n", videoInfo->blit_hw_CC ? "yes" : "no"); + RTPrintf(" Screen to screen alpha blits accelerated: %s\n", videoInfo->blit_hw_A ? "yes" : "no"); + RTPrintf(" Memory to screen blits accelerated: %s\n", videoInfo->blit_sw ? "yes" : "no"); + RTPrintf(" Memory to screen colorkey blits accelerated: %s\n", videoInfo->blit_sw_CC ? "yes" : "no"); + RTPrintf(" Memory to screen alpha blits accelerated: %s\n", videoInfo->blit_sw_A ? "yes" : "no"); + RTPrintf(" Color fills accelerated: %s\n", videoInfo->blit_fill ? "yes" : "no"); + RTPrintf(" Video memory in kilobytes: %d\n", videoInfo->video_mem); + RTPrintf(" Optimal bpp mode: %d\n", videoInfo->vfmt->BitsPerPixel); + char buf[256]; + RTPrintf("Video driver SDL_VIDEODRIVER / active: %s/%s\n", RTEnvGet("SDL_VIDEODRIVER"), + SDL_VideoDriverName(buf, sizeof(buf))); + + RTPrintf("\n" + "Starting tests. Any key pressed inside the SDL window will abort this\n" + "program at the end of the current test. Iterations = %u\n", guLoop); + +#ifdef VBOX_OPENGL + RTPrintf("\n========== "ESC_BOLD"OpenGL is %s"ESC_NORM" ==========\n", + gfOpenGL ? "ON" : "OFF"); +#endif + bench( 640, 480, 16); bench( 640, 480, 24); bench( 640, 480, 32); + bench(1024, 768, 16); bench(1024, 768, 24); bench(1024, 768, 32); + bench(1280, 1024, 16); bench(1280, 1024, 24); bench(1280, 1024, 32); + + RTPrintf("\nSuccess!\n"); + return 0; +} + +/** + * Method that does the actual resize of the guest framebuffer and + * then changes the SDL framebuffer setup. + */ +static void bench(unsigned long w, unsigned long h, unsigned long bpp) +{ + Uint32 Rmask, Gmask, Bmask, Amask = 0; + Uint32 Rsize, Gsize, Bsize; + Uint32 newWidth, newHeight; + + guGuestXRes = w; + guGuestYRes = h; + guGuestBpp = bpp; + + RTPrintf("\n"); + + /* a different format we support directly? */ + switch (guGuestBpp) + { + case 16: + { + Rmask = 0xF800; + Gmask = 0x07E0; + Bmask = 0x001F; + Amask = 0x0000; + Rsize = 5; + Gsize = 6; + Bsize = 5; + break; + } + + case 24: + { + Rmask = 0x00FF0000; + Gmask = 0x0000FF00; + Bmask = 0x000000FF; + Amask = 0x00000000; + Rsize = 8; + Gsize = 8; + Bsize = 8; + break; + } + + default: + Rmask = 0x00FF0000; + Gmask = 0x0000FF00; + Bmask = 0x000000FF; + Amask = 0x00000000; + Rsize = 8; + Gsize = 8; + Bsize = 8; + break; + } + + int sdlFlags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; +#ifdef VBOX_OPENGL + if (gfOpenGL) + sdlFlags |= SDL_OPENGL; +#endif + if (gfResizable) + sdlFlags |= SDL_RESIZABLE; + if (gfFullscreen) + sdlFlags |= SDL_FULLSCREEN; + + /* + * Now we have to check whether there are video mode restrictions + */ + SDL_Rect **modes; + /* Get available fullscreen/hardware modes */ + modes = SDL_ListModes(NULL, sdlFlags); + if (modes == NULL) + { + RTPrintf("Error: SDL_ListModes failed with message '%s'\n", SDL_GetError()); + return; + } + + /* -1 means that any mode is possible (usually non fullscreen) */ + if (modes != (SDL_Rect **)-1) + { + /* + * according to the SDL documentation, the API guarantees that + * the modes are sorted from larger to smaller, so we just + * take the first entry as the maximum. + */ + guMaxScreenWidth = modes[0]->w; + guMaxScreenHeight = modes[0]->h; + } + else + { + /* no restriction */ + guMaxScreenWidth = ~0U; + guMaxScreenHeight = ~0U; + } + + newWidth = RT_MIN(guMaxScreenWidth, guGuestXRes); + newHeight = RT_MIN(guMaxScreenHeight, guGuestYRes); + + /* + * Now set the screen resolution and get the surface pointer + * @todo BPP is not supported! + */ +#ifdef VBOX_OPENGL + if (gfOpenGL) + { + checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_RED_SIZE, Rsize)); + checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, Gsize)); + checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, Bsize)); + checkSDL("SDL_GL_SetAttribute", SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0)); + } +#else + NOREF(Rsize); NOREF(Gsize); NOREF(Bsize); +#endif + + RTPrintf("Testing " ESC_BOLD "%ldx%ld@%ld" ESC_NORM "\n", guGuestXRes, guGuestYRes, guGuestBpp); + + gScreen = SDL_SetVideoMode(newWidth, newHeight, 0, sdlFlags); + if (!gScreen) + { + RTPrintf("SDL_SetVideoMode failed (%s)\n", SDL_GetError()); + return; + } + + /* first free the current surface */ + if (gSurfVRAM) + { + SDL_FreeSurface(gSurfVRAM); + gSurfVRAM = NULL; + } + if (gPtrVRAM) + { + free(gPtrVRAM); + gPtrVRAM = NULL; + } + + if (gScreen->format->BitsPerPixel != guGuestBpp) + { + /* Create a source surface from guest VRAM. */ + int bytes_per_pixel = (guGuestBpp + 7) / 8; + gPtrVRAM = malloc(guGuestXRes * guGuestYRes * bytes_per_pixel); + gSurfVRAM = SDL_CreateRGBSurfaceFrom(gPtrVRAM, guGuestXRes, guGuestYRes, guGuestBpp, + bytes_per_pixel * guGuestXRes, + Rmask, Gmask, Bmask, Amask); + } + else + { + /* Create a software surface for which SDL allocates the RAM */ + gSurfVRAM = SDL_CreateRGBSurface(SDL_SWSURFACE, guGuestXRes, guGuestYRes, guGuestBpp, + Rmask, Gmask, Bmask, Amask); + } + + if (!gSurfVRAM) + { + RTPrintf("Failed to allocate surface %ldx%ld@%ld\n", + guGuestXRes, guGuestYRes, guGuestBpp); + return; + } + + RTPrintf(" gScreen=%dx%d@%d (surface: %s)\n", + gScreen->w, gScreen->h, gScreen->format->BitsPerPixel, + (gScreen->flags & SDL_HWSURFACE) == 0 ? "software" : "hardware"); + + SDL_Rect rect = { 0, 0, (Uint16)guGuestXRes, (Uint16)guGuestYRes }; + checkSDL("SDL_FillRect", + SDL_FillRect(gSurfVRAM, &rect, + SDL_MapRGB(gSurfVRAM->format, 0x5F, 0x6F, 0x1F))); + +#ifdef VBOX_OPENGL + if (gfOpenGL) + { + int r, g, b, d, o; + SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &r); + SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &g); + SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &b); + SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &d); + SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &o); + RTPrintf(" OpenGL ctxt red=%d, green=%d, blue=%d, depth=%d, dbl=%d", r, g, b, d, o); + + glEnable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + glGenTextures(1, &gTexture); + glBindTexture(GL_TEXTURE_2D, gTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + + for (guTextureWidth = 32; guTextureWidth < newWidth; guTextureWidth <<= 1) + ; + for (guTextureHeight = 32; guTextureHeight < newHeight; guTextureHeight <<= 1) + ; + RTPrintf(", tex %ldx%ld\n", guTextureWidth, guTextureHeight); + + switch (guGuestBpp) + { + case 16: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5, guTextureWidth, guTextureHeight, 0, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + break; + case 24: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, guTextureWidth, guTextureHeight, 0, + GL_BGR, GL_UNSIGNED_BYTE, 0); + break; + case 32: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, guTextureWidth, guTextureHeight, 0, + GL_BGRA, GL_UNSIGNED_BYTE, 0); + break; + default: RTPrintf("guGuestBpp=%d?\n", guGuestBpp); + return; + } + + glViewport(0, 0, newWidth, newHeight); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, newWidth, newHeight, 0.0, -1.0, 1.0); + } +#endif + + checkEvents(); + benchExecute(); + +#ifdef VBOX_OPENGL + if (gfOpenGL) + { + glDeleteTextures(1, &gTexture); + } +#endif +} + +static void benchExecute() +{ + SDL_Rect rect = { 0, 0, (Uint16)guGuestXRes, (Uint16)guGuestYRes }; + RTTIMESPEC t1, t2; + + RTTimeNow(&t1); + for (unsigned i=0; i<guLoop; i++) + { +#ifdef VBOX_OPENGL + if (!gfOpenGL) + { +#endif + /* SDL backend */ + checkSDL("SDL_BlitSurface", SDL_BlitSurface(gSurfVRAM, &rect, gScreen, &rect)); + if ((gScreen->flags & SDL_HWSURFACE) == 0) + SDL_UpdateRect(gScreen, rect.x, rect.y, rect.w, rect.h); +#ifdef VBOX_OPENGL + } + else + { + /* OpenGL backend */ + glBindTexture(GL_TEXTURE_2D, gTexture); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x); + glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y); + glPixelStorei(GL_UNPACK_ROW_LENGTH, gSurfVRAM->pitch / gSurfVRAM->format->BytesPerPixel); + switch (gSurfVRAM->format->BitsPerPixel) + { + case 16: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rect.w, rect.h, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, gSurfVRAM->pixels); + break; + case 24: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rect.w, rect.h, + GL_BGR, GL_UNSIGNED_BYTE, gSurfVRAM->pixels); + break; + case 32: glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rect.w, rect.h, + GL_BGRA, GL_UNSIGNED_BYTE, gSurfVRAM->pixels); + break; + default: RTPrintf("BitsPerPixel=%d?\n", gSurfVRAM->format->BitsPerPixel); + return; + } + GLfloat tx = (GLfloat)((float)rect.w) / guTextureWidth; + GLfloat ty = (GLfloat)((float)rect.h) / guTextureHeight; + glBegin(GL_QUADS); + glColor4f(1.0, 1.0, 1.0, 1.0); + glTexCoord2f(0.0, 0.0); glVertex2i(rect.x, rect.y ); + glTexCoord2f(0.0, ty); glVertex2i(rect.x, rect.y + rect.h); + glTexCoord2f(tx, ty); glVertex2i(rect.x + rect.w, rect.y + rect.h); + glTexCoord2f(tx, 0.0); glVertex2i(rect.x + rect.w, rect.y ); + glEnd(); + glFlush(); + } +#endif + } + RTTimeNow(&t2); + int64_t ms = RTTimeSpecGetMilli(&t2) - RTTimeSpecGetMilli(&t1); + printf(" %.1fms/frame\n", (double)ms / guLoop); +} + +static int checkSDL(const char *fn, int rc) +{ + if (rc == -1) + RTPrintf("" ESC_BOLD "%s() failed:" ESC_NORM " '%s'\n", fn, SDL_GetError()); + + return rc; +} + +static void checkEvents(void) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_KEYDOWN: + RTPrintf("\nKey pressed, exiting ...\n"); + exit(-1); + break; + } + } +} diff --git a/src/VBox/Frontends/VBoxSDL/ico64x01.pnm b/src/VBox/Frontends/VBoxSDL/ico64x01.pnm Binary files differnew file mode 100644 index 00000000..a551c6ba --- /dev/null +++ b/src/VBox/Frontends/VBoxSDL/ico64x01.pnm |