VirtualBox

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

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

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

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

© 2023 Oracle
ContactPrivacy policyTerms of Use