VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxSDL/VBoxSDL.cpp@ 106131

Last change on this file since 106131 was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 140.9 KB
Line 
1/* $Id: VBoxSDL.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * VBox frontends: VBoxSDL (simple frontend based on SDL):
4 * Main code
5 */
6
7/*
8 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
9 *
10 * This file is part of VirtualBox base platform packages, as
11 * available from https://www.virtualbox.org.
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation, in version 3 of the
16 * License.
17 *
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, see <https://www.gnu.org/licenses>.
25 *
26 * SPDX-License-Identifier: GPL-3.0-only
27 */
28
29
30/*********************************************************************************************************************************
31* Header Files *
32*********************************************************************************************************************************/
33#define LOG_GROUP LOG_GROUP_GUI
34
35#include <iprt/stream.h>
36
37#include <VBox/com/com.h>
38#include <VBox/com/string.h>
39#include <VBox/com/Guid.h>
40#include <VBox/com/array.h>
41#include <VBox/com/ErrorInfo.h>
42#include <VBox/com/errorprint.h>
43
44#include <VBox/com/NativeEventQueue.h>
45#include <VBox/com/VirtualBox.h>
46
47using namespace com;
48
49#if defined(VBOXSDL_WITH_X11)
50# include <VBox/VBoxKeyboard.h>
51
52# include <X11/Xlib.h>
53# include <X11/cursorfont.h> /* for XC_left_ptr */
54# if !defined(VBOX_WITHOUT_XCURSOR)
55# include <X11/Xcursor/Xcursor.h>
56# endif
57# include <unistd.h>
58#endif
59
60#include "VBoxSDL.h"
61
62#ifdef _MSC_VER
63# pragma warning(push)
64# pragma warning(disable: 4121) /* warning C4121: 'SDL_SysWMmsg' : alignment of a member was sensitive to packing*/
65#endif
66#ifndef RT_OS_DARWIN
67# include <SDL_syswm.h> /* for SDL_GetWMInfo() */
68#endif
69#ifdef _MSC_VER
70# pragma warning(pop)
71#endif
72
73#include "Framebuffer.h"
74#include "Helper.h"
75
76#include <VBox/types.h>
77#include <VBox/err.h>
78#include <VBox/param.h>
79#include <VBox/log.h>
80#include <VBox/version.h>
81#include <VBoxVideo.h>
82#include <VBox/com/listeners.h>
83
84#include <iprt/alloca.h>
85#include <iprt/asm.h>
86#include <iprt/assert.h>
87#include <iprt/ctype.h>
88#include <iprt/env.h>
89#include <iprt/file.h>
90#include <iprt/ldr.h>
91#include <iprt/initterm.h>
92#include <iprt/message.h>
93#include <iprt/path.h>
94#include <iprt/process.h>
95#include <iprt/semaphore.h>
96#include <iprt/string.h>
97#include <iprt/stream.h>
98#include <iprt/uuid.h>
99
100#include <signal.h>
101
102#include <vector>
103#include <list>
104
105#include "PasswordInput.h"
106
107/* Xlib would re-define our enums */
108#undef True
109#undef False
110
111
112/*********************************************************************************************************************************
113* Defined Constants And Macros *
114*********************************************************************************************************************************/
115/** Enables the rawr[0|3], patm, and casm options. */
116#define VBOXSDL_ADVANCED_OPTIONS
117
118
119/*********************************************************************************************************************************
120* Structures and Typedefs *
121*********************************************************************************************************************************/
122/** Pointer shape change event data structure */
123struct PointerShapeChangeData
124{
125 PointerShapeChangeData(BOOL aVisible, BOOL aAlpha, ULONG aXHot, ULONG aYHot,
126 ULONG aWidth, ULONG aHeight, ComSafeArrayIn(BYTE,pShape))
127 : visible(aVisible), alpha(aAlpha), xHot(aXHot), yHot(aYHot),
128 width(aWidth), height(aHeight)
129 {
130 // make a copy of the shape
131 com::SafeArray<BYTE> aShape(ComSafeArrayInArg(pShape));
132 size_t cbShapeSize = aShape.size();
133 if (cbShapeSize > 0)
134 {
135 shape.resize(cbShapeSize);
136 ::memcpy(shape.raw(), aShape.raw(), cbShapeSize);
137 }
138 }
139
140 ~PointerShapeChangeData()
141 {
142 }
143
144 const BOOL visible;
145 const BOOL alpha;
146 const ULONG xHot;
147 const ULONG yHot;
148 const ULONG width;
149 const ULONG height;
150 com::SafeArray<BYTE> shape;
151};
152
153enum TitlebarMode
154{
155 TITLEBAR_NORMAL = 1,
156 TITLEBAR_STARTUP = 2,
157 TITLEBAR_SAVE = 3,
158 TITLEBAR_SNAPSHOT = 4
159};
160
161
162/*********************************************************************************************************************************
163* Internal Functions *
164*********************************************************************************************************************************/
165static bool UseAbsoluteMouse(void);
166static void ResetKeys(void);
167static void ProcessKey(SDL_KeyboardEvent *ev);
168static void InputGrabStart(void);
169static void InputGrabEnd(void);
170static void SendMouseEvent(VBoxSDLFB *fb, int dz, int button, int down);
171static void UpdateTitlebar(TitlebarMode mode, uint32_t u32User = 0);
172static void SetPointerShape(const PointerShapeChangeData *data);
173static void HandleGuestCapsChanged(void);
174static int HandleHostKey(const SDL_KeyboardEvent *pEv);
175static Uint32 StartupTimer(Uint32 interval, void *param) RT_NOTHROW_PROTO;
176static Uint32 ResizeTimer(Uint32 interval, void *param) RT_NOTHROW_PROTO;
177static Uint32 QuitTimer(Uint32 interval, void *param) RT_NOTHROW_PROTO;
178static int WaitSDLEvent(SDL_Event *event);
179static void SetFullscreen(bool enable);
180static VBoxSDLFB *getFbFromWinId(Uint32 id);
181
182
183/*********************************************************************************************************************************
184* Global Variables *
185*********************************************************************************************************************************/
186static int gHostKeyMod = KMOD_RCTRL;
187static int gHostKeySym1 = SDLK_RCTRL;
188static int gHostKeySym2 = SDLK_UNKNOWN;
189static const char *gHostKeyDisabledCombinations = "";
190static const char *gpszPidFile;
191static BOOL gfGrabbed = FALSE;
192static BOOL gfGrabOnMouseClick = TRUE;
193static BOOL gfFullscreenResize = FALSE;
194static BOOL gfIgnoreNextResize = FALSE;
195static BOOL gfAllowFullscreenToggle = TRUE;
196static BOOL gfAbsoluteMouseHost = FALSE;
197static BOOL gfAbsoluteMouseGuest = FALSE;
198static BOOL gfRelativeMouseGuest = TRUE;
199static BOOL gfGuestNeedsHostCursor = FALSE;
200static BOOL gfOffCursorActive = FALSE;
201static BOOL gfGuestNumLockPressed = FALSE;
202static BOOL gfGuestCapsLockPressed = FALSE;
203static BOOL gfGuestScrollLockPressed = FALSE;
204static BOOL gfACPITerm = FALSE;
205#if defined(VBOXSDL_WITH_X11) && !defined(VBOX_WITHOUT_XCURSOR)
206 static BOOL gfXCursorEnabled = FALSE;
207#endif
208static int gcGuestNumLockAdaptions = 2;
209static int gcGuestCapsLockAdaptions = 2;
210static uint32_t gmGuestNormalXRes;
211static uint32_t gmGuestNormalYRes;
212
213/** modifier keypress status (scancode as index) */
214static uint8_t gaModifiersState[256];
215
216static ComPtr<IMachine> gpMachine;
217static ComPtr<IConsole> gpConsole;
218static ComPtr<IMachineDebugger> gpMachineDebugger;
219static ComPtr<IKeyboard> gpKeyboard;
220static ComPtr<IMouse> gpMouse;
221ComPtr<IDisplay> gpDisplay;
222static ComPtr<IVRDEServer> gpVRDEServer;
223static ComPtr<IProgress> gpProgress;
224
225static ULONG gcMonitors = 1;
226static ComObjPtr<VBoxSDLFB> gpFramebuffer[64];
227static Bstr gaFramebufferId[64];
228static SDL_Cursor *gpDefaultCursor = NULL;
229static SDL_Cursor *gpOffCursor = NULL;
230static SDL_TimerID gSdlResizeTimer = 0;
231static SDL_TimerID gSdlQuitTimer = 0;
232
233static RTSEMEVENT g_EventSemSDLEvents;
234static volatile int32_t g_cNotifyUpdateEventsPending;
235
236/**
237 * Event handler for VirtualBoxClient events
238 */
239class VBoxSDLClientEventListener
240{
241public:
242 VBoxSDLClientEventListener()
243 {
244 }
245
246 virtual ~VBoxSDLClientEventListener()
247 {
248 }
249
250 HRESULT init()
251 {
252 return S_OK;
253 }
254
255 void uninit()
256 {
257 }
258
259 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent)
260 {
261 switch (aType)
262 {
263 case VBoxEventType_OnVBoxSVCAvailabilityChanged:
264 {
265 ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
266 Assert(pVSACEv);
267 BOOL fAvailable = FALSE;
268 pVSACEv->COMGETTER(Available)(&fAvailable);
269 if (!fAvailable)
270 {
271 LogRel(("VBoxSDL: VBoxSVC became unavailable, exiting.\n"));
272 RTPrintf("VBoxSVC became unavailable, exiting.\n");
273 /* Send QUIT event to terminate the VM as cleanly as possible
274 * given that VBoxSVC is no longer present. */
275 SDL_Event event = {0};
276 event.type = SDL_QUIT;
277 PushSDLEventForSure(&event);
278 }
279 break;
280 }
281
282 default:
283 AssertFailed();
284 }
285
286 return S_OK;
287 }
288};
289
290/**
291 * Event handler for VirtualBox (server) events
292 */
293class VBoxSDLEventListener
294{
295public:
296 VBoxSDLEventListener()
297 {
298 }
299
300 virtual ~VBoxSDLEventListener()
301 {
302 }
303
304 HRESULT init()
305 {
306 return S_OK;
307 }
308
309 void uninit()
310 {
311 }
312
313 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent)
314 {
315 RT_NOREF(aEvent);
316 switch (aType)
317 {
318 case VBoxEventType_OnExtraDataChanged:
319 break;
320 default:
321 AssertFailed();
322 }
323
324 return S_OK;
325 }
326};
327
328/**
329 * Event handler for Console events
330 */
331class VBoxSDLConsoleEventListener
332{
333public:
334 VBoxSDLConsoleEventListener() : m_fIgnorePowerOffEvents(false)
335 {
336 }
337
338 virtual ~VBoxSDLConsoleEventListener()
339 {
340 }
341
342 HRESULT init()
343 {
344 return S_OK;
345 }
346
347 void uninit()
348 {
349 }
350
351 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent * aEvent)
352 {
353 // likely all this double copy is now excessive, and we can just use existing event object
354 /// @todo eliminate it
355 switch (aType)
356 {
357 case VBoxEventType_OnMousePointerShapeChanged:
358 {
359 ComPtr<IMousePointerShapeChangedEvent> pMPSCEv = aEvent;
360 Assert(pMPSCEv);
361 PointerShapeChangeData *data;
362 BOOL visible, alpha;
363 ULONG xHot, yHot, width, height;
364 com::SafeArray<BYTE> shape;
365
366 pMPSCEv->COMGETTER(Visible)(&visible);
367 pMPSCEv->COMGETTER(Alpha)(&alpha);
368 pMPSCEv->COMGETTER(Xhot)(&xHot);
369 pMPSCEv->COMGETTER(Yhot)(&yHot);
370 pMPSCEv->COMGETTER(Width)(&width);
371 pMPSCEv->COMGETTER(Height)(&height);
372 pMPSCEv->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape));
373 data = new PointerShapeChangeData(visible, alpha, xHot, yHot, width, height,
374 ComSafeArrayAsInParam(shape));
375 Assert(data);
376 if (!data)
377 break;
378
379 SDL_Event event = {0};
380 event.type = SDL_USEREVENT;
381 event.user.type = SDL_USER_EVENT_POINTER_CHANGE;
382 event.user.data1 = data;
383
384 int rc = PushSDLEventForSure(&event);
385 if (rc)
386 delete data;
387
388 break;
389 }
390 case VBoxEventType_OnMouseCapabilityChanged:
391 {
392 ComPtr<IMouseCapabilityChangedEvent> pMCCEv = aEvent;
393 Assert(pMCCEv);
394 pMCCEv->COMGETTER(SupportsAbsolute)(&gfAbsoluteMouseGuest);
395 pMCCEv->COMGETTER(SupportsRelative)(&gfRelativeMouseGuest);
396 pMCCEv->COMGETTER(NeedsHostCursor)(&gfGuestNeedsHostCursor);
397 SDL_Event event = {0};
398 event.type = SDL_USEREVENT;
399 event.user.type = SDL_USER_EVENT_GUEST_CAP_CHANGED;
400
401 PushSDLEventForSure(&event);
402 break;
403 }
404 case VBoxEventType_OnKeyboardLedsChanged:
405 {
406 ComPtr<IKeyboardLedsChangedEvent> pCLCEv = aEvent;
407 Assert(pCLCEv);
408 BOOL fNumLock, fCapsLock, fScrollLock;
409 pCLCEv->COMGETTER(NumLock)(&fNumLock);
410 pCLCEv->COMGETTER(CapsLock)(&fCapsLock);
411 pCLCEv->COMGETTER(ScrollLock)(&fScrollLock);
412 /* Don't bother the guest with NumLock scancodes if he doesn't set the NumLock LED */
413 if (gfGuestNumLockPressed != fNumLock)
414 gcGuestNumLockAdaptions = 2;
415 if (gfGuestCapsLockPressed != fCapsLock)
416 gcGuestCapsLockAdaptions = 2;
417 gfGuestNumLockPressed = fNumLock;
418 gfGuestCapsLockPressed = fCapsLock;
419 gfGuestScrollLockPressed = fScrollLock;
420 break;
421 }
422
423 case VBoxEventType_OnStateChanged:
424 {
425 ComPtr<IStateChangedEvent> pSCEv = aEvent;
426 Assert(pSCEv);
427 MachineState_T machineState;
428 pSCEv->COMGETTER(State)(&machineState);
429 LogFlow(("OnStateChange: machineState = %d (%s)\n", machineState, GetStateName(machineState)));
430 SDL_Event event = {0};
431
432 if ( machineState == MachineState_Aborted
433 || machineState == MachineState_Teleported
434 || (machineState == MachineState_Saved && !m_fIgnorePowerOffEvents)
435 || (machineState == MachineState_AbortedSaved && !m_fIgnorePowerOffEvents)
436 || (machineState == MachineState_PoweredOff && !m_fIgnorePowerOffEvents)
437 )
438 {
439 /*
440 * We have to inform the SDL thread that the application has be terminated
441 */
442 event.type = SDL_USEREVENT;
443 event.user.type = SDL_USER_EVENT_TERMINATE;
444 event.user.code = machineState == MachineState_Aborted
445 ? VBOXSDL_TERM_ABEND
446 : VBOXSDL_TERM_NORMAL;
447 }
448 else
449 {
450 /*
451 * Inform the SDL thread to refresh the titlebar
452 */
453 event.type = SDL_USEREVENT;
454 event.user.type = SDL_USER_EVENT_UPDATE_TITLEBAR;
455 }
456
457 PushSDLEventForSure(&event);
458 break;
459 }
460
461 case VBoxEventType_OnRuntimeError:
462 {
463 ComPtr<IRuntimeErrorEvent> pRTEEv = aEvent;
464 Assert(pRTEEv);
465 BOOL fFatal;
466
467 pRTEEv->COMGETTER(Fatal)(&fFatal);
468 MachineState_T machineState;
469 gpMachine->COMGETTER(State)(&machineState);
470 const char *pszType;
471 bool fPaused = machineState == MachineState_Paused;
472 if (fFatal)
473 pszType = "FATAL ERROR";
474 else if (machineState == MachineState_Paused)
475 pszType = "Non-fatal ERROR";
476 else
477 pszType = "WARNING";
478 Bstr bstrId, bstrMessage;
479 pRTEEv->COMGETTER(Id)(bstrId.asOutParam());
480 pRTEEv->COMGETTER(Message)(bstrMessage.asOutParam());
481 RTPrintf("\n%s: ** %ls **\n%ls\n%s\n", pszType, bstrId.raw(), bstrMessage.raw(),
482 fPaused ? "The VM was paused. Continue with HostKey + P after you solved the problem.\n" : "");
483 break;
484 }
485
486 case VBoxEventType_OnCanShowWindow:
487 {
488 ComPtr<ICanShowWindowEvent> pCSWEv = aEvent;
489 Assert(pCSWEv);
490#ifdef RT_OS_DARWIN
491 /* SDL feature not available on Quartz */
492#else
493 bool fCanShow = false;
494 Uint32 winId = 0;
495 VBoxSDLFB *fb = getFbFromWinId(winId);
496 if (fb) /* Framebuffer might not be around (yet). */
497 {
498 SDL_SysWMinfo info;
499 SDL_VERSION(&info.version);
500 if (SDL_GetWindowWMInfo(fb->getWindow(), &info))
501 fCanShow = true;
502 if (fCanShow)
503 pCSWEv->AddApproval(NULL);
504 else
505 pCSWEv->AddVeto(NULL);
506 }
507#endif
508 break;
509 }
510
511 case VBoxEventType_OnShowWindow:
512 {
513 ComPtr<IShowWindowEvent> pSWEv = aEvent;
514 Assert(pSWEv);
515 LONG64 winId = 0;
516 pSWEv->COMGETTER(WinId)(&winId);
517 if (winId != 0)
518 break; /* WinId already set by some other listener. */
519#ifndef RT_OS_DARWIN
520 SDL_SysWMinfo info;
521 SDL_VERSION(&info.version);
522 VBoxSDLFB *fb = getFbFromWinId(winId);
523 if (SDL_GetWindowWMInfo(fb->getWindow(), &info))
524 {
525# if defined(VBOXSDL_WITH_X11)
526 pSWEv->COMSETTER(WinId)((LONG64)info.info.x11.window);
527# elif defined(RT_OS_WINDOWS)
528 pSWEv->COMSETTER(WinId)((intptr_t)info.info.win.window);
529# else /* !RT_OS_WINDOWS */
530 AssertFailed();
531# endif
532 }
533#endif /* !RT_OS_DARWIN */
534 break;
535 }
536
537 default:
538 AssertFailed();
539 }
540 return S_OK;
541 }
542
543 static const char *GetStateName(MachineState_T machineState)
544 {
545 switch (machineState)
546 {
547 case MachineState_Null: return "<null>";
548 case MachineState_PoweredOff: return "PoweredOff";
549 case MachineState_Saved: return "Saved";
550 case MachineState_Teleported: return "Teleported";
551 case MachineState_Aborted: return "Aborted";
552 case MachineState_AbortedSaved: return "Aborted-Saved";
553 case MachineState_Running: return "Running";
554 case MachineState_Teleporting: return "Teleporting";
555 case MachineState_LiveSnapshotting: return "LiveSnapshotting";
556 case MachineState_Paused: return "Paused";
557 case MachineState_Stuck: return "GuruMeditation";
558 case MachineState_Starting: return "Starting";
559 case MachineState_Stopping: return "Stopping";
560 case MachineState_Saving: return "Saving";
561 case MachineState_Restoring: return "Restoring";
562 case MachineState_TeleportingPausedVM: return "TeleportingPausedVM";
563 case MachineState_TeleportingIn: return "TeleportingIn";
564 case MachineState_RestoringSnapshot: return "RestoringSnapshot";
565 case MachineState_DeletingSnapshot: return "DeletingSnapshot";
566 case MachineState_SettingUp: return "SettingUp";
567 default: return "no idea";
568 }
569 }
570
571 void ignorePowerOffEvents(bool fIgnore)
572 {
573 m_fIgnorePowerOffEvents = fIgnore;
574 }
575
576private:
577 bool m_fIgnorePowerOffEvents;
578};
579
580typedef ListenerImpl<VBoxSDLClientEventListener> VBoxSDLClientEventListenerImpl;
581typedef ListenerImpl<VBoxSDLEventListener> VBoxSDLEventListenerImpl;
582typedef ListenerImpl<VBoxSDLConsoleEventListener> VBoxSDLConsoleEventListenerImpl;
583
584static void show_usage()
585{
586 RTPrintf("Usage:\n"
587 " --startvm <uuid|name> Virtual machine to start, either UUID or name\n"
588 " --separate Run a separate VM process or attach to a running VM\n"
589 " --hda <file> Set temporary first hard disk to file\n"
590 " --fda <file> Set temporary first floppy disk to file\n"
591 " --cdrom <file> Set temporary CDROM/DVD to file/device ('none' to unmount)\n"
592 " --boot <a|c|d|n> Set temporary boot device (a = floppy, c = 1st HD, d = DVD, n = network)\n"
593 " --memory <size> Set temporary memory size in megabytes\n"
594 " --vram <size> Set temporary size of video memory in megabytes\n"
595 " --fullscreen Start VM in fullscreen mode\n"
596 " --fullscreenresize Resize the guest on fullscreen\n"
597 " --fixedmode <w> <h> <bpp> Use a fixed SDL video mode with given width, height and bits per pixel\n"
598 " --nofstoggle Forbid switching to/from fullscreen mode\n"
599 " --noresize Make the SDL frame non resizable\n"
600 " --nohostkey Disable all hostkey combinations\n"
601 " --nohostkeys ... Disable specific hostkey combinations, see below for valid keys\n"
602 " --nograbonclick Disable mouse/keyboard grabbing on mouse click w/o additions\n"
603 " --detecthostkey Get the hostkey identifier and modifier state\n"
604 " --hostkey <key> {<key2>} <mod> Set the host key to the values obtained using --detecthostkey\n"
605 " --termacpi Send an ACPI power button event when closing the window\n"
606 " --vrdp <ports> Listen for VRDP connections on one of specified ports (default if not specified)\n"
607 " --discardstate Discard saved state (if present) and revert to last snapshot (if present)\n"
608 " --settingspw <pw> Specify the settings password\n"
609 " --settingspwfile <file> Specify a file containing the settings password\n"
610#ifdef VBOXSDL_ADVANCED_OPTIONS
611 " --warpdrive <pct> Sets the warp driver rate in percent (100 = normal)\n"
612#endif
613 "\n"
614 "Key bindings:\n"
615 " <hostkey> + f Switch to full screen / restore to previous view\n"
616 " h Press ACPI power button\n"
617 " n Take a snapshot and continue execution\n"
618 " p Pause / resume execution\n"
619 " q Power off\n"
620 " r VM reset\n"
621 " s Save state and power off\n"
622 " <del> Send <ctrl><alt><del>\n"
623 " <F1>...<F12> Send <ctrl><alt><Fx>\n"
624#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
625 "\n"
626 "Further key bindings useful for debugging:\n"
627 " LCtrl + Alt + F12 Reset statistics counter\n"
628 " LCtrl + Alt + F11 Dump statistics to logfile\n"
629 " Alt + F8 Toggle single step mode\n"
630 " LCtrl/RCtrl + F12 Toggle logger\n"
631 " F12 Write log marker to logfile\n"
632#endif
633 "\n");
634}
635
636static void PrintError(const char *pszName, CBSTR pwszDescr, CBSTR pwszComponent=NULL)
637{
638 const char *pszFile, *pszFunc, *pszStat;
639 char pszBuffer[1024];
640 com::ErrorInfo info;
641
642 RTStrPrintf(pszBuffer, sizeof(pszBuffer), "%ls", pwszDescr);
643
644 RTPrintf("\n%s! Error info:\n", pszName);
645 if ( (pszFile = strstr(pszBuffer, "At '"))
646 && (pszFunc = strstr(pszBuffer, ") in "))
647 && (pszStat = strstr(pszBuffer, "VBox status code: ")))
648 RTPrintf(" %.*s %.*s\n In%.*s %s",
649 pszFile-pszBuffer, pszBuffer,
650 pszFunc-pszFile+1, pszFile,
651 pszStat-pszFunc-4, pszFunc+4,
652 pszStat);
653 else
654 RTPrintf("%s\n", pszBuffer);
655
656 if (pwszComponent)
657 RTPrintf("(component %ls).\n", pwszComponent);
658
659 RTPrintf("\n");
660}
661
662#ifdef VBOXSDL_WITH_X11
663/**
664 * Custom signal handler. Currently it is only used to release modifier
665 * keys when receiving the USR1 signal. When switching VTs, we might not
666 * get release events for Ctrl-Alt and in case a savestate is performed
667 * on the new VT, the VM will be saved with modifier keys stuck. This is
668 * annoying enough for introducing this hack.
669 */
670void signal_handler_SIGUSR1(int sig, siginfo_t *info, void *secret)
671{
672 RT_NOREF(info, secret);
673
674 /* only SIGUSR1 is interesting */
675 if (sig == SIGUSR1)
676 {
677 /* just release the modifiers */
678 ResetKeys();
679 }
680}
681
682/**
683 * Custom signal handler for catching exit events.
684 */
685void signal_handler_SIGINT(int sig)
686{
687 if (gpszPidFile)
688 RTFileDelete(gpszPidFile);
689 signal(SIGINT, SIG_DFL);
690 signal(SIGQUIT, SIG_DFL);
691 signal(SIGSEGV, SIG_DFL);
692 kill(getpid(), sig);
693}
694#endif /* VBOXSDL_WITH_X11 */
695
696/**
697 * Returns a stringified version of a keyboard modifier.
698 *
699 * @returns Stringified version of the keyboard modifier.
700 * @param mod Modifier code to return a stringified version for.
701 */
702static const char *keyModToStr(unsigned mod)
703{
704 switch (mod)
705 {
706 RT_CASE_RET_STR(KMOD_NONE);
707 RT_CASE_RET_STR(KMOD_LSHIFT);
708 RT_CASE_RET_STR(KMOD_RSHIFT);
709 RT_CASE_RET_STR(KMOD_LCTRL);
710 RT_CASE_RET_STR(KMOD_RCTRL);
711 RT_CASE_RET_STR(KMOD_LALT);
712 RT_CASE_RET_STR(KMOD_RALT);
713 RT_CASE_RET_STR(KMOD_LGUI);
714 RT_CASE_RET_STR(KMOD_RGUI);
715 RT_CASE_RET_STR(KMOD_NUM);
716 RT_CASE_RET_STR(KMOD_CAPS);
717 RT_CASE_RET_STR(KMOD_MODE);
718 RT_CASE_RET_STR(KMOD_SCROLL);
719 default:
720 break;
721 }
722
723 return "<Unknown>";
724}
725
726/**
727 * Handles detecting a host key by printing its values to stdout.
728 *
729 * @returns RTEXITCODE
730 */
731static RTEXITCODE handleDetectHostKey(void)
732{
733 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
734
735 int rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER);
736 if (rc == 0)
737 {
738 /* We need a window, otherwise we won't get any keypress events. */
739 SDL_Window *pWnd = SDL_CreateWindow("VBoxSDL",
740 SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
741 RTPrintf("Please hit one or two function key(s) to get the --hostkey value. ..\n");
742 RTPrintf("Press CTRL+C to quit.\n");
743 SDL_Event e1;
744 while (SDL_WaitEvent(&e1))
745 {
746 if ( e1.key.keysym.sym == SDLK_c
747 && (e1.key.keysym.mod & KMOD_CTRL) != 0)
748 break;
749 if (e1.type == SDL_QUIT)
750 break;
751 if (e1.type == SDL_KEYDOWN)
752 {
753 unsigned const mod = SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED);
754 RTPrintf("--hostkey %d", e1.key.keysym.sym);
755 if (mod)
756 RTPrintf(" %d\n", mod);
757 else
758 RTPrintf("\n");
759
760 if (mod)
761 RTPrintf("Host key is '%s' + '%s'\n", keyModToStr(mod), SDL_GetKeyName(e1.key.keysym.sym));
762 else
763 RTPrintf("Host key is '%s'\n", SDL_GetKeyName(e1.key.keysym.sym));
764 }
765 }
766 SDL_DestroyWindow(pWnd);
767 SDL_Quit();
768 }
769 else
770 {
771 RTPrintf("Error: SDL_InitSubSystem failed with message '%s'\n", SDL_GetError());
772 rcExit = RTEXITCODE_FAILURE;
773 }
774
775 return rcExit;
776}
777
778/** entry point */
779extern "C"
780DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp)
781{
782 RT_NOREF(envp);
783#ifdef RT_OS_WINDOWS
784 /* As we run with the WINDOWS subsystem, we need to either attach to or create an own console
785 * to get any stdout / stderr output. */
786 bool fAllocConsole = IsDebuggerPresent();
787 if (!fAllocConsole)
788 {
789 if (!AttachConsole(ATTACH_PARENT_PROCESS))
790 fAllocConsole = true;
791 }
792
793 if (fAllocConsole)
794 {
795 if (!AllocConsole())
796 MessageBox(GetDesktopWindow(), L"Unable to attach to or allocate a console!", L"VBoxSDL", MB_OK | MB_ICONERROR);
797 /* Continue running. */
798 }
799
800 RTFILE hStdIn;
801 RTFileFromNative(&hStdIn, (RTHCINTPTR)GetStdHandle(STD_INPUT_HANDLE));
802 /** @todo Closing of standard handles not support via IPRT (yet). */
803 RTStrmOpenFileHandle(hStdIn, "r", 0, &g_pStdIn);
804
805 RTFILE hStdOut;
806 RTFileFromNative(&hStdOut, (RTHCINTPTR)GetStdHandle(STD_OUTPUT_HANDLE));
807 /** @todo Closing of standard handles not support via IPRT (yet). */
808 RTStrmOpenFileHandle(hStdOut, "wt", 0, &g_pStdOut);
809
810 RTFILE hStdErr;
811 RTFileFromNative(&hStdErr, (RTHCINTPTR)GetStdHandle(STD_ERROR_HANDLE));
812 RTStrmOpenFileHandle(hStdErr, "wt", 0, &g_pStdErr);
813
814 if (!fAllocConsole) /* When attaching to the parent console, make sure we start on a fresh line. */
815 RTPrintf("\n");
816
817 ATL::CComModule _Module; /* Required internally by ATL (constructor records instance in global variable). */
818#endif /* RT_OS_WINDOWS */
819
820#ifdef Q_WS_X11
821 if (!XInitThreads())
822 return 1;
823#endif
824#ifdef VBOXSDL_WITH_X11
825 /*
826 * Lock keys on SDL behave different from normal keys: A KeyPress event is generated
827 * if the lock mode gets active and a keyRelease event is generated if the lock mode
828 * gets inactive, that is KeyPress and KeyRelease are sent when pressing the lock key
829 * to change the mode. The current lock mode is reflected in SDL_GetModState().
830 *
831 * Debian patched libSDL to make the lock keys behave like normal keys
832 * generating a KeyPress/KeyRelease event if the lock key was
833 * pressed/released. With the new behaviour, the lock status is not
834 * reflected in the mod status anymore, but the user can request the old
835 * behaviour by setting an environment variable. To confuse matters further
836 * version 1.2.14 (fortunately including the Debian packaged versions)
837 * adopted the Debian behaviour officially, but inverted the meaning of the
838 * environment variable to select the new behaviour, keeping the old as the
839 * default. We disable the new behaviour to ensure a defined environment
840 * and work around the missing KeyPress/KeyRelease events in ProcessKeys().
841 */
842 {
843#if 0
844 const SDL_version *pVersion = SDL_Linked_Version();
845 if ( SDL_VERSIONNUM(pVersion->major, pVersion->minor, pVersion->patch)
846 < SDL_VERSIONNUM(1, 2, 14))
847 RTEnvSet("SDL_DISABLE_LOCK_KEYS", "1");
848#endif
849 }
850#endif
851
852 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
853
854 /*
855 * the hostkey detection mode is unrelated to VM processing, so handle it before
856 * we initialize anything COM related
857 */
858 if (argc == 2 && ( !strcmp(argv[1], "-detecthostkey")
859 || !strcmp(argv[1], "--detecthostkey")))
860 {
861 rcExit = handleDetectHostKey();
862#ifdef RT_OS_WINDOWS
863 FreeConsole(); /* Detach or destroy (from) console. */
864#endif
865 return rcExit;
866 }
867
868 /** @todo r=andy This function is waaaaaay to long, uses goto's and leaks stuff. Use RTGetOpt handling. */
869
870 HRESULT hrc;
871 int vrc;
872 Guid uuidVM;
873 char *vmName = NULL;
874 bool fSeparate = false;
875 DeviceType_T bootDevice = DeviceType_Null;
876 uint32_t memorySize = 0;
877 uint32_t vramSize = 0;
878 ComPtr<IEventListener> pVBoxClientListener;
879 ComPtr<IEventListener> pVBoxListener;
880 ComObjPtr<VBoxSDLConsoleEventListenerImpl> pConsoleListener;
881
882 bool fFullscreen = false;
883 bool fResizable = true;
884#ifdef USE_XPCOM_QUEUE_THREAD
885 bool fXPCOMEventThreadSignaled = false;
886#endif
887 const char *pcszHdaFile = NULL;
888 const char *pcszCdromFile = NULL;
889 const char *pcszFdaFile = NULL;
890 const char *pszPortVRDP = NULL;
891 bool fDiscardState = false;
892 const char *pcszSettingsPw = NULL;
893 const char *pcszSettingsPwFile = NULL;
894#ifdef VBOXSDL_ADVANCED_OPTIONS
895 uint32_t u32WarpDrive = 0;
896#endif
897#ifdef VBOX_WIN32_UI
898 bool fWin32UI = true;
899 int64_t winId = 0;
900#endif
901 bool fShowSDLConfig = false;
902 uint32_t fixedWidth = ~(uint32_t)0;
903 uint32_t fixedHeight = ~(uint32_t)0;
904 uint32_t fixedBPP = ~(uint32_t)0;
905 uint32_t uResizeWidth = ~(uint32_t)0;
906 uint32_t uResizeHeight = ~(uint32_t)0;
907
908 /* The damned GOTOs forces this to be up here - totally out of place. */
909 /*
910 * Host key handling.
911 *
912 * The golden rule is that host-key combinations should not be seen
913 * by the guest. For instance a CAD should not have any extra RCtrl down
914 * and RCtrl up around itself. Nor should a resume be followed by a Ctrl-P
915 * that could encourage applications to start printing.
916 *
917 * We must not confuse the hostkey processing into any release sequences
918 * either, the host key is supposed to be explicitly pressing one key.
919 *
920 * Quick state diagram:
921 *
922 * host key down alone
923 * (Normal) ---------------
924 * ^ ^ |
925 * | | v host combination key down
926 * | | (Host key down) ----------------
927 * | | host key up v | |
928 * | |-------------- | other key down v host combination key down
929 * | | (host key used) -------------
930 * | | | ^ |
931 * | (not host key)-- | |---------------
932 * | | | | |
933 * | | ---- other |
934 * | modifiers = 0 v v
935 * -----------------------------------------------
936 */
937 enum HKEYSTATE
938 {
939 /** The initial and most common state, pass keystrokes to the guest.
940 * Next state: HKEYSTATE_DOWN
941 * Prev state: Any */
942 HKEYSTATE_NORMAL = 1,
943 /** The first host key was pressed down
944 */
945 HKEYSTATE_DOWN_1ST,
946 /** The second host key was pressed down (if gHostKeySym2 != SDLK_UNKNOWN)
947 */
948 HKEYSTATE_DOWN_2ND,
949 /** The host key has been pressed down.
950 * Prev state: HKEYSTATE_NORMAL
951 * Next state: HKEYSTATE_NORMAL - host key up, capture toggle.
952 * Next state: HKEYSTATE_USED - host key combination down.
953 * Next state: HKEYSTATE_NOT_IT - non-host key combination down.
954 */
955 HKEYSTATE_DOWN,
956 /** A host key combination was pressed.
957 * Prev state: HKEYSTATE_DOWN
958 * Next state: HKEYSTATE_NORMAL - when modifiers are all 0
959 */
960 HKEYSTATE_USED,
961 /** A non-host key combination was attempted. Send hostkey down to the
962 * guest and continue until all modifiers have been released.
963 * Prev state: HKEYSTATE_DOWN
964 * Next state: HKEYSTATE_NORMAL - when modifiers are all 0
965 */
966 HKEYSTATE_NOT_IT
967 } enmHKeyState = HKEYSTATE_NORMAL;
968 /** The host key down event which we have been hiding from the guest.
969 * Used when going from HKEYSTATE_DOWN to HKEYSTATE_NOT_IT. */
970 SDL_Event EvHKeyDown1;
971 SDL_Event EvHKeyDown2;
972
973 LogFlow(("SDL GUI started\n"));
974 RTPrintf(VBOX_PRODUCT " SDL GUI version %s\n"
975 "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n",
976 VBOX_VERSION_STRING);
977
978 // less than one parameter is not possible
979 if (argc < 2)
980 {
981 show_usage();
982 return 1;
983 }
984
985 // command line argument parsing stuff
986 for (int curArg = 1; curArg < argc; curArg++)
987 {
988 if ( !strcmp(argv[curArg], "--vm")
989 || !strcmp(argv[curArg], "-vm")
990 || !strcmp(argv[curArg], "--startvm")
991 || !strcmp(argv[curArg], "-startvm")
992 || !strcmp(argv[curArg], "-s")
993 )
994 {
995 if (++curArg >= argc)
996 {
997 RTPrintf("Error: VM not specified (UUID or name)!\n");
998 return 1;
999 }
1000 // first check if a UUID was supplied
1001 uuidVM = argv[curArg];
1002
1003 if (!uuidVM.isValid())
1004 {
1005 LogFlow(("invalid UUID format, assuming it's a VM name\n"));
1006 vmName = argv[curArg];
1007 }
1008 else if (uuidVM.isZero())
1009 {
1010 RTPrintf("Error: UUID argument is zero!\n");
1011 return 1;
1012 }
1013 }
1014 else if ( !strcmp(argv[curArg], "--separate")
1015 || !strcmp(argv[curArg], "-separate"))
1016 {
1017 fSeparate = true;
1018 }
1019 else if ( !strcmp(argv[curArg], "--comment")
1020 || !strcmp(argv[curArg], "-comment"))
1021 {
1022 if (++curArg >= argc)
1023 {
1024 RTPrintf("Error: missing argument for comment!\n");
1025 return 1;
1026 }
1027 }
1028 else if ( !strcmp(argv[curArg], "--boot")
1029 || !strcmp(argv[curArg], "-boot"))
1030 {
1031 if (++curArg >= argc)
1032 {
1033 RTPrintf("Error: missing argument for boot drive!\n");
1034 return 1;
1035 }
1036 switch (argv[curArg][0])
1037 {
1038 case 'a':
1039 {
1040 bootDevice = DeviceType_Floppy;
1041 break;
1042 }
1043
1044 case 'c':
1045 {
1046 bootDevice = DeviceType_HardDisk;
1047 break;
1048 }
1049
1050 case 'd':
1051 {
1052 bootDevice = DeviceType_DVD;
1053 break;
1054 }
1055
1056 case 'n':
1057 {
1058 bootDevice = DeviceType_Network;
1059 break;
1060 }
1061
1062 default:
1063 {
1064 RTPrintf("Error: wrong argument for boot drive!\n");
1065 return 1;
1066 }
1067 }
1068 }
1069 else if ( !strcmp(argv[curArg], "--detecthostkey")
1070 || !strcmp(argv[curArg], "-detecthostkey"))
1071 {
1072 RTPrintf("Error: please specify \"%s\" without any additional parameters!\n",
1073 argv[curArg]);
1074 return 1;
1075 }
1076 else if ( !strcmp(argv[curArg], "--memory")
1077 || !strcmp(argv[curArg], "-memory")
1078 || !strcmp(argv[curArg], "-m"))
1079 {
1080 if (++curArg >= argc)
1081 {
1082 RTPrintf("Error: missing argument for memory size!\n");
1083 return 1;
1084 }
1085 memorySize = atoi(argv[curArg]);
1086 }
1087 else if ( !strcmp(argv[curArg], "--vram")
1088 || !strcmp(argv[curArg], "-vram"))
1089 {
1090 if (++curArg >= argc)
1091 {
1092 RTPrintf("Error: missing argument for vram size!\n");
1093 return 1;
1094 }
1095 vramSize = atoi(argv[curArg]);
1096 }
1097 else if ( !strcmp(argv[curArg], "--fullscreen")
1098 || !strcmp(argv[curArg], "-fullscreen"))
1099 {
1100 fFullscreen = true;
1101 }
1102 else if ( !strcmp(argv[curArg], "--fullscreenresize")
1103 || !strcmp(argv[curArg], "-fullscreenresize"))
1104 {
1105 gfFullscreenResize = true;
1106#ifdef VBOXSDL_WITH_X11
1107 RTEnvSet("SDL_VIDEO_X11_VIDMODE", "0");
1108#endif
1109 }
1110 else if ( !strcmp(argv[curArg], "--fixedmode")
1111 || !strcmp(argv[curArg], "-fixedmode"))
1112 {
1113 /* three parameters follow */
1114 if (curArg + 3 >= argc)
1115 {
1116 RTPrintf("Error: missing arguments for fixed video mode!\n");
1117 return 1;
1118 }
1119 fixedWidth = atoi(argv[++curArg]);
1120 fixedHeight = atoi(argv[++curArg]);
1121 fixedBPP = atoi(argv[++curArg]);
1122 }
1123 else if ( !strcmp(argv[curArg], "--nofstoggle")
1124 || !strcmp(argv[curArg], "-nofstoggle"))
1125 {
1126 gfAllowFullscreenToggle = FALSE;
1127 }
1128 else if ( !strcmp(argv[curArg], "--noresize")
1129 || !strcmp(argv[curArg], "-noresize"))
1130 {
1131 fResizable = false;
1132 }
1133 else if ( !strcmp(argv[curArg], "--nohostkey")
1134 || !strcmp(argv[curArg], "-nohostkey"))
1135 {
1136 gHostKeyMod = 0;
1137 gHostKeySym1 = 0;
1138 }
1139 else if ( !strcmp(argv[curArg], "--nohostkeys")
1140 || !strcmp(argv[curArg], "-nohostkeys"))
1141 {
1142 if (++curArg >= argc)
1143 {
1144 RTPrintf("Error: missing a string of disabled hostkey combinations\n");
1145 return 1;
1146 }
1147 gHostKeyDisabledCombinations = argv[curArg];
1148 size_t cch = strlen(gHostKeyDisabledCombinations);
1149 for (size_t i = 0; i < cch; i++)
1150 {
1151 if (!strchr("fhnpqrs", gHostKeyDisabledCombinations[i]))
1152 {
1153 RTPrintf("Error: <hostkey> + '%c' is not a valid combination\n",
1154 gHostKeyDisabledCombinations[i]);
1155 return 1;
1156 }
1157 }
1158 }
1159 else if ( !strcmp(argv[curArg], "--nograbonclick")
1160 || !strcmp(argv[curArg], "-nograbonclick"))
1161 {
1162 gfGrabOnMouseClick = FALSE;
1163 }
1164 else if ( !strcmp(argv[curArg], "--termacpi")
1165 || !strcmp(argv[curArg], "-termacpi"))
1166 {
1167 gfACPITerm = TRUE;
1168 }
1169 else if ( !strcmp(argv[curArg], "--pidfile")
1170 || !strcmp(argv[curArg], "-pidfile"))
1171 {
1172 if (++curArg >= argc)
1173 {
1174 RTPrintf("Error: missing file name for --pidfile!\n");
1175 return 1;
1176 }
1177 gpszPidFile = argv[curArg];
1178 }
1179 else if ( !strcmp(argv[curArg], "--hda")
1180 || !strcmp(argv[curArg], "-hda"))
1181 {
1182 if (++curArg >= argc)
1183 {
1184 RTPrintf("Error: missing file name for first hard disk!\n");
1185 return 1;
1186 }
1187 /* resolve it. */
1188 if (RTPathExists(argv[curArg]))
1189 pcszHdaFile = RTPathRealDup(argv[curArg]);
1190 if (!pcszHdaFile)
1191 {
1192 RTPrintf("Error: The path to the specified harddisk, '%s', could not be resolved.\n", argv[curArg]);
1193 return 1;
1194 }
1195 }
1196 else if ( !strcmp(argv[curArg], "--fda")
1197 || !strcmp(argv[curArg], "-fda"))
1198 {
1199 if (++curArg >= argc)
1200 {
1201 RTPrintf("Error: missing file/device name for first floppy disk!\n");
1202 return 1;
1203 }
1204 /* resolve it. */
1205 if (RTPathExists(argv[curArg]))
1206 pcszFdaFile = RTPathRealDup(argv[curArg]);
1207 if (!pcszFdaFile)
1208 {
1209 RTPrintf("Error: The path to the specified floppy disk, '%s', could not be resolved.\n", argv[curArg]);
1210 return 1;
1211 }
1212 }
1213 else if ( !strcmp(argv[curArg], "--cdrom")
1214 || !strcmp(argv[curArg], "-cdrom"))
1215 {
1216 if (++curArg >= argc)
1217 {
1218 RTPrintf("Error: missing file/device name for cdrom!\n");
1219 return 1;
1220 }
1221 /* resolve it. */
1222 if (RTPathExists(argv[curArg]))
1223 pcszCdromFile = RTPathRealDup(argv[curArg]);
1224 if (!pcszCdromFile)
1225 {
1226 RTPrintf("Error: The path to the specified cdrom, '%s', could not be resolved.\n", argv[curArg]);
1227 return 1;
1228 }
1229 }
1230 else if ( !strcmp(argv[curArg], "--vrdp")
1231 || !strcmp(argv[curArg], "-vrdp"))
1232 {
1233 // start with the standard VRDP port
1234 pszPortVRDP = "0";
1235
1236 // is there another argument
1237 if (argc > (curArg + 1))
1238 {
1239 curArg++;
1240 pszPortVRDP = argv[curArg];
1241 LogFlow(("Using non standard VRDP port %s\n", pszPortVRDP));
1242 }
1243 }
1244 else if ( !strcmp(argv[curArg], "--discardstate")
1245 || !strcmp(argv[curArg], "-discardstate"))
1246 {
1247 fDiscardState = true;
1248 }
1249 else if (!strcmp(argv[curArg], "--settingspw"))
1250 {
1251 if (++curArg >= argc)
1252 {
1253 RTPrintf("Error: missing password");
1254 return 1;
1255 }
1256 pcszSettingsPw = argv[curArg];
1257 }
1258 else if (!strcmp(argv[curArg], "--settingspwfile"))
1259 {
1260 if (++curArg >= argc)
1261 {
1262 RTPrintf("Error: missing password file\n");
1263 return 1;
1264 }
1265 pcszSettingsPwFile = argv[curArg];
1266 }
1267#ifdef VBOXSDL_ADVANCED_OPTIONS
1268 else if ( !strcmp(argv[curArg], "--warpdrive")
1269 || !strcmp(argv[curArg], "-warpdrive"))
1270 {
1271 if (++curArg >= argc)
1272 {
1273 RTPrintf("Error: missing the rate value for the --warpdrive option!\n");
1274 return 1;
1275 }
1276 u32WarpDrive = RTStrToUInt32(argv[curArg]);
1277 if (u32WarpDrive < 2 || u32WarpDrive > 20000)
1278 {
1279 RTPrintf("Error: the warp drive rate is restricted to [2..20000]. (%d)\n", u32WarpDrive);
1280 return 1;
1281 }
1282 }
1283#endif /* VBOXSDL_ADVANCED_OPTIONS */
1284#ifdef VBOX_WIN32_UI
1285 else if ( !strcmp(argv[curArg], "--win32ui")
1286 || !strcmp(argv[curArg], "-win32ui"))
1287 fWin32UI = true;
1288#endif
1289 else if ( !strcmp(argv[curArg], "--showsdlconfig")
1290 || !strcmp(argv[curArg], "-showsdlconfig"))
1291 fShowSDLConfig = true;
1292 else if ( !strcmp(argv[curArg], "--hostkey")
1293 || !strcmp(argv[curArg], "-hostkey"))
1294 {
1295 if (++curArg + 1 >= argc)
1296 {
1297 RTPrintf("Error: not enough arguments for host keys!\n");
1298 return 1;
1299 }
1300 gHostKeySym1 = atoi(argv[curArg++]);
1301 if (curArg + 1 < argc && (argv[curArg+1][0] == '0' || atoi(argv[curArg+1]) > 0))
1302 {
1303 /* two-key sequence as host key specified */
1304 gHostKeySym2 = atoi(argv[curArg++]);
1305 }
1306 gHostKeyMod = atoi(argv[curArg]);
1307 }
1308 /* just show the help screen */
1309 else
1310 {
1311 if ( strcmp(argv[curArg], "-h")
1312 && strcmp(argv[curArg], "-help")
1313 && strcmp(argv[curArg], "--help"))
1314 RTPrintf("Error: unrecognized switch '%s'\n", argv[curArg]);
1315 show_usage();
1316 return 1;
1317 }
1318 }
1319
1320 hrc = com::Initialize();
1321#ifdef VBOX_WITH_XPCOM
1322 if (hrc == NS_ERROR_FILE_ACCESS_DENIED)
1323 {
1324 char szHome[RTPATH_MAX] = "";
1325 com::GetVBoxUserHomeDirectory(szHome, sizeof(szHome));
1326 RTPrintf("Failed to initialize COM because the global settings directory '%s' is not accessible!\n", szHome);
1327 return 1;
1328 }
1329#endif
1330 if (FAILED(hrc))
1331 {
1332 RTPrintf("Error: COM initialization failed (rc=%Rhrc)!\n", hrc);
1333 return 1;
1334 }
1335
1336 /* NOTE: do not convert the following scope to a "do {} while (0);", as
1337 * this would make it all too tempting to use "break;" incorrectly - it
1338 * would skip over the cleanup. */
1339 {
1340 // scopes all the stuff till shutdown
1341 ////////////////////////////////////////////////////////////////////////////
1342
1343 ComPtr<IVirtualBoxClient> pVirtualBoxClient;
1344 ComPtr<IVirtualBox> pVirtualBox;
1345 ComPtr<ISession> pSession;
1346 bool sessionOpened = false;
1347 NativeEventQueue* eventQ = com::NativeEventQueue::getMainEventQueue();
1348
1349 ComPtr<IMachine> pMachine;
1350 ComPtr<IGraphicsAdapter> pGraphicsAdapter;
1351
1352 hrc = pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
1353 if (FAILED(hrc))
1354 {
1355 com::ErrorInfo info;
1356 if (info.isFullAvailable())
1357 PrintError("Failed to create VirtualBoxClient object",
1358 info.getText().raw(), info.getComponent().raw());
1359 else
1360 RTPrintf("Failed to create VirtualBoxClient object! No error information available (rc=%Rhrc).\n", hrc);
1361 goto leave;
1362 }
1363
1364 hrc = pVirtualBoxClient->COMGETTER(VirtualBox)(pVirtualBox.asOutParam());
1365 if (FAILED(hrc))
1366 {
1367 RTPrintf("Failed to get VirtualBox object (rc=%Rhrc)!\n", hrc);
1368 goto leave;
1369 }
1370 hrc = pVirtualBoxClient->COMGETTER(Session)(pSession.asOutParam());
1371 if (FAILED(hrc))
1372 {
1373 RTPrintf("Failed to get session object (rc=%Rhrc)!\n", hrc);
1374 goto leave;
1375 }
1376
1377 if (pcszSettingsPw)
1378 {
1379 CHECK_ERROR(pVirtualBox, SetSettingsSecret(Bstr(pcszSettingsPw).raw()));
1380 if (FAILED(hrc))
1381 goto leave;
1382 }
1383 else if (pcszSettingsPwFile)
1384 {
1385 rcExit = settingsPasswordFile(pVirtualBox, pcszSettingsPwFile);
1386 if (rcExit != RTEXITCODE_SUCCESS)
1387 goto leave;
1388 }
1389
1390 /*
1391 * Do we have a UUID?
1392 */
1393 if (uuidVM.isValid())
1394 {
1395 hrc = pVirtualBox->FindMachine(uuidVM.toUtf16().raw(), pMachine.asOutParam());
1396 if (FAILED(hrc) || !pMachine)
1397 {
1398 RTPrintf("Error: machine with the given ID not found!\n");
1399 goto leave;
1400 }
1401 }
1402 else if (vmName)
1403 {
1404 /*
1405 * Do we have a name but no UUID?
1406 */
1407 hrc = pVirtualBox->FindMachine(Bstr(vmName).raw(), pMachine.asOutParam());
1408 if ((hrc == S_OK) && pMachine)
1409 {
1410 Bstr bstrId;
1411 pMachine->COMGETTER(Id)(bstrId.asOutParam());
1412 uuidVM = Guid(bstrId);
1413 }
1414 else
1415 {
1416 RTPrintf("Error: machine with the given name not found!\n");
1417 RTPrintf("Check if this VM has been corrupted and is now inaccessible.");
1418 goto leave;
1419 }
1420 }
1421
1422 /* create SDL event semaphore */
1423 vrc = RTSemEventCreate(&g_EventSemSDLEvents);
1424 AssertReleaseRC(vrc);
1425
1426 hrc = pVirtualBoxClient->CheckMachineError(pMachine);
1427 if (FAILED(hrc))
1428 {
1429 com::ErrorInfo info;
1430 if (info.isFullAvailable())
1431 PrintError("The VM has errors",
1432 info.getText().raw(), info.getComponent().raw());
1433 else
1434 RTPrintf("Failed to check for VM errors! No error information available (rc=%Rhrc).\n", hrc);
1435 goto leave;
1436 }
1437
1438 if (fSeparate)
1439 {
1440 MachineState_T machineState = MachineState_Null;
1441 pMachine->COMGETTER(State)(&machineState);
1442 if ( machineState == MachineState_Running
1443 || machineState == MachineState_Teleporting
1444 || machineState == MachineState_LiveSnapshotting
1445 || machineState == MachineState_Paused
1446 || machineState == MachineState_TeleportingPausedVM
1447 )
1448 {
1449 RTPrintf("VM is already running.\n");
1450 }
1451 else
1452 {
1453 ComPtr<IProgress> progress;
1454 hrc = pMachine->LaunchVMProcess(pSession, Bstr("headless").raw(), ComSafeArrayNullInParam(), progress.asOutParam());
1455 if (SUCCEEDED(hrc) && !progress.isNull())
1456 {
1457 RTPrintf("Waiting for VM to power on...\n");
1458 hrc = progress->WaitForCompletion(-1);
1459 if (SUCCEEDED(hrc))
1460 {
1461 BOOL completed = true;
1462 hrc = progress->COMGETTER(Completed)(&completed);
1463 if (SUCCEEDED(hrc))
1464 {
1465 LONG iRc;
1466 hrc = progress->COMGETTER(ResultCode)(&iRc);
1467 if (SUCCEEDED(hrc))
1468 {
1469 if (FAILED(iRc))
1470 {
1471 ProgressErrorInfo info(progress);
1472 com::GluePrintErrorInfo(info);
1473 }
1474 else
1475 {
1476 RTPrintf("VM has been successfully started.\n");
1477 /* LaunchVMProcess obtains a shared lock on the machine.
1478 * Unlock it here, because the lock will be obtained below
1479 * in the common code path as for already running VM.
1480 */
1481 pSession->UnlockMachine();
1482 }
1483 }
1484 }
1485 }
1486 }
1487 }
1488 if (FAILED(hrc))
1489 {
1490 RTPrintf("Error: failed to power up VM! No error text available.\n");
1491 goto leave;
1492 }
1493
1494 hrc = pMachine->LockMachine(pSession, LockType_Shared);
1495 }
1496 else
1497 {
1498 pSession->COMSETTER(Name)(Bstr("GUI/SDL").raw());
1499 hrc = pMachine->LockMachine(pSession, LockType_VM);
1500 }
1501
1502 if (FAILED(hrc))
1503 {
1504 com::ErrorInfo info;
1505 if (info.isFullAvailable())
1506 PrintError("Could not open VirtualBox session",
1507 info.getText().raw(), info.getComponent().raw());
1508 goto leave;
1509 }
1510 if (!pSession)
1511 {
1512 RTPrintf("Could not open VirtualBox session!\n");
1513 goto leave;
1514 }
1515 sessionOpened = true;
1516 // get the mutable VM we're dealing with
1517 pSession->COMGETTER(Machine)(gpMachine.asOutParam());
1518 if (!gpMachine)
1519 {
1520 com::ErrorInfo info;
1521 if (info.isFullAvailable())
1522 PrintError("Cannot start VM!",
1523 info.getText().raw(), info.getComponent().raw());
1524 else
1525 RTPrintf("Error: given machine not found!\n");
1526 goto leave;
1527 }
1528
1529 // get the VM console
1530 pSession->COMGETTER(Console)(gpConsole.asOutParam());
1531 if (!gpConsole)
1532 {
1533 RTPrintf("Given console not found!\n");
1534 goto leave;
1535 }
1536
1537 /*
1538 * Are we supposed to use a different hard disk file?
1539 */
1540 if (pcszHdaFile)
1541 {
1542 ComPtr<IMedium> pMedium;
1543
1544 /*
1545 * Strategy: if any registered hard disk points to the same file,
1546 * assign it. If not, register a new image and assign it to the VM.
1547 */
1548 Bstr bstrHdaFile(pcszHdaFile);
1549 pVirtualBox->OpenMedium(bstrHdaFile.raw(), DeviceType_HardDisk,
1550 AccessMode_ReadWrite, FALSE /* fForceNewUuid */,
1551 pMedium.asOutParam());
1552 if (!pMedium)
1553 {
1554 /* we've not found the image */
1555 RTPrintf("Adding hard disk '%s'...\n", pcszHdaFile);
1556 pVirtualBox->OpenMedium(bstrHdaFile.raw(), DeviceType_HardDisk,
1557 AccessMode_ReadWrite, FALSE /* fForceNewUuid */,
1558 pMedium.asOutParam());
1559 }
1560 /* do we have the right image now? */
1561 if (pMedium)
1562 {
1563 Bstr bstrSCName;
1564
1565 /* get the first IDE controller to attach the harddisk to
1566 * and if there is none, add one temporarily */
1567 {
1568 ComPtr<IStorageController> pStorageCtl;
1569 com::SafeIfaceArray<IStorageController> aStorageControllers;
1570 CHECK_ERROR(gpMachine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(aStorageControllers)));
1571 for (size_t i = 0; i < aStorageControllers.size(); ++ i)
1572 {
1573 StorageBus_T storageBus = StorageBus_Null;
1574
1575 CHECK_ERROR(aStorageControllers[i], COMGETTER(Bus)(&storageBus));
1576 if (storageBus == StorageBus_IDE)
1577 {
1578 pStorageCtl = aStorageControllers[i];
1579 break;
1580 }
1581 }
1582
1583 if (pStorageCtl)
1584 {
1585 CHECK_ERROR(pStorageCtl, COMGETTER(Name)(bstrSCName.asOutParam()));
1586 gpMachine->DetachDevice(bstrSCName.raw(), 0, 0);
1587 }
1588 else
1589 {
1590 bstrSCName = "IDE Controller";
1591 CHECK_ERROR(gpMachine, AddStorageController(bstrSCName.raw(),
1592 StorageBus_IDE,
1593 pStorageCtl.asOutParam()));
1594 }
1595 }
1596
1597 CHECK_ERROR(gpMachine, AttachDevice(bstrSCName.raw(), 0, 0,
1598 DeviceType_HardDisk, pMedium));
1599 /// @todo why is this attachment saved?
1600 }
1601 else
1602 {
1603 RTPrintf("Error: failed to mount the specified hard disk image!\n");
1604 goto leave;
1605 }
1606 }
1607
1608 /*
1609 * Mount a floppy if requested.
1610 */
1611 if (pcszFdaFile)
1612 do
1613 {
1614 ComPtr<IMedium> pMedium;
1615
1616 /* unmount? */
1617 if (!strcmp(pcszFdaFile, "none"))
1618 {
1619 /* nothing to do, NULL object will cause unmount */
1620 }
1621 else
1622 {
1623 Bstr bstrFdaFile(pcszFdaFile);
1624
1625 /* Assume it's a host drive name */
1626 ComPtr<IHost> pHost;
1627 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(Host)(pHost.asOutParam()));
1628 hrc = pHost->FindHostFloppyDrive(bstrFdaFile.raw(),
1629 pMedium.asOutParam());
1630 if (FAILED(hrc))
1631 {
1632 /* try to find an existing one */
1633 hrc = pVirtualBox->OpenMedium(bstrFdaFile.raw(),
1634 DeviceType_Floppy,
1635 AccessMode_ReadWrite,
1636 FALSE /* fForceNewUuid */,
1637 pMedium.asOutParam());
1638 if (FAILED(hrc))
1639 {
1640 /* try to add to the list */
1641 RTPrintf("Adding floppy image '%s'...\n", pcszFdaFile);
1642 CHECK_ERROR_BREAK(pVirtualBox,
1643 OpenMedium(bstrFdaFile.raw(),
1644 DeviceType_Floppy,
1645 AccessMode_ReadWrite,
1646 FALSE /* fForceNewUuid */,
1647 pMedium.asOutParam()));
1648 }
1649 }
1650 }
1651
1652 Bstr bstrSCName;
1653
1654 /* get the first floppy controller to attach the floppy to
1655 * and if there is none, add one temporarily */
1656 {
1657 ComPtr<IStorageController> pStorageCtl;
1658 com::SafeIfaceArray<IStorageController> aStorageControllers;
1659 CHECK_ERROR(gpMachine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(aStorageControllers)));
1660 for (size_t i = 0; i < aStorageControllers.size(); ++ i)
1661 {
1662 StorageBus_T storageBus = StorageBus_Null;
1663
1664 CHECK_ERROR(aStorageControllers[i], COMGETTER(Bus)(&storageBus));
1665 if (storageBus == StorageBus_Floppy)
1666 {
1667 pStorageCtl = aStorageControllers[i];
1668 break;
1669 }
1670 }
1671
1672 if (pStorageCtl)
1673 {
1674 CHECK_ERROR(pStorageCtl, COMGETTER(Name)(bstrSCName.asOutParam()));
1675 gpMachine->DetachDevice(bstrSCName.raw(), 0, 0);
1676 }
1677 else
1678 {
1679 bstrSCName = "Floppy Controller";
1680 CHECK_ERROR(gpMachine, AddStorageController(bstrSCName.raw(),
1681 StorageBus_Floppy,
1682 pStorageCtl.asOutParam()));
1683 }
1684 }
1685
1686 CHECK_ERROR(gpMachine, AttachDevice(bstrSCName.raw(), 0, 0,
1687 DeviceType_Floppy, pMedium));
1688 }
1689 while (0);
1690 if (FAILED(hrc))
1691 goto leave;
1692
1693 /*
1694 * Mount a CD-ROM if requested.
1695 */
1696 if (pcszCdromFile)
1697 do
1698 {
1699 ComPtr<IMedium> pMedium;
1700
1701 /* unmount? */
1702 if (!strcmp(pcszCdromFile, "none"))
1703 {
1704 /* nothing to do, NULL object will cause unmount */
1705 }
1706 else
1707 {
1708 Bstr bstrCdromFile(pcszCdromFile);
1709
1710 /* Assume it's a host drive name */
1711 ComPtr<IHost> pHost;
1712 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(Host)(pHost.asOutParam()));
1713 hrc = pHost->FindHostDVDDrive(bstrCdromFile.raw(), pMedium.asOutParam());
1714 if (FAILED(hrc))
1715 {
1716 /* try to find an existing one */
1717 hrc = pVirtualBox->OpenMedium(bstrCdromFile.raw(),
1718 DeviceType_DVD,
1719 AccessMode_ReadWrite,
1720 FALSE /* fForceNewUuid */,
1721 pMedium.asOutParam());
1722 if (FAILED(hrc))
1723 {
1724 /* try to add to the list */
1725 RTPrintf("Adding ISO image '%s'...\n", pcszCdromFile);
1726 CHECK_ERROR_BREAK(pVirtualBox,
1727 OpenMedium(bstrCdromFile.raw(),
1728 DeviceType_DVD,
1729 AccessMode_ReadWrite,
1730 FALSE /* fForceNewUuid */,
1731 pMedium.asOutParam()));
1732 }
1733 }
1734 }
1735
1736 Bstr bstrSCName;
1737
1738 /* get the first IDE controller to attach the DVD drive to
1739 * and if there is none, add one temporarily */
1740 {
1741 ComPtr<IStorageController> pStorageCtl;
1742 com::SafeIfaceArray<IStorageController> aStorageControllers;
1743 CHECK_ERROR(gpMachine, COMGETTER(StorageControllers)(ComSafeArrayAsOutParam(aStorageControllers)));
1744 for (size_t i = 0; i < aStorageControllers.size(); ++ i)
1745 {
1746 StorageBus_T storageBus = StorageBus_Null;
1747
1748 CHECK_ERROR(aStorageControllers[i], COMGETTER(Bus)(&storageBus));
1749 if (storageBus == StorageBus_IDE)
1750 {
1751 pStorageCtl = aStorageControllers[i];
1752 break;
1753 }
1754 }
1755
1756 if (pStorageCtl)
1757 {
1758 CHECK_ERROR(pStorageCtl, COMGETTER(Name)(bstrSCName.asOutParam()));
1759 gpMachine->DetachDevice(bstrSCName.raw(), 1, 0);
1760 }
1761 else
1762 {
1763 bstrSCName = "IDE Controller";
1764 CHECK_ERROR(gpMachine, AddStorageController(bstrSCName.raw(),
1765 StorageBus_IDE,
1766 pStorageCtl.asOutParam()));
1767 }
1768 }
1769
1770 CHECK_ERROR(gpMachine, AttachDevice(bstrSCName.raw(), 1, 0,
1771 DeviceType_DVD, pMedium));
1772 }
1773 while (0);
1774 if (FAILED(hrc))
1775 goto leave;
1776
1777 if (fDiscardState)
1778 {
1779 /*
1780 * If the machine is currently saved,
1781 * discard the saved state first.
1782 */
1783 MachineState_T machineState;
1784 gpMachine->COMGETTER(State)(&machineState);
1785 if (machineState == MachineState_Saved || machineState == MachineState_AbortedSaved)
1786 {
1787 CHECK_ERROR(gpMachine, DiscardSavedState(true /* fDeleteFile */));
1788 }
1789 /*
1790 * If there are snapshots, discard the current state,
1791 * i.e. revert to the last snapshot.
1792 */
1793 ULONG cSnapshots;
1794 gpMachine->COMGETTER(SnapshotCount)(&cSnapshots);
1795 if (cSnapshots)
1796 {
1797 gpProgress = NULL;
1798
1799 ComPtr<ISnapshot> pCurrentSnapshot;
1800 CHECK_ERROR(gpMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam()));
1801 if (FAILED(hrc))
1802 goto leave;
1803
1804 CHECK_ERROR(gpMachine, RestoreSnapshot(pCurrentSnapshot, gpProgress.asOutParam()));
1805 hrc = gpProgress->WaitForCompletion(-1);
1806 }
1807 }
1808
1809 // get the machine debugger (does not have to be there)
1810 gpConsole->COMGETTER(Debugger)(gpMachineDebugger.asOutParam());
1811 if (gpMachineDebugger)
1812 {
1813 Log(("Machine debugger available!\n"));
1814 }
1815 gpConsole->COMGETTER(Display)(gpDisplay.asOutParam());
1816 if (!gpDisplay)
1817 {
1818 RTPrintf("Error: could not get display object!\n");
1819 goto leave;
1820 }
1821
1822 // set the boot drive
1823 if (bootDevice != DeviceType_Null)
1824 {
1825 hrc = gpMachine->SetBootOrder(1, bootDevice);
1826 if (hrc != S_OK)
1827 {
1828 RTPrintf("Error: could not set boot device, using default.\n");
1829 }
1830 }
1831
1832 // set the memory size if not default
1833 if (memorySize)
1834 {
1835 hrc = gpMachine->COMSETTER(MemorySize)(memorySize);
1836 if (hrc != S_OK)
1837 {
1838 ULONG ramSize = 0;
1839 gpMachine->COMGETTER(MemorySize)(&ramSize);
1840 RTPrintf("Error: could not set memory size, using current setting of %d MBytes\n", ramSize);
1841 }
1842 }
1843
1844 hrc = gpMachine->COMGETTER(GraphicsAdapter)(pGraphicsAdapter.asOutParam());
1845 if (hrc != S_OK)
1846 {
1847 RTPrintf("Error: could not get graphics adapter object\n");
1848 goto leave;
1849 }
1850
1851 if (vramSize)
1852 {
1853 hrc = pGraphicsAdapter->COMSETTER(VRAMSize)(vramSize);
1854 if (hrc != S_OK)
1855 {
1856 pGraphicsAdapter->COMGETTER(VRAMSize)((ULONG*)&vramSize);
1857 RTPrintf("Error: could not set VRAM size, using current setting of %d MBytes\n", vramSize);
1858 }
1859 }
1860
1861 // we're always able to process absolute mouse events and we prefer that
1862 gfAbsoluteMouseHost = TRUE;
1863
1864#ifdef VBOX_WIN32_UI
1865 if (fWin32UI)
1866 {
1867 /* initialize the Win32 user interface inside which SDL will be embedded */
1868 if (initUI(fResizable, winId))
1869 return 1;
1870 }
1871#endif
1872
1873 /* static initialization of the SDL stuff */
1874 if (!VBoxSDLFB::init(fShowSDLConfig))
1875 goto leave;
1876
1877 pGraphicsAdapter->COMGETTER(MonitorCount)(&gcMonitors);
1878 if (gcMonitors > 64)
1879 gcMonitors = 64;
1880
1881 for (unsigned i = 0; i < gcMonitors; i++)
1882 {
1883 // create our SDL framebuffer instance
1884 gpFramebuffer[i].createObject();
1885 hrc = gpFramebuffer[i]->init(i, fFullscreen, fResizable, fShowSDLConfig, false,
1886 fixedWidth, fixedHeight, fixedBPP, fSeparate);
1887 if (FAILED(hrc))
1888 {
1889 RTPrintf("Error: could not create framebuffer object!\n");
1890 goto leave;
1891 }
1892 }
1893
1894#ifdef VBOX_WIN32_UI
1895 gpFramebuffer[0]->setWinId(winId);
1896#endif
1897
1898 for (unsigned i = 0; i < gcMonitors; i++)
1899 {
1900 if (!gpFramebuffer[i]->initialized())
1901 goto leave;
1902 gpFramebuffer[i]->AddRef();
1903 if (fFullscreen)
1904 SetFullscreen(true);
1905 }
1906
1907#ifdef VBOXSDL_WITH_X11
1908 /* NOTE1: We still want Ctrl-C to work, so we undo the SDL redirections.
1909 * NOTE2: We have to remove the PidFile if this file exists. */
1910 signal(SIGINT, signal_handler_SIGINT);
1911 signal(SIGQUIT, signal_handler_SIGINT);
1912 signal(SIGSEGV, signal_handler_SIGINT);
1913#endif
1914
1915
1916 for (ULONG i = 0; i < gcMonitors; i++)
1917 {
1918 // register our framebuffer
1919 hrc = gpDisplay->AttachFramebuffer(i, gpFramebuffer[i], gaFramebufferId[i].asOutParam());
1920 if (FAILED(hrc))
1921 {
1922 RTPrintf("Error: could not register framebuffer object!\n");
1923 goto leave;
1924 }
1925 ULONG dummy;
1926 LONG xOrigin, yOrigin;
1927 GuestMonitorStatus_T monitorStatus;
1928 hrc = gpDisplay->GetScreenResolution(i, &dummy, &dummy, &dummy, &xOrigin, &yOrigin, &monitorStatus);
1929 gpFramebuffer[i]->setOrigin(xOrigin, yOrigin);
1930 }
1931
1932 {
1933 // register listener for VirtualBoxClient events
1934 ComPtr<IEventSource> pES;
1935 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
1936 ComObjPtr<VBoxSDLClientEventListenerImpl> listener;
1937 listener.createObject();
1938 listener->init(new VBoxSDLClientEventListener());
1939 pVBoxClientListener = listener;
1940 com::SafeArray<VBoxEventType_T> eventTypes;
1941 eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
1942 CHECK_ERROR(pES, RegisterListener(pVBoxClientListener, ComSafeArrayAsInParam(eventTypes), true));
1943 }
1944
1945 {
1946 // register listener for VirtualBox (server) events
1947 ComPtr<IEventSource> pES;
1948 CHECK_ERROR(pVirtualBox, COMGETTER(EventSource)(pES.asOutParam()));
1949 ComObjPtr<VBoxSDLEventListenerImpl> listener;
1950 listener.createObject();
1951 listener->init(new VBoxSDLEventListener());
1952 pVBoxListener = listener;
1953 com::SafeArray<VBoxEventType_T> eventTypes;
1954 eventTypes.push_back(VBoxEventType_OnExtraDataChanged);
1955 CHECK_ERROR(pES, RegisterListener(pVBoxListener, ComSafeArrayAsInParam(eventTypes), true));
1956 }
1957
1958 {
1959 // register listener for Console events
1960 ComPtr<IEventSource> pES;
1961 CHECK_ERROR(gpConsole, COMGETTER(EventSource)(pES.asOutParam()));
1962 pConsoleListener.createObject();
1963 pConsoleListener->init(new VBoxSDLConsoleEventListener());
1964 com::SafeArray<VBoxEventType_T> eventTypes;
1965 eventTypes.push_back(VBoxEventType_OnMousePointerShapeChanged);
1966 eventTypes.push_back(VBoxEventType_OnMouseCapabilityChanged);
1967 eventTypes.push_back(VBoxEventType_OnKeyboardLedsChanged);
1968 eventTypes.push_back(VBoxEventType_OnStateChanged);
1969 eventTypes.push_back(VBoxEventType_OnRuntimeError);
1970 eventTypes.push_back(VBoxEventType_OnCanShowWindow);
1971 eventTypes.push_back(VBoxEventType_OnShowWindow);
1972 CHECK_ERROR(pES, RegisterListener(pConsoleListener, ComSafeArrayAsInParam(eventTypes), true));
1973 // until we've tried to to start the VM, ignore power off events
1974 pConsoleListener->getWrapped()->ignorePowerOffEvents(true);
1975 }
1976
1977 if (pszPortVRDP)
1978 {
1979 hrc = gpMachine->COMGETTER(VRDEServer)(gpVRDEServer.asOutParam());
1980 AssertMsg((hrc == S_OK) && gpVRDEServer, ("Could not get VRDP Server! rc = 0x%x\n", hrc));
1981 if (gpVRDEServer)
1982 {
1983 // has a non standard VRDP port been requested?
1984 if (strcmp(pszPortVRDP, "0"))
1985 {
1986 hrc = gpVRDEServer->SetVRDEProperty(Bstr("TCP/Ports").raw(), Bstr(pszPortVRDP).raw());
1987 if (hrc != S_OK)
1988 {
1989 RTPrintf("Error: could not set VRDP port! rc = 0x%x\n", hrc);
1990 goto leave;
1991 }
1992 }
1993 // now enable VRDP
1994 hrc = gpVRDEServer->COMSETTER(Enabled)(TRUE);
1995 if (hrc != S_OK)
1996 {
1997 RTPrintf("Error: could not enable VRDP server! rc = 0x%x\n", hrc);
1998 goto leave;
1999 }
2000 }
2001 }
2002
2003 hrc = E_FAIL;
2004#ifdef VBOXSDL_ADVANCED_OPTIONS
2005 if (u32WarpDrive != 0)
2006 {
2007 if (!gpMachineDebugger)
2008 {
2009 RTPrintf("Error: No debugger object; --warpdrive %d cannot be executed!\n", u32WarpDrive);
2010 goto leave;
2011 }
2012 gpMachineDebugger->COMSETTER(VirtualTimeRate)(u32WarpDrive);
2013 }
2014#endif /* VBOXSDL_ADVANCED_OPTIONS */
2015
2016 /* start with something in the titlebar */
2017 UpdateTitlebar(TITLEBAR_NORMAL);
2018
2019 /* memorize the default cursor */
2020 gpDefaultCursor = SDL_GetCursor();
2021 /*
2022 * Register our user signal handler.
2023 */
2024#ifdef VBOXSDL_WITH_X11
2025 struct sigaction sa;
2026 sa.sa_sigaction = signal_handler_SIGUSR1;
2027 sigemptyset(&sa.sa_mask);
2028 sa.sa_flags = SA_RESTART | SA_SIGINFO;
2029 sigaction(SIGUSR1, &sa, NULL);
2030#endif /* VBOXSDL_WITH_X11 */
2031
2032 /*
2033 * Start the VM execution thread. This has to be done
2034 * asynchronously as powering up can take some time
2035 * (accessing devices such as the host DVD drive). In
2036 * the meantime, we have to service the SDL event loop.
2037 */
2038 SDL_Event event;
2039
2040 if (!fSeparate)
2041 {
2042 LogFlow(("Powering up the VM...\n"));
2043 hrc = gpConsole->PowerUp(gpProgress.asOutParam());
2044 if (hrc != S_OK)
2045 {
2046 com::ErrorInfo info(gpConsole, COM_IIDOF(IConsole));
2047 if (info.isBasicAvailable())
2048 PrintError("Failed to power up VM", info.getText().raw());
2049 else
2050 RTPrintf("Error: failed to power up VM! No error text available.\n");
2051 goto leave;
2052 }
2053 }
2054
2055#ifdef USE_XPCOM_QUEUE_THREAD
2056 /*
2057 * Before we starting to do stuff, we have to launch the XPCOM
2058 * event queue thread. It will wait for events and send messages
2059 * to the SDL thread. After having done this, we should fairly
2060 * quickly start to process the SDL event queue as an XPCOM
2061 * event storm might arrive. Stupid SDL has a ridiculously small
2062 * event queue buffer!
2063 */
2064 startXPCOMEventQueueThread(eventQ->getSelectFD());
2065#endif /* USE_XPCOM_QUEUE_THREAD */
2066
2067 /* termination flag */
2068 bool fTerminateDuringStartup;
2069 fTerminateDuringStartup = false;
2070
2071 LogRel(("VBoxSDL: NUM lock initially %s, CAPS lock initially %s\n",
2072 !!(SDL_GetModState() & KMOD_NUM) ? "ON" : "OFF",
2073 !!(SDL_GetModState() & KMOD_CAPS) ? "ON" : "OFF"));
2074
2075 /* start regular timer so we don't starve in the event loop */
2076 SDL_TimerID sdlTimer;
2077 sdlTimer = SDL_AddTimer(100, StartupTimer, NULL);
2078
2079 /* loop until the powerup processing is done */
2080 MachineState_T machineState;
2081 do
2082 {
2083 hrc = gpMachine->COMGETTER(State)(&machineState);
2084 if ( hrc == S_OK
2085 && ( machineState == MachineState_Starting
2086 || machineState == MachineState_Restoring
2087 || machineState == MachineState_TeleportingIn
2088 )
2089 )
2090 {
2091 /*
2092 * wait for the next event. This is uncritical as
2093 * power up guarantees to change the machine state
2094 * to either running or aborted and a machine state
2095 * change will send us an event. However, we have to
2096 * service the XPCOM event queue!
2097 */
2098#ifdef USE_XPCOM_QUEUE_THREAD
2099 if (!fXPCOMEventThreadSignaled)
2100 {
2101 signalXPCOMEventQueueThread();
2102 fXPCOMEventThreadSignaled = true;
2103 }
2104#endif
2105 /*
2106 * Wait for SDL events.
2107 */
2108 if (WaitSDLEvent(&event))
2109 {
2110 switch (event.type)
2111 {
2112 /*
2113 * Timer event. Used to have the titlebar updated.
2114 */
2115 case SDL_USER_EVENT_TIMER:
2116 {
2117 /*
2118 * Update the title bar.
2119 */
2120 UpdateTitlebar(TITLEBAR_STARTUP);
2121 break;
2122 }
2123
2124 /*
2125 * User specific framebuffer change event.
2126 */
2127 case SDL_USER_EVENT_NOTIFYCHANGE:
2128 {
2129 LogFlow(("SDL_USER_EVENT_NOTIFYCHANGE\n"));
2130 LONG xOrigin, yOrigin;
2131 gpFramebuffer[event.user.code]->notifyChange(event.user.code);
2132 /* update xOrigin, yOrigin -> mouse */
2133 ULONG dummy;
2134 GuestMonitorStatus_T monitorStatus;
2135 hrc = gpDisplay->GetScreenResolution(event.user.code, &dummy, &dummy, &dummy, &xOrigin, &yOrigin, &monitorStatus);
2136 gpFramebuffer[event.user.code]->setOrigin(xOrigin, yOrigin);
2137 break;
2138 }
2139
2140#ifdef USE_XPCOM_QUEUE_THREAD
2141 /*
2142 * User specific XPCOM event queue event
2143 */
2144 case SDL_USER_EVENT_XPCOM_EVENTQUEUE:
2145 {
2146 LogFlow(("SDL_USER_EVENT_XPCOM_EVENTQUEUE: processing XPCOM event queue...\n"));
2147 eventQ->processEventQueue(0);
2148 signalXPCOMEventQueueThread();
2149 break;
2150 }
2151#endif /* USE_XPCOM_QUEUE_THREAD */
2152
2153 /*
2154 * Termination event from the on state change callback.
2155 */
2156 case SDL_USER_EVENT_TERMINATE:
2157 {
2158 if (event.user.code != VBOXSDL_TERM_NORMAL)
2159 {
2160 com::ProgressErrorInfo info(gpProgress);
2161 if (info.isBasicAvailable())
2162 PrintError("Failed to power up VM", info.getText().raw());
2163 else
2164 RTPrintf("Error: failed to power up VM! No error text available.\n");
2165 }
2166 fTerminateDuringStartup = true;
2167 break;
2168 }
2169
2170 default:
2171 {
2172 Log8(("VBoxSDL: Unknown SDL event %d (pre)\n", event.type));
2173 break;
2174 }
2175 }
2176
2177 }
2178 }
2179 eventQ->processEventQueue(0);
2180 } while ( hrc == S_OK
2181 && ( machineState == MachineState_Starting
2182 || machineState == MachineState_Restoring
2183 || machineState == MachineState_TeleportingIn
2184 )
2185 );
2186
2187 /* kill the timer again */
2188 SDL_RemoveTimer(sdlTimer);
2189 sdlTimer = 0;
2190
2191 /* are we supposed to terminate the process? */
2192 if (fTerminateDuringStartup)
2193 goto leave;
2194
2195 /* did the power up succeed? */
2196 if (machineState != MachineState_Running)
2197 {
2198 com::ProgressErrorInfo info(gpProgress);
2199 if (info.isBasicAvailable())
2200 PrintError("Failed to power up VM", info.getText().raw());
2201 else
2202 RTPrintf("Error: failed to power up VM! No error text available (rc = 0x%x state = %d)\n", hrc, machineState);
2203 goto leave;
2204 }
2205
2206 // accept power off events from now on because we're running
2207 // note that there's a possible race condition here...
2208 pConsoleListener->getWrapped()->ignorePowerOffEvents(false);
2209
2210 hrc = gpConsole->COMGETTER(Keyboard)(gpKeyboard.asOutParam());
2211 if (!gpKeyboard)
2212 {
2213 RTPrintf("Error: could not get keyboard object!\n");
2214 goto leave;
2215 }
2216 gpConsole->COMGETTER(Mouse)(gpMouse.asOutParam());
2217 if (!gpMouse)
2218 {
2219 RTPrintf("Error: could not get mouse object!\n");
2220 goto leave;
2221 }
2222
2223 if (fSeparate && gpMouse)
2224 {
2225 LogFlow(("Fetching mouse caps\n"));
2226
2227 /* Fetch current mouse status, etc */
2228 gpMouse->COMGETTER(AbsoluteSupported)(&gfAbsoluteMouseGuest);
2229 gpMouse->COMGETTER(RelativeSupported)(&gfRelativeMouseGuest);
2230 gpMouse->COMGETTER(NeedsHostCursor)(&gfGuestNeedsHostCursor);
2231
2232 HandleGuestCapsChanged();
2233
2234 ComPtr<IMousePointerShape> mps;
2235 gpMouse->COMGETTER(PointerShape)(mps.asOutParam());
2236 if (!mps.isNull())
2237 {
2238 BOOL visible, alpha;
2239 ULONG hotX, hotY, width, height;
2240 com::SafeArray <BYTE> shape;
2241
2242 mps->COMGETTER(Visible)(&visible);
2243 mps->COMGETTER(Alpha)(&alpha);
2244 mps->COMGETTER(HotX)(&hotX);
2245 mps->COMGETTER(HotY)(&hotY);
2246 mps->COMGETTER(Width)(&width);
2247 mps->COMGETTER(Height)(&height);
2248 mps->COMGETTER(Shape)(ComSafeArrayAsOutParam(shape));
2249
2250 if (shape.size() > 0)
2251 {
2252 PointerShapeChangeData data(visible, alpha, hotX, hotY, width, height,
2253 ComSafeArrayAsInParam(shape));
2254 SetPointerShape(&data);
2255 }
2256 }
2257 }
2258
2259 UpdateTitlebar(TITLEBAR_NORMAL);
2260
2261 /*
2262 * Create PID file.
2263 */
2264 if (gpszPidFile)
2265 {
2266 char szBuf[32];
2267 const char *pcszLf = "\n";
2268 RTFILE PidFile;
2269 RTFileOpen(&PidFile, gpszPidFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE);
2270 RTStrFormatNumber(szBuf, RTProcSelf(), 10, 0, 0, 0);
2271 RTFileWrite(PidFile, szBuf, strlen(szBuf), NULL);
2272 RTFileWrite(PidFile, pcszLf, strlen(pcszLf), NULL);
2273 RTFileClose(PidFile);
2274 }
2275
2276 /*
2277 * Main event loop
2278 */
2279#ifdef USE_XPCOM_QUEUE_THREAD
2280 if (!fXPCOMEventThreadSignaled)
2281 {
2282 signalXPCOMEventQueueThread();
2283 }
2284#endif
2285 LogFlow(("VBoxSDL: Entering big event loop\n"));
2286 while (WaitSDLEvent(&event))
2287 {
2288 switch (event.type)
2289 {
2290 /*
2291 * The screen needs to be repainted.
2292 */
2293 case SDL_WINDOWEVENT:
2294 {
2295 switch (event.window.event)
2296 {
2297 case SDL_WINDOWEVENT_EXPOSED:
2298 {
2299 VBoxSDLFB *fb = getFbFromWinId(event.window.windowID);
2300 if (fb)
2301 fb->repaint();
2302 break;
2303 }
2304 case SDL_WINDOWEVENT_FOCUS_GAINED:
2305 {
2306 break;
2307 }
2308 case SDL_WINDOWEVENT_FOCUS_LOST:
2309 {
2310 break;
2311 }
2312 case SDL_WINDOWEVENT_RESIZED:
2313 {
2314 if (gpDisplay)
2315 {
2316 if (gfIgnoreNextResize)
2317 {
2318 gfIgnoreNextResize = FALSE;
2319 break;
2320 }
2321 uResizeWidth = event.window.data1;
2322 uResizeHeight = event.window.data2;
2323 if (gSdlResizeTimer)
2324 SDL_RemoveTimer(gSdlResizeTimer);
2325 gSdlResizeTimer = SDL_AddTimer(300, ResizeTimer, NULL);
2326 }
2327 break;
2328 }
2329 default:
2330 break;
2331 }
2332 break;
2333 }
2334
2335 /*
2336 * Keyboard events.
2337 */
2338 case SDL_KEYDOWN:
2339 case SDL_KEYUP:
2340 {
2341 SDL_Keycode ksym = event.key.keysym.sym;
2342 switch (enmHKeyState)
2343 {
2344 case HKEYSTATE_NORMAL:
2345 {
2346 if ( event.type == SDL_KEYDOWN
2347 && ksym != SDLK_UNKNOWN
2348 && (ksym == gHostKeySym1 || ksym == gHostKeySym2))
2349 {
2350 EvHKeyDown1 = event;
2351 enmHKeyState = ksym == gHostKeySym1 ? HKEYSTATE_DOWN_1ST
2352 : HKEYSTATE_DOWN_2ND;
2353 break;
2354 }
2355 ProcessKey(&event.key);
2356 break;
2357 }
2358
2359 case HKEYSTATE_DOWN_1ST:
2360 case HKEYSTATE_DOWN_2ND:
2361 {
2362 if (gHostKeySym2 != SDLK_UNKNOWN)
2363 {
2364 if ( event.type == SDL_KEYDOWN
2365 && ksym != SDLK_UNKNOWN
2366 && ( (enmHKeyState == HKEYSTATE_DOWN_1ST && ksym == gHostKeySym2)
2367 || (enmHKeyState == HKEYSTATE_DOWN_2ND && ksym == gHostKeySym1)))
2368 {
2369 EvHKeyDown2 = event;
2370 enmHKeyState = HKEYSTATE_DOWN;
2371 break;
2372 }
2373 enmHKeyState = event.type == SDL_KEYUP ? HKEYSTATE_NORMAL
2374 : HKEYSTATE_NOT_IT;
2375 ProcessKey(&EvHKeyDown1.key);
2376 /* ugly hack: Some guests (e.g. mstsc.exe on Windows XP)
2377 * expect a small delay between two key events. 5ms work
2378 * reliable here so use 10ms to be on the safe side. A
2379 * better but more complicated fix would be to introduce
2380 * a new state and don't wait here. */
2381 RTThreadSleep(10);
2382 ProcessKey(&event.key);
2383 break;
2384 }
2385 }
2386 RT_FALL_THRU();
2387
2388 case HKEYSTATE_DOWN:
2389 {
2390 if (event.type == SDL_KEYDOWN)
2391 {
2392 /* potential host key combination, try execute it */
2393 int irc = HandleHostKey(&event.key);
2394 if (irc == VINF_SUCCESS)
2395 {
2396 enmHKeyState = HKEYSTATE_USED;
2397 break;
2398 }
2399 if (RT_SUCCESS(irc))
2400 goto leave;
2401 }
2402 else /* SDL_KEYUP */
2403 {
2404 if ( ksym != SDLK_UNKNOWN
2405 && (ksym == gHostKeySym1 || ksym == gHostKeySym2))
2406 {
2407 /* toggle grabbing state */
2408 if (!gfGrabbed)
2409 InputGrabStart();
2410 else
2411 InputGrabEnd();
2412
2413 /* SDL doesn't always reset the keystates, correct it */
2414 ResetKeys();
2415 enmHKeyState = HKEYSTATE_NORMAL;
2416 break;
2417 }
2418 }
2419
2420 /* not host key */
2421 enmHKeyState = HKEYSTATE_NOT_IT;
2422 ProcessKey(&EvHKeyDown1.key);
2423 /* see the comment for the 2-key case above */
2424 RTThreadSleep(10);
2425 if (gHostKeySym2 != SDLK_UNKNOWN)
2426 {
2427 ProcessKey(&EvHKeyDown2.key);
2428 /* see the comment for the 2-key case above */
2429 RTThreadSleep(10);
2430 }
2431 ProcessKey(&event.key);
2432 break;
2433 }
2434
2435 case HKEYSTATE_USED:
2436 {
2437 if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == 0)
2438 enmHKeyState = HKEYSTATE_NORMAL;
2439 if (event.type == SDL_KEYDOWN)
2440 {
2441 int irc = HandleHostKey(&event.key);
2442 if (RT_SUCCESS(irc) && irc != VINF_SUCCESS)
2443 goto leave;
2444 }
2445 break;
2446 }
2447
2448 default:
2449 AssertMsgFailed(("enmHKeyState=%d\n", enmHKeyState));
2450 RT_FALL_THRU();
2451 case HKEYSTATE_NOT_IT:
2452 {
2453 if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) == 0)
2454 enmHKeyState = HKEYSTATE_NORMAL;
2455 ProcessKey(&event.key);
2456 break;
2457 }
2458 } /* state switch */
2459 break;
2460 }
2461
2462 /*
2463 * The window was closed.
2464 */
2465 case SDL_QUIT:
2466 {
2467 if (!gfACPITerm || gSdlQuitTimer)
2468 goto leave;
2469 if (gpConsole)
2470 gpConsole->PowerButton();
2471 gSdlQuitTimer = SDL_AddTimer(1000, QuitTimer, NULL);
2472 break;
2473 }
2474
2475 /*
2476 * The mouse has moved
2477 */
2478 case SDL_MOUSEMOTION:
2479 {
2480 if (gfGrabbed || UseAbsoluteMouse())
2481 {
2482 VBoxSDLFB *fb = getFbFromWinId(event.motion.windowID);
2483 if (fb)
2484 SendMouseEvent(fb, 0, 0, 0);
2485 }
2486 break;
2487 }
2488
2489 case SDL_MOUSEWHEEL:
2490 {
2491 VBoxSDLFB *fb = getFbFromWinId(event.button.windowID);
2492 if (fb)
2493 SendMouseEvent(fb, -1 * event.wheel.y, 0, 0);
2494 break;
2495 }
2496 /*
2497 * A mouse button has been clicked or released.
2498 */
2499 case SDL_MOUSEBUTTONDOWN:
2500 case SDL_MOUSEBUTTONUP:
2501 {
2502 SDL_MouseButtonEvent *bev = &event.button;
2503 /* don't grab on mouse click if we have guest additions */
2504 if (!gfGrabbed && !UseAbsoluteMouse() && gfGrabOnMouseClick)
2505 {
2506 if (event.type == SDL_MOUSEBUTTONDOWN && (bev->state & SDL_BUTTON_LMASK))
2507 {
2508 /* start grabbing all events */
2509 InputGrabStart();
2510 }
2511 }
2512 else if (gfGrabbed || UseAbsoluteMouse())
2513 {
2514 /* end host key combination (CTRL+MouseButton) */
2515 switch (enmHKeyState)
2516 {
2517 case HKEYSTATE_DOWN_1ST:
2518 case HKEYSTATE_DOWN_2ND:
2519 enmHKeyState = HKEYSTATE_NOT_IT;
2520 ProcessKey(&EvHKeyDown1.key);
2521 /* ugly hack: small delay to ensure that the key event is
2522 * actually handled _prior_ to the mouse click event */
2523 RTThreadSleep(20);
2524 break;
2525 case HKEYSTATE_DOWN:
2526 enmHKeyState = HKEYSTATE_NOT_IT;
2527 ProcessKey(&EvHKeyDown1.key);
2528 if (gHostKeySym2 != SDLK_UNKNOWN)
2529 ProcessKey(&EvHKeyDown2.key);
2530 /* ugly hack: small delay to ensure that the key event is
2531 * actually handled _prior_ to the mouse click event */
2532 RTThreadSleep(20);
2533 break;
2534 default:
2535 break;
2536 }
2537
2538 VBoxSDLFB *fb;
2539 fb = getFbFromWinId(event.button.windowID);
2540 if (fb)
2541 SendMouseEvent(fb, 0 /*wheel vertical movement*/, event.type == SDL_MOUSEBUTTONDOWN, bev->button);
2542 }
2543 break;
2544 }
2545
2546#if 0
2547 /*
2548 * The window has gained or lost focus.
2549 */
2550 case SDL_ACTIVEEVENT: /** @todo Needs to be also fixed with SDL2? Check! */
2551 {
2552 /*
2553 * There is a strange behaviour in SDL when running without a window
2554 * manager: When SDL_WM_GrabInput(SDL_GRAB_ON) is called we receive two
2555 * consecutive events SDL_ACTIVEEVENTs (input lost, input gained).
2556 * Asking SDL_GetAppState() seems the better choice.
2557 */
2558 if (gfGrabbed && (SDL_GetAppState() & SDL_APPINPUTFOCUS) == 0)
2559 {
2560 /*
2561 * another window has stolen the (keyboard) input focus
2562 */
2563 InputGrabEnd();
2564 }
2565 break;
2566 }
2567
2568 /*
2569 * The SDL window was resized.
2570 * For SDL2 this is done in SDL_WINDOWEVENT.
2571 */
2572 case SDL_VIDEORESIZE:
2573 {
2574 if (gpDisplay)
2575 {
2576 if (gfIgnoreNextResize)
2577 {
2578 gfIgnoreNextResize = FALSE;
2579 break;
2580 }
2581 uResizeWidth = event.resize.w;
2582 uResizeHeight = event.resize.h;
2583 if (gSdlResizeTimer)
2584 SDL_RemoveTimer(gSdlResizeTimer);
2585 gSdlResizeTimer = SDL_AddTimer(300, ResizeTimer, NULL);
2586 }
2587 break;
2588 }
2589#endif
2590 /*
2591 * User specific update event.
2592 */
2593 /** @todo use a common user event handler so that SDL_PeepEvents() won't
2594 * possibly remove other events in the queue!
2595 */
2596 case SDL_USER_EVENT_UPDATERECT:
2597 {
2598 /*
2599 * Decode event parameters.
2600 */
2601 ASMAtomicDecS32(&g_cNotifyUpdateEventsPending);
2602
2603 SDL_Rect *pUpdateRect = (SDL_Rect *)event.user.data1;
2604 AssertPtrBreak(pUpdateRect);
2605
2606 int const x = pUpdateRect->x;
2607 int const y = pUpdateRect->y;
2608 int const w = pUpdateRect->w;
2609 int const h = pUpdateRect->h;
2610
2611 RTMemFree(event.user.data1);
2612
2613 Log3Func(("SDL_USER_EVENT_UPDATERECT: x=%d y=%d, w=%d, h=%d\n", x, y, w, h));
2614
2615 Assert(gpFramebuffer[event.user.code]);
2616 gpFramebuffer[event.user.code]->update(x, y, w, h, true /* fGuestRelative */);
2617 break;
2618 }
2619
2620 /*
2621 * User event: Window resize done
2622 */
2623 case SDL_USER_EVENT_WINDOW_RESIZE_DONE:
2624 {
2625 /**
2626 * @todo This is a workaround for synchronization problems between EMT and the
2627 * SDL main thread. It can happen that the SDL thread already starts a
2628 * new resize operation while the EMT is still busy with the old one
2629 * leading to a deadlock. Therefore we call SetVideoModeHint only once
2630 * when the mouse button was released.
2631 */
2632 /* communicate the resize event to the guest */
2633 gpDisplay->SetVideoModeHint(0 /*=display*/, true /*=enabled*/, false /*=changeOrigin*/,
2634 0 /*=originX*/, 0 /*=originY*/,
2635 uResizeWidth, uResizeHeight, 0 /*=don't change bpp*/, true /*=notify*/);
2636 break;
2637
2638 }
2639
2640 /*
2641 * User specific framebuffer change event.
2642 */
2643 case SDL_USER_EVENT_NOTIFYCHANGE:
2644 {
2645 LogFlow(("SDL_USER_EVENT_NOTIFYCHANGE\n"));
2646 LONG xOrigin, yOrigin;
2647 gpFramebuffer[event.user.code]->notifyChange(event.user.code);
2648 /* update xOrigin, yOrigin -> mouse */
2649 ULONG dummy;
2650 GuestMonitorStatus_T monitorStatus;
2651 hrc = gpDisplay->GetScreenResolution(event.user.code, &dummy, &dummy, &dummy, &xOrigin, &yOrigin, &monitorStatus);
2652 gpFramebuffer[event.user.code]->setOrigin(xOrigin, yOrigin);
2653 break;
2654 }
2655
2656#ifdef USE_XPCOM_QUEUE_THREAD
2657 /*
2658 * User specific XPCOM event queue event
2659 */
2660 case SDL_USER_EVENT_XPCOM_EVENTQUEUE:
2661 {
2662 LogFlow(("SDL_USER_EVENT_XPCOM_EVENTQUEUE: processing XPCOM event queue...\n"));
2663 eventQ->processEventQueue(0);
2664 signalXPCOMEventQueueThread();
2665 break;
2666 }
2667#endif /* USE_XPCOM_QUEUE_THREAD */
2668
2669 /*
2670 * User specific update title bar notification event
2671 */
2672 case SDL_USER_EVENT_UPDATE_TITLEBAR:
2673 {
2674 UpdateTitlebar(TITLEBAR_NORMAL);
2675 break;
2676 }
2677
2678 /*
2679 * User specific termination event
2680 */
2681 case SDL_USER_EVENT_TERMINATE:
2682 {
2683 if (event.user.code != VBOXSDL_TERM_NORMAL)
2684 RTPrintf("Error: VM terminated abnormally!\n");
2685 goto leave;
2686 }
2687 /*
2688 * User specific pointer shape change event
2689 */
2690 case SDL_USER_EVENT_POINTER_CHANGE:
2691 {
2692 PointerShapeChangeData *data = (PointerShapeChangeData *)event.user.data1;
2693 SetPointerShape (data);
2694 delete data;
2695 break;
2696 }
2697
2698 /*
2699 * User specific guest capabilities changed
2700 */
2701 case SDL_USER_EVENT_GUEST_CAP_CHANGED:
2702 {
2703 HandleGuestCapsChanged();
2704 break;
2705 }
2706
2707 default:
2708 {
2709 Log8(("unknown SDL event %d\n", event.type));
2710 break;
2711 }
2712 }
2713 }
2714
2715leave:
2716 if (gpszPidFile)
2717 RTFileDelete(gpszPidFile);
2718
2719 LogFlow(("leaving...\n"));
2720#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2)
2721 /* make sure the XPCOM event queue thread doesn't do anything harmful */
2722 terminateXPCOMQueueThread();
2723#endif /* VBOX_WITH_XPCOM */
2724
2725 if (gpVRDEServer)
2726 hrc = gpVRDEServer->COMSETTER(Enabled)(FALSE);
2727
2728 /*
2729 * Get the machine state.
2730 */
2731 if (gpMachine)
2732 gpMachine->COMGETTER(State)(&machineState);
2733 else
2734 machineState = MachineState_Aborted;
2735
2736 if (!fSeparate)
2737 {
2738 /*
2739 * Turn off the VM if it's running
2740 */
2741 if ( gpConsole
2742 && ( machineState == MachineState_Running
2743 || machineState == MachineState_Teleporting
2744 || machineState == MachineState_LiveSnapshotting
2745 /** @todo power off paused VMs too? */
2746 )
2747 )
2748 do
2749 {
2750 pConsoleListener->getWrapped()->ignorePowerOffEvents(true);
2751 ComPtr<IProgress> pProgress;
2752 CHECK_ERROR_BREAK(gpConsole, PowerDown(pProgress.asOutParam()));
2753 CHECK_ERROR_BREAK(pProgress, WaitForCompletion(-1));
2754 BOOL completed;
2755 CHECK_ERROR_BREAK(pProgress, COMGETTER(Completed)(&completed));
2756 ASSERT(completed);
2757 LONG hrc2;
2758 CHECK_ERROR_BREAK(pProgress, COMGETTER(ResultCode)(&hrc2));
2759 if (FAILED(hrc2))
2760 {
2761 com::ErrorInfo info;
2762 if (info.isFullAvailable())
2763 PrintError("Failed to power down VM",
2764 info.getText().raw(), info.getComponent().raw());
2765 else
2766 RTPrintf("Failed to power down virtual machine! No error information available (rc=%Rhrc).\n", hrc2);
2767 break;
2768 }
2769 } while (0);
2770 }
2771
2772 /* unregister Console listener */
2773 if (pConsoleListener)
2774 {
2775 ComPtr<IEventSource> pES;
2776 CHECK_ERROR(gpConsole, COMGETTER(EventSource)(pES.asOutParam()));
2777 if (!pES.isNull())
2778 CHECK_ERROR(pES, UnregisterListener(pConsoleListener));
2779 pConsoleListener.setNull();
2780 }
2781
2782 /*
2783 * Now we discard all settings so that our changes will
2784 * not be flushed to the permanent configuration
2785 */
2786 if ( gpMachine
2787 && machineState != MachineState_Saved
2788 && machineState != MachineState_AbortedSaved)
2789 {
2790 hrc = gpMachine->DiscardSettings();
2791 AssertMsg(SUCCEEDED(hrc), ("DiscardSettings %Rhrc, machineState %d\n", hrc, machineState));
2792 }
2793
2794 /* close the session */
2795 if (sessionOpened)
2796 {
2797 hrc = pSession->UnlockMachine();
2798 AssertComRC(hrc);
2799 }
2800
2801 LogFlow(("Releasing mouse, keyboard, remote desktop server, display, console...\n"));
2802 if (gpDisplay)
2803 {
2804 for (unsigned i = 0; i < gcMonitors; i++)
2805 gpDisplay->DetachFramebuffer(i, gaFramebufferId[i].raw());
2806 }
2807
2808 gpMouse = NULL;
2809 gpKeyboard = NULL;
2810 gpVRDEServer = NULL;
2811 gpDisplay = NULL;
2812 gpConsole = NULL;
2813 gpMachineDebugger = NULL;
2814 gpProgress = NULL;
2815 // we can only uninitialize SDL here because it is not threadsafe
2816
2817 for (unsigned i = 0; i < gcMonitors; i++)
2818 {
2819 if (gpFramebuffer[i])
2820 {
2821 LogFlow(("Releasing framebuffer...\n"));
2822 gpFramebuffer[i]->Release();
2823 gpFramebuffer[i] = NULL;
2824 }
2825 }
2826
2827 VBoxSDLFB::uninit();
2828
2829 /* VirtualBox (server) listener unregistration. */
2830 if (pVBoxListener)
2831 {
2832 ComPtr<IEventSource> pES;
2833 CHECK_ERROR(pVirtualBox, COMGETTER(EventSource)(pES.asOutParam()));
2834 if (!pES.isNull())
2835 CHECK_ERROR(pES, UnregisterListener(pVBoxListener));
2836 pVBoxListener.setNull();
2837 }
2838
2839 /* VirtualBoxClient listener unregistration. */
2840 if (pVBoxClientListener)
2841 {
2842 ComPtr<IEventSource> pES;
2843 CHECK_ERROR(pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
2844 if (!pES.isNull())
2845 CHECK_ERROR(pES, UnregisterListener(pVBoxClientListener));
2846 pVBoxClientListener.setNull();
2847 }
2848
2849 LogFlow(("Releasing machine, session...\n"));
2850 gpMachine = NULL;
2851 pSession = NULL;
2852 LogFlow(("Releasing VirtualBox object...\n"));
2853 pVirtualBox = NULL;
2854 LogFlow(("Releasing VirtualBoxClient object...\n"));
2855 pVirtualBoxClient = NULL;
2856
2857 // end "all-stuff" scope
2858 ////////////////////////////////////////////////////////////////////////////
2859 }
2860
2861 /* Must be before com::Shutdown() */
2862 LogFlow(("Uninitializing COM...\n"));
2863 com::Shutdown();
2864
2865 LogFlow(("Returning from main()!\n"));
2866 RTLogFlush(NULL);
2867
2868#ifdef RT_OS_WINDOWS
2869 FreeConsole(); /* Detach or destroy (from) console. */
2870#endif
2871
2872 return FAILED(hrc) ? 1 : 0;
2873}
2874
2875#ifndef VBOX_WITH_HARDENING
2876/**
2877 * Main entry point
2878 */
2879int main(int argc, char **argv)
2880{
2881#ifdef Q_WS_X11
2882 if (!XInitThreads())
2883 return 1;
2884#endif
2885 /*
2886 * Before we do *anything*, we initialize the runtime.
2887 */
2888 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB);
2889 if (RT_FAILURE(rc))
2890 return RTMsgInitFailure(rc);
2891
2892 return TrustedMain(argc, argv, NULL);
2893}
2894#endif /* !VBOX_WITH_HARDENING */
2895
2896
2897/**
2898 * Returns whether the absolute mouse is in use, i.e. both host
2899 * and guest have opted to enable it.
2900 *
2901 * @returns bool Flag whether the absolute mouse is in use
2902 */
2903static bool UseAbsoluteMouse(void)
2904{
2905 return (gfAbsoluteMouseHost && gfAbsoluteMouseGuest);
2906}
2907
2908
2909/**
2910 * Releases any modifier keys that are currently in pressed state.
2911 */
2912static void ResetKeys(void)
2913{
2914 int i;
2915
2916 if (!gpKeyboard)
2917 return;
2918
2919 for(i = 0; i < 256; i++)
2920 {
2921 if (gaModifiersState[i])
2922 {
2923 if (i & 0x80)
2924 gpKeyboard->PutScancode(0xe0);
2925 gpKeyboard->PutScancode(i | 0x80);
2926 gaModifiersState[i] = 0;
2927 }
2928 }
2929}
2930
2931/**
2932 * Keyboard event handler.
2933 *
2934 * @param ev SDL keyboard event.
2935 */
2936static void ProcessKey(SDL_KeyboardEvent *ev)
2937{
2938 /* According to SDL2/SDL_scancodes.h ev->keysym.sym stores scancodes which are
2939 * based on USB usage page standard. This is what we can directly pass to
2940 * IKeyboard::putUsageCode. */
2941 gpKeyboard->PutUsageCode(SDL_GetScancodeFromKey(ev->keysym.sym), 0x07 /*usage code page id*/, ev->type == SDL_KEYUP ? TRUE : FALSE);
2942}
2943
2944#ifdef RT_OS_DARWIN
2945#include <Carbon/Carbon.h>
2946RT_C_DECLS_BEGIN
2947/* Private interface in 10.3 and later. */
2948typedef int CGSConnection;
2949typedef enum
2950{
2951 kCGSGlobalHotKeyEnable = 0,
2952 kCGSGlobalHotKeyDisable,
2953 kCGSGlobalHotKeyInvalid = -1 /* bird */
2954} CGSGlobalHotKeyOperatingMode;
2955extern CGSConnection _CGSDefaultConnection(void);
2956extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode *enmMode);
2957extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode enmMode);
2958RT_C_DECLS_END
2959
2960/** Keeping track of whether we disabled the hotkeys or not. */
2961static bool g_fHotKeysDisabled = false;
2962/** Whether we've connected or not. */
2963static bool g_fConnectedToCGS = false;
2964/** Cached connection. */
2965static CGSConnection g_CGSConnection;
2966
2967/**
2968 * Disables or enabled global hot keys.
2969 */
2970static void DisableGlobalHotKeys(bool fDisable)
2971{
2972 if (!g_fConnectedToCGS)
2973 {
2974 g_CGSConnection = _CGSDefaultConnection();
2975 g_fConnectedToCGS = true;
2976 }
2977
2978 /* get current mode. */
2979 CGSGlobalHotKeyOperatingMode enmMode = kCGSGlobalHotKeyInvalid;
2980 CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmMode);
2981
2982 /* calc new mode. */
2983 if (fDisable)
2984 {
2985 if (enmMode != kCGSGlobalHotKeyEnable)
2986 return;
2987 enmMode = kCGSGlobalHotKeyDisable;
2988 }
2989 else
2990 {
2991 if ( enmMode != kCGSGlobalHotKeyDisable
2992 /*|| !g_fHotKeysDisabled*/)
2993 return;
2994 enmMode = kCGSGlobalHotKeyEnable;
2995 }
2996
2997 /* try set it and check the actual result. */
2998 CGSSetGlobalHotKeyOperatingMode(g_CGSConnection, enmMode);
2999 CGSGlobalHotKeyOperatingMode enmNewMode = kCGSGlobalHotKeyInvalid;
3000 CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmNewMode);
3001 if (enmNewMode == enmMode)
3002 g_fHotKeysDisabled = enmMode == kCGSGlobalHotKeyDisable;
3003}
3004#endif /* RT_OS_DARWIN */
3005
3006/**
3007 * Start grabbing the mouse.
3008 */
3009static void InputGrabStart(void)
3010{
3011#ifdef RT_OS_DARWIN
3012 DisableGlobalHotKeys(true);
3013#endif
3014 if (!gfGuestNeedsHostCursor && gfRelativeMouseGuest)
3015 SDL_ShowCursor(SDL_DISABLE);
3016 SDL_SetRelativeMouseMode(SDL_TRUE);
3017 gfGrabbed = TRUE;
3018 UpdateTitlebar(TITLEBAR_NORMAL);
3019}
3020
3021/**
3022 * End mouse grabbing.
3023 */
3024static void InputGrabEnd(void)
3025{
3026 SDL_SetRelativeMouseMode(SDL_FALSE);
3027 if (!gfGuestNeedsHostCursor && gfRelativeMouseGuest)
3028 SDL_ShowCursor(SDL_ENABLE);
3029#ifdef RT_OS_DARWIN
3030 DisableGlobalHotKeys(false);
3031#endif
3032 gfGrabbed = FALSE;
3033 UpdateTitlebar(TITLEBAR_NORMAL);
3034}
3035
3036/**
3037 * Query mouse position and button state from SDL and send to the VM
3038 *
3039 * @param dz Relative mouse wheel movement
3040 */
3041static void SendMouseEvent(VBoxSDLFB *fb, int dz, int down, int button)
3042{
3043 int x, y, state, buttons;
3044 bool abs;
3045
3046 if (!fb)
3047 {
3048 SDL_GetMouseState(&x, &y);
3049 RTPrintf("MouseEvent: Cannot find fb mouse = %d,%d\n", x, y);
3050 return;
3051 }
3052
3053 /*
3054 * If supported and we're not in grabbed mode, we'll use the absolute mouse.
3055 * If we are in grabbed mode and the guest is not able to draw the mouse cursor
3056 * itself, or can't handle relative reporting, we have to use absolute
3057 * coordinates, otherwise the host cursor and
3058 * the coordinates the guest thinks the mouse is at could get out-of-sync. From
3059 * the SDL mailing list:
3060 *
3061 * "The event processing is usually asynchronous and so somewhat delayed, and
3062 * SDL_GetMouseState is returning the immediate mouse state. So at the time you
3063 * call SDL_GetMouseState, the "button" is already up."
3064 */
3065 abs = (UseAbsoluteMouse() && !gfGrabbed)
3066 || gfGuestNeedsHostCursor
3067 || !gfRelativeMouseGuest;
3068
3069 /* only used if abs == TRUE */
3070 int xOrigin = fb->getOriginX();
3071 int yOrigin = fb->getOriginY();
3072 int xMin = fb->getXOffset() + xOrigin;
3073 int yMin = fb->getYOffset() + yOrigin;
3074 int xMax = xMin + (int)fb->getGuestXRes();
3075 int yMax = yMin + (int)fb->getGuestYRes();
3076
3077 state = abs ? SDL_GetMouseState(&x, &y)
3078 : SDL_GetRelativeMouseState(&x, &y);
3079
3080 /*
3081 * process buttons
3082 */
3083 buttons = 0;
3084 if (state & SDL_BUTTON(SDL_BUTTON_LEFT))
3085 buttons |= MouseButtonState_LeftButton;
3086 if (state & SDL_BUTTON(SDL_BUTTON_RIGHT))
3087 buttons |= MouseButtonState_RightButton;
3088 if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE))
3089 buttons |= MouseButtonState_MiddleButton;
3090
3091 if (abs)
3092 {
3093 x += xOrigin;
3094 y += yOrigin;
3095
3096 /*
3097 * Check if the mouse event is inside the guest area. This solves the
3098 * following problem: Some guests switch off the VBox hardware mouse
3099 * cursor and draw the mouse cursor itself instead. Moving the mouse
3100 * outside the guest area then leads to annoying mouse hangs if we
3101 * don't pass mouse motion events into the guest.
3102 */
3103 if (x < xMin || y < yMin || x > xMax || y > yMax)
3104 {
3105 /*
3106 * Cursor outside of valid guest area (outside window or in secure
3107 * label area. Don't allow any mouse button press.
3108 */
3109 button = 0;
3110
3111 /*
3112 * Release any pressed button.
3113 */
3114#if 0
3115 /* disabled on customers request */
3116 buttons &= ~(MouseButtonState_LeftButton |
3117 MouseButtonState_MiddleButton |
3118 MouseButtonState_RightButton);
3119#endif
3120
3121 /*
3122 * Prevent negative coordinates.
3123 */
3124 if (x < xMin) x = xMin;
3125 if (x > xMax) x = xMax;
3126 if (y < yMin) y = yMin;
3127 if (y > yMax) y = yMax;
3128
3129 if (!gpOffCursor)
3130 {
3131 gpOffCursor = SDL_GetCursor(); /* Cursor image */
3132 gfOffCursorActive = SDL_ShowCursor(-1); /* enabled / disabled */
3133 SDL_SetCursor(gpDefaultCursor);
3134 SDL_ShowCursor(SDL_ENABLE);
3135 }
3136 }
3137 else
3138 {
3139 if (gpOffCursor)
3140 {
3141 /*
3142 * We just entered the valid guest area. Restore the guest mouse
3143 * cursor.
3144 */
3145 SDL_SetCursor(gpOffCursor);
3146 SDL_ShowCursor(gfOffCursorActive ? SDL_ENABLE : SDL_DISABLE);
3147 gpOffCursor = NULL;
3148 }
3149 }
3150 }
3151
3152 /*
3153 * Button was pressed but that press is not reflected in the button state?
3154 */
3155 if (down && !(state & SDL_BUTTON(button)))
3156 {
3157 /*
3158 * It can happen that a mouse up event follows a mouse down event immediately
3159 * and we see the events when the bit in the button state is already cleared
3160 * again. In that case we simulate the mouse down event.
3161 */
3162 int tmp_button = 0;
3163 switch (button)
3164 {
3165 case SDL_BUTTON_LEFT: tmp_button = MouseButtonState_LeftButton; break;
3166 case SDL_BUTTON_MIDDLE: tmp_button = MouseButtonState_MiddleButton; break;
3167 case SDL_BUTTON_RIGHT: tmp_button = MouseButtonState_RightButton; break;
3168 }
3169
3170 if (abs)
3171 {
3172 /**
3173 * @todo
3174 * PutMouseEventAbsolute() expects x and y starting from 1,1.
3175 * should we do the increment internally in PutMouseEventAbsolute()
3176 * or state it in PutMouseEventAbsolute() docs?
3177 */
3178 gpMouse->PutMouseEventAbsolute(x + 1 - xMin + xOrigin,
3179 y + 1 - yMin + yOrigin,
3180 dz, 0 /* horizontal scroll wheel */,
3181 buttons | tmp_button);
3182 }
3183 else
3184 {
3185 gpMouse->PutMouseEvent(0, 0, dz,
3186 0 /* horizontal scroll wheel */,
3187 buttons | tmp_button);
3188 }
3189 }
3190
3191 // now send the mouse event
3192 if (abs)
3193 {
3194 /**
3195 * @todo
3196 * PutMouseEventAbsolute() expects x and y starting from 1,1.
3197 * should we do the increment internally in PutMouseEventAbsolute()
3198 * or state it in PutMouseEventAbsolute() docs?
3199 */
3200 gpMouse->PutMouseEventAbsolute(x + 1 - xMin + xOrigin,
3201 y + 1 - yMin + yOrigin,
3202 dz, 0 /* Horizontal wheel */, buttons);
3203 }
3204 else
3205 {
3206 gpMouse->PutMouseEvent(x, y, dz, 0 /* Horizontal wheel */, buttons);
3207 }
3208}
3209
3210/**
3211 * Resets the VM
3212 */
3213void ResetVM(void)
3214{
3215 if (gpConsole)
3216 gpConsole->Reset();
3217}
3218
3219/**
3220 * Initiates a saved state and updates the titlebar with progress information
3221 */
3222void SaveState(void)
3223{
3224 ResetKeys();
3225 RTThreadYield();
3226 if (gfGrabbed)
3227 InputGrabEnd();
3228 RTThreadYield();
3229 UpdateTitlebar(TITLEBAR_SAVE);
3230 gpProgress = NULL;
3231 HRESULT hrc = gpMachine->SaveState(gpProgress.asOutParam());
3232 if (FAILED(hrc))
3233 {
3234 RTPrintf("Error saving state! rc=%Rhrc\n", hrc);
3235 return;
3236 }
3237 Assert(gpProgress);
3238
3239 /*
3240 * Wait for the operation to be completed and work
3241 * the title bar in the mean while.
3242 */
3243 ULONG cPercent = 0;
3244#ifndef RT_OS_DARWIN /* don't break the other guys yet. */
3245 for (;;)
3246 {
3247 BOOL fCompleted = false;
3248 hrc = gpProgress->COMGETTER(Completed)(&fCompleted);
3249 if (FAILED(hrc) || fCompleted)
3250 break;
3251 ULONG cPercentNow;
3252 hrc = gpProgress->COMGETTER(Percent)(&cPercentNow);
3253 if (FAILED(hrc))
3254 break;
3255 if (cPercentNow != cPercent)
3256 {
3257 UpdateTitlebar(TITLEBAR_SAVE, cPercent);
3258 cPercent = cPercentNow;
3259 }
3260
3261 /* wait */
3262 hrc = gpProgress->WaitForCompletion(100);
3263 if (FAILED(hrc))
3264 break;
3265 /// @todo process gui events.
3266 }
3267
3268#else /* new loop which processes GUI events while saving. */
3269
3270 /* start regular timer so we don't starve in the event loop */
3271 SDL_TimerID sdlTimer;
3272 sdlTimer = SDL_AddTimer(100, StartupTimer, NULL);
3273
3274 for (;;)
3275 {
3276 /*
3277 * Check for completion.
3278 */
3279 BOOL fCompleted = false;
3280 hrc = gpProgress->COMGETTER(Completed)(&fCompleted);
3281 if (FAILED(hrc) || fCompleted)
3282 break;
3283 ULONG cPercentNow;
3284 hrc = gpProgress->COMGETTER(Percent)(&cPercentNow);
3285 if (FAILED(hrc))
3286 break;
3287 if (cPercentNow != cPercent)
3288 {
3289 UpdateTitlebar(TITLEBAR_SAVE, cPercent);
3290 cPercent = cPercentNow;
3291 }
3292
3293 /*
3294 * Wait for and process GUI a event.
3295 * This is necessary for XPCOM IPC and for updating the
3296 * title bar on the Mac.
3297 */
3298 SDL_Event event;
3299 if (WaitSDLEvent(&event))
3300 {
3301 switch (event.type)
3302 {
3303 /*
3304 * Timer event preventing us from getting stuck.
3305 */
3306 case SDL_USER_EVENT_TIMER:
3307 break;
3308
3309#ifdef USE_XPCOM_QUEUE_THREAD
3310 /*
3311 * User specific XPCOM event queue event
3312 */
3313 case SDL_USER_EVENT_XPCOM_EVENTQUEUE:
3314 {
3315 LogFlow(("SDL_USER_EVENT_XPCOM_EVENTQUEUE: processing XPCOM event queue...\n"));
3316 eventQ->ProcessPendingEvents();
3317 signalXPCOMEventQueueThread();
3318 break;
3319 }
3320#endif /* USE_XPCOM_QUEUE_THREAD */
3321
3322
3323 /*
3324 * Ignore all other events.
3325 */
3326 case SDL_USER_EVENT_NOTIFYCHANGE:
3327 case SDL_USER_EVENT_TERMINATE:
3328 default:
3329 break;
3330 }
3331 }
3332 }
3333
3334 /* kill the timer */
3335 SDL_RemoveTimer(sdlTimer);
3336 sdlTimer = 0;
3337
3338#endif /* RT_OS_DARWIN */
3339
3340 /*
3341 * What's the result of the operation?
3342 */
3343 LONG lrc;
3344 hrc = gpProgress->COMGETTER(ResultCode)(&lrc);
3345 if (FAILED(hrc))
3346 lrc = ~0;
3347 if (!lrc)
3348 {
3349 UpdateTitlebar(TITLEBAR_SAVE, 100);
3350 RTThreadYield();
3351 RTPrintf("Saved the state successfully.\n");
3352 }
3353 else
3354 RTPrintf("Error saving state, lrc=%d (%#x)\n", lrc, lrc);
3355}
3356
3357/**
3358 * Build the titlebar string
3359 */
3360static void UpdateTitlebar(TitlebarMode mode, uint32_t u32User)
3361{
3362 static char szTitle[1024] = {0};
3363
3364 /* back up current title */
3365 char szPrevTitle[1024];
3366 strcpy(szPrevTitle, szTitle);
3367
3368 Bstr bstrName;
3369 gpMachine->COMGETTER(Name)(bstrName.asOutParam());
3370
3371 RTStrPrintf(szTitle, sizeof(szTitle), "%s - " VBOX_PRODUCT,
3372 !bstrName.isEmpty() ? Utf8Str(bstrName).c_str() : "<noname>");
3373
3374 /* which mode are we in? */
3375 switch (mode)
3376 {
3377 case TITLEBAR_NORMAL:
3378 {
3379 MachineState_T machineState;
3380 gpMachine->COMGETTER(State)(&machineState);
3381 if (machineState == MachineState_Paused)
3382 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), " - [Paused]");
3383
3384 if (gfGrabbed)
3385 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle), " - [Input captured]");
3386
3387#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
3388 // do we have a debugger interface
3389 if (gpMachineDebugger)
3390 {
3391 // query the machine state
3392 BOOL singlestepEnabled = FALSE;
3393 BOOL logEnabled = FALSE;
3394 VMExecutionEngine_T enmExecEngine = VMExecutionEngine_NotSet;
3395 ULONG virtualTimeRate = 100;
3396 gpMachineDebugger->COMGETTER(LogEnabled)(&logEnabled);
3397 gpMachineDebugger->COMGETTER(SingleStep)(&singlestepEnabled);
3398 gpMachineDebugger->COMGETTER(ExecutionEngine)(&enmExecEngine);
3399 gpMachineDebugger->COMGETTER(VirtualTimeRate)(&virtualTimeRate);
3400 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3401 " [STEP=%d LOG=%d EXEC=%s",
3402 singlestepEnabled == TRUE, logEnabled == TRUE,
3403 enmExecEngine == VMExecutionEngine_NotSet ? "NotSet"
3404 : enmExecEngine == VMExecutionEngine_Default ? "Default"
3405 : enmExecEngine == VMExecutionEngine_HwVirt ? "HM"
3406 : enmExecEngine == VMExecutionEngine_NativeApi ? "NEM"
3407 : enmExecEngine == VMExecutionEngine_Interpreter ? "Interpreter"
3408 : enmExecEngine == VMExecutionEngine_Recompiler ? "Recompiler" : "UNK");
3409 char *psz = strchr(szTitle, '\0');
3410 if (virtualTimeRate != 100)
3411 RTStrPrintf(psz, &szTitle[sizeof(szTitle)] - psz, " WD=%d%%]", virtualTimeRate);
3412 else
3413 RTStrPrintf(psz, &szTitle[sizeof(szTitle)] - psz, "]");
3414 }
3415#endif /* DEBUG || VBOX_WITH_STATISTICS */
3416 break;
3417 }
3418
3419 case TITLEBAR_STARTUP:
3420 {
3421 /*
3422 * Format it.
3423 */
3424 MachineState_T machineState;
3425 gpMachine->COMGETTER(State)(&machineState);
3426 if (machineState == MachineState_Starting)
3427 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3428 " - Starting...");
3429 else if (machineState == MachineState_Restoring)
3430 {
3431 ULONG cPercentNow;
3432 HRESULT hrc = gpProgress->COMGETTER(Percent)(&cPercentNow);
3433 if (SUCCEEDED(hrc))
3434 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3435 " - Restoring %d%%...", (int)cPercentNow);
3436 else
3437 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3438 " - Restoring...");
3439 }
3440 else if (machineState == MachineState_TeleportingIn)
3441 {
3442 ULONG cPercentNow;
3443 HRESULT hrc = gpProgress->COMGETTER(Percent)(&cPercentNow);
3444 if (SUCCEEDED(hrc))
3445 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3446 " - Teleporting %d%%...", (int)cPercentNow);
3447 else
3448 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3449 " - Teleporting...");
3450 }
3451 /* ignore other states, we could already be in running or aborted state */
3452 break;
3453 }
3454
3455 case TITLEBAR_SAVE:
3456 {
3457 AssertMsg(u32User <= 100, ("%d\n", u32User));
3458 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3459 " - Saving %d%%...", u32User);
3460 break;
3461 }
3462
3463 case TITLEBAR_SNAPSHOT:
3464 {
3465 AssertMsg(u32User <= 100, ("%d\n", u32User));
3466 RTStrPrintf(szTitle + strlen(szTitle), sizeof(szTitle) - strlen(szTitle),
3467 " - Taking snapshot %d%%...", u32User);
3468 break;
3469 }
3470
3471 default:
3472 RTPrintf("Error: Invalid title bar mode %d!\n", mode);
3473 return;
3474 }
3475
3476 /*
3477 * Don't update if it didn't change.
3478 */
3479 if (!strcmp(szTitle, szPrevTitle))
3480 return;
3481
3482 /*
3483 * Set the new title
3484 */
3485#ifdef VBOX_WIN32_UI
3486 setUITitle(szTitle);
3487#else
3488 for (unsigned i = 0; i < gcMonitors; i++)
3489 gpFramebuffer[i]->setWindowTitle(szTitle);
3490#endif
3491}
3492
3493#if 0
3494static void vbox_show_shape(unsigned short w, unsigned short h,
3495 uint32_t bg, const uint8_t *image)
3496{
3497 size_t x, y;
3498 unsigned short pitch;
3499 const uint32_t *color;
3500 const uint8_t *mask;
3501 size_t size_mask;
3502
3503 mask = image;
3504 pitch = (w + 7) / 8;
3505 size_mask = (pitch * h + 3) & ~3;
3506
3507 color = (const uint32_t *)(image + size_mask);
3508
3509 printf("show_shape %dx%d pitch %d size mask %d\n",
3510 w, h, pitch, size_mask);
3511 for (y = 0; y < h; ++y, mask += pitch, color += w)
3512 {
3513 for (x = 0; x < w; ++x) {
3514 if (mask[x / 8] & (1 << (7 - (x % 8))))
3515 printf(" ");
3516 else
3517 {
3518 uint32_t c = color[x];
3519 if (c == bg)
3520 printf("Y");
3521 else
3522 printf("X");
3523 }
3524 }
3525 printf("\n");
3526 }
3527}
3528#endif
3529
3530/**
3531 * Sets the pointer shape according to parameters.
3532 * Must be called only from the main SDL thread.
3533 */
3534static void SetPointerShape(const PointerShapeChangeData *data)
3535{
3536 /*
3537 * don't allow to change the pointer shape if we are outside the valid
3538 * guest area. In that case set standard mouse pointer is set and should
3539 * not get overridden.
3540 */
3541 if (gpOffCursor)
3542 return;
3543
3544 if (data->shape.size() > 0)
3545 {
3546 bool ok = false;
3547
3548#if defined(RT_OS_WINDOWS) || (defined(VBOXSDL_WITH_X11) && !defined(VBOX_WITHOUT_XCURSOR))
3549 AssertReturnVoid(data->height); /* Prevent division by zero. */
3550 uint32_t const andMaskSize = (data->width + 7) / 8 * data->height;
3551 uint32_t const srcShapePtrScan = data->width * 4;
3552
3553 uint8_t const *shape = data->shape.raw();
3554 uint8_t const *srcAndMaskPtr = shape;
3555 uint8_t const *srcShapePtr = shape + ((andMaskSize + 3) & ~3);
3556#endif
3557
3558#if 0
3559 /* pointer debugging code */
3560 // vbox_show_shape(data->width, data->height, 0, data->shape);
3561 uint32_t shapeSize = ((((data->width + 7) / 8) * data->height + 3) & ~3) + data->width * 4 * data->height;
3562 printf("visible: %d\n", data->visible);
3563 printf("width = %d\n", data->width);
3564 printf("height = %d\n", data->height);
3565 printf("alpha = %d\n", data->alpha);
3566 printf("xhot = %d\n", data->xHot);
3567 printf("yhot = %d\n", data->yHot);
3568 printf("uint8_t pointerdata[] = { ");
3569 for (uint32_t i = 0; i < shapeSize; i++)
3570 {
3571 printf("0x%x, ", data->shape[i]);
3572 }
3573 printf("};\n");
3574#endif
3575
3576#if defined(RT_OS_WINDOWS)
3577 BITMAPV5HEADER bi;
3578 HBITMAP hBitmap;
3579 void *lpBits;
3580
3581 ::ZeroMemory(&bi, sizeof(BITMAPV5HEADER));
3582 bi.bV5Size = sizeof(BITMAPV5HEADER);
3583 bi.bV5Width = data->width;
3584 bi.bV5Height = -(LONG)data->height;
3585 bi.bV5Planes = 1;
3586 bi.bV5BitCount = 32;
3587 bi.bV5Compression = BI_BITFIELDS;
3588 // specify a supported 32 BPP alpha format for Windows XP
3589 bi.bV5RedMask = 0x00FF0000;
3590 bi.bV5GreenMask = 0x0000FF00;
3591 bi.bV5BlueMask = 0x000000FF;
3592 if (data->alpha)
3593 bi.bV5AlphaMask = 0xFF000000;
3594 else
3595 bi.bV5AlphaMask = 0;
3596
3597 HDC hdc = ::GetDC(NULL);
3598
3599 // create the DIB section with an alpha channel
3600 hBitmap = ::CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS,
3601 (void **)&lpBits, NULL, (DWORD)0);
3602
3603 ::ReleaseDC(NULL, hdc);
3604
3605 HBITMAP hMonoBitmap = NULL;
3606 if (data->alpha)
3607 {
3608 // create an empty mask bitmap
3609 hMonoBitmap = ::CreateBitmap(data->width, data->height, 1, 1, NULL);
3610 }
3611 else
3612 {
3613 /* Word aligned AND mask. Will be allocated and created if necessary. */
3614 uint8_t *pu8AndMaskWordAligned = NULL;
3615
3616 /* Width in bytes of the original AND mask scan line. */
3617 uint32_t cbAndMaskScan = (data->width + 7) / 8;
3618
3619 if (cbAndMaskScan & 1)
3620 {
3621 /* Original AND mask is not word aligned. */
3622
3623 /* Allocate memory for aligned AND mask. */
3624 pu8AndMaskWordAligned = (uint8_t *)RTMemTmpAllocZ((cbAndMaskScan + 1) * data->height);
3625
3626 Assert(pu8AndMaskWordAligned);
3627
3628 if (pu8AndMaskWordAligned)
3629 {
3630 /* According to MSDN the padding bits must be 0.
3631 * Compute the bit mask to set padding bits to 0 in the last byte of original AND mask.
3632 */
3633 uint32_t u32PaddingBits = cbAndMaskScan * 8 - data->width;
3634 Assert(u32PaddingBits < 8);
3635 uint8_t u8LastBytesPaddingMask = (uint8_t)(0xFF << u32PaddingBits);
3636
3637 Log(("u8LastBytesPaddingMask = %02X, aligned w = %d, width = %d, cbAndMaskScan = %d\n",
3638 u8LastBytesPaddingMask, (cbAndMaskScan + 1) * 8, data->width, cbAndMaskScan));
3639
3640 uint8_t *src = (uint8_t *)srcAndMaskPtr;
3641 uint8_t *dst = pu8AndMaskWordAligned;
3642
3643 unsigned i;
3644 for (i = 0; i < data->height; i++)
3645 {
3646 memcpy(dst, src, cbAndMaskScan);
3647
3648 dst[cbAndMaskScan - 1] &= u8LastBytesPaddingMask;
3649
3650 src += cbAndMaskScan;
3651 dst += cbAndMaskScan + 1;
3652 }
3653 }
3654 }
3655
3656 // create the AND mask bitmap
3657 hMonoBitmap = ::CreateBitmap(data->width, data->height, 1, 1,
3658 pu8AndMaskWordAligned? pu8AndMaskWordAligned: srcAndMaskPtr);
3659
3660 if (pu8AndMaskWordAligned)
3661 {
3662 RTMemTmpFree(pu8AndMaskWordAligned);
3663 }
3664 }
3665
3666 Assert(hBitmap);
3667 Assert(hMonoBitmap);
3668 if (hBitmap && hMonoBitmap)
3669 {
3670 DWORD *dstShapePtr = (DWORD *)lpBits;
3671
3672 for (uint32_t y = 0; y < data->height; y ++)
3673 {
3674 memcpy(dstShapePtr, srcShapePtr, srcShapePtrScan);
3675 srcShapePtr += srcShapePtrScan;
3676 dstShapePtr += data->width;
3677 }
3678 }
3679
3680 if (hMonoBitmap)
3681 ::DeleteObject(hMonoBitmap);
3682 if (hBitmap)
3683 ::DeleteObject(hBitmap);
3684
3685#elif defined(VBOXSDL_WITH_X11) && !defined(VBOX_WITHOUT_XCURSOR)
3686
3687 if (gfXCursorEnabled)
3688 {
3689 XcursorImage *img = XcursorImageCreate(data->width, data->height);
3690 Assert(img);
3691 if (img)
3692 {
3693 img->xhot = data->xHot;
3694 img->yhot = data->yHot;
3695
3696 XcursorPixel *dstShapePtr = img->pixels;
3697
3698 for (uint32_t y = 0; y < data->height; y ++)
3699 {
3700 memcpy(dstShapePtr, srcShapePtr, srcShapePtrScan);
3701
3702 if (!data->alpha)
3703 {
3704 // convert AND mask to the alpha channel
3705 uint8_t byte = 0;
3706 for (uint32_t x = 0; x < data->width; x ++)
3707 {
3708 if (!(x % 8))
3709 byte = *(srcAndMaskPtr ++);
3710 else
3711 byte <<= 1;
3712
3713 if (byte & 0x80)
3714 {
3715 // Linux doesn't support inverted pixels (XOR ops,
3716 // to be exact) in cursor shapes, so we detect such
3717 // pixels and always replace them with black ones to
3718 // make them visible at least over light colors
3719 if (dstShapePtr [x] & 0x00FFFFFF)
3720 dstShapePtr [x] = 0xFF000000;
3721 else
3722 dstShapePtr [x] = 0x00000000;
3723 }
3724 else
3725 dstShapePtr [x] |= 0xFF000000;
3726 }
3727 }
3728
3729 srcShapePtr += srcShapePtrScan;
3730 dstShapePtr += data->width;
3731 }
3732 }
3733 XcursorImageDestroy(img);
3734 }
3735
3736#endif /* VBOXSDL_WITH_X11 && !VBOX_WITHOUT_XCURSOR */
3737
3738 if (!ok)
3739 {
3740 SDL_SetCursor(gpDefaultCursor);
3741 SDL_ShowCursor(SDL_ENABLE);
3742 }
3743 }
3744 else
3745 {
3746 if (data->visible)
3747 SDL_ShowCursor(SDL_ENABLE);
3748 else if (gfAbsoluteMouseGuest)
3749 /* Don't disable the cursor if the guest additions are not active (anymore) */
3750 SDL_ShowCursor(SDL_DISABLE);
3751 }
3752}
3753
3754/**
3755 * Handle changed mouse capabilities
3756 */
3757static void HandleGuestCapsChanged(void)
3758{
3759 if (!gfAbsoluteMouseGuest)
3760 {
3761 // Cursor could be overwritten by the guest tools
3762 SDL_SetCursor(gpDefaultCursor);
3763 SDL_ShowCursor(SDL_ENABLE);
3764 gpOffCursor = NULL;
3765 }
3766 if (gpMouse && UseAbsoluteMouse())
3767 {
3768 // Actually switch to absolute coordinates
3769 if (gfGrabbed)
3770 InputGrabEnd();
3771 gpMouse->PutMouseEventAbsolute(-1, -1, 0, 0, 0);
3772 }
3773}
3774
3775/**
3776 * Handles a host key down event
3777 */
3778static int HandleHostKey(const SDL_KeyboardEvent *pEv)
3779{
3780 /*
3781 * Revalidate the host key modifier
3782 */
3783 if ((SDL_GetModState() & ~(KMOD_MODE | KMOD_NUM | KMOD_RESERVED)) != gHostKeyMod)
3784 return VERR_NOT_SUPPORTED;
3785
3786 /*
3787 * What was pressed?
3788 */
3789 switch (pEv->keysym.sym)
3790 {
3791 /* Control-Alt-Delete */
3792 case SDLK_DELETE:
3793 {
3794 gpKeyboard->PutCAD();
3795 break;
3796 }
3797
3798 /*
3799 * Fullscreen / Windowed toggle.
3800 */
3801 case SDLK_f:
3802 {
3803 if ( strchr(gHostKeyDisabledCombinations, 'f')
3804 || !gfAllowFullscreenToggle)
3805 return VERR_NOT_SUPPORTED;
3806
3807 /*
3808 * We have to pause/resume the machine during this
3809 * process because there might be a short moment
3810 * without a valid framebuffer
3811 */
3812 MachineState_T machineState;
3813 gpMachine->COMGETTER(State)(&machineState);
3814 bool fPauseIt = machineState == MachineState_Running
3815 || machineState == MachineState_Teleporting
3816 || machineState == MachineState_LiveSnapshotting;
3817 if (fPauseIt)
3818 gpConsole->Pause();
3819 SetFullscreen(!gpFramebuffer[0]->getFullscreen());
3820 if (fPauseIt)
3821 gpConsole->Resume();
3822
3823 /*
3824 * We have switched from/to fullscreen, so request a full
3825 * screen repaint, just to be sure.
3826 */
3827 gpDisplay->InvalidateAndUpdate();
3828 break;
3829 }
3830
3831 /*
3832 * Pause / Resume toggle.
3833 */
3834 case SDLK_p:
3835 {
3836 if (strchr(gHostKeyDisabledCombinations, 'p'))
3837 return VERR_NOT_SUPPORTED;
3838
3839 MachineState_T machineState;
3840 gpMachine->COMGETTER(State)(&machineState);
3841 if ( machineState == MachineState_Running
3842 || machineState == MachineState_Teleporting
3843 || machineState == MachineState_LiveSnapshotting
3844 )
3845 {
3846 if (gfGrabbed)
3847 InputGrabEnd();
3848 gpConsole->Pause();
3849 }
3850 else if (machineState == MachineState_Paused)
3851 {
3852 gpConsole->Resume();
3853 }
3854 UpdateTitlebar(TITLEBAR_NORMAL);
3855 break;
3856 }
3857
3858 /*
3859 * Reset the VM
3860 */
3861 case SDLK_r:
3862 {
3863 if (strchr(gHostKeyDisabledCombinations, 'r'))
3864 return VERR_NOT_SUPPORTED;
3865
3866 ResetVM();
3867 break;
3868 }
3869
3870 /*
3871 * Terminate the VM
3872 */
3873 case SDLK_q:
3874 {
3875 if (strchr(gHostKeyDisabledCombinations, 'q'))
3876 return VERR_NOT_SUPPORTED;
3877
3878 return VINF_EM_TERMINATE;
3879 }
3880
3881 /*
3882 * Save the machine's state and exit
3883 */
3884 case SDLK_s:
3885 {
3886 if (strchr(gHostKeyDisabledCombinations, 's'))
3887 return VERR_NOT_SUPPORTED;
3888
3889 SaveState();
3890 return VINF_EM_TERMINATE;
3891 }
3892
3893 case SDLK_h:
3894 {
3895 if (strchr(gHostKeyDisabledCombinations, 'h'))
3896 return VERR_NOT_SUPPORTED;
3897
3898 if (gpConsole)
3899 gpConsole->PowerButton();
3900 break;
3901 }
3902
3903 /*
3904 * Perform an online snapshot. Continue operation.
3905 */
3906 case SDLK_n:
3907 {
3908 if (strchr(gHostKeyDisabledCombinations, 'n'))
3909 return VERR_NOT_SUPPORTED;
3910
3911 RTThreadYield();
3912 ULONG cSnapshots = 0;
3913 gpMachine->COMGETTER(SnapshotCount)(&cSnapshots);
3914 char pszSnapshotName[20];
3915 RTStrPrintf(pszSnapshotName, sizeof(pszSnapshotName), "Snapshot %d", cSnapshots + 1);
3916 gpProgress = NULL;
3917 HRESULT hrc;
3918 Bstr snapId;
3919 CHECK_ERROR(gpMachine, TakeSnapshot(Bstr(pszSnapshotName).raw(),
3920 Bstr("Taken by VBoxSDL").raw(),
3921 TRUE, snapId.asOutParam(),
3922 gpProgress.asOutParam()));
3923 if (FAILED(hrc))
3924 {
3925 RTPrintf("Error taking snapshot! rc=%Rhrc\n", hrc);
3926 /* continue operation */
3927 return VINF_SUCCESS;
3928 }
3929 /*
3930 * Wait for the operation to be completed and work
3931 * the title bar in the mean while.
3932 */
3933 ULONG cPercent = 0;
3934 for (;;)
3935 {
3936 BOOL fCompleted = false;
3937 hrc = gpProgress->COMGETTER(Completed)(&fCompleted);
3938 if (FAILED(hrc) || fCompleted)
3939 break;
3940 ULONG cPercentNow;
3941 hrc = gpProgress->COMGETTER(Percent)(&cPercentNow);
3942 if (FAILED(hrc))
3943 break;
3944 if (cPercentNow != cPercent)
3945 {
3946 UpdateTitlebar(TITLEBAR_SNAPSHOT, cPercent);
3947 cPercent = cPercentNow;
3948 }
3949
3950 /* wait */
3951 hrc = gpProgress->WaitForCompletion(100);
3952 if (FAILED(hrc))
3953 break;
3954 /// @todo process gui events.
3955 }
3956
3957 /* continue operation */
3958 return VINF_SUCCESS;
3959 }
3960
3961 case SDLK_F1: case SDLK_F2: case SDLK_F3:
3962 case SDLK_F4: case SDLK_F5: case SDLK_F6:
3963 case SDLK_F7: case SDLK_F8: case SDLK_F9:
3964 case SDLK_F10: case SDLK_F11: case SDLK_F12:
3965 {
3966 /* send Ctrl-Alt-Fx to guest */
3967 gpKeyboard->PutUsageCode(0xE0 /*left ctrl*/, 0x07 /*usage code page id*/, FALSE);
3968 gpKeyboard->PutUsageCode(0xE2 /*left alt*/, 0x07 /*usage code page id*/, FALSE);
3969 gpKeyboard->PutUsageCode(pEv->keysym.sym, 0x07 /*usage code page id*/, FALSE);
3970 gpKeyboard->PutUsageCode(pEv->keysym.sym, 0x07 /*usage code page id*/, TRUE);
3971 gpKeyboard->PutUsageCode(0xE0 /*left ctrl*/, 0x07 /*usage code page id*/, TRUE);
3972 gpKeyboard->PutUsageCode(0xE2 /*left alt*/, 0x07 /*usage code page id*/, TRUE);
3973 return VINF_SUCCESS;
3974 }
3975
3976 /*
3977 * Not a host key combination.
3978 * Indicate this by returning false.
3979 */
3980 default:
3981 return VERR_NOT_SUPPORTED;
3982 }
3983
3984 return VINF_SUCCESS;
3985}
3986
3987/**
3988 * Timer callback function for startup processing
3989 */
3990static Uint32 StartupTimer(Uint32 interval, void *param) RT_NOTHROW_DEF
3991{
3992 RT_NOREF(param);
3993
3994 /* post message so we can do something in the startup loop */
3995 SDL_Event event = {0};
3996 event.type = SDL_USEREVENT;
3997 event.user.type = SDL_USER_EVENT_TIMER;
3998 SDL_PushEvent(&event);
3999 RTSemEventSignal(g_EventSemSDLEvents);
4000 return interval;
4001}
4002
4003/**
4004 * Timer callback function to check if resizing is finished
4005 */
4006static Uint32 ResizeTimer(Uint32 interval, void *param) RT_NOTHROW_DEF
4007{
4008 RT_NOREF(interval, param);
4009
4010 /* post message so the window is actually resized */
4011 SDL_Event event = {0};
4012 event.type = SDL_USEREVENT;
4013 event.user.type = SDL_USER_EVENT_WINDOW_RESIZE_DONE;
4014 PushSDLEventForSure(&event);
4015 /* one-shot */
4016 return 0;
4017}
4018
4019/**
4020 * Timer callback function to check if an ACPI power button event was handled by the guest.
4021 */
4022static Uint32 QuitTimer(Uint32 interval, void *param) RT_NOTHROW_DEF
4023{
4024 RT_NOREF(interval, param);
4025
4026 BOOL fHandled = FALSE;
4027
4028 gSdlQuitTimer = 0;
4029 if (gpConsole)
4030 {
4031 int rc = gpConsole->GetPowerButtonHandled(&fHandled);
4032 LogRel(("QuitTimer: rc=%d handled=%d\n", rc, fHandled));
4033 if (RT_FAILURE(rc) || !fHandled)
4034 {
4035 /* event was not handled, power down the guest */
4036 gfACPITerm = FALSE;
4037 SDL_Event event = {0};
4038 event.type = SDL_QUIT;
4039 PushSDLEventForSure(&event);
4040 }
4041 }
4042 /* one-shot */
4043 return 0;
4044}
4045
4046/**
4047 * Wait for the next SDL event. Don't use SDL_WaitEvent since this function
4048 * calls SDL_Delay(10) if the event queue is empty.
4049 */
4050static int WaitSDLEvent(SDL_Event *event)
4051{
4052 for (;;)
4053 {
4054 int rc = SDL_PollEvent(event);
4055 if (rc == 1)
4056 {
4057#ifdef USE_XPCOM_QUEUE_THREAD
4058 if (event->type == SDL_USER_EVENT_XPCOM_EVENTQUEUE)
4059 consumedXPCOMUserEvent();
4060#endif
4061 return 1;
4062 }
4063 /* Immediately wake up if new SDL events are available. This does not
4064 * work for internal SDL events. Don't wait more than 10ms. */
4065 RTSemEventWait(g_EventSemSDLEvents, 10);
4066 }
4067}
4068
4069/**
4070 * Ensure that an SDL event is really enqueued. Try multiple times if necessary.
4071 */
4072int PushSDLEventForSure(SDL_Event *event)
4073{
4074 int ntries = 10;
4075 for (; ntries > 0; ntries--)
4076 {
4077 int rc = SDL_PushEvent(event);
4078 RTSemEventSignal(g_EventSemSDLEvents);
4079 if (rc == 1)
4080 return 0;
4081 Log(("PushSDLEventForSure: waiting for 2ms (rc = %d)\n", rc));
4082 RTThreadSleep(2);
4083 }
4084 LogRel(("WARNING: Failed to enqueue SDL event %d.%d!\n",
4085 event->type, event->type == SDL_USEREVENT ? event->user.type : 0));
4086 return -1;
4087}
4088
4089#if defined(VBOXSDL_WITH_X11) || defined(RT_OS_DARWIN)
4090/**
4091 * Special SDL_PushEvent function for NotifyUpdate events. These events may occur in bursts
4092 * so make sure they don't flood the SDL event queue.
4093 */
4094void PushNotifyUpdateEvent(SDL_Event *event)
4095{
4096 int rc = SDL_PushEvent(event);
4097 bool fSuccess = (rc == 1);
4098
4099 RTSemEventSignal(g_EventSemSDLEvents);
4100 AssertMsg(fSuccess, ("SDL_PushEvent returned SDL error\n"));
4101 /* A global counter is faster than SDL_PeepEvents() */
4102 if (fSuccess)
4103 ASMAtomicIncS32(&g_cNotifyUpdateEventsPending);
4104 /* In order to not flood the SDL event queue, yield the CPU or (if there are already many
4105 * events queued) even sleep */
4106 if (g_cNotifyUpdateEventsPending > 96)
4107 {
4108 /* Too many NotifyUpdate events, sleep for a small amount to give the main thread time
4109 * to handle these events. The SDL queue can hold up to 128 events. */
4110 Log(("PushNotifyUpdateEvent: Sleep 1ms\n"));
4111 RTThreadSleep(1);
4112 }
4113 else
4114 RTThreadYield();
4115}
4116#endif /* VBOXSDL_WITH_X11 */
4117
4118/**
4119 *
4120 */
4121static void SetFullscreen(bool enable)
4122{
4123 if (enable == gpFramebuffer[0]->getFullscreen())
4124 return;
4125
4126 if (!gfFullscreenResize)
4127 {
4128 /*
4129 * The old/default way: SDL will resize the host to fit the guest screen resolution.
4130 */
4131 gpFramebuffer[0]->setFullscreen(enable);
4132 }
4133 else
4134 {
4135 /*
4136 * The alternate way: Switch to fullscreen with the host screen resolution and adapt
4137 * the guest screen resolution to the host window geometry.
4138 */
4139 uint32_t NewWidth = 0, NewHeight = 0;
4140 if (enable)
4141 {
4142 /* switch to fullscreen */
4143 gmGuestNormalXRes = gpFramebuffer[0]->getGuestXRes();
4144 gmGuestNormalYRes = gpFramebuffer[0]->getGuestYRes();
4145 gpFramebuffer[0]->getFullscreenGeometry(&NewWidth, &NewHeight);
4146 }
4147 else
4148 {
4149 /* switch back to saved geometry */
4150 NewWidth = gmGuestNormalXRes;
4151 NewHeight = gmGuestNormalYRes;
4152 }
4153 if (NewWidth != 0 && NewHeight != 0)
4154 {
4155 gpFramebuffer[0]->setFullscreen(enable);
4156 gfIgnoreNextResize = TRUE;
4157 gpDisplay->SetVideoModeHint(0 /*=display*/, true /*=enabled*/,
4158 false /*=changeOrigin*/, 0 /*=originX*/, 0 /*=originY*/,
4159 NewWidth, NewHeight, 0 /*don't change bpp*/, true /*=notify*/);
4160 }
4161 }
4162}
4163
4164static VBoxSDLFB *getFbFromWinId(Uint32 id)
4165{
4166 for (unsigned i = 0; i < gcMonitors; i++)
4167 if (gpFramebuffer[i]->hasWindow(id))
4168 return gpFramebuffer[i];
4169
4170 return NULL;
4171}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette