summaryrefslogtreecommitdiffstats
path: root/src/VBox/Frontends/VBoxSDL
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Frontends/VBoxSDL
parentInitial commit. (diff)
downloadvirtualbox-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-settings32
-rw-r--r--src/VBox/Frontends/VBoxSDL/Framebuffer-darwin.m51
-rw-r--r--src/VBox/Frontends/VBoxSDL/Framebuffer.cpp959
-rw-r--r--src/VBox/Frontends/VBoxSDL/Framebuffer.h266
-rw-r--r--src/VBox/Frontends/VBoxSDL/Helper.cpp179
-rw-r--r--src/VBox/Frontends/VBoxSDL/Helper.h67
-rw-r--r--src/VBox/Frontends/VBoxSDL/Makefile.kmk167
-rw-r--r--src/VBox/Frontends/VBoxSDL/VBoxSDL.cpp4798
-rw-r--r--src/VBox/Frontends/VBoxSDL/VBoxSDL.h103
-rw-r--r--src/VBox/Frontends/VBoxSDL/VBoxSDLHardened.cpp35
-rw-r--r--src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.h12
-rw-r--r--src/VBox/Frontends/VBoxSDL/VBoxSDLMain-darwin.m386
-rw-r--r--src/VBox/Frontends/VBoxSDL/VBoxSDLTest.cpp478
-rw-r--r--src/VBox/Frontends/VBoxSDL/ico64x01.pnmbin0 -> 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
new file mode 100644
index 00000000..a551c6ba
--- /dev/null
+++ b/src/VBox/Frontends/VBoxSDL/ico64x01.pnm
Binary files differ