VirtualBox

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

Last change on this file was 106611, checked in by vboxsync, 6 weeks ago

FE/Qt: bugref:10407. Handle machine view key press event in keyboard handler.

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