VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/runtime/UIMachineView.cpp@ 100064

Last change on this file since 100064 was 100064, checked in by vboxsync, 13 months ago

FE/Qt: bugref:10421. Replacing VBOX_WS_X11 with VBOX_WS_NIX.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 79.2 KB
Line 
1/* $Id: UIMachineView.cpp 100064 2023-06-04 09:10:01Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMachineView class implementation.
4 */
5
6/*
7 * Copyright (C) 2010-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QBitmap>
30#include <QMainWindow>
31#include <QPainter>
32#include <QScrollBar>
33#include <QTimer>
34#include <QAbstractNativeEventFilter>
35
36/* GUI includes: */
37#include "UICommon.h"
38#include "UIActionPoolRuntime.h"
39#include "UIDesktopWidgetWatchdog.h"
40#include "UIExtraDataManager.h"
41#include "UIMachine.h"
42#include "UIMessageCenter.h"
43#include "UIMachineLogic.h"
44#include "UIMachineWindow.h"
45#include "UIMachineViewNormal.h"
46#include "UIMachineViewFullscreen.h"
47#include "UIMachineViewSeamless.h"
48#include "UIMachineViewScale.h"
49#include "UINotificationCenter.h"
50#include "UIKeyboardHandler.h"
51#include "UIMouseHandler.h"
52#include "UIFrameBuffer.h"
53#ifdef VBOX_WS_MAC
54# include "UICocoaApplication.h"
55# include "DarwinKeyboard.h"
56# include "DockIconPreview.h"
57#endif
58#ifdef VBOX_WITH_DRAG_AND_DROP
59# include "UIDnDHandler.h"
60#endif
61
62/* VirtualBox interface declarations: */
63#include <VBox/com/VirtualBox.h>
64
65/* COM includes: */
66#include "CGraphicsAdapter.h"
67#include "CSession.h"
68#include "CFramebuffer.h"
69#ifdef VBOX_WITH_DRAG_AND_DROP
70# include "CDnDSource.h"
71# include "CDnDTarget.h"
72# include "CGuest.h"
73# include "CGuestDnDSource.h"
74# include "CGuestDnDTarget.h"
75#endif
76
77/* Other VBox includes: */
78#include <VBox/VBoxOGL.h>
79#include <VBoxVideo.h>
80#include <iprt/asm.h>
81#include <iprt/errcore.h>
82
83/* External includes: */
84#include <math.h>
85#ifdef VBOX_WITH_DRAG_AND_DROP
86# include <new> /* For bad_alloc. */
87#endif
88#ifdef VBOX_WS_MAC
89# include <Carbon/Carbon.h>
90#endif
91#ifdef VBOX_WS_NIX
92# include <xcb/xcb.h>
93#endif
94
95#ifdef DEBUG_andy
96/* Macro for debugging drag and drop actions which usually would
97 * go to Main's logging group. */
98# define DNDDEBUG(x) LogFlowFunc(x)
99#else
100# define DNDDEBUG(x)
101#endif
102
103
104/** QAbstractNativeEventFilter extension
105 * allowing to pre-process native platform events. */
106class UINativeEventFilter : public QAbstractNativeEventFilter
107{
108public:
109
110 /** Constructs native event filter storing @a pParent to redirect events to. */
111 UINativeEventFilter(UIMachineView *pParent)
112 : m_pParent(pParent)
113 {}
114
115 /** Redirects all the native events to parent. */
116#ifdef VBOX_IS_QT6_OR_LATER /* long replaced with qintptr since 6.0 */
117 bool nativeEventFilter(const QByteArray &eventType, void *pMessage, qintptr*)
118#else
119 bool nativeEventFilter(const QByteArray &eventType, void *pMessage, long*)
120#endif
121 {
122 return m_pParent->nativeEventPreprocessor(eventType, pMessage);
123 }
124
125private:
126
127 /** Holds the passed parent reference. */
128 UIMachineView *m_pParent;
129};
130
131
132/* static */
133UIMachineView* UIMachineView::create(UIMachineWindow *pMachineWindow, ulong uScreenId, UIVisualStateType visualStateType)
134{
135 UIMachineView *pMachineView = 0;
136 switch (visualStateType)
137 {
138 case UIVisualStateType_Normal:
139 pMachineView = new UIMachineViewNormal(pMachineWindow, uScreenId);
140 break;
141 case UIVisualStateType_Fullscreen:
142 pMachineView = new UIMachineViewFullscreen(pMachineWindow, uScreenId);
143 break;
144 case UIVisualStateType_Seamless:
145 pMachineView = new UIMachineViewSeamless(pMachineWindow, uScreenId);
146 break;
147 case UIVisualStateType_Scale:
148 pMachineView = new UIMachineViewScale(pMachineWindow, uScreenId);
149 break;
150 default:
151 break;
152 }
153
154 /* Load machine-view settings: */
155 pMachineView->loadMachineViewSettings();
156
157 /* Prepare viewport: */
158 pMachineView->prepareViewport();
159
160 /* Prepare frame-buffer: */
161 pMachineView->prepareFrameBuffer();
162
163 /* Prepare common things: */
164 pMachineView->prepareCommon();
165
166#ifdef VBOX_WITH_DRAG_AND_DROP
167 /* Prepare DnD: */
168 /* rc ignored */ pMachineView->prepareDnd();
169#endif
170
171 /* Prepare event-filters: */
172 pMachineView->prepareFilters();
173
174 /* Prepare connections: */
175 pMachineView->prepareConnections();
176
177 /* Prepare console connections: */
178 pMachineView->prepareConsoleConnections();
179
180 /* Initialization: */
181 pMachineView->sltMachineStateChanged();
182 /** @todo Can we move the call to sltAdditionsStateChanged() from the
183 * subclass constructors here too? It is called for Normal and Seamless,
184 * but not for Fullscreen and Scale. However for Scale it is a no op.,
185 * so it would not hurt. Would it hurt for fullscreen? */
186
187 /* Set a preliminary maximum size: */
188 pMachineView->setMaximumGuestSize();
189
190 /* Resend the last resize hint finally: */
191 pMachineView->resendSizeHint();
192
193 /* Return the created view: */
194 return pMachineView;
195}
196
197/* static */
198void UIMachineView::destroy(UIMachineView *pMachineView)
199{
200 if (!pMachineView)
201 return;
202
203#ifdef VBOX_WITH_DRAG_AND_DROP
204 /* Cleanup DnD: */
205 pMachineView->cleanupDnd();
206#endif
207
208 /* Cleanup frame-buffer: */
209 pMachineView->cleanupFrameBuffer();
210
211 /* Cleanup native filters: */
212 pMachineView->cleanupNativeFilters();
213
214 delete pMachineView;
215}
216
217void UIMachineView::applyMachineViewScaleFactor()
218{
219 /* Sanity check: */
220 if (!frameBuffer())
221 return;
222
223 /* Acquire selected scale-factor: */
224 double dScaleFactor = gEDataManager->scaleFactor(uiCommon().managedVMUuid(), m_uScreenId);
225
226 /* Take the device-pixel-ratio into account: */
227 frameBuffer()->setDevicePixelRatio(UIDesktopWidgetWatchdog::devicePixelRatio(machineWindow()));
228 frameBuffer()->setDevicePixelRatioActual(UIDesktopWidgetWatchdog::devicePixelRatioActual(machineWindow()));
229 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
230 const bool fUseUnscaledHiDPIOutput = dScaleFactor != dDevicePixelRatioActual;
231 dScaleFactor = fUseUnscaledHiDPIOutput ? dScaleFactor : 1.0;
232
233 /* Assign frame-buffer with new values: */
234 frameBuffer()->setScaleFactor(dScaleFactor);
235 frameBuffer()->setUseUnscaledHiDPIOutput(fUseUnscaledHiDPIOutput);
236
237 /* Propagate the scale-factor related attributes to 3D service if necessary: */
238 bool fAccelerate3DEnabled = false;
239 uimachine()->acquireWhetherAccelerate3DEnabled(fAccelerate3DEnabled);
240 if (fAccelerate3DEnabled)
241 {
242 double dScaleFactorFor3D = dScaleFactor;
243#if defined(VBOX_WS_WIN) || defined(VBOX_WS_NIX)
244 // WORKAROUND:
245 // On Windows and Linux opposing to macOS it's only Qt which can auto scale up,
246 // not 3D overlay itself, so for auto scale-up mode we have to take that into account.
247 if (!fUseUnscaledHiDPIOutput)
248 dScaleFactorFor3D *= dDevicePixelRatioActual;
249#endif /* VBOX_WS_WIN || VBOX_WS_NIX */
250 uimachine()->notifyScaleFactorChange(m_uScreenId,
251 (uint32_t)(dScaleFactorFor3D * VBOX_OGL_SCALE_FACTOR_MULTIPLIER),
252 (uint32_t)(dScaleFactorFor3D * VBOX_OGL_SCALE_FACTOR_MULTIPLIER));
253 uimachine()->notifyHiDPIOutputPolicyChange(fUseUnscaledHiDPIOutput);
254 }
255
256 /* Perform frame-buffer rescaling: */
257 frameBuffer()->performRescale();
258
259 /* Update console's display viewport and 3D overlay: */
260 updateViewport();
261}
262
263UIMachine *UIMachineView::uimachine() const
264{
265 return machineWindow()->uimachine();
266}
267
268UIMachineLogic *UIMachineView::machineLogic() const
269{
270 return machineWindow()->machineLogic();
271}
272
273UIFrameBuffer *UIMachineView::frameBuffer() const
274{
275 return uimachine()->frameBuffer(m_uScreenId);
276}
277
278int UIMachineView::contentsWidth() const
279{
280 return frameBuffer()->width();
281}
282
283int UIMachineView::contentsHeight() const
284{
285 return frameBuffer()->height();
286}
287
288int UIMachineView::contentsX() const
289{
290 return horizontalScrollBar()->value();
291}
292
293int UIMachineView::contentsY() const
294{
295 return verticalScrollBar()->value();
296}
297
298int UIMachineView::visibleWidth() const
299{
300 return horizontalScrollBar()->pageStep();
301}
302
303int UIMachineView::visibleHeight() const
304{
305 return verticalScrollBar()->pageStep();
306}
307
308QPoint UIMachineView::viewportToContents(const QPoint &viewportPoint) const
309{
310 /* Get physical contents shifts of scroll-bars: */
311 int iContentsX = contentsX();
312 int iContentsY = contentsY();
313
314 /* Take the device-pixel-ratio into account: */
315 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
316 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
317 if (!frameBuffer()->useUnscaledHiDPIOutput())
318 {
319 iContentsX *= dDevicePixelRatioActual;
320 iContentsY *= dDevicePixelRatioActual;
321 }
322 iContentsX /= dDevicePixelRatioFormal;
323 iContentsY /= dDevicePixelRatioFormal;
324
325 /* Return point shifted according scroll-bars: */
326 return QPoint(viewportPoint.x() + iContentsX, viewportPoint.y() + iContentsY);
327}
328
329void UIMachineView::scrollBy(int iDx, int iDy)
330{
331 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + iDx);
332 verticalScrollBar()->setValue(verticalScrollBar()->value() + iDy);
333}
334
335UIVisualStateType UIMachineView::visualStateType() const
336{
337 return machineLogic()->visualStateType();
338}
339
340double UIMachineView::aspectRatio() const
341{
342 return frameBuffer() ? (double)(frameBuffer()->width()) / frameBuffer()->height() : 0;
343}
344
345void UIMachineView::setMaximumGuestSize(const QSize &minimumSizeHint /* = QSize() */)
346{
347 QSize maxSize;
348 switch (m_enmMaximumGuestScreenSizePolicy)
349 {
350 case MaximumGuestScreenSizePolicy_Fixed:
351 maxSize = m_fixedMaxGuestSize;
352 break;
353 case MaximumGuestScreenSizePolicy_Automatic:
354 maxSize = calculateMaxGuestSize().expandedTo(minimumSizeHint);
355 break;
356 case MaximumGuestScreenSizePolicy_Any:
357 /* (0, 0) means any of course. */
358 maxSize = QSize(0, 0);
359 }
360 ASMAtomicWriteU64(&m_u64MaximumGuestSize,
361 RT_MAKE_U64(maxSize.height(), maxSize.width()));
362}
363
364QSize UIMachineView::maximumGuestSize()
365{
366 uint64_t u64Size = ASMAtomicReadU64(&m_u64MaximumGuestSize);
367 return QSize(int(RT_HI_U32(u64Size)), int(RT_LO_U32(u64Size)));
368}
369
370void UIMachineView::updateViewport()
371{
372 uimachine()->viewportChanged(screenId(), contentsX(), contentsY(), visibleWidth(), visibleHeight());
373}
374
375#ifdef VBOX_WITH_DRAG_AND_DROP
376int UIMachineView::dragCheckPending()
377{
378 int rc;
379
380 if (!dragAndDropIsActive())
381 rc = VERR_ACCESS_DENIED;
382# ifdef VBOX_WITH_DRAG_AND_DROP_GH
383 else if (!m_fIsDraggingFromGuest)
384 {
385 /// @todo Add guest->guest DnD functionality here by getting
386 // the source of guest B (when copying from B to A).
387 rc = m_pDnDHandler->dragCheckPending(screenId());
388 if (RT_SUCCESS(rc))
389 m_fIsDraggingFromGuest = true;
390 }
391 else /* Already dragging, so report success. */
392 rc = VINF_SUCCESS;
393# else
394 rc = VERR_NOT_SUPPORTED;
395# endif
396
397 DNDDEBUG(("DnD: dragCheckPending ended with rc=%Rrc\n", rc));
398 return rc;
399}
400
401int UIMachineView::dragStart()
402{
403 int rc;
404
405 if (!dragAndDropIsActive())
406 rc = VERR_ACCESS_DENIED;
407# ifdef VBOX_WITH_DRAG_AND_DROP_GH
408 else if (!m_fIsDraggingFromGuest)
409 rc = VERR_WRONG_ORDER;
410 else
411 {
412 /// @todo Add guest->guest DnD functionality here by getting
413 // the source of guest B (when copying from B to A).
414 rc = m_pDnDHandler->dragStart(screenId());
415
416 m_fIsDraggingFromGuest = false;
417 }
418# else
419 rc = VERR_NOT_SUPPORTED;
420# endif
421
422 DNDDEBUG(("DnD: dragStart ended with rc=%Rrc\n", rc));
423 return rc;
424}
425
426int UIMachineView::dragStop()
427{
428 int rc;
429
430 if (!dragAndDropIsActive())
431 rc = VERR_ACCESS_DENIED;
432# ifdef VBOX_WITH_DRAG_AND_DROP_GH
433 else if (!m_fIsDraggingFromGuest)
434 rc = VERR_WRONG_ORDER;
435 else
436 rc = m_pDnDHandler->dragStop(screenId());
437# else
438 rc = VERR_NOT_SUPPORTED;
439# endif
440
441 DNDDEBUG(("DnD: dragStop ended with rc=%Rrc\n", rc));
442 return rc;
443}
444#endif /* VBOX_WITH_DRAG_AND_DROP */
445
446bool UIMachineView::nativeEventPreprocessor(const QByteArray &eventType, void *pMessage)
447{
448 /* Check if some event should be filtered out.
449 * Returning @c true means filtering-out,
450 * Returning @c false means passing event to Qt. */
451
452# if defined(VBOX_WS_MAC)
453
454 /* Make sure it's generic NSEvent: */
455 if (eventType != "mac_generic_NSEvent")
456 return false;
457 EventRef event = static_cast<EventRef>(darwinCocoaToCarbonEvent(pMessage));
458
459 switch (::GetEventClass(event))
460 {
461 // Keep in mind that this stuff should not be enabled while we are still using
462 // own native keyboard filter installed through cocoa API, to be reworked.
463 // S.a. registerForNativeEvents call in UIKeyboardHandler implementation.
464#if 0
465 /* Watch for keyboard-events: */
466 case kEventClassKeyboard:
467 {
468 switch (::GetEventKind(event))
469 {
470 /* Watch for key-events: */
471 case kEventRawKeyDown:
472 case kEventRawKeyRepeat:
473 case kEventRawKeyUp:
474 case kEventRawKeyModifiersChanged:
475 {
476 /* Delegate key-event handling to the keyboard-handler: */
477 return machineLogic()->keyboardHandler()->nativeEventFilter(pMessage, screenId());
478 }
479 default:
480 break;
481 }
482 break;
483 }
484#endif
485 /* Watch for mouse-events: */
486 case kEventClassMouse:
487 {
488 switch (::GetEventKind(event))
489 {
490 /* Watch for button-events: */
491 case kEventMouseDown:
492 case kEventMouseUp:
493 {
494 /* Delegate button-event handling to the mouse-handler: */
495 return machineLogic()->mouseHandler()->nativeEventFilter(pMessage, screenId());
496 }
497 default:
498 break;
499 }
500 break;
501 }
502 default:
503 break;
504 }
505
506# elif defined(VBOX_WS_WIN)
507
508 /* Make sure it's generic MSG event: */
509 if (eventType != "windows_generic_MSG")
510 return false;
511 MSG *pEvent = static_cast<MSG*>(pMessage);
512
513 switch (pEvent->message)
514 {
515 /* Watch for key-events: */
516 case WM_KEYDOWN:
517 case WM_SYSKEYDOWN:
518 case WM_KEYUP:
519 case WM_SYSKEYUP:
520 {
521 // WORKAROUND:
522 // There is an issue in the Windows Qt5 event processing sequence
523 // causing QAbstractNativeEventFilter to receive Windows native events
524 // coming not just to the top-level window but to actual target as well.
525 // They are calling one - "global event" and another one - "context event".
526 // That way native events are always duplicated with almost no possibility
527 // to distinguish copies except the fact that synthetic event always have
528 // time set to 0 (actually that field was not initialized at all, we had
529 // fixed that in our private Qt tool). We should skip such events instantly.
530 if (pEvent->time == 0)
531 return false;
532
533 /* Delegate key-event handling to the keyboard-handler: */
534 return machineLogic()->keyboardHandler()->nativeEventFilter(pMessage, screenId());
535 }
536 default:
537 break;
538 }
539
540# elif defined(VBOX_WS_NIX)
541
542 if (uiCommon().X11ServerAvailable())
543 {
544 /* Make sure it's generic XCB event: */
545 if (eventType != "xcb_generic_event_t")
546 return false;
547 xcb_generic_event_t *pEvent = static_cast<xcb_generic_event_t*>(pMessage);
548
549 switch (pEvent->response_type & ~0x80)
550 {
551 /* Watch for key-events: */
552 case XCB_KEY_PRESS:
553 case XCB_KEY_RELEASE:
554 {
555 /* Delegate key-event handling to the keyboard-handler: */
556 return machineLogic()->keyboardHandler()->nativeEventFilter(pMessage, screenId());
557 }
558 /* Watch for button-events: */
559 case XCB_BUTTON_PRESS:
560 case XCB_BUTTON_RELEASE:
561 {
562 /* Delegate button-event handling to the mouse-handler: */
563 return machineLogic()->mouseHandler()->nativeEventFilter(pMessage, screenId());
564 }
565 default:
566 break;
567 }
568 }
569
570# else
571
572# warning "port me!"
573
574# endif
575
576 /* Filter nothing by default: */
577 return false;
578}
579
580#ifdef VBOX_WS_MAC
581CGImageRef UIMachineView::vmContentImage()
582{
583 /* Use pause-image if exists: */
584 if (!pausePixmap().isNull())
585 return darwinToCGImageRef(&pausePixmap());
586
587 /* Create the image ref out of the frame-buffer: */
588 return frameBuffertoCGImageRef(frameBuffer());
589}
590#endif /* VBOX_WS_MAC */
591
592void UIMachineView::sltHandleNotifyChange(int iWidth, int iHeight)
593{
594 /* Sanity check: */
595 if (!frameBuffer())
596 return;
597
598 LogRel2(("GUI: UIMachineView::sltHandleNotifyChange: Screen=%d, Size=%dx%d\n",
599 (unsigned long)m_uScreenId, iWidth, iHeight));
600
601 /* Some situations require frame-buffer resize-events to be ignored at all,
602 * leaving machine-window, machine-view and frame-buffer sizes preserved: */
603 if (uimachine()->isGuestResizeIgnored())
604 return;
605
606 /* In some situations especially in some VM states, guest-screen is not drawable: */
607 if (uimachine()->isGuestScreenUnDrawable())
608 return;
609
610 /* Get old frame-buffer size: */
611 const QSize frameBufferSizeOld = QSize(frameBuffer()->width(),
612 frameBuffer()->height());
613
614 /* Perform frame-buffer mode-change: */
615 frameBuffer()->handleNotifyChange(iWidth, iHeight);
616
617 /* Get new frame-buffer size: */
618 const QSize frameBufferSizeNew = QSize(frameBuffer()->width(),
619 frameBuffer()->height());
620
621 /* For 'scale' mode: */
622 if (visualStateType() == UIVisualStateType_Scale)
623 {
624 /* Assign new frame-buffer logical-size: */
625 QSize scaledSize = size();
626 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
627 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
628 scaledSize *= dDevicePixelRatioFormal;
629 if (!frameBuffer()->useUnscaledHiDPIOutput())
630 scaledSize /= dDevicePixelRatioActual;
631 frameBuffer()->setScaledSize(scaledSize);
632
633 /* Forget the last full-screen size: */
634 uimachine()->setLastFullScreenSize(screenId(), QSize(-1, -1));
635 }
636 /* For other than 'scale' mode: */
637 else
638 {
639 /* Adjust maximum-size restriction for machine-view: */
640 setMaximumSize(sizeHint());
641
642 /* Disable the resize hint override hack and forget the last full-screen size: */
643 m_sizeHintOverride = QSize(-1, -1);
644 if (visualStateType() == UIVisualStateType_Normal)
645 uimachine()->setLastFullScreenSize(screenId(), QSize(-1, -1));
646
647 /* Force machine-window update own layout: */
648 QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
649
650 /* Update machine-view sliders: */
651 updateSliders();
652
653 /* By some reason Win host forgets to update machine-window central-widget
654 * after main-layout was updated, let's do it for all the hosts: */
655 machineWindow()->centralWidget()->update();
656
657 /* Normalize 'normal' machine-window geometry if necessary: */
658 if (visualStateType() == UIVisualStateType_Normal &&
659 frameBufferSizeNew != frameBufferSizeOld)
660 machineWindow()->normalizeGeometry(true /* adjust position */, machineWindow()->shouldResizeToGuestDisplay());
661 }
662
663 /* Perform frame-buffer rescaling: */
664 frameBuffer()->performRescale();
665
666#ifdef VBOX_WS_MAC
667 /* Update MacOS X dock icon size: */
668 machineLogic()->updateDockIconSize(screenId(), frameBufferSizeNew.width(), frameBufferSizeNew.height());
669#endif /* VBOX_WS_MAC */
670
671 /* Notify frame-buffer resize: */
672 emit sigFrameBufferResize();
673
674 /* Ask for just required guest display update (it will also update
675 * the viewport through IFramebuffer::NotifyUpdate): */
676 uimachine()->invalidateAndUpdateScreen(m_uScreenId);
677
678 /* Acquire graphics controller type: */
679 KGraphicsControllerType enmType = KGraphicsControllerType_Null;
680 uimachine()->acquireGraphicsControllerType(enmType);
681
682 /* If we are in normal or scaled mode and if GA are active,
683 * remember the guest-screen size to be able to restore it when necessary: */
684 /* As machines with Linux/Solaris and VMSVGA are not able to tell us
685 * whether a resize was due to the system or user interaction we currently
686 * do not store hints for these systems except when we explicitly send them
687 * ourselves. Windows guests should use VBoxVGA controllers, not VMSVGA. */
688 if ( !isFullscreenOrSeamless()
689 && uimachine()->isGuestSupportsGraphics()
690 && (enmType != KGraphicsControllerType_VMSVGA))
691 setStoredGuestScreenSizeHint(frameBufferSizeNew);
692
693 LogRel2(("GUI: UIMachineView::sltHandleNotifyChange: Complete for Screen=%d, Size=%dx%d\n",
694 (unsigned long)m_uScreenId, frameBufferSizeNew.width(), frameBufferSizeNew.height()));
695}
696
697void UIMachineView::sltHandleNotifyUpdate(int iX, int iY, int iWidth, int iHeight)
698{
699 /* Sanity check: */
700 if (!frameBuffer())
701 return;
702
703 /* Prepare corresponding viewport part: */
704 QRect rect(iX, iY, iWidth, iHeight);
705
706 /* Take the scaling into account: */
707 const double dScaleFactor = frameBuffer()->scaleFactor();
708 const QSize scaledSize = frameBuffer()->scaledSize();
709 if (scaledSize.isValid())
710 {
711 /* Calculate corresponding scale-factors: */
712 const double xScaleFactor = visualStateType() == UIVisualStateType_Scale ?
713 (double)scaledSize.width() / frameBuffer()->width() : dScaleFactor;
714 const double yScaleFactor = visualStateType() == UIVisualStateType_Scale ?
715 (double)scaledSize.height() / frameBuffer()->height() : dScaleFactor;
716 /* Adjust corresponding viewport part: */
717 rect.moveTo((int)floor((double)rect.x() * xScaleFactor) - 1,
718 (int)floor((double)rect.y() * yScaleFactor) - 1);
719 rect.setSize(QSize((int)ceil((double)rect.width() * xScaleFactor) + 2,
720 (int)ceil((double)rect.height() * yScaleFactor) + 2));
721 }
722
723 /* Shift has to be scaled by the device-pixel-ratio
724 * but not scaled by the scale-factor. */
725 rect.translate(-contentsX(), -contentsY());
726
727 /* Take the device-pixel-ratio into account: */
728 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
729 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
730 if (!frameBuffer()->useUnscaledHiDPIOutput() && dDevicePixelRatioActual != 1.0)
731 {
732 rect.moveTo((int)floor((double)rect.x() * dDevicePixelRatioActual) - 1,
733 (int)floor((double)rect.y() * dDevicePixelRatioActual) - 1);
734 rect.setSize(QSize((int)ceil((double)rect.width() * dDevicePixelRatioActual) + 2,
735 (int)ceil((double)rect.height() * dDevicePixelRatioActual) + 2));
736 }
737 if (dDevicePixelRatioFormal != 1.0)
738 {
739 rect.moveTo((int)floor((double)rect.x() / dDevicePixelRatioFormal) - 1,
740 (int)floor((double)rect.y() / dDevicePixelRatioFormal) - 1);
741 rect.setSize(QSize((int)ceil((double)rect.width() / dDevicePixelRatioFormal) + 2,
742 (int)ceil((double)rect.height() / dDevicePixelRatioFormal) + 2));
743 }
744
745 /* Limit the resulting part by the viewport rectangle: */
746 rect &= viewport()->rect();
747
748 /* Update corresponding viewport part: */
749 viewport()->update(rect);
750}
751
752void UIMachineView::sltHandleSetVisibleRegion(QRegion region)
753{
754 /* Used only in seamless-mode. */
755 Q_UNUSED(region);
756}
757
758void UIMachineView::sltPerformGuestResize(const QSize &toSize)
759{
760 /* There is a couple of things to keep in mind:
761 *
762 * First of all, passed size can be invalid (or even not sane one, where one of values equal to zero). Usually that happens
763 * if this function being invoked with default argument for example by some slot. In such case we get the available size for
764 * the guest-screen we have. We assume here that centralWidget() contains this view only and gives it all available space.
765 * In all other cases we have a valid non-zero size which should be handled as usual.
766 *
767 * Besides that, passed size or size taken from centralWidget() is _not_ absolute one, it's in widget's coordinate system
768 * which can and will be be transformed by scale-factor when appropriate, so before passing this size to a guest it has to
769 * be scaled backward. This is the key aspect in which internal resize differs from resize initiated from the outside. */
770
771 /* Make sure we have valid size to work with: */
772 QSize size( toSize.isValid() && toSize.width() > 0 && toSize.height() > 0
773 ? toSize : machineWindow()->centralWidget()->size());
774 AssertMsgReturnVoid(size.isValid() && size.width() > 0 && size.height() > 0,
775 ("Size should be valid (%dx%d)!\n", size.width(), size.height()));
776
777 /* Take the scale-factor(s) into account: */
778 size = scaledBackward(size);
779
780 /* Update current window size limitations: */
781 setMaximumGuestSize(size);
782
783 /* Record the hint to extra data, needed for guests using VMSVGA:
784 * This should be done before the actual hint is sent in case the guest overrides it.
785 * Do not send a hint if nothing has changed to prevent the guest being notified about its own changes. */
786 if ( !isFullscreenOrSeamless()
787 && uimachine()->isGuestSupportsGraphics()
788 && ( (int)frameBuffer()->width() != size.width()
789 || (int)frameBuffer()->height() != size.height()
790 || uimachine()->isScreenVisible(screenId()) != uimachine()->isScreenVisibleHostDesires(screenId())))
791 setStoredGuestScreenSizeHint(size);
792
793 /* If auto-mount of guest-screens (auto-pilot) enabled: */
794 if (gEDataManager->autoMountGuestScreensEnabled(uiCommon().managedVMUuid()))
795 {
796 /* If host and guest have same opinion about guest-screen visibility: */
797 if (uimachine()->isScreenVisible(screenId()) == uimachine()->isScreenVisibleHostDesires(screenId()))
798 {
799 /* Do not send a hint if nothing has changed to prevent the guest being notified about its own changes: */
800 if ((int)frameBuffer()->width() != size.width() || (int)frameBuffer()->height() != size.height())
801 {
802 LogRel(("GUI: UIMachineView::sltPerformGuestResize: Auto-pilot resizing screen %d as %dx%d\n",
803 (int)screenId(), size.width(), size.height()));
804 uimachine()->setVideoModeHint(screenId(),
805 uimachine()->isScreenVisible(screenId()),
806 false /* change origin? */,
807 0 /* origin x */, 0 /* origin y */,
808 size.width(), size.height(),
809 0 /* bits per pixel */,
810 true /* notify? */);
811 }
812 }
813 else
814 {
815 /* If host desires to have guest-screen enabled and guest-screen is disabled, retrying: */
816 if (uimachine()->isScreenVisibleHostDesires(screenId()))
817 {
818 /* Send enabling size-hint to the guest: */
819 LogRel(("GUI: UIMachineView::sltPerformGuestResize: Auto-pilot enabling guest-screen %d\n", (int)screenId()));
820 uimachine()->setVideoModeHint(screenId(),
821 true /* enabled? */,
822 false /* change origin? */,
823 0 /* origin x */, 0 /* origin y */,
824 size.width(), size.height(),
825 0 /* bits per pixel */,
826 true /* notify? */);
827 }
828 /* If host desires to have guest-screen disabled and guest-screen is enabled, retrying: */
829 else
830 {
831 /* Send disabling size-hint to the guest: */
832 LogRel(("GUI: UIMachineView::sltPerformGuestResize: Auto-pilot disabling guest-screen %d\n", (int)screenId()));
833 uimachine()->setVideoModeHint(screenId(),
834 false /* enabled? */,
835 false /* change origin? */,
836 0 /* origin x */, 0 /* origin y */,
837 0 /* width */, 0 /* height */,
838 0 /* bits per pixel */,
839 true /* notify? */);
840 }
841 }
842 }
843 /* If auto-mount of guest-screens (auto-pilot) disabled: */
844 else
845 {
846 /* Should we send a hint? */
847 bool fSendHint = true;
848 /* Do not send a hint if nothing has changed to prevent the guest being notified about its own changes: */
849 if (fSendHint && (int)frameBuffer()->width() == size.width() && (int)frameBuffer()->height() == size.height())
850 {
851 LogRel(("GUI: UIMachineView::sltPerformGuestResize: Omitting to send size-hint %dx%d to guest-screen %d "
852 "because frame-buffer is already of the same size.\n", size.width(), size.height(), (int)screenId()));
853 fSendHint = false;
854 }
855 /* Do not send a hint if GA supports graphics and we have sent that hint already: */
856 if (fSendHint && uimachine()->isGuestSupportsGraphics() && m_lastSizeHint == size)
857 {
858 LogRel(("GUI: UIMachineView::sltPerformGuestResize: Omitting to send size-hint %dx%d to guest-screen %d "
859 "because this hint was previously sent.\n", size.width(), size.height(), (int)screenId()));
860 fSendHint = false;
861 }
862 if (fSendHint)
863 {
864 LogRel(("GUI: UIMachineView::sltPerformGuestResize: Sending guest size-hint to screen %d as %dx%d\n",
865 (int)screenId(), size.width(), size.height()));
866 uimachine()->setVideoModeHint(screenId(),
867 uimachine()->isScreenVisible(screenId()),
868 false /* change origin? */,
869 0 /* origin x */, 0 /* origin y */,
870 size.width(), size.height(),
871 0 /* bits per pixel */,
872 true /* notify? */);
873 m_lastSizeHint = size;
874 }
875 }
876}
877
878void UIMachineView::sltHandleActionTriggerViewScreenToggle(int iScreen, bool fEnabled)
879{
880 /* Skip unrelated guest-screen index: */
881 if (iScreen != (int)screenId())
882 return;
883
884 /* Acquire current resolution: */
885 ulong uWidth = 0, uHeight = 0, uDummy = 0;
886 long iDummy = 0;
887 KGuestMonitorStatus enmDummy = KGuestMonitorStatus_Disabled;
888 const bool fSuccess = uimachine()->acquireGuestScreenParameters(screenId(), uWidth, uHeight,
889 uDummy, iDummy, iDummy, enmDummy);
890 if (!fSuccess)
891 return;
892
893 /* Update desirable screen status: */
894 uimachine()->setScreenVisibleHostDesires(screenId(), fEnabled);
895
896 /* Send enabling size-hint: */
897 if (fEnabled)
898 {
899 /* Defaults: */
900 if (!uWidth)
901 uWidth = 800;
902 if (!uHeight)
903 uHeight = 600;
904
905 /* Update current window size limitations: */
906 setMaximumGuestSize(QSize(uWidth, uHeight));
907
908 /* Record the hint to extra data, needed for guests using VMSVGA:
909 * This should be done before the actual hint is sent in case the guest overrides it.
910 * Do not send a hint if nothing has changed to prevent the guest being notified about its own changes. */
911 if ( !isFullscreenOrSeamless()
912 && uimachine()->isGuestSupportsGraphics()
913 && ( frameBuffer()->width() != uWidth
914 || frameBuffer()->height() != uHeight
915 || uimachine()->isScreenVisible(screenId()) != uimachine()->isScreenVisibleHostDesires(screenId())))
916 setStoredGuestScreenSizeHint(QSize(uWidth, uHeight));
917
918 /* Send enabling size-hint to the guest: */
919 LogRel(("GUI: UIMachineView::sltHandleActionTriggerViewScreenToggle: Enabling guest-screen %d\n", (int)screenId()));
920 uimachine()->setVideoModeHint(screenId(),
921 true /* enabled? */,
922 false /* change origin? */,
923 0 /* origin x */, 0 /* origin y */,
924 uWidth, uHeight,
925 0 /* bits per pixel */,
926 true /* notify? */);
927 }
928 else
929 {
930 /* Send disabling size-hint to the guest: */
931 LogRel(("GUI: UIMachineView::sltHandleActionTriggerViewScreenToggle: Disabling guest-screen %d\n", (int)screenId()));
932 uimachine()->setVideoModeHint(screenId(),
933 false /* enabled? */,
934 false /* change origin? */,
935 0 /* origin x */, 0 /* origin y */,
936 0 /* width */, 0 /* height */,
937 0 /* bits per pixel */,
938 true /* notify? */);
939 }
940}
941
942void UIMachineView::sltHandleActionTriggerViewScreenResize(int iScreen, const QSize &size)
943{
944 /* Skip unrelated guest-screen index: */
945 if (iScreen != (int)m_uScreenId)
946 return;
947
948 /* Make sure we have valid size to work with: */
949 AssertMsgReturnVoid(size.isValid() && size.width() > 0 && size.height() > 0,
950 ("Size should be valid (%dx%d)!\n", size.width(), size.height()));
951
952 /* Update current window size limitations: */
953 setMaximumGuestSize(size);
954
955 /* Record the hint to extra data, needed for guests using VMSVGA:
956 * This should be done before the actual hint is sent in case the guest overrides it.
957 * Do not send a hint if nothing has changed to prevent the guest being notified about its own changes. */
958 if ( !isFullscreenOrSeamless()
959 && uimachine()->isGuestSupportsGraphics()
960 && ( (int)frameBuffer()->width() != size.width()
961 || (int)frameBuffer()->height() != size.height()
962 || uimachine()->isScreenVisible(screenId()) != uimachine()->isScreenVisibleHostDesires(screenId())))
963 setStoredGuestScreenSizeHint(size);
964
965 /* Send enabling size-hint to the guest: */
966 LogRel(("GUI: UIMachineView::sltHandleActionTriggerViewScreenResize: Resizing guest-screen %d\n", (int)screenId()));
967 uimachine()->setVideoModeHint(screenId(),
968 true /* enabled? */,
969 false /* change origin? */,
970 0 /* origin x */, 0 /* origin y */,
971 size.width(), size.height(),
972 0 /* bits per pixel */,
973 true /* notify? */);
974}
975
976void UIMachineView::sltDesktopResized()
977{
978 setMaximumGuestSize();
979}
980
981void UIMachineView::sltHandleScaleFactorChange(const QUuid &uMachineID)
982{
983 /* Skip unrelated machine IDs: */
984 if (uMachineID != uiCommon().managedVMUuid())
985 return;
986
987 /* Acquire selected scale-factor: */
988 double dScaleFactor = gEDataManager->scaleFactor(uiCommon().managedVMUuid(), m_uScreenId);
989
990 /* Take the device-pixel-ratio into account: */
991 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
992 const bool fUseUnscaledHiDPIOutput = dScaleFactor != dDevicePixelRatioActual;
993 dScaleFactor = fUseUnscaledHiDPIOutput ? dScaleFactor : 1.0;
994
995 /* Assign frame-buffer with new values: */
996 frameBuffer()->setScaleFactor(dScaleFactor);
997 frameBuffer()->setUseUnscaledHiDPIOutput(fUseUnscaledHiDPIOutput);
998
999 /* Propagate the scale-factor related attributes to 3D service if necessary: */
1000 bool fAccelerate3DEnabled = false;
1001 uimachine()->acquireWhetherAccelerate3DEnabled(fAccelerate3DEnabled);
1002 if (fAccelerate3DEnabled)
1003 {
1004 double dScaleFactorFor3D = dScaleFactor;
1005#if defined(VBOX_WS_WIN) || defined(VBOX_WS_NIX)
1006 // WORKAROUND:
1007 // On Windows and Linux opposing to macOS it's only Qt which can auto scale up,
1008 // not 3D overlay itself, so for auto scale-up mode we have to take that into account.
1009 if (!fUseUnscaledHiDPIOutput)
1010 dScaleFactorFor3D *= frameBuffer()->devicePixelRatioActual();
1011#endif /* VBOX_WS_WIN || VBOX_WS_NIX */
1012 uimachine()->notifyScaleFactorChange(m_uScreenId,
1013 (uint32_t)(dScaleFactorFor3D * VBOX_OGL_SCALE_FACTOR_MULTIPLIER),
1014 (uint32_t)(dScaleFactorFor3D * VBOX_OGL_SCALE_FACTOR_MULTIPLIER));
1015 uimachine()->notifyHiDPIOutputPolicyChange(fUseUnscaledHiDPIOutput);
1016 }
1017
1018 /* Handle scale attributes change: */
1019 handleScaleChange();
1020 /* Adjust guest-screen size: */
1021 adjustGuestScreenSize();
1022
1023 /* Update scaled pause pixmap, if necessary: */
1024 updateScaledPausePixmap();
1025 viewport()->update();
1026
1027 /* Update console's display viewport and 3D overlay: */
1028 updateViewport();
1029}
1030
1031void UIMachineView::sltHandleScalingOptimizationChange(const QUuid &uMachineID)
1032{
1033 /* Skip unrelated machine IDs: */
1034 if (uMachineID != uiCommon().managedVMUuid())
1035 return;
1036
1037 /* Take the scaling-optimization type into account: */
1038 frameBuffer()->setScalingOptimizationType(gEDataManager->scalingOptimizationType(uiCommon().managedVMUuid()));
1039
1040 /* Update viewport: */
1041 viewport()->update();
1042}
1043
1044void UIMachineView::sltMachineStateChanged()
1045{
1046 /* Get machine state: */
1047 KMachineState state = uimachine()->machineState();
1048 switch (state)
1049 {
1050 case KMachineState_Paused:
1051 case KMachineState_TeleportingPausedVM:
1052 {
1053 if ( frameBuffer()
1054 && ( state != KMachineState_TeleportingPausedVM
1055 || m_previousState != KMachineState_Teleporting))
1056 {
1057 // WORKAROUND:
1058 // We can't take pause pixmap if actual state is Saving, this produces
1059 // a lock and GUI will be frozen until SaveState call is complete...
1060 KMachineState enmActualState = KMachineState_Null;
1061 uimachine()->acquireLiveMachineState(enmActualState);
1062 if (enmActualState != KMachineState_Saving)
1063 {
1064 /* Take live pause-pixmap: */
1065 takePausePixmapLive();
1066 /* Fully repaint to pick up pause-pixmap: */
1067 viewport()->update();
1068 }
1069 }
1070 break;
1071 }
1072 case KMachineState_Restoring:
1073 {
1074 /* Only works with the primary screen currently. */
1075 if (screenId() == 0)
1076 {
1077 /* Take snapshot pause-pixmap: */
1078 takePausePixmapSnapshot();
1079 /* Fully repaint to pick up pause-pixmap: */
1080 viewport()->update();
1081 }
1082 break;
1083 }
1084 case KMachineState_Running:
1085 {
1086 if (m_previousState == KMachineState_Paused ||
1087 m_previousState == KMachineState_TeleportingPausedVM ||
1088 m_previousState == KMachineState_Restoring)
1089 {
1090 if (frameBuffer())
1091 {
1092 /* Reset pause-pixmap: */
1093 resetPausePixmap();
1094 /* Ask for full guest display update (it will also update
1095 * the viewport through IFramebuffer::NotifyUpdate): */
1096 uimachine()->invalidateAndUpdate();
1097 }
1098 }
1099 /* Reapply machine-view scale-factor: */
1100 applyMachineViewScaleFactor();
1101 break;
1102 }
1103 default:
1104 break;
1105 }
1106
1107 m_previousState = state;
1108}
1109
1110void UIMachineView::sltMousePointerShapeChange()
1111{
1112 /* Fetch the shape and the mask: */
1113 QPixmap pixmapShape = uimachine()->cursorShapePixmap();
1114 QPixmap pixmapMask = uimachine()->cursorMaskPixmap();
1115 const QPoint hotspot = uimachine()->cursorHotspot();
1116 uint uXHot = hotspot.x();
1117 uint uYHot = hotspot.y();
1118
1119 /* If there is no mask: */
1120 if (pixmapMask.isNull())
1121 {
1122 /* Scale the shape pixmap and
1123 * compose the cursor on the basis of shape only: */
1124 updateMousePointerPixmapScaling(pixmapShape, uXHot, uYHot);
1125 m_cursor = QCursor(pixmapShape, uXHot, uYHot);
1126 }
1127 /* Otherwise: */
1128 else
1129 {
1130 /* Scale the shape and the mask pixmaps and
1131 * compose the cursor on the basis of shape and mask both: */
1132 updateMousePointerPixmapScaling(pixmapShape, uXHot, uYHot);
1133 /// @todo updateMousePointerPixmapScaling(pixmapMask, uXHot, uYHot);
1134#ifdef VBOX_IS_QT6_OR_LATER /* since qt6 explicit constructor is replaced with QBitmap::fromPixmap static method */
1135 m_cursor = QCursor(QBitmap::fromPixmap(pixmapShape), QBitmap::fromPixmap(pixmapMask), uXHot, uYHot);
1136#else
1137 m_cursor = QCursor(pixmapShape, pixmapMask, uXHot, uYHot);
1138#endif
1139 }
1140
1141 /* Let the listeners know: */
1142 emit sigMousePointerShapeChange();
1143}
1144
1145void UIMachineView::sltDetachCOM()
1146{
1147#ifdef VBOX_WITH_DRAG_AND_DROP
1148 /* Cleanup DnD: */
1149 cleanupDnd();
1150#endif
1151}
1152
1153UIMachineView::UIMachineView(UIMachineWindow *pMachineWindow, ulong uScreenId)
1154 : QAbstractScrollArea(pMachineWindow->centralWidget())
1155 , m_pMachineWindow(pMachineWindow)
1156 , m_uScreenId(uScreenId)
1157 , m_previousState(KMachineState_Null)
1158 , m_iHostScreenNumber(0)
1159 , m_enmMaximumGuestScreenSizePolicy(MaximumGuestScreenSizePolicy_Automatic)
1160 , m_u64MaximumGuestSize(0)
1161#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1162 , m_fIsDraggingFromGuest(false)
1163#endif
1164 , m_pNativeEventFilter(0)
1165{
1166}
1167
1168void UIMachineView::loadMachineViewSettings()
1169{
1170 /* Global settings: */
1171 {
1172 /* Remember the maximum guest size policy for
1173 * telling the guest about video modes we like: */
1174 m_enmMaximumGuestScreenSizePolicy = gEDataManager->maxGuestResolutionPolicy();
1175 if (m_enmMaximumGuestScreenSizePolicy == MaximumGuestScreenSizePolicy_Fixed)
1176 m_fixedMaxGuestSize = gEDataManager->maxGuestResolutionForPolicyFixed();
1177 }
1178}
1179
1180void UIMachineView::prepareViewport()
1181{
1182 /* Prepare viewport: */
1183 AssertPtrReturnVoid(viewport());
1184 {
1185 /* Enable manual painting: */
1186 viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
1187 /* Enable multi-touch support: */
1188 viewport()->setAttribute(Qt::WA_AcceptTouchEvents);
1189 }
1190}
1191
1192void UIMachineView::prepareFrameBuffer()
1193{
1194 /* Make sure frame-buffer exists: */
1195 if (!frameBuffer())
1196 return;
1197
1198 /* If frame-buffer NOT yet initialized: */
1199 if (!frameBuffer()->isInitialized())
1200 {
1201 LogRelFlow(("GUI: UIMachineView::prepareFrameBuffer: Start EMT callbacks accepting for screen: %d\n", screenId()));
1202 /* Initialize for this view: */
1203 frameBuffer()->init(this);
1204 /* Apply machine-view scale-factor: */
1205 applyMachineViewScaleFactor();
1206 }
1207 /* Otherwise it must be unused yet: */
1208 else
1209 {
1210 LogRelFlow(("GUI: UIMachineView::prepareFrameBuffer: Restart EMT callbacks accepting for screen: %d\n", screenId()));
1211 /* Assign it's view: */
1212 frameBuffer()->setView(this);
1213 /* Mark frame-buffer as used again: */
1214 frameBuffer()->setMarkAsUnused(false);
1215 }
1216
1217 /* Make sure frame-buffer was prepared: */
1218 AssertPtrReturnVoid(frameBuffer());
1219
1220 /* Reattach to IDisplay: */
1221 frameBuffer()->detach();
1222 frameBuffer()->attach();
1223
1224 /* Calculate frame-buffer size: */
1225 QSize size;
1226 {
1227 /* Acquire actual machine state to be sure: */
1228 KMachineState enmActualState = KMachineState_Null;
1229 uimachine()->acquireLiveMachineState(enmActualState);
1230
1231#ifdef VBOX_WS_NIX
1232 // WORKAROUND:
1233 // No idea why this was required for X11 before.
1234 // Currently I don't see a reason why I should keep it.
1235# if 0
1236 if (enmActualState == KMachineState_Saved || enmActualState == KMachineState_AbortedSaved)
1237 size = storedGuestScreenSizeHint();
1238# endif
1239#endif /* VBOX_WS_NIX */
1240
1241 /* If there is a preview image saved, we will resize the framebuffer to the size of that image: */
1242 if (enmActualState == KMachineState_Saved || enmActualState == KMachineState_AbortedSaved)
1243 {
1244 ulong uWidth = 0, uHeight = 0;
1245 QVector<KBitmapFormat> formats;
1246 uimachine()->acquireSavedScreenshotInfo(m_uScreenId, uWidth, uHeight, formats);
1247 if (formats.size() > 0)
1248 {
1249 /* Init with the screenshot size: */
1250 size = QSize(uWidth, uHeight);
1251 /* Try to get the real guest dimensions from the save-state: */
1252 long iDummy = 0;
1253 ulong uGuestWidth = 0, uGuestHeight = 0;
1254 bool fDummy = true;
1255 uimachine()->acquireSavedGuestScreenInfo(m_uScreenId,
1256 iDummy, iDummy,
1257 uGuestWidth, uGuestHeight, fDummy);
1258 if (uGuestWidth > 0 && uGuestHeight > 0)
1259 size = QSize(uGuestWidth, uGuestHeight);
1260 }
1261 }
1262
1263 /* If we have a valid size, resize/rescale the frame-buffer. */
1264 if (size.width() > 0 && size.height() > 0)
1265 {
1266 frameBuffer()->performResize(size.width(), size.height());
1267 frameBuffer()->performRescale();
1268 }
1269 }
1270}
1271
1272void UIMachineView::prepareCommon()
1273{
1274 /* Prepare view frame: */
1275 setFrameStyle(QFrame::NoFrame);
1276
1277 /* Setup palette: */
1278 QPalette palette(viewport()->palette());
1279 palette.setColor(viewport()->backgroundRole(), Qt::black);
1280 viewport()->setPalette(palette);
1281
1282 /* Setup focus policy: */
1283 setFocusPolicy(Qt::WheelFocus);
1284}
1285
1286#ifdef VBOX_WITH_DRAG_AND_DROP
1287int UIMachineView::prepareDnd(void)
1288{
1289 /* Enable drag & drop: */
1290 setAcceptDrops(true);
1291
1292 int vrc;
1293
1294 /* Create the drag and drop handler instance: */
1295 m_pDnDHandler = new UIDnDHandler(uimachine(), this /* pParent */);
1296 if (m_pDnDHandler)
1297 {
1298 vrc = m_pDnDHandler->init();
1299 }
1300 else
1301 vrc = VERR_NO_MEMORY;
1302
1303 if (RT_FAILURE(vrc))
1304 LogRel(("DnD: Initialization failed with %Rrc\n", vrc));
1305 return vrc;
1306}
1307#endif /* VBOX_WITH_DRAG_AND_DROP */
1308
1309void UIMachineView::prepareFilters()
1310{
1311 /* Enable MouseMove events: */
1312 viewport()->setMouseTracking(true);
1313
1314 /* We have to watch for own events too: */
1315 installEventFilter(this);
1316
1317 /* QScrollView does the below on its own, but let's
1318 * do it anyway for the case it will not do it in the future: */
1319 viewport()->installEventFilter(this);
1320
1321 /* We want to be notified on some parent's events: */
1322 machineWindow()->installEventFilter(this);
1323}
1324
1325void UIMachineView::prepareConnections()
1326{
1327 /* UICommon connections: */
1328 connect(&uiCommon(), &UICommon::sigAskToDetachCOM, this, &UIMachineView::sltDetachCOM);
1329 /* Desktop resolution change (e.g. monitor hotplug): */
1330 connect(gpDesktop, &UIDesktopWidgetWatchdog::sigHostScreenResized,
1331 this, &UIMachineView::sltDesktopResized);
1332 /* Scale-factor change: */
1333 connect(gEDataManager, &UIExtraDataManager::sigScaleFactorChange,
1334 this, &UIMachineView::sltHandleScaleFactorChange);
1335 /* Scaling-optimization change: */
1336 connect(gEDataManager, &UIExtraDataManager::sigScalingOptimizationTypeChange,
1337 this, &UIMachineView::sltHandleScalingOptimizationChange);
1338 /* Action-pool connections: */
1339 UIActionPoolRuntime *pActionPoolRuntime = qobject_cast<UIActionPoolRuntime*>(actionPool());
1340 if (pActionPoolRuntime)
1341 {
1342 connect(pActionPoolRuntime, &UIActionPoolRuntime::sigNotifyAboutTriggeringViewScreenToggle,
1343 this, &UIMachineView::sltHandleActionTriggerViewScreenToggle);
1344 connect(pActionPoolRuntime, &UIActionPoolRuntime::sigNotifyAboutTriggeringViewScreenResize,
1345 this, &UIMachineView::sltHandleActionTriggerViewScreenResize);
1346 }
1347}
1348
1349void UIMachineView::prepareConsoleConnections()
1350{
1351 /* Machine state-change updater: */
1352 connect(uimachine(), &UIMachine::sigMachineStateChange, this, &UIMachineView::sltMachineStateChanged);
1353 /* Mouse pointer shape updater: */
1354 connect(uimachine(), &UIMachine::sigMousePointerShapeChange, this, &UIMachineView::sltMousePointerShapeChange);
1355}
1356
1357#ifdef VBOX_WITH_DRAG_AND_DROP
1358void UIMachineView::cleanupDnd()
1359{
1360 delete m_pDnDHandler;
1361 m_pDnDHandler = 0;
1362}
1363#endif /* VBOX_WITH_DRAG_AND_DROP */
1364
1365void UIMachineView::cleanupFrameBuffer()
1366{
1367 /* Make sure framebuffer still present: */
1368 if (!frameBuffer())
1369 return;
1370
1371 /* Mark framebuffer as unused: */
1372 LogRelFlow(("GUI: UIMachineView::cleanupFrameBuffer: Stop EMT callbacks accepting for screen: %d\n", screenId()));
1373 frameBuffer()->setMarkAsUnused(true);
1374
1375 /* Process pending framebuffer events: */
1376 QApplication::sendPostedEvents(this, QEvent::MetaCall);
1377
1378 /* Temporarily detach the framebuffer from IDisplay before detaching
1379 * from view in order to respect the thread synchonisation logic (see UIFrameBuffer.h).
1380 * Note: VBOX_WITH_CROGL additionally requires us to call DetachFramebuffer
1381 * to ensure 3D gets notified of view being destroyed... */
1382 frameBuffer()->detach();
1383
1384 /* Detach framebuffer from view: */
1385 frameBuffer()->setView(0);
1386}
1387
1388void UIMachineView::cleanupNativeFilters()
1389{
1390 /* If native event filter exists: */
1391 if (m_pNativeEventFilter)
1392 {
1393 /* Uninstall/destroy existing native event filter: */
1394 qApp->removeNativeEventFilter(m_pNativeEventFilter);
1395 delete m_pNativeEventFilter;
1396 m_pNativeEventFilter = 0;
1397 }
1398}
1399
1400UIActionPool* UIMachineView::actionPool() const
1401{
1402 return machineWindow()->actionPool();
1403}
1404
1405QSize UIMachineView::sizeHint() const
1406{
1407 /* Make sure frame-buffer exists: */
1408 QSize size;
1409 if (!frameBuffer())
1410 size = QSize(640, 480);
1411 else
1412 {
1413 // WORKAROUND:
1414 // Temporarily restrict the size to prevent a brief resize to the frame-buffer dimensions when
1415 // we exit full-screen. This is only applied if the frame-buffer is at full-screen dimensions
1416 // and until the first machine view resize.
1417 /* Get the frame-buffer dimensions: */
1418 QSize frameBufferSize(frameBuffer()->width(), frameBuffer()->height());
1419 /* Take the scale-factor(s) into account: */
1420 frameBufferSize = scaledForward(frameBufferSize);
1421 /* Check against the last full-screen size: */
1422 if (frameBufferSize == uimachine()->lastFullScreenSize(screenId()) && m_sizeHintOverride.isValid())
1423 return m_sizeHintOverride;
1424
1425 /* Get frame-buffer size-hint: */
1426 size = QSize(frameBuffer()->width(), frameBuffer()->height());
1427 /* Take the scale-factor(s) into account: */
1428 size = scaledForward(size);
1429
1430#ifdef VBOX_WITH_DEBUGGER_GUI
1431 /// @todo Fix all DEBUGGER stuff!
1432 // WORKAROUND:
1433 // Really ugly workaround for the resizing to 9x1
1434 // done by DevVGA if provoked before power on.
1435 if (size.width() < 16 || size.height() < 16)
1436 if (uiCommon().shouldStartPaused() || uiCommon().isDebuggerAutoShowEnabled())
1437 size = QSize(640, 480);
1438#endif /* VBOX_WITH_DEBUGGER_GUI */
1439 }
1440
1441 /* Return the resulting size-hint: */
1442 return QSize(size.width() + frameWidth() * 2, size.height() + frameWidth() * 2);
1443}
1444
1445QSize UIMachineView::storedGuestScreenSizeHint() const
1446{
1447 /* Load guest-screen size-hint: */
1448 QSize sizeHint = gEDataManager->lastGuestScreenSizeHint(m_uScreenId, uiCommon().managedVMUuid());
1449
1450 /* Invent the default if necessary: */
1451 if (!sizeHint.isValid())
1452 sizeHint = QSize(800, 600);
1453
1454 /* Take the scale-factor(s) into account: */
1455 sizeHint = scaledForward(sizeHint);
1456
1457 /* Return size-hint: */
1458 LogRel2(("GUI: UIMachineView::storedGuestScreenSizeHint: Acquired guest-screen size-hint for screen %d as %dx%d\n",
1459 (int)screenId(), sizeHint.width(), sizeHint.height()));
1460 return sizeHint;
1461}
1462
1463void UIMachineView::setStoredGuestScreenSizeHint(const QSize &sizeHint)
1464{
1465 /* Save guest-screen size-hint: */
1466 LogRel2(("GUI: UIMachineView::setStoredGuestScreenSizeHint: Storing guest-screen size-hint for screen %d as %dx%d\n",
1467 (int)screenId(), sizeHint.width(), sizeHint.height()));
1468 gEDataManager->setLastGuestScreenSizeHint(m_uScreenId, sizeHint, uiCommon().managedVMUuid());
1469}
1470
1471QSize UIMachineView::requestedGuestScreenSizeHint() const
1472{
1473 /* Acquire last guest-screen size-hint set, if any: */
1474 bool fDummy = false;
1475 long iDummy = 0;
1476 ulong uWidth = 0, uHeight = 0, uDummy = 0;
1477 uimachine()->acquireVideoModeHint(screenId(), fDummy, fDummy,
1478 iDummy, iDummy, uWidth, uHeight,
1479 uDummy);
1480
1481 /* Acquire effective frame-buffer size otherwise: */
1482 if (uWidth == 0 || uHeight == 0)
1483 {
1484 uWidth = frameBuffer()->width();
1485 uHeight = frameBuffer()->height();
1486 }
1487
1488 /* Return result: */
1489 return QSize((int)uWidth, (int)uHeight);
1490}
1491
1492bool UIMachineView::guestScreenVisibilityStatus() const
1493{
1494 /* Always 'true' for primary guest-screen: */
1495 if (m_uScreenId == 0)
1496 return true;
1497
1498 /* Actual value for other guest-screens: */
1499 return gEDataManager->lastGuestScreenVisibilityStatus(m_uScreenId, uiCommon().managedVMUuid());
1500}
1501
1502void UIMachineView::handleScaleChange()
1503{
1504 LogRel(("GUI: UIMachineView::handleScaleChange: Screen=%d\n",
1505 (unsigned long)m_uScreenId));
1506
1507 /* If machine-window is visible: */
1508 if (uimachine()->isScreenVisible(m_uScreenId))
1509 {
1510 /* For 'scale' mode: */
1511 if (visualStateType() == UIVisualStateType_Scale)
1512 {
1513 /* Assign new frame-buffer logical-size: */
1514 QSize scaledSize = size();
1515 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
1516 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
1517 scaledSize *= dDevicePixelRatioFormal;
1518 if (!frameBuffer()->useUnscaledHiDPIOutput())
1519 scaledSize /= dDevicePixelRatioActual;
1520 frameBuffer()->setScaledSize(scaledSize);
1521 }
1522 /* For other than 'scale' mode: */
1523 else
1524 {
1525 /* Adjust maximum-size restriction for machine-view: */
1526 setMaximumSize(sizeHint());
1527
1528 /* Force machine-window update own layout: */
1529 QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
1530
1531 /* Update machine-view sliders: */
1532 updateSliders();
1533
1534 /* By some reason Win host forgets to update machine-window central-widget
1535 * after main-layout was updated, let's do it for all the hosts: */
1536 machineWindow()->centralWidget()->update();
1537
1538 /* Normalize 'normal' machine-window geometry: */
1539 if (visualStateType() == UIVisualStateType_Normal)
1540 machineWindow()->normalizeGeometry(true /* adjust position */, machineWindow()->shouldResizeToGuestDisplay());
1541 }
1542
1543 /* Perform frame-buffer rescaling: */
1544 frameBuffer()->performRescale();
1545 }
1546
1547 LogRelFlow(("GUI: UIMachineView::handleScaleChange: Complete for Screen=%d\n",
1548 (unsigned long)m_uScreenId));
1549}
1550
1551void UIMachineView::resetPausePixmap()
1552{
1553 /* Reset pixmap(s): */
1554 m_pausePixmap = QPixmap();
1555 m_pausePixmapScaled = QPixmap();
1556}
1557
1558void UIMachineView::takePausePixmapLive()
1559{
1560 /* Prepare a screen-shot: */
1561 QImage screenShot = QImage(frameBuffer()->width(), frameBuffer()->height(), QImage::Format_RGB32);
1562 /* Which will be a 'black image' by default. */
1563 screenShot.fill(0);
1564
1565 /* Acquire screen-shot image: */
1566 uimachine()->acquireScreenShot(screenId(), screenShot.width(), screenShot.height(), KBitmapFormat_BGR0, screenShot.bits());
1567
1568 /* Take the device-pixel-ratio into account: */
1569 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
1570 if (!frameBuffer()->useUnscaledHiDPIOutput() && dDevicePixelRatioActual != 1.0)
1571 screenShot = screenShot.scaled(screenShot.size() * dDevicePixelRatioActual,
1572 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1573
1574 /* Dim screen-shot if it is Ok: */
1575 if (!screenShot.isNull())
1576 dimImage(screenShot);
1577
1578 /* Finally copy the screen-shot to pause-pixmap: */
1579 m_pausePixmap = QPixmap::fromImage(screenShot);
1580
1581 /* Take the device-pixel-ratio into account: */
1582 m_pausePixmap.setDevicePixelRatio(frameBuffer()->devicePixelRatio());
1583
1584 /* Update scaled pause pixmap: */
1585 updateScaledPausePixmap();
1586}
1587
1588void UIMachineView::takePausePixmapSnapshot()
1589{
1590 /* Acquire the screen-data from the saved-state: */
1591 ulong uDummy = 0;
1592 QVector<BYTE> screenData;
1593 uimachine()->acquireSavedScreenshot(m_uScreenId, KBitmapFormat_PNG, uDummy, uDummy, screenData);
1594 if (screenData.isEmpty())
1595 return;
1596
1597 /* Acquire the screen-data properties from the saved-state: */
1598 long iDummy = 0;
1599 ulong uGuestWidth = 0, uGuestHeight = 0;
1600 bool fDummy = true;
1601 uimachine()->acquireSavedGuestScreenInfo(m_uScreenId, iDummy, iDummy, uGuestWidth, uGuestHeight, fDummy);
1602
1603 /* Calculate effective size: */
1604 QSize effectiveSize = uGuestWidth > 0 ? QSize(uGuestWidth, uGuestHeight) : storedGuestScreenSizeHint();
1605
1606 /* Take the device-pixel-ratio into account: */
1607 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
1608 if (!frameBuffer()->useUnscaledHiDPIOutput() && dDevicePixelRatioActual != 1.0)
1609 effectiveSize *= dDevicePixelRatioActual;
1610
1611 /* Create a screen-shot on the basis of the screen-data we have in saved-state: */
1612 QImage screenShot = QImage::fromData(screenData.data(), screenData.size(), "PNG").scaled(effectiveSize);
1613
1614 /* Dim screen-shot if it is Ok: */
1615 if (!screenShot.isNull())
1616 dimImage(screenShot);
1617
1618 /* Finally copy the screen-shot to pause-pixmap: */
1619 m_pausePixmap = QPixmap::fromImage(screenShot);
1620
1621 /* Take the device-pixel-ratio into account: */
1622 m_pausePixmap.setDevicePixelRatio(frameBuffer()->devicePixelRatio());
1623
1624 /* Update scaled pause pixmap: */
1625 updateScaledPausePixmap();
1626}
1627
1628void UIMachineView::updateScaledPausePixmap()
1629{
1630 /* Make sure pause pixmap is not null: */
1631 if (pausePixmap().isNull())
1632 return;
1633
1634 /* Make sure scaled-size is not null: */
1635 QSize scaledSize = frameBuffer()->scaledSize();
1636 if (!scaledSize.isValid())
1637 return;
1638
1639 /* Take the device-pixel-ratio into account: */
1640 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
1641 if (!frameBuffer()->useUnscaledHiDPIOutput() && dDevicePixelRatioActual != 1.0)
1642 scaledSize *= dDevicePixelRatioActual;
1643
1644 /* Update pause pixmap finally: */
1645 m_pausePixmapScaled = pausePixmap().scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1646
1647 /* Take the device-pixel-ratio into account: */
1648 m_pausePixmapScaled.setDevicePixelRatio(frameBuffer()->devicePixelRatio());
1649}
1650
1651void UIMachineView::updateSliders()
1652{
1653 /* Make sure framebuffer still present: */
1654 if (!frameBuffer())
1655 return;
1656
1657 /* Get current viewport size: */
1658 QSize curViewportSize = viewport()->size();
1659 /* Get maximum viewport size: */
1660 const QSize maxViewportSize = maximumViewportSize();
1661 /* Get current frame-buffer size: */
1662 QSize frameBufferSize = QSize(frameBuffer()->width(), frameBuffer()->height());
1663
1664 /* Take the scale-factor(s) into account: */
1665 frameBufferSize = scaledForward(frameBufferSize);
1666
1667 /* If maximum viewport size can cover whole frame-buffer => no scroll-bars required: */
1668 if (maxViewportSize.expandedTo(frameBufferSize) == maxViewportSize)
1669 curViewportSize = maxViewportSize;
1670
1671 /* What length we want scroll-bars of? */
1672 int xRange = frameBufferSize.width() - curViewportSize.width();
1673 int yRange = frameBufferSize.height() - curViewportSize.height();
1674
1675 /* Take the device-pixel-ratio into account: */
1676 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
1677 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
1678 xRange *= dDevicePixelRatioFormal;
1679 yRange *= dDevicePixelRatioFormal;
1680 if (!frameBuffer()->useUnscaledHiDPIOutput())
1681 {
1682 xRange /= dDevicePixelRatioActual;
1683 yRange /= dDevicePixelRatioActual;
1684 }
1685
1686 /* Configure scroll-bars: */
1687 horizontalScrollBar()->setRange(0, xRange);
1688 verticalScrollBar()->setRange(0, yRange);
1689 horizontalScrollBar()->setPageStep(curViewportSize.width());
1690 verticalScrollBar()->setPageStep(curViewportSize.height());
1691}
1692
1693void UIMachineView::dimImage(QImage &img)
1694{
1695 for (int y = 0; y < img.height(); ++ y)
1696 {
1697 if (y % 2)
1698 {
1699 if (img.depth() == 32)
1700 {
1701 for (int x = 0; x < img.width(); ++ x)
1702 {
1703 int gray = qGray(img.pixel (x, y)) / 2;
1704 img.setPixel(x, y, qRgb (gray, gray, gray));
1705 }
1706 }
1707 else
1708 {
1709 ::memset(img.scanLine (y), 0, img.bytesPerLine());
1710 }
1711 }
1712 else
1713 {
1714 if (img.depth() == 32)
1715 {
1716 for (int x = 0; x < img.width(); ++ x)
1717 {
1718 int gray = (2 * qGray (img.pixel (x, y))) / 3;
1719 img.setPixel(x, y, qRgb (gray, gray, gray));
1720 }
1721 }
1722 }
1723 }
1724}
1725
1726void UIMachineView::scrollContentsBy(int dx, int dy)
1727{
1728 /* Call to base-class: */
1729 QAbstractScrollArea::scrollContentsBy(dx, dy);
1730
1731 /* Update console's display viewport and 3D overlay: */
1732 updateViewport();
1733}
1734
1735#ifdef VBOX_WS_MAC
1736void UIMachineView::updateDockIcon()
1737{
1738 machineLogic()->updateDockIcon();
1739}
1740
1741CGImageRef UIMachineView::frameBuffertoCGImageRef(UIFrameBuffer *pFrameBuffer)
1742{
1743 CGImageRef ir = 0;
1744 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
1745 if (cs)
1746 {
1747 /* Create the image copy of the framebuffer */
1748 CGDataProviderRef dp = CGDataProviderCreateWithData(pFrameBuffer, pFrameBuffer->address(), pFrameBuffer->bitsPerPixel() / 8 * pFrameBuffer->width() * pFrameBuffer->height(), NULL);
1749 if (dp)
1750 {
1751 ir = CGImageCreate(pFrameBuffer->width(), pFrameBuffer->height(), 8, 32, pFrameBuffer->bytesPerLine(), cs,
1752 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host, dp, 0, false,
1753 kCGRenderingIntentDefault);
1754 CGDataProviderRelease(dp);
1755 }
1756 CGColorSpaceRelease(cs);
1757 }
1758 return ir;
1759}
1760#endif /* VBOX_WS_MAC */
1761
1762bool UIMachineView::isFullscreenOrSeamless() const
1763{
1764 return visualStateType() == UIVisualStateType_Fullscreen
1765 || visualStateType() == UIVisualStateType_Seamless;
1766}
1767
1768bool UIMachineView::event(QEvent *pEvent)
1769{
1770 switch ((UIEventType)pEvent->type())
1771 {
1772#ifdef VBOX_WS_MAC
1773 /* Event posted OnShowWindow: */
1774 case ShowWindowEventType:
1775 {
1776 /* Dunno what Qt3 thinks a window that has minimized to the dock should be - it is not hidden,
1777 * neither is it minimized. OTOH it is marked shown and visible, but not activated.
1778 * This latter isn't of much help though, since at this point nothing is marked activated.
1779 * I might have overlooked something, but I'm buggered what if I know what. So, I'll just always
1780 * show & activate the stupid window to make it get out of the dock when the user wishes to show a VM: */
1781 window()->show();
1782 window()->activateWindow();
1783 return true;
1784 }
1785#endif /* VBOX_WS_MAC */
1786
1787 default:
1788 break;
1789 }
1790
1791 return QAbstractScrollArea::event(pEvent);
1792}
1793
1794bool UIMachineView::eventFilter(QObject *pWatched, QEvent *pEvent)
1795{
1796 if (pWatched == viewport())
1797 {
1798 switch (pEvent->type())
1799 {
1800 case QEvent::Resize:
1801 {
1802 /* Notify framebuffer about viewport resize: */
1803 QResizeEvent *pResizeEvent = static_cast<QResizeEvent*>(pEvent);
1804 if (frameBuffer())
1805 frameBuffer()->viewportResized(pResizeEvent);
1806 /* Update console's display viewport and 3D overlay: */
1807 updateViewport();
1808 break;
1809 }
1810 default:
1811 break;
1812 }
1813 }
1814
1815 if (pWatched == this)
1816 {
1817 switch (pEvent->type())
1818 {
1819 case QEvent::Move:
1820 {
1821 /* Update console's display viewport and 3D overlay: */
1822 updateViewport();
1823 break;
1824 }
1825 default:
1826 break;
1827 }
1828 }
1829
1830 if (pWatched == machineWindow())
1831 {
1832 switch (pEvent->type())
1833 {
1834 case QEvent::WindowStateChange:
1835 {
1836 /* During minimizing and state restoring machineWindow() gets
1837 * the focus which belongs to console view window, so returning it properly. */
1838 QWindowStateChangeEvent *pWindowEvent = static_cast<QWindowStateChangeEvent*>(pEvent);
1839 if (pWindowEvent->oldState() & Qt::WindowMinimized)
1840 {
1841 if (QApplication::focusWidget())
1842 {
1843 QApplication::focusWidget()->clearFocus();
1844 qApp->processEvents();
1845 }
1846 QTimer::singleShot(0, this, SLOT(setFocus()));
1847 }
1848 break;
1849 }
1850 case QEvent::Move:
1851 {
1852 /* Get current host-screen number: */
1853 const int iCurrentHostScreenNumber = UIDesktopWidgetWatchdog::screenNumber(this);
1854 if (m_iHostScreenNumber != iCurrentHostScreenNumber)
1855 {
1856 /* Recache current host screen: */
1857 m_iHostScreenNumber = iCurrentHostScreenNumber;
1858 /* Reapply machine-view scale-factor if necessary: */
1859 applyMachineViewScaleFactor();
1860 /* For 'normal'/'scaled' visual state type: */
1861 if ( visualStateType() == UIVisualStateType_Normal
1862 || visualStateType() == UIVisualStateType_Scale)
1863 {
1864 /* Make sure action-pool is of 'runtime' type: */
1865 UIActionPoolRuntime *pActionPool = actionPool() && actionPool()->toRuntime() ? actionPool()->toRuntime() : 0;
1866 AssertPtr(pActionPool);
1867 if (pActionPool)
1868 {
1869 /* Inform action-pool about current guest-to-host screen mapping: */
1870 QMap<int, int> screenMap = pActionPool->hostScreenForGuestScreenMap();
1871 screenMap[m_uScreenId] = m_iHostScreenNumber;
1872 pActionPool->setHostScreenForGuestScreenMap(screenMap);
1873 }
1874 }
1875 }
1876 break;
1877 }
1878 default:
1879 break;
1880 }
1881 }
1882
1883 return QAbstractScrollArea::eventFilter(pWatched, pEvent);
1884}
1885
1886void UIMachineView::resizeEvent(QResizeEvent *pEvent)
1887{
1888 updateSliders();
1889 return QAbstractScrollArea::resizeEvent(pEvent);
1890}
1891
1892void UIMachineView::moveEvent(QMoveEvent *pEvent)
1893{
1894 return QAbstractScrollArea::moveEvent(pEvent);
1895}
1896
1897void UIMachineView::paintEvent(QPaintEvent *pPaintEvent)
1898{
1899 /* Use pause-image if exists: */
1900 if (!pausePixmap().isNull())
1901 {
1902 /* Create viewport painter: */
1903 QPainter painter(viewport());
1904 /* Avoid painting more than necessary: */
1905 painter.setClipRect(pPaintEvent->rect());
1906 /* Can be NULL when the event arrive during COM cleanup: */
1907 UIFrameBuffer *pFramebuffer = frameBuffer();
1908 /* Take the scale-factor into account: */
1909 if ( pFramebuffer
1910 ? pFramebuffer->scaleFactor() == 1.0 && !pFramebuffer->scaledSize().isValid()
1911 : pausePixmapScaled().isNull())
1912 painter.drawPixmap(viewport()->rect().topLeft(), pausePixmap());
1913 else
1914 painter.drawPixmap(viewport()->rect().topLeft(), pausePixmapScaled());
1915#ifdef VBOX_WS_MAC
1916 /* Update the dock icon: */
1917 updateDockIcon();
1918#endif /* VBOX_WS_MAC */
1919 return;
1920 }
1921
1922 /* Delegate the paint function to the UIFrameBuffer interface: */
1923 if (frameBuffer())
1924 frameBuffer()->handlePaintEvent(pPaintEvent);
1925#ifdef VBOX_WS_MAC
1926 /* Update the dock icon if we are in the running state: */
1927 if (uimachine()->isRunning())
1928 updateDockIcon();
1929#endif /* VBOX_WS_MAC */
1930}
1931
1932void UIMachineView::focusInEvent(QFocusEvent *pEvent)
1933{
1934 /* Call to base-class: */
1935 QAbstractScrollArea::focusInEvent(pEvent);
1936
1937 /* If native event filter isn't exists: */
1938 if (!m_pNativeEventFilter)
1939 {
1940 /* Create/install new native event filter: */
1941 m_pNativeEventFilter = new UINativeEventFilter(this);
1942 qApp->installNativeEventFilter(m_pNativeEventFilter);
1943 }
1944}
1945
1946void UIMachineView::focusOutEvent(QFocusEvent *pEvent)
1947{
1948 /* If native event filter exists: */
1949 if (m_pNativeEventFilter)
1950 {
1951 /* Uninstall/destroy existing native event filter: */
1952 qApp->removeNativeEventFilter(m_pNativeEventFilter);
1953 delete m_pNativeEventFilter;
1954 m_pNativeEventFilter = 0;
1955 }
1956
1957 /* Call to base-class: */
1958 QAbstractScrollArea::focusOutEvent(pEvent);
1959}
1960#ifdef VBOX_WS_NIX
1961void UIMachineView::keyPressEvent(QKeyEvent *pEvent)
1962{
1963 if (uiCommon().displayServerType() == VBGHDISPLAYSERVERTYPE_PURE_WAYLAND)
1964 uimachine()->putScancode(pEvent->nativeScanCode() - 8);
1965 QAbstractScrollArea::keyPressEvent(pEvent);
1966}
1967
1968void UIMachineView::keyReleaseEvent(QKeyEvent *pEvent)
1969{
1970 if (uiCommon().displayServerType() == VBGHDISPLAYSERVERTYPE_PURE_WAYLAND)
1971 uimachine()->putScancode((pEvent->nativeScanCode() - 8) | 0x80);
1972 QAbstractScrollArea::keyReleaseEvent(pEvent);
1973}
1974#endif
1975#ifdef VBOX_WITH_DRAG_AND_DROP
1976
1977bool UIMachineView::dragAndDropCanAccept() const
1978{
1979 bool fAccept = m_pDnDHandler;
1980# ifdef VBOX_WITH_DRAG_AND_DROP_GH
1981 if (fAccept)
1982 fAccept = !m_fIsDraggingFromGuest;
1983# endif
1984 if (fAccept)
1985 {
1986 KDnDMode enmDnDMode = KDnDMode_Disabled;
1987 uimachine()->acquireDnDMode(enmDnDMode);
1988 fAccept = enmDnDMode != KDnDMode_Disabled;
1989 }
1990 return fAccept;
1991}
1992
1993bool UIMachineView::dragAndDropIsActive() const
1994{
1995 bool fActive = m_pDnDHandler;
1996 if (fActive)
1997 {
1998 KDnDMode enmDnDMode = KDnDMode_Disabled;
1999 uimachine()->acquireDnDMode(enmDnDMode);
2000 fActive = enmDnDMode != KDnDMode_Disabled;
2001 }
2002 return fActive;
2003}
2004
2005void UIMachineView::dragEnterEvent(QDragEnterEvent *pEvent)
2006{
2007 AssertPtrReturnVoid(pEvent);
2008
2009 int rc = dragAndDropCanAccept() ? VINF_SUCCESS : VERR_ACCESS_DENIED;
2010 if (RT_SUCCESS(rc))
2011 {
2012 /* Get mouse-pointer location. */
2013 const QPoint &cpnt = viewportToContents(pEvent->pos());
2014
2015 /* Ask the target for starting a DnD event. */
2016 Qt::DropAction result = m_pDnDHandler->dragEnter(screenId(),
2017 frameBuffer()->convertHostXTo(cpnt.x()),
2018 frameBuffer()->convertHostYTo(cpnt.y()),
2019 pEvent->proposedAction(),
2020 pEvent->possibleActions(),
2021 pEvent->mimeData());
2022
2023 /* Set the DnD action returned by the guest. */
2024 pEvent->setDropAction(result);
2025 pEvent->accept();
2026 }
2027
2028 DNDDEBUG(("DnD: dragEnterEvent ended with rc=%Rrc\n", rc));
2029}
2030
2031void UIMachineView::dragMoveEvent(QDragMoveEvent *pEvent)
2032{
2033 AssertPtrReturnVoid(pEvent);
2034
2035 int rc = dragAndDropCanAccept() ? VINF_SUCCESS : VERR_ACCESS_DENIED;
2036 if (RT_SUCCESS(rc))
2037 {
2038 /* Get mouse-pointer location. */
2039 const QPoint &cpnt = viewportToContents(pEvent->pos());
2040
2041 /* Ask the guest for moving the drop cursor. */
2042 Qt::DropAction result = m_pDnDHandler->dragMove(screenId(),
2043 frameBuffer()->convertHostXTo(cpnt.x()),
2044 frameBuffer()->convertHostYTo(cpnt.y()),
2045 pEvent->proposedAction(),
2046 pEvent->possibleActions(),
2047 pEvent->mimeData());
2048
2049 /* Set the DnD action returned by the guest. */
2050 pEvent->setDropAction(result);
2051 pEvent->accept();
2052 }
2053
2054 DNDDEBUG(("DnD: dragMoveEvent ended with rc=%Rrc\n", rc));
2055}
2056
2057void UIMachineView::dragLeaveEvent(QDragLeaveEvent *pEvent)
2058{
2059 AssertPtrReturnVoid(pEvent);
2060
2061 int rc = dragAndDropCanAccept() ? VINF_SUCCESS : VERR_ACCESS_DENIED;
2062 if (RT_SUCCESS(rc))
2063 {
2064 m_pDnDHandler->dragLeave(screenId());
2065
2066 pEvent->accept();
2067 }
2068
2069 DNDDEBUG(("DnD: dragLeaveEvent ended with rc=%Rrc\n", rc));
2070}
2071
2072void UIMachineView::dropEvent(QDropEvent *pEvent)
2073{
2074 AssertPtrReturnVoid(pEvent);
2075
2076 int rc = dragAndDropCanAccept() ? VINF_SUCCESS : VERR_ACCESS_DENIED;
2077 if (RT_SUCCESS(rc))
2078 {
2079 /* Get mouse-pointer location. */
2080 const QPoint &cpnt = viewportToContents(pEvent->pos());
2081
2082 /* Ask the guest for dropping data. */
2083 Qt::DropAction result = m_pDnDHandler->dragDrop(screenId(),
2084 frameBuffer()->convertHostXTo(cpnt.x()),
2085 frameBuffer()->convertHostYTo(cpnt.y()),
2086 pEvent->proposedAction(),
2087 pEvent->possibleActions(),
2088 pEvent->mimeData());
2089
2090 /* Set the DnD action returned by the guest. */
2091 pEvent->setDropAction(result);
2092 pEvent->accept();
2093 }
2094
2095 DNDDEBUG(("DnD: dropEvent ended with rc=%Rrc\n", rc));
2096}
2097
2098#endif /* VBOX_WITH_DRAG_AND_DROP */
2099
2100QSize UIMachineView::scaledForward(QSize size) const
2101{
2102 /* Take the scale-factor into account: */
2103 const double dScaleFactor = frameBuffer()->scaleFactor();
2104 if (dScaleFactor != 1.0)
2105 size = QSize((int)(size.width() * dScaleFactor), (int)(size.height() * dScaleFactor));
2106
2107 /* Take the device-pixel-ratio into account: */
2108 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
2109 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
2110 if (!frameBuffer()->useUnscaledHiDPIOutput())
2111 size = QSize(size.width() * dDevicePixelRatioActual, size.height() * dDevicePixelRatioActual);
2112 size = QSize(size.width() / dDevicePixelRatioFormal, size.height() / dDevicePixelRatioFormal);
2113
2114 /* Return result: */
2115 return size;
2116}
2117
2118QSize UIMachineView::scaledBackward(QSize size) const
2119{
2120 /* Take the device-pixel-ratio into account: */
2121 const double dDevicePixelRatioFormal = frameBuffer()->devicePixelRatio();
2122 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
2123 size = QSize(size.width() * dDevicePixelRatioFormal, size.height() * dDevicePixelRatioFormal);
2124 if (!frameBuffer()->useUnscaledHiDPIOutput())
2125 size = QSize(size.width() / dDevicePixelRatioActual, size.height() / dDevicePixelRatioActual);
2126
2127 /* Take the scale-factor into account: */
2128 const double dScaleFactor = frameBuffer()->scaleFactor();
2129 if (dScaleFactor != 1.0)
2130 size = QSize((int)(size.width() / dScaleFactor), (int)(size.height() / dScaleFactor));
2131
2132 /* Return result: */
2133 return size;
2134}
2135
2136void UIMachineView::updateMousePointerPixmapScaling(QPixmap &pixmap, uint &uXHot, uint &uYHot)
2137{
2138#if defined(VBOX_WS_MAC)
2139
2140 /* Take into account scale-factor if necessary: */
2141 const double dScaleFactor = frameBuffer()->scaleFactor();
2142 //printf("Scale-factor: %f\n", dScaleFactor);
2143 if (dScaleFactor > 1.0)
2144 {
2145 /* Scale the pixmap up: */
2146 pixmap = pixmap.scaled(pixmap.width() * dScaleFactor, pixmap.height() * dScaleFactor,
2147 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2148 uXHot *= dScaleFactor;
2149 uYHot *= dScaleFactor;
2150 }
2151
2152 /* Take into account device-pixel-ratio if necessary: */
2153 const double dDevicePixelRatio = frameBuffer()->devicePixelRatio();
2154 const bool fUseUnscaledHiDPIOutput = frameBuffer()->useUnscaledHiDPIOutput();
2155 //printf("Device-pixel-ratio: %f, Unscaled HiDPI Output: %d\n",
2156 // dDevicePixelRatio, fUseUnscaledHiDPIOutput);
2157 if (dDevicePixelRatio > 1.0 && fUseUnscaledHiDPIOutput)
2158 {
2159 /* Scale the pixmap down: */
2160 pixmap.setDevicePixelRatio(dDevicePixelRatio);
2161 uXHot /= dDevicePixelRatio;
2162 uYHot /= dDevicePixelRatio;
2163 }
2164
2165#elif defined(VBOX_WS_WIN) || defined(VBOX_WS_NIX)
2166
2167 /* We want to scale the pixmap just once, so let's prepare cumulative multiplier: */
2168 double dScaleMultiplier = 1.0;
2169
2170 /* Take into account scale-factor if necessary: */
2171 const double dScaleFactor = frameBuffer()->scaleFactor();
2172 //printf("Scale-factor: %f\n", dScaleFactor);
2173 if (dScaleFactor > 1.0)
2174 dScaleMultiplier *= dScaleFactor;
2175
2176 /* Take into account device-pixel-ratio if necessary: */
2177# ifdef VBOX_WS_WIN
2178 const double dDevicePixelRatio = frameBuffer()->devicePixelRatio();
2179# endif
2180 const double dDevicePixelRatioActual = frameBuffer()->devicePixelRatioActual();
2181 const bool fUseUnscaledHiDPIOutput = frameBuffer()->useUnscaledHiDPIOutput();
2182 //printf("Device-pixel-ratio/actual: %f/%f, Unscaled HiDPI Output: %d\n",
2183 // dDevicePixelRatio, dDevicePixelRatioActual, fUseUnscaledHiDPIOutput);
2184 if (dDevicePixelRatioActual > 1.0 && !fUseUnscaledHiDPIOutput)
2185 dScaleMultiplier *= dDevicePixelRatioActual;
2186
2187 /* If scale multiplier was set: */
2188 if (dScaleMultiplier > 1.0)
2189 {
2190 /* Scale the pixmap up: */
2191 pixmap = pixmap.scaled(pixmap.width() * dScaleMultiplier, pixmap.height() * dScaleMultiplier,
2192 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2193 uXHot *= dScaleMultiplier;
2194 uYHot *= dScaleMultiplier;
2195 }
2196
2197# ifdef VBOX_WS_WIN
2198 /* If device pixel ratio was set: */
2199 if (dDevicePixelRatio > 1.0)
2200 {
2201 /* Scale the pixmap down: */
2202 pixmap.setDevicePixelRatio(dDevicePixelRatio);
2203 uXHot /= dDevicePixelRatio;
2204 uYHot /= dDevicePixelRatio;
2205 }
2206# endif
2207
2208#else
2209
2210 Q_UNUSED(pixmap);
2211 Q_UNUSED(uXHot);
2212 Q_UNUSED(uYHot);
2213
2214#endif
2215}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use