VirtualBox

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

Last change on this file was 104358, checked in by vboxsync, 4 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.5 KB
Line 
1/* $Id: UIMouseHandler.cpp 104358 2024-04-18 05:33:40Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMouseHandler 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 <QApplication>
30#include <QtMath>
31#include <QMouseEvent>
32#include <QTimer>
33#include <QTouchEvent>
34
35/* GUI includes: */
36#include "UICommon.h"
37#include "UIDesktopWidgetWatchdog.h"
38#include "UIExtraDataManager.h"
39#include "UIFrameBuffer.h"
40#include "UIKeyboardHandler.h"
41#include "UILoggingDefs.h"
42#include "UIMachine.h"
43#include "UIMachineLogic.h"
44#include "UIMachineView.h"
45#include "UIMachineWindow.h"
46#include "UIMessageCenter.h"
47#include "UIMouseHandler.h"
48#include "UINotificationCenter.h"
49#ifdef VBOX_WS_MAC
50# include "VBoxUtils-darwin.h"
51# include "CocoaEventHelper.h"
52#endif
53#ifdef VBOX_WS_WIN
54# include "VBoxUtils-win.h"
55#endif
56#ifdef VBOX_WS_NIX
57# include "VBoxUtils-nix.h"
58#endif
59
60/* Other VBox includes: */
61#include <iprt/time.h>
62
63/* COM includes: */
64#include "KMouseButtonState.h"
65#include "KTouchContactState.h"
66
67/* Factory function to create mouse-handler: */
68UIMouseHandler* UIMouseHandler::create(UIMachineLogic *pMachineLogic,
69 UIVisualStateType visualStateType)
70{
71 /* Prepare mouse-handler: */
72 UIMouseHandler *pMouseHandler = 0;
73 /* Depending on visual-state type: */
74 switch (visualStateType)
75 {
76 /* For now all the states using common mouse-handler: */
77 case UIVisualStateType_Normal:
78 case UIVisualStateType_Fullscreen:
79 case UIVisualStateType_Seamless:
80 case UIVisualStateType_Scale:
81 pMouseHandler = new UIMouseHandler(pMachineLogic);
82 break;
83 default:
84 break;
85 }
86 /* Return prepared mouse-handler: */
87 return pMouseHandler;
88}
89
90/* Factory function to destroy mouse-handler: */
91void UIMouseHandler::destroy(UIMouseHandler *pMouseHandler)
92{
93 /* Delete mouse-handler: */
94 delete pMouseHandler;
95}
96
97/* Prepare listener for particular machine-window: */
98void UIMouseHandler::prepareListener(ulong uIndex, UIMachineWindow *pMachineWindow)
99{
100 /* If that window is NOT registered yet: */
101 if (!m_windows.contains(uIndex))
102 {
103 /* Register machine-window: */
104 m_windows.insert(uIndex, pMachineWindow);
105 /* Install event-filter for machine-window: */
106 m_windows[uIndex]->installEventFilter(this);
107 }
108
109 /* If that view is NOT registered yet: */
110 if (!m_views.contains(uIndex))
111 {
112 /* Register machine-view: */
113 m_views.insert(uIndex, pMachineWindow->machineView());
114 /* Install event-filter for machine-view: */
115 m_views[uIndex]->installEventFilter(this);
116 /* Make machine-view notify mouse-handler about mouse pointer shape change: */
117 connect(m_views[uIndex], &UIMachineView::sigMousePointerShapeChange, this, &UIMouseHandler::sltMousePointerShapeChanged);
118 /* Make machine-view notify mouse-handler about frame-buffer resize: */
119 connect(m_views[uIndex], &UIMachineView::sigFrameBufferResize, this, &UIMouseHandler::sltMousePointerShapeChanged);
120 }
121
122 /* If that viewport is NOT registered yet: */
123 if (!m_viewports.contains(uIndex))
124 {
125 /* Register machine-view-viewport: */
126 m_viewports.insert(uIndex, pMachineWindow->machineView()->viewport());
127 /* Install event-filter for machine-view-viewport: */
128 m_viewports[uIndex]->installEventFilter(this);
129 }
130}
131
132/* Cleanup listener for particular machine-window: */
133void UIMouseHandler::cleanupListener(ulong uIndex)
134{
135 /* Check if we should release mouse first: */
136 if ((int)uIndex == m_iMouseCaptureViewIndex)
137 releaseMouse();
138
139 /* If that window still registered: */
140 if (m_windows.contains(uIndex))
141 {
142 /* Unregister machine-window: */
143 m_windows.remove(uIndex);
144 }
145
146 /* If that view still registered: */
147 if (m_views.contains(uIndex))
148 {
149 /* Unregister machine-view: */
150 m_views.remove(uIndex);
151 }
152
153 /* If that viewport still registered: */
154 if (m_viewports.contains(uIndex))
155 {
156 /* Unregister machine-view-viewport: */
157 m_viewports.remove(uIndex);
158 }
159}
160
161void UIMouseHandler::captureMouse(ulong uScreenId)
162{
163 /* Do not try to capture mouse if its captured already: */
164 if (uimachine()->isMouseCaptured())
165 return;
166
167 /* If such viewport exists: */
168 if (m_viewports.contains(uScreenId))
169 {
170 /* Store mouse-capturing state value: */
171 uimachine()->setMouseCaptured(true);
172
173 /* Memorize the index of machine-view-viewport captured mouse: */
174 m_iMouseCaptureViewIndex = uScreenId;
175
176 /* Memorize the host position where the cursor was captured: */
177 m_capturedMousePos = QCursor::pos();
178 /* Determine geometry of screen cursor was captured at: */
179 m_capturedScreenGeo = gpDesktop->screenGeometry(m_capturedMousePos);
180
181 /* Acquiring visible viewport rectangle in global coodrinates: */
182 QRect visibleRectangle = m_viewports[m_iMouseCaptureViewIndex]->visibleRegion().boundingRect();
183 QPoint visibleRectanglePos = m_views[m_iMouseCaptureViewIndex]->mapToGlobal(m_viewports[m_iMouseCaptureViewIndex]->pos());
184 visibleRectangle.translate(visibleRectanglePos);
185 visibleRectangle = visibleRectangle.intersected(gpDesktop->availableGeometry(machineLogic()->machineWindows()[m_iMouseCaptureViewIndex]));
186
187#ifdef VBOX_WS_WIN
188 /* Move the mouse to the center of the visible area: */
189 m_lastMousePos = visibleRectangle.center();
190 QCursor::setPos(m_lastMousePos);
191 /* Update mouse clipping: */
192 updateMouseCursorClipping();
193#elif defined (VBOX_WS_MAC)
194 /* Grab all mouse events: */
195 ::darwinMouseGrab(m_viewports[m_iMouseCaptureViewIndex]);
196#else /* VBOX_WS_MAC */
197 /* Remember current mouse position: */
198 m_lastMousePos = QCursor::pos();
199 /* Grab all mouse events: */
200 m_viewports[m_iMouseCaptureViewIndex]->grabMouse();
201#endif /* !VBOX_WS_MAC */
202
203 /* Switch guest mouse to the relative mode: */
204 uimachine()->putMouseEvent(0, 0, 0, 0, 0);
205
206 /* Notify all the listeners: */
207 emit sigStateChange(state());
208 }
209}
210
211void UIMouseHandler::releaseMouse()
212{
213 /* Do not try to release mouse if its released already: */
214 if (!uimachine()->isMouseCaptured())
215 return;
216
217 /* If such viewport exists: */
218 if (m_viewports.contains(m_iMouseCaptureViewIndex))
219 {
220 /* Store mouse-capturing state value: */
221 uimachine()->setMouseCaptured(false);
222
223 /* Return the cursor to where it was when we captured it: */
224 QCursor::setPos(m_capturedMousePos);
225#ifdef VBOX_WS_WIN
226 /* Update mouse clipping: */
227 updateMouseCursorClipping();
228#elif defined(VBOX_WS_MAC)
229 /* Releasing grabbed mouse from that view: */
230 ::darwinMouseRelease(m_viewports[m_iMouseCaptureViewIndex]);
231#else /* VBOX_WS_MAC */
232 /* Releasing grabbed mouse from that view: */
233 m_viewports[m_iMouseCaptureViewIndex]->releaseMouse();
234#endif /* !VBOX_WS_MAC */
235 /* Reset mouse-capture index: */
236 m_iMouseCaptureViewIndex = -1;
237
238 /* Notify all the listeners: */
239 emit sigStateChange(state());
240 }
241}
242
243/* Setter for mouse-integration feature: */
244void UIMouseHandler::setMouseIntegrationEnabled(bool fEnabled)
245{
246 /* Do not do anything if its already done: */
247 if (uimachine()->isMouseIntegrated() == fEnabled)
248 return;
249
250 /* Store mouse-integration state value: */
251 uimachine()->setMouseIntegrated(fEnabled);
252
253 /* Reuse sltMouseCapabilityChanged() to update mouse state: */
254 sltMouseCapabilityChanged();
255}
256
257/* Current mouse state: */
258int UIMouseHandler::state() const
259{
260 return uimachine()->machineState() == KMachineState_Null ? 0 :
261 (uimachine()->isMouseCaptured() ? UIMouseStateType_MouseCaptured : 0) |
262 (uimachine()->isMouseSupportsAbsolute() ? UIMouseStateType_MouseAbsolute : 0) |
263 (uimachine()->isMouseIntegrated() ? 0 : UIMouseStateType_MouseAbsoluteDisabled);
264}
265
266bool UIMouseHandler::nativeEventFilter(void *pMessage, ulong uScreenId)
267{
268 /* Make sure view with passed index exists: */
269 if (!m_views.contains(uScreenId))
270 return false;
271
272 /* Check if some system event should be filtered out.
273 * Returning @c true means filtering-out,
274 * Returning @c false means passing event to Qt. */
275 bool fResult = false; /* Pass to Qt by default. */
276
277# if defined(VBOX_WS_MAC)
278
279 /* Acquire carbon event reference from the cocoa one: */
280 EventRef event = static_cast<EventRef>(darwinCocoaToCarbonEvent(pMessage));
281
282 /* Depending on event kind: */
283 const UInt32 uEventKind = ::GetEventKind(event);
284 switch (uEventKind)
285 {
286 /* Watch for button-events: */
287 case kEventMouseDown:
288 case kEventMouseUp:
289 {
290 /* Acquire button number: */
291 EventMouseButton enmButton = 0;
292 ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton,
293 NULL, sizeof(enmButton), NULL, &enmButton);
294 /* If the event comes for primary mouse button: */
295 if (enmButton == kEventMouseButtonPrimary)
296 {
297 /* Acquire modifiers: */
298 UInt32 uKeyModifiers = ~0U;
299 ::GetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
300 NULL, sizeof(uKeyModifiers), NULL, &uKeyModifiers);
301 /* If the event comes with Control modifier: */
302 if (uKeyModifiers == controlKey)
303 {
304 /* Replacing it with the stripped one: */
305 darwinPostStrippedMouseEvent(pMessage);
306 /* And filter out initial one: */
307 return true;
308 }
309 }
310 }
311 }
312
313# elif defined(VBOX_WS_WIN)
314
315 /* Nothing for now. */
316 RT_NOREF(pMessage, uScreenId);
317
318# elif defined(VBOX_WS_NIX)
319
320 if (uiCommon().X11ServerAvailable())
321 {
322 /* Cast to XCB event: */
323 xcb_generic_event_t *pEvent = static_cast<xcb_generic_event_t*>(pMessage);
324
325 /* Depending on event type: */
326 switch (pEvent->response_type & ~0x80)
327 {
328 /* Watch for button-events: */
329 case XCB_BUTTON_PRESS:
330 {
331 /* Do nothing if mouse is actively grabbed: */
332 if (uimachine()->isMouseCaptured())
333 break;
334
335 /* If we see a mouse press from a grab while the mouse is not captured,
336 * release the keyboard before letting the event owner see it. This is
337 * because some owners cannot deal with failures to grab the keyboard
338 * themselves (e.g. window managers dragging windows). */
339
340 /* Cast to XCB button-event: */
341 xcb_button_press_event_t *pButtonEvent = static_cast<xcb_button_press_event_t*>(pMessage);
342
343 /* If this event is from our button grab then it will be reported relative to the root
344 * window and not to ours. In that case release the keyboard capture, re-capture it
345 * delayed, which will fail if we have lost the input focus in the mean-time, replay
346 * the button event for normal delivery (possibly straight back to us, but not relative
347 * to root this time) and tell Qt not to further process this event: */
348 if (pButtonEvent->event == pButtonEvent->root)
349 {
350 machineLogic()->keyboardHandler()->releaseKeyboard();
351 /** @todo It would be nicer to do this in the normal Qt button event
352 * handler to avoid avoidable races if the event was not for us. */
353 machineLogic()->keyboardHandler()->captureKeyboard(uScreenId);
354 /* Re-send the event so that the window which it was meant for gets it: */
355 xcb_allow_events_checked(NativeWindowSubsystem::X11GetConnection(), XCB_ALLOW_REPLAY_POINTER, pButtonEvent->time);
356 /* Do not let Qt see the event: */
357 return true;
358 }
359 break;
360 }
361
362 default:
363 break;
364 }
365 }
366# else
367
368# warning "port me!"
369
370# endif
371
372 /* Return result: */
373 return fResult;
374}
375
376/* Machine state-change handler: */
377void UIMouseHandler::sltMachineStateChanged()
378{
379 /* Get cached machine states: */
380 const KMachineState enmState = uimachine()->machineState();
381
382 /* Handle particular machine states: */
383 switch (enmState)
384 {
385 case KMachineState_Paused:
386 case KMachineState_TeleportingPausedVM:
387 case KMachineState_Stuck:
388 {
389 /* Release the mouse: */
390 releaseMouse();
391 break;
392 }
393 default:
394 break;
395 }
396
397 /* Recall reminder about paused VM input
398 * if we are not in paused VM state already: */
399 if (machineLogic()->activeMachineWindow() &&
400 enmState != KMachineState_Paused &&
401 enmState != KMachineState_TeleportingPausedVM)
402 UINotificationMessage::forgetAboutPausedVMInput();
403
404 /* Notify all the listeners: */
405 emit sigStateChange(state());
406}
407
408/* Mouse capability-change handler: */
409void UIMouseHandler::sltMouseCapabilityChanged()
410{
411 /* If mouse supports absolute pointing and mouse-integration activated: */
412 if (uimachine()->isMouseSupportsAbsolute() && uimachine()->isMouseIntegrated())
413 {
414 /* Release the mouse: */
415 releaseMouse();
416 /* Also we should switch guest mouse to the absolute mode: */
417 uimachine()->putMouseEventAbsolute(-1, -1, 0, 0, 0);
418 }
419#if 0 /* current team's decision is NOT to capture mouse on mouse-absolute mode loosing! */
420 /* If mouse-integration deactivated or mouse doesn't supports absolute pointing: */
421 else
422 {
423 /* Search for the machine-view focused now: */
424 int iFocusedView = -1;
425 QList<ulong> screenIds = m_views.keys();
426 for (int i = 0; i < screenIds.size(); ++i)
427 {
428 if (m_views[screenIds[i]]->hasFocus())
429 {
430 iFocusedView = screenIds[i];
431 break;
432 }
433 }
434 /* If there is no focused view but views are present we will use the first one: */
435 if (iFocusedView == -1 && !screenIds.isEmpty())
436 iFocusedView = screenIds[0];
437 /* Capture mouse using that view: */
438 if (iFocusedView != -1)
439 captureMouse(iFocusedView);
440 }
441#else /* but just to switch the guest mouse into relative mode! */
442 /* If mouse-integration deactivated or mouse doesn't supports absolute pointing: */
443 else
444 {
445 /* Switch guest mouse to the relative mode: */
446 uimachine()->putMouseEvent(0, 0, 0, 0, 0);
447 }
448#endif
449
450 /* Notify user whether mouse supports absolute pointing
451 * if that method was called by corresponding signal: */
452 if (sender())
453 {
454 /* Do not annoy user while restoring VM: */
455 if (uimachine()->machineState() != KMachineState_Restoring)
456 UINotificationMessage::remindAboutMouseIntegration(uimachine()->isMouseSupportsAbsolute());
457 }
458
459 /* Notify all the listeners: */
460 emit sigStateChange(state());
461}
462
463/* Mouse pointer-shape-change handler: */
464void UIMouseHandler::sltMousePointerShapeChanged()
465{
466 /* First of all, we should check if the host pointer should be visible.
467 * We should hide host pointer in case of:
468 * 1. mouse is 'captured' or
469 * 2. machine is NOT 'paused' and mouse is NOT 'captured' and 'integrated' and 'absolute' but host pointer is 'hidden' by the guest. */
470 if (uimachine()->isMouseCaptured() ||
471 (!uimachine()->isPaused() &&
472 uimachine()->isMouseIntegrated() &&
473 uimachine()->isMouseSupportsAbsolute() &&
474 uimachine()->isHidingHostPointer()))
475 {
476 QList<ulong> screenIds = m_viewports.keys();
477 for (int i = 0; i < screenIds.size(); ++i)
478 m_viewports[screenIds[i]]->setCursor(Qt::BlankCursor);
479 }
480
481 else
482
483 /* Otherwise we should show host pointer with guest shape assigned to it if:
484 * machine is NOT 'paused', mouse is 'integrated' and 'absolute' and valid pointer shape is present. */
485 if (!uimachine()->isPaused() &&
486 uimachine()->isMouseIntegrated() &&
487 uimachine()->isMouseSupportsAbsolute() &&
488 uimachine()->isValidPointerShapePresent())
489 {
490 QList<ulong> screenIds = m_viewports.keys();
491 for (int i = 0; i < screenIds.size(); ++i)
492 m_viewports[screenIds[i]]->setCursor(m_views[screenIds[i]]->cursor());
493 }
494
495 else
496
497 /* There could be other states covering such situations as:
498 * 1. machine is 'paused' or
499 * 2. mouse is NOT 'captured' and 'integrated' but NOT 'absolute' or
500 * 3. mouse is NOT 'captured' and 'absolute' but NOT 'integrated'.
501 * We have nothing to do with that except just unset the cursor. */
502 {
503 QList<ulong> screenIds = m_viewports.keys();
504 for (int i = 0; i < screenIds.size(); ++i)
505 m_viewports[screenIds[i]]->unsetCursor();
506 }
507}
508
509void UIMouseHandler::sltMaybeActivateHoveredWindow()
510{
511 /* Are we still have hovered window to activate? */
512 if (m_pHoveredWindow && !m_pHoveredWindow->isActiveWindow())
513 {
514 /* Activate it: */
515 m_pHoveredWindow->activateWindow();
516#ifdef VBOX_WS_NIX
517 /* On X11 its not enough to just activate window if you
518 * want to raise it also, so we will make it separately: */
519 m_pHoveredWindow->raise();
520#endif /* VBOX_WS_NIX */
521 }
522}
523
524/* Mouse-handler constructor: */
525UIMouseHandler::UIMouseHandler(UIMachineLogic *pMachineLogic)
526 : QObject(pMachineLogic)
527 , m_pMachineLogic(pMachineLogic)
528 , m_iLastMouseWheelDelta(0)
529 , m_iMouseCaptureViewIndex(-1)
530#ifdef VBOX_WS_WIN
531 , m_fCursorPositionReseted(false)
532#endif
533{
534 /* Machine state-change updater: */
535 connect(uimachine(), &UIMachine::sigMachineStateChange, this, &UIMouseHandler::sltMachineStateChanged);
536
537 /* Mouse capability state-change updater: */
538 connect(uimachine(), &UIMachine::sigMouseCapabilityChange, this, &UIMouseHandler::sltMouseCapabilityChanged);
539 /* Mouse cursor position state-change updater: */
540 connect(uimachine(), &UIMachine::sigCursorPositionChange, this, &UIMouseHandler::sltMousePointerShapeChanged);
541
542 /* Mouse pointer shape state-change updater: */
543 connect(this, &UIMouseHandler::sigStateChange, this, &UIMouseHandler::sltMousePointerShapeChanged);
544
545 /* Initialize: */
546 sltMachineStateChanged();
547 sltMousePointerShapeChanged();
548 sltMouseCapabilityChanged();
549}
550
551/* Mouse-handler destructor: */
552UIMouseHandler::~UIMouseHandler()
553{
554}
555
556UIMachine *UIMouseHandler::uimachine() const
557{
558 return machineLogic()->uimachine();
559}
560
561/* Event handler for registered machine-view(s): */
562bool UIMouseHandler::eventFilter(QObject *pWatched, QEvent *pEvent)
563{
564 /* If that object is of QWidget type: */
565 if (QWidget *pWatchedWidget = qobject_cast<QWidget*>(pWatched))
566 {
567 /* Check if that widget is in windows list: */
568 if (m_windows.values().contains(pWatchedWidget))
569 {
570#ifdef VBOX_WS_WIN
571 /* Handle window events: */
572 switch (pEvent->type())
573 {
574 case QEvent::Move:
575 {
576 /* Update mouse clipping if window was moved
577 * by Operating System desktop manager: */
578 updateMouseCursorClipping();
579 break;
580 }
581 default:
582 break;
583 }
584#endif /* VBOX_WS_WIN */
585 }
586
587 else
588
589 /* Check if that widget is of UIMachineView type: */
590 if (UIMachineView *pWatchedMachineView = qobject_cast<UIMachineView*>(pWatchedWidget))
591 {
592 /* Check if that widget is in views list: */
593 if (m_views.values().contains(pWatchedMachineView))
594 {
595 /* Handle view events: */
596 switch (pEvent->type())
597 {
598 case QEvent::FocusOut:
599 {
600 /* Release the mouse: */
601 releaseMouse();
602 break;
603 }
604 default:
605 break;
606 }
607 }
608 }
609
610 else
611
612 /* Check if that widget is in viewports list: */
613 if (m_viewports.values().contains(pWatchedWidget))
614 {
615 /* Get current watched widget screen id: */
616 ulong uScreenId = m_viewports.key(pWatchedWidget);
617 /* Handle viewport events: */
618 switch (pEvent->type())
619 {
620#ifdef VBOX_WS_MAC
621 case UIGrabMouseEvent::GrabMouseEvent:
622 {
623 UIGrabMouseEvent *pDeltaEvent = static_cast<UIGrabMouseEvent*>(pEvent);
624 QPoint p = QPoint(pDeltaEvent->xDelta() + m_lastMousePos.x(),
625 pDeltaEvent->yDelta() + m_lastMousePos.y());
626 if (mouseEvent(pDeltaEvent->mouseEventType(), uScreenId,
627 m_viewports[uScreenId]->mapFromGlobal(p), p,
628 pDeltaEvent->buttons(),
629 pDeltaEvent->wheelDelta(), pDeltaEvent->orientation()))
630 return true;
631 break;
632 }
633#endif /* VBOX_WS_MAC */
634 case QEvent::MouseMove:
635 {
636#ifdef VBOX_WS_MAC
637 // WORKAROUND:
638 // Since we are handling mouse move/drag events in the same thread
639 // where we are painting guest content changes corresponding to those
640 // events, we can have input event queue overloaded with the move/drag
641 // events, so we should discard current one if there is subsequent already.
642 EventTypeSpec list[2];
643 list[0].eventClass = kEventClassMouse;
644 list[0].eventKind = kEventMouseMoved;
645 list[1].eventClass = kEventClassMouse;
646 list[1].eventKind = kEventMouseDragged;
647 if (AcquireFirstMatchingEventInQueue(GetCurrentEventQueue(), 2, list,
648 kEventQueueOptionsNone) != NULL)
649 return true;
650#endif /* VBOX_WS_MAC */
651
652 /* This event should be also processed using next 'case': */
653 }
654 RT_FALL_THRU();
655 case QEvent::MouseButtonRelease:
656 {
657 /* Get mouse-event: */
658 QMouseEvent *pOldMouseEvent = static_cast<QMouseEvent*>(pEvent);
659 const QPoint gPos = pOldMouseEvent->globalPosition().toPoint();
660
661 /* Check which viewport(s) we *probably* hover: */
662 QWidgetList probablyHoveredViewports;
663 foreach (QWidget *pViewport, m_viewports)
664 {
665 QPoint posInViewport = pViewport->mapFromGlobal(gPos);
666 if (pViewport->geometry().adjusted(0, 0, 1, 1).contains(posInViewport))
667 probablyHoveredViewports << pViewport;
668 }
669 /* Determine actually hovered viewport: */
670 QWidget *pHoveredWidget = probablyHoveredViewports.isEmpty() ? 0 :
671 probablyHoveredViewports.contains(pWatchedWidget) ? pWatchedWidget :
672 probablyHoveredViewports.first();
673
674 /* Check if we should propagate this event to another window: */
675 if (pHoveredWidget && pHoveredWidget != pWatchedWidget && m_viewports.values().contains(pHoveredWidget))
676 {
677 /* Prepare redirected mouse-move event: */
678 QMouseEvent *pNewMouseEvent = new QMouseEvent(pOldMouseEvent->type(),
679 pHoveredWidget->mapFromGlobal(gPos),
680 gPos,
681 pOldMouseEvent->button(),
682 pOldMouseEvent->buttons(),
683 pOldMouseEvent->modifiers());
684
685 /* Send that event to real destination: */
686 QApplication::postEvent(pHoveredWidget, pNewMouseEvent);
687
688 /* Filter out that event: */
689 return true;
690 }
691
692#ifdef VBOX_WS_NIX
693 /* Make sure that we are focused after a click. Rather
694 * ugly, but works around a problem with GNOME
695 * screensaver, which sometimes removes our input focus
696 * and gives us no way to get it back. */
697 if (pEvent->type() == QEvent::MouseButtonRelease)
698 pWatchedWidget->window()->activateWindow();
699#endif /* VBOX_WS_NIX */
700 /* Check if we should activate window under cursor: */
701 if (gEDataManager->activateHoveredMachineWindow() &&
702 !uimachine()->isMouseCaptured() &&
703 QApplication::activeWindow() &&
704 m_windows.values().contains(QApplication::activeWindow()) &&
705 m_windows.values().contains(pWatchedWidget->window()) &&
706 QApplication::activeWindow() != pWatchedWidget->window())
707 {
708 /* Put request for hovered window activation in 300msec: */
709 m_pHoveredWindow = pWatchedWidget->window();
710 QTimer::singleShot(300, this, SLOT(sltMaybeActivateHoveredWindow()));
711 }
712 else
713 {
714 /* Revoke request for hovered window activation: */
715 m_pHoveredWindow = 0;
716 }
717
718 /* This event should be also processed using next 'case': */
719 }
720 RT_FALL_THRU();
721 case QEvent::MouseButtonPress:
722 case QEvent::MouseButtonDblClick:
723 {
724 QMouseEvent *pMouseEvent = static_cast<QMouseEvent*>(pEvent);
725#ifdef VBOX_WS_NIX
726 /* When the keyboard is captured, we also capture mouse button
727 * events, and release the keyboard and re-capture it delayed
728 * on every mouse click. When the click is inside our window
729 * area though the delay is not needed or wanted. Calling
730 * finaliseCaptureKeyboard() skips the delay if a delayed
731 * capture is in progress and has no effect if not: */
732 if (pEvent->type() == QEvent::MouseButtonPress)
733 machineLogic()->keyboardHandler()->finaliseCaptureKeyboard();
734#endif /* VBOX_WS_NIX */
735
736 /* For various mouse click related events
737 * we also reset last mouse wheel delta: */
738 if (pEvent->type() != QEvent::MouseMove)
739 m_iLastMouseWheelDelta = 0;
740
741 if (mouseEvent(pMouseEvent->type(), uScreenId,
742 pMouseEvent->position().toPoint(), pMouseEvent->globalPosition().toPoint(),
743 pMouseEvent->buttons(), 0, Qt::Horizontal))
744 return true;
745 break;
746 }
747 case QEvent::TouchBegin:
748 case QEvent::TouchUpdate:
749 case QEvent::TouchEnd:
750 {
751 if (uimachine()->isMouseSupportsTouchScreen() || uimachine()->isMouseSupportsTouchPad())
752 return multiTouchEvent(static_cast<QTouchEvent*>(pEvent), uScreenId);
753 break;
754 }
755 case QEvent::Wheel:
756 {
757 QWheelEvent *pWheelEvent = static_cast<QWheelEvent*>(pEvent);
758 /* There are pointing devices which send smaller values for the delta than 120.
759 * Here we sum them up until we are greater than 120. This allows to have finer control
760 * over the speed acceleration & enables such devices to send a valid wheel event to our
761 * guest mouse device at all: */
762 int iDelta = 0;
763 const Qt::Orientation enmOrientation = qFabs(pWheelEvent->angleDelta().x())
764 > qFabs(pWheelEvent->angleDelta().y())
765 ? Qt::Horizontal
766 : Qt::Vertical;
767 m_iLastMouseWheelDelta += enmOrientation == Qt::Horizontal
768 ? pWheelEvent->angleDelta().x()
769 : pWheelEvent->angleDelta().y();
770 if (qAbs(m_iLastMouseWheelDelta) >= 120)
771 {
772 /* Rounding iDelta to the nearest multiple of 120: */
773 iDelta = m_iLastMouseWheelDelta / 120;
774 iDelta *= 120;
775 m_iLastMouseWheelDelta = m_iLastMouseWheelDelta % 120;
776 }
777 if (mouseEvent(pWheelEvent->type(),
778 uScreenId,
779#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
780 pWheelEvent->position().toPoint(),
781 pWheelEvent->globalPosition().toPoint(),
782#else
783 pWheelEvent->pos(),
784 pWheelEvent->globalPos(),
785#endif
786#ifdef VBOX_WS_MAC
787 // WORKAROUND:
788 // Qt Cocoa is buggy. It always reports a left button pressed when the
789 // mouse wheel event occurs. A workaround is to ask the application which
790 // buttons are pressed currently:
791 QApplication::mouseButtons(),
792#else /* !VBOX_WS_MAC */
793 pWheelEvent->buttons(),
794#endif /* !VBOX_WS_MAC */
795 iDelta,
796 enmOrientation)
797 )
798 return true;
799 break;
800 }
801#ifdef VBOX_WS_MAC
802 case QEvent::Leave:
803 {
804 /* Enable mouse event compression if we leave the VM view.
805 * This is necessary for having smooth resizing of the VM/other windows: */
806 ::darwinSetMouseCoalescingEnabled(true);
807 break;
808 }
809 case QEvent::Enter:
810 {
811 /* Disable mouse event compression if we enter the VM view.
812 * So all mouse events are registered in the VM.
813 * Only do this if the keyboard/mouse is grabbed
814 * (this is when we have a valid event handler): */
815 if (machineLogic()->keyboardHandler()->isKeyboardGrabbed())
816 darwinSetMouseCoalescingEnabled(false);
817 break;
818 }
819#endif /* VBOX_WS_MAC */
820#ifdef VBOX_WS_WIN
821 case QEvent::Resize:
822 {
823 /* Update mouse clipping: */
824 updateMouseCursorClipping();
825 break;
826 }
827#endif /* VBOX_WS_WIN */
828 default:
829 break;
830 }
831 }
832 }
833 return QObject::eventFilter(pWatched, pEvent);
834}
835
836/* Try to detect if the mouse event is fake and actually generated by a touch device. */
837#ifdef VBOX_WS_WIN
838#if (WINVER < 0x0601)
839typedef enum tagINPUT_MESSAGE_DEVICE_TYPE {
840 IMDT_UNAVAILABLE = 0, // 0x0
841 IMDT_KEYBOARD = 1, // 0x1
842 IMDT_MOUSE = 2, // 0x2
843 IMDT_TOUCH = 4, // 0x4
844 IMDT_PEN = 8 // 0x8
845} INPUT_MESSAGE_DEVICE_TYPE;
846
847typedef enum tagINPUT_MESSAGE_ORIGIN_ID {
848 IMO_UNAVAILABLE = 0x00000000,
849 IMO_HARDWARE = 0x00000001,
850 IMO_INJECTED = 0x00000002,
851 IMO_SYSTEM = 0x00000004
852} INPUT_MESSAGE_ORIGIN_ID;
853
854typedef struct tagINPUT_MESSAGE_SOURCE {
855 INPUT_MESSAGE_DEVICE_TYPE deviceType;
856 INPUT_MESSAGE_ORIGIN_ID originId;
857} INPUT_MESSAGE_SOURCE;
858#endif /* WINVER < 0x0601 */
859
860#define MOUSEEVENTF_FROMTOUCH 0xFF515700
861#define MOUSEEVENTF_MASK 0xFFFFFF00
862
863typedef BOOL WINAPI FNGetCurrentInputMessageSource(INPUT_MESSAGE_SOURCE *inputMessageSource);
864typedef FNGetCurrentInputMessageSource *PFNGetCurrentInputMessageSource;
865
866static bool mouseIsTouchSource(int iEventType, Qt::MouseButtons mouseButtons)
867{
868 NOREF(mouseButtons);
869
870 static PFNGetCurrentInputMessageSource pfnGetCurrentInputMessageSource = (PFNGetCurrentInputMessageSource)-1;
871 if (pfnGetCurrentInputMessageSource == (PFNGetCurrentInputMessageSource)-1)
872 {
873 HMODULE hUser = GetModuleHandle(L"user32.dll");
874 if (hUser)
875 pfnGetCurrentInputMessageSource =
876 (PFNGetCurrentInputMessageSource)GetProcAddress(hUser, "GetCurrentInputMessageSource");
877 }
878
879 int deviceType = -1;
880 if (pfnGetCurrentInputMessageSource)
881 {
882 INPUT_MESSAGE_SOURCE inputMessageSource;
883 BOOL fSuccess = pfnGetCurrentInputMessageSource(&inputMessageSource);
884 deviceType = fSuccess? inputMessageSource.deviceType: -2;
885 }
886 else
887 {
888 if ( iEventType == QEvent::MouseButtonPress
889 || iEventType == QEvent::MouseButtonRelease
890 || iEventType == QEvent::MouseMove)
891 deviceType = (GetMessageExtraInfo() & MOUSEEVENTF_MASK) == MOUSEEVENTF_FROMTOUCH? IMDT_TOUCH: -3;
892 }
893
894 LogRelFlow(("mouseIsTouchSource: deviceType %d\n", deviceType));
895 return deviceType == IMDT_TOUCH || deviceType == IMDT_PEN;
896}
897#else
898/* Apparently VBOX_WS_MAC does not generate fake mouse events.
899 * Other platforms, which have no known method to detect fake events are handled here too.
900 */
901static bool mouseIsTouchSource(int iEventType, Qt::MouseButtons mouseButtons)
902{
903 NOREF(iEventType);
904 NOREF(mouseButtons);
905 return false;
906}
907#endif
908
909/* Separate function to handle most of existing mouse-events: */
910bool UIMouseHandler::mouseEvent(int iEventType, ulong uScreenId,
911 const QPoint &relativePos, const QPoint &globalPos,
912 Qt::MouseButtons mouseButtons,
913 int wheelDelta, Qt::Orientation wheelDirection)
914{
915 /* Ignore fake mouse events. */
916 if ( (uimachine()->isMouseSupportsTouchScreen() || uimachine()->isMouseSupportsTouchPad())
917 && mouseIsTouchSource(iEventType, mouseButtons))
918 return true;
919
920 /* Check if machine is still running: */
921 if (!uimachine()->isRunning())
922 return true;
923
924 /* Check if such view & viewport are registered: */
925 if (!m_views.contains(uScreenId) || !m_viewports.contains(uScreenId))
926 return true;
927
928 int iMouseButtonsState = 0;
929 if (mouseButtons & Qt::LeftButton)
930 iMouseButtonsState |= KMouseButtonState_LeftButton;
931 if (mouseButtons & Qt::RightButton)
932 iMouseButtonsState |= KMouseButtonState_RightButton;
933 if (mouseButtons & Qt::MiddleButton)
934 iMouseButtonsState |= KMouseButtonState_MiddleButton;
935 if (mouseButtons & Qt::XButton1)
936 iMouseButtonsState |= KMouseButtonState_XButton1;
937 if (mouseButtons & Qt::XButton2)
938 iMouseButtonsState |= KMouseButtonState_XButton2;
939
940#ifdef VBOX_WS_MAC
941 /* Simulate the right click on host-key + left-mouse-button: */
942 if (machineLogic()->keyboardHandler()->isHostKeyPressed() &&
943 machineLogic()->keyboardHandler()->isHostKeyAlone() &&
944 iMouseButtonsState == KMouseButtonState_LeftButton)
945 iMouseButtonsState = KMouseButtonState_RightButton;
946#endif /* VBOX_WS_MAC */
947
948 int iWheelVertical = 0;
949 int iWheelHorizontal = 0;
950 if (wheelDirection == Qt::Vertical)
951 {
952 /* The absolute value of wheel delta is 120 units per every wheel move;
953 * positive deltas correspond to counterclockwise rotations (usually up),
954 * negative deltas correspond to clockwise (usually down). */
955 iWheelVertical = - (wheelDelta / 120);
956 }
957 else if (wheelDirection == Qt::Horizontal)
958 iWheelHorizontal = wheelDelta / 120;
959
960 if (uimachine()->isMouseCaptured())
961 {
962#ifdef VBOX_WS_WIN
963 /* Send pending WM_PAINT events: */
964 ::UpdateWindow((HWND)m_viewports[uScreenId]->winId());
965#endif
966
967#ifdef VBOX_WS_WIN
968 // WORKAROUND:
969 // There are situations at least on Windows host that we are receiving
970 // previously posted (but not yet handled) mouse event right after we
971 // have manually teleported mouse cursor to simulate infinite movement,
972 // this makes cursor blink for a large amount of space, so we should
973 // ignore such blinks .. well, at least once.
974 const QPoint shiftingSpace = globalPos - m_lastMousePos;
975 if (m_fCursorPositionReseted && shiftingSpace.manhattanLength() >= 10)
976 {
977 m_fCursorPositionReseted = false;
978 return true;
979 }
980#endif
981
982 /* Pass event to the guest: */
983 uimachine()->putMouseEvent(globalPos.x() - m_lastMousePos.x(),
984 globalPos.y() - m_lastMousePos.y(),
985 iWheelVertical, iWheelHorizontal, iMouseButtonsState);
986
987#ifdef VBOX_WS_WIN
988 /* Compose viewport-rectangle in local coordinates: */
989 QRect viewportRectangle = m_mouseCursorClippingRect;
990 QPoint viewportRectangleGlobalPos = m_views[uScreenId]->mapToGlobal(m_viewports[uScreenId]->pos());
991 viewportRectangle.translate(-viewportRectangleGlobalPos);
992
993 /* Compose boundaries: */
994 const int iX1 = viewportRectangle.left() + 1;
995 const int iY1 = viewportRectangle.top() + 1;
996 const int iX2 = viewportRectangle.right() - 1;
997 const int iY2 = viewportRectangle.bottom() - 1;
998
999 /* Simulate infinite movement: */
1000 QPoint p = relativePos;
1001 if (relativePos.x() <= iX1)
1002 p.setX(iX2 - 1);
1003 else if (relativePos.x() >= iX2)
1004 p.setX(iX1 + 1);
1005 if (relativePos.y() <= iY1)
1006 p.setY(iY2 - 1);
1007 else if (relativePos.y() >= iY2)
1008 p.setY(iY1 + 1);
1009 if (p != relativePos)
1010 {
1011 // WORKAROUND:
1012 // Underlying QCursor::setPos call requires coordinates, rescaled according to primary screen.
1013 // For that we have to map logical coordinates to relative origin (to make logical=>physical conversion).
1014 // Besides that we have to make sure m_lastMousePos still uses logical coordinates afterwards.
1015 const double dDprPrimary = UIDesktopWidgetWatchdog::devicePixelRatio(UIDesktopWidgetWatchdog::primaryScreenNumber());
1016 const double dDprCurrent = UIDesktopWidgetWatchdog::devicePixelRatio(m_windows.value(m_iMouseCaptureViewIndex));
1017 const QRect screenGeometry = gpDesktop->screenGeometry(m_windows.value(m_iMouseCaptureViewIndex));
1018 QPoint requiredMousePos = (m_viewports[uScreenId]->mapToGlobal(p) - screenGeometry.topLeft()) * dDprCurrent + screenGeometry.topLeft();
1019 QCursor::setPos(requiredMousePos / dDprPrimary);
1020 m_lastMousePos = requiredMousePos / dDprCurrent;
1021 m_fCursorPositionReseted = true;
1022 }
1023 else
1024 {
1025 m_lastMousePos = globalPos;
1026 m_fCursorPositionReseted = false;
1027 }
1028#else /* !VBOX_WS_WIN */
1029 /* Compose boundaries: */
1030 const int iX1 = m_capturedScreenGeo.left() + 1;
1031 const int iY1 = m_capturedScreenGeo.top() + 1;
1032 const int iX2 = m_capturedScreenGeo.right() - 1;
1033 const int iY2 = m_capturedScreenGeo.bottom() - 1;
1034
1035 /* Simulate infinite movement: */
1036 QPoint p = globalPos;
1037 if (globalPos.x() <= iX1)
1038 p.setX(iX2 - 1);
1039 else if (globalPos.x() >= iX2)
1040 p.setX(iX1 + 1);
1041 if (globalPos.y() <= iY1)
1042 p.setY(iY2 - 1);
1043 else if (globalPos.y() >= iY2)
1044 p.setY(iY1 + 1);
1045
1046 if (p != globalPos)
1047 {
1048 m_lastMousePos = p;
1049 /* No need for cursor updating on the Mac, there is no one. */
1050# ifndef VBOX_WS_MAC
1051 QCursor::setPos(m_lastMousePos);
1052# endif /* VBOX_WS_MAC */
1053 }
1054 else
1055 m_lastMousePos = globalPos;
1056#endif /* !VBOX_WS_WIN */
1057 return true; /* stop further event handling */
1058 }
1059 else /* !uimachine()->isMouseCaptured() */
1060 {
1061 if (uimachine()->isMouseSupportsAbsolute() && uimachine()->isMouseIntegrated())
1062 {
1063 int iCw = m_views[uScreenId]->contentsWidth(), iCh = m_views[uScreenId]->contentsHeight();
1064 int iVw = m_views[uScreenId]->visibleWidth(), iVh = m_views[uScreenId]->visibleHeight();
1065
1066 /* Try to automatically scroll the guest canvas if the
1067 * mouse goes outside its visible part: */
1068 int iDx = 0;
1069 if (relativePos.x() > iVw) iDx = relativePos.x() - iVw;
1070 else if (relativePos.x() < 0) iDx = relativePos.x();
1071 int iDy = 0;
1072 if (relativePos.y() > iVh) iDy = relativePos.y() - iVh;
1073 else if (relativePos.y() < 0) iDy = relativePos.y();
1074 if (iDx != 0 || iDy != 0) m_views[uScreenId]->scrollBy(iDx, iDy);
1075
1076 /* Get mouse-pointer location: */
1077 QPoint cpnt = m_views[uScreenId]->viewportToContents(relativePos);
1078
1079 /* Take the scale-factor(s) into account: */
1080 const UIFrameBuffer *pFrameBuffer = m_views[uScreenId]->frameBuffer();
1081 if (pFrameBuffer)
1082 {
1083 const QSize scaledSize = pFrameBuffer->scaledSize();
1084 if (scaledSize.isValid())
1085 {
1086 const double xScaleFactor = (double)scaledSize.width() / pFrameBuffer->width();
1087 const double yScaleFactor = (double)scaledSize.height() / pFrameBuffer->height();
1088 cpnt.setX((int)(cpnt.x() / xScaleFactor));
1089 cpnt.setY((int)(cpnt.y() / yScaleFactor));
1090 }
1091 }
1092
1093 /* Take the device-pixel-ratio into account: */
1094 const double dDevicePixelRatioFormal = pFrameBuffer->devicePixelRatio();
1095 const double dDevicePixelRatioActual = pFrameBuffer->devicePixelRatioActual();
1096 cpnt.setX(cpnt.x() * dDevicePixelRatioFormal);
1097 cpnt.setY(cpnt.y() * dDevicePixelRatioFormal);
1098 if (!pFrameBuffer->useUnscaledHiDPIOutput())
1099 {
1100 cpnt.setX(cpnt.x() / dDevicePixelRatioActual);
1101 cpnt.setY(cpnt.y() / dDevicePixelRatioActual);
1102 }
1103
1104#ifdef VBOX_WITH_DRAG_AND_DROP
1105# ifdef VBOX_WITH_DRAG_AND_DROP_GH
1106 QPointer<UIMachineView> pView = m_views[uScreenId];
1107 bool fHandleDnDPending = RT_BOOL(mouseButtons.testFlag(Qt::LeftButton));
1108
1109 /* Mouse pointer outside VM window? */
1110 if ( cpnt.x() < 0
1111 || cpnt.x() > iCw - 1
1112 || cpnt.y() < 0
1113 || cpnt.y() > iCh - 1)
1114 {
1115 if (fHandleDnDPending)
1116 {
1117 LogRel2(("DnD: Drag and drop operation from guest to host started\n"));
1118
1119 int rc = pView->dragCheckPending();
1120 if (RT_SUCCESS(rc))
1121 {
1122 pView->dragStart();
1123 return true; /* Bail out -- we're done here. */
1124 }
1125 }
1126 }
1127 else /* Inside VM window? */
1128 {
1129 if (fHandleDnDPending)
1130 pView->dragStop();
1131 }
1132# endif
1133#endif /* VBOX_WITH_DRAG_AND_DROP */
1134
1135 /* Bound coordinates: */
1136 if (cpnt.x() < 0) cpnt.setX(0);
1137 else if (cpnt.x() > iCw - 1) cpnt.setX(iCw - 1);
1138 if (cpnt.y() < 0) cpnt.setY(0);
1139 else if (cpnt.y() > iCh - 1) cpnt.setY(iCh - 1);
1140
1141 /* Determine shifting: */
1142 ulong uDummy;
1143 long xShift = 0, yShift = 0;
1144 KGuestMonitorStatus enmDummy = KGuestMonitorStatus_Disabled;
1145 uimachine()->acquireGuestScreenParameters(uScreenId, uDummy, uDummy, uDummy, xShift, yShift, enmDummy);
1146 /* Set shifting: */
1147 cpnt.setX(cpnt.x() + xShift);
1148 cpnt.setY(cpnt.y() + yShift);
1149
1150 /* Post absolute mouse-event into guest: */
1151 uimachine()->putMouseEventAbsolute(cpnt.x() + 1, cpnt.y() + 1, iWheelVertical, iWheelHorizontal, iMouseButtonsState);
1152 return true;
1153 }
1154 else
1155 {
1156 if (m_views[uScreenId]->hasFocus() && (iEventType == QEvent::MouseButtonRelease && mouseButtons == Qt::NoButton))
1157 {
1158 if (uimachine()->isPaused())
1159 UINotificationMessage::remindAboutPausedVMInput();
1160 else if (uimachine()->isRunning())
1161 {
1162 /* Temporarily disable auto capture that will take place after this dialog is dismissed because
1163 * the capture state is to be defined by the dialog result itself: */
1164 uimachine()->setAutoCaptureDisabled(true);
1165 bool fIsAutoConfirmed = false;
1166 bool ok = msgCenter().confirmInputCapture(fIsAutoConfirmed);
1167 if (fIsAutoConfirmed)
1168 uimachine()->setAutoCaptureDisabled(false);
1169 /* Otherwise, the disable flag will be reset in the next console view's focus in event (since
1170 * may happen asynchronously on some platforms, after we return from this code): */
1171 if (ok)
1172 {
1173#ifdef VBOX_WS_NIX
1174 /* Make sure that pending FocusOut events from the previous message box are handled,
1175 * otherwise the mouse is immediately ungrabbed again: */
1176 qApp->processEvents();
1177#endif /* VBOX_WS_NIX */
1178 machineLogic()->keyboardHandler()->captureKeyboard(uScreenId);
1179 const MouseCapturePolicy mcp = gEDataManager->mouseCapturePolicy(uiCommon().managedVMUuid());
1180 if (mcp == MouseCapturePolicy_Default)
1181 captureMouse(uScreenId);
1182 }
1183 }
1184 }
1185 }
1186 }
1187
1188 return false;
1189}
1190
1191bool UIMouseHandler::multiTouchEvent(QTouchEvent *pTouchEvent, ulong uScreenId)
1192{
1193 /* Eat if machine isn't running: */
1194 if (!uimachine()->isRunning())
1195 return true;
1196
1197 /* Eat if such view & viewport aren't registered: */
1198 if (!m_views.contains(uScreenId) || !m_viewports.contains(uScreenId))
1199 return true;
1200
1201 QVector<LONG64> contacts(pTouchEvent->points().size());
1202
1203 long xShift = 0, yShift = 0;
1204
1205 bool fTouchScreen = (pTouchEvent->device()->type() == QInputDevice::DeviceType::TouchScreen);
1206 /* Compatibility with previous behavior. If there is no touchpad configured
1207 * then treat all multitouch events as touchscreen ones: */
1208 fTouchScreen |= !uimachine()->isMouseSupportsTouchPad();
1209
1210 if (fTouchScreen)
1211 {
1212 ulong uDummy;
1213 KGuestMonitorStatus enmDummy = KGuestMonitorStatus_Disabled;
1214 uimachine()->acquireGuestScreenParameters(uScreenId, uDummy, uDummy, uDummy, xShift, yShift, enmDummy);
1215 }
1216
1217 /* Pass all multi-touch events into guest: */
1218 int iTouchPointIndex = 0;
1219 foreach (const QEventPoint &touchPoint, pTouchEvent->points())
1220 {
1221 /* Get touch-point state: */
1222 LONG iTouchPointState = KTouchContactState_None;
1223 switch (touchPoint.state())
1224 {
1225 case QEventPoint::Pressed:
1226 case QEventPoint::Updated:
1227 case QEventPoint::Stationary:
1228 iTouchPointState = KTouchContactState_InContact;
1229 if (fTouchScreen)
1230 iTouchPointState |= KTouchContactState_InRange;
1231 break;
1232 default:
1233 break;
1234 }
1235
1236 if (fTouchScreen)
1237 {
1238 /* Get absolute touch-point origin: */
1239 QPoint currentTouchPoint = touchPoint.position().toPoint();
1240
1241 /* Pass absolute touch-point data: */
1242 LogRelFlow(("UIMouseHandler::multiTouchEvent: TouchScreen, Origin: %dx%d, Id: %d, State: %d\n",
1243 currentTouchPoint.x(), currentTouchPoint.y(), touchPoint.id(), iTouchPointState));
1244
1245 contacts[iTouchPointIndex] = RT_MAKE_U64_FROM_U16((uint16_t)currentTouchPoint.x() + 1 + xShift,
1246 (uint16_t)currentTouchPoint.y() + 1 + yShift,
1247 RT_MAKE_U16(touchPoint.id(), iTouchPointState),
1248 0);
1249 } else {
1250 /* Get relative touch-point normalized position: */
1251 QPointF rawTouchPoint = touchPoint.normalizedPosition();
1252
1253 /* Pass relative touch-point data as Normalized Integer: */
1254 uint16_t xNorm = rawTouchPoint.x() * 0xffff;
1255 uint16_t yNorm = rawTouchPoint.y() * 0xffff;
1256 LogRelFlow(("UIMouseHandler::multiTouchEvent: TouchPad, Normalized Position: %ux%u, Id: %d, State: %d\n",
1257 xNorm, yNorm, touchPoint.id(), iTouchPointState));
1258
1259 contacts[iTouchPointIndex] = RT_MAKE_U64_FROM_U16(xNorm, yNorm,
1260 RT_MAKE_U16(touchPoint.id(), iTouchPointState),
1261 0);
1262 }
1263
1264 LogRelFlow(("UIMouseHandler::multiTouchEvent: %RX64\n", contacts[iTouchPointIndex]));
1265
1266 ++iTouchPointIndex;
1267 }
1268
1269 uimachine()->putEventMultiTouch(contacts.size(),
1270 contacts,
1271 fTouchScreen,
1272 RTTimeMilliTS());
1273
1274 /* Eat by default? */
1275 return true;
1276}
1277
1278#ifdef VBOX_WS_WIN
1279/* This method is actually required only because under win-host
1280 * we do not really grab the mouse in case of capturing it: */
1281void UIMouseHandler::updateMouseCursorClipping()
1282{
1283 /* Check if such view && viewport are registered: */
1284 if (!m_views.contains(m_iMouseCaptureViewIndex) || !m_viewports.contains(m_iMouseCaptureViewIndex))
1285 return;
1286
1287 if (uimachine()->isMouseCaptured())
1288 {
1289 /* Get full-viewport-rectangle in global coordinates: */
1290 QRect viewportRectangle = m_viewports[m_iMouseCaptureViewIndex]->visibleRegion().boundingRect();
1291 const QPoint viewportRectangleGlobalPos = m_views[m_iMouseCaptureViewIndex]->mapToGlobal(m_viewports[m_iMouseCaptureViewIndex]->pos());
1292 viewportRectangle.translate(viewportRectangleGlobalPos);
1293
1294 /* Trim full-viewport-rectangle by available geometry: */
1295 viewportRectangle = viewportRectangle.intersected(gpDesktop->availableGeometry(machineLogic()->machineWindows()[m_iMouseCaptureViewIndex]));
1296
1297 /* Trim partial-viewport-rectangle by top-most windows: */
1298 QRegion viewportRegion = QRegion(viewportRectangle) - NativeWindowSubsystem::areaCoveredByTopMostWindows();
1299 /* Check if partial-viewport-region consists of 1 rectangle: */
1300 if (viewportRegion.rectCount() > 1)
1301 {
1302 /* Choose the largest rectangle: */
1303 QRect largestRect;
1304 for (QRegion::const_iterator it = viewportRegion.begin(); it != viewportRegion.end(); ++it)
1305 {
1306 const QRect rect = *it;
1307 largestRect = largestRect.width() * largestRect.height() < rect.width() * rect.height() ? rect : largestRect;
1308 }
1309 /* Assign the partial-viewport-region to the largest rect: */
1310 viewportRegion = largestRect;
1311 }
1312 /* Assign the partial-viewport-rectangle to the partial-viewport-region: */
1313 viewportRectangle = viewportRegion.boundingRect();
1314
1315 /* Assign the visible-viewport-rectangle to the partial-viewport-rectangle: */
1316 m_mouseCursorClippingRect = viewportRectangle;
1317
1318 /* Prepare clipping area: */
1319 // WORKAROUND:
1320 // Underlying ClipCursor call requires physical coordinates, not logical upscaled Qt stuff.
1321 // But we will have to map to relative origin (to make logical=>physical conversion) first.
1322 const double dDpr = UIDesktopWidgetWatchdog::devicePixelRatio(m_windows.value(m_iMouseCaptureViewIndex));
1323 const QRect screenGeometry = gpDesktop->screenGeometry(m_windows.value(m_iMouseCaptureViewIndex));
1324 viewportRectangle.moveTo((viewportRectangle.topLeft() - screenGeometry.topLeft()) * dDpr + screenGeometry.topLeft());
1325 viewportRectangle.setSize(viewportRectangle.size() * dDpr);
1326 RECT rect = { viewportRectangle.left() + 1, viewportRectangle.top() + 1, viewportRectangle.right(), viewportRectangle.bottom() };
1327 ::ClipCursor(&rect);
1328 }
1329 else
1330 {
1331 ::ClipCursor(NULL);
1332 }
1333}
1334#endif /* VBOX_WS_WIN */
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use