VirtualBox

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

Last change on this file was 106349, checked in by vboxsync, 7 weeks ago

FE/Qt: bugref:10407. Default to Wayland QPA even if XWayland is around.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.4 KB
Line 
1/* $Id: UIMouseHandler.cpp 106349 2024-10-16 10:03:28Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMouseHandler class implementation.
4 */
5
6/*
7 * Copyright (C) 2010-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <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 int iResult = 0;
261 if (uimachine()->isMouseCaptured())
262 iResult |= UIMouseStateType_MouseCaptured;
263 if (uimachine()->isMouseSupportsAbsolute())
264 {
265 iResult |= UIMouseStateType_MouseSupportsAbsolute;
266 if (uimachine()->isMouseIntegrated())
267 iResult |= UIMouseStateType_MouseIntegrated;
268 }
269 return iResult;
270}
271
272bool UIMouseHandler::nativeEventFilter(void *pMessage, ulong uScreenId)
273{
274 /* Make sure view with passed index exists: */
275 if (!m_views.contains(uScreenId))
276 return false;
277
278 /* Check if some system event should be filtered out.
279 * Returning @c true means filtering-out,
280 * Returning @c false means passing event to Qt. */
281 bool fResult = false; /* Pass to Qt by default. */
282
283# if defined(VBOX_WS_MAC)
284
285 /* Acquire carbon event reference from the cocoa one: */
286 EventRef event = static_cast<EventRef>(darwinCocoaToCarbonEvent(pMessage));
287
288 /* Depending on event kind: */
289 const UInt32 uEventKind = ::GetEventKind(event);
290 switch (uEventKind)
291 {
292 /* Watch for button-events: */
293 case kEventMouseDown:
294 case kEventMouseUp:
295 {
296 /* Acquire button number: */
297 EventMouseButton enmButton = 0;
298 ::GetEventParameter(event, kEventParamMouseButton, typeMouseButton,
299 NULL, sizeof(enmButton), NULL, &enmButton);
300 /* If the event comes for primary mouse button: */
301 if (enmButton == kEventMouseButtonPrimary)
302 {
303 /* Acquire modifiers: */
304 UInt32 uKeyModifiers = ~0U;
305 ::GetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
306 NULL, sizeof(uKeyModifiers), NULL, &uKeyModifiers);
307 /* If the event comes with Control modifier: */
308 if (uKeyModifiers == controlKey)
309 {
310 /* Replacing it with the stripped one: */
311 darwinPostStrippedMouseEvent(pMessage);
312 /* And filter out initial one: */
313 return true;
314 }
315 }
316 }
317 }
318
319# elif defined(VBOX_WS_WIN)
320
321 /* Nothing for now. */
322 RT_NOREF(pMessage, uScreenId);
323
324# elif defined(VBOX_WS_NIX)
325 if (NativeWindowSubsystem::displayServerType() == VBGHDISPLAYSERVERTYPE_X11)
326 {
327 /* Cast to XCB event: */
328 xcb_generic_event_t *pEvent = static_cast<xcb_generic_event_t*>(pMessage);
329
330 /* Depending on event type: */
331 switch (pEvent->response_type & ~0x80)
332 {
333 /* Watch for button-events: */
334 case XCB_BUTTON_PRESS:
335 {
336 /* Do nothing if mouse is actively grabbed: */
337 if (uimachine()->isMouseCaptured())
338 break;
339
340 /* If we see a mouse press from a grab while the mouse is not captured,
341 * release the keyboard before letting the event owner see it. This is
342 * because some owners cannot deal with failures to grab the keyboard
343 * themselves (e.g. window managers dragging windows). */
344
345 /* Cast to XCB button-event: */
346 xcb_button_press_event_t *pButtonEvent = static_cast<xcb_button_press_event_t*>(pMessage);
347
348 /* If this event is from our button grab then it will be reported relative to the root
349 * window and not to ours. In that case release the keyboard capture, re-capture it
350 * delayed, which will fail if we have lost the input focus in the mean-time, replay
351 * the button event for normal delivery (possibly straight back to us, but not relative
352 * to root this time) and tell Qt not to further process this event: */
353 if (pButtonEvent->event == pButtonEvent->root)
354 {
355 machineLogic()->keyboardHandler()->releaseKeyboard();
356 /** @todo It would be nicer to do this in the normal Qt button event
357 * handler to avoid avoidable races if the event was not for us. */
358 machineLogic()->keyboardHandler()->captureKeyboard(uScreenId);
359 /* Re-send the event so that the window which it was meant for gets it: */
360 xcb_allow_events_checked(NativeWindowSubsystem::X11GetConnection(), XCB_ALLOW_REPLAY_POINTER, pButtonEvent->time);
361 /* Do not let Qt see the event: */
362 return true;
363 }
364 break;
365 }
366
367 default:
368 break;
369 }
370 }
371# else
372
373# warning "port me!"
374
375# endif
376
377 /* Return result: */
378 return fResult;
379}
380
381/* Machine state-change handler: */
382void UIMouseHandler::sltMachineStateChanged()
383{
384 /* Get cached machine states: */
385 const KMachineState enmState = uimachine()->machineState();
386
387 /* Handle particular machine states: */
388 switch (enmState)
389 {
390 case KMachineState_Paused:
391 case KMachineState_TeleportingPausedVM:
392 case KMachineState_Stuck:
393 {
394 /* Release the mouse: */
395 releaseMouse();
396 break;
397 }
398 default:
399 break;
400 }
401
402 /* Recall reminder about paused VM input
403 * if we are not in paused VM state already: */
404 if (machineLogic()->activeMachineWindow() &&
405 enmState != KMachineState_Paused &&
406 enmState != KMachineState_TeleportingPausedVM)
407 UINotificationMessage::forgetAboutPausedVMInput();
408
409 /* Notify all the listeners: */
410 emit sigStateChange(state());
411}
412
413/* Mouse capability-change handler: */
414void UIMouseHandler::sltMouseCapabilityChanged()
415{
416 /* If mouse supports absolute pointing and mouse-integration activated: */
417 if (uimachine()->isMouseSupportsAbsolute() && uimachine()->isMouseIntegrated())
418 {
419 /* Release the mouse: */
420 releaseMouse();
421 /* Also we should switch guest mouse to the absolute mode: */
422 uimachine()->putMouseEventAbsolute(-1, -1, 0, 0, 0);
423 }
424#if 0 /* current team's decision is NOT to capture mouse on mouse-absolute mode loosing! */
425 /* If mouse-integration deactivated or mouse doesn't supports absolute pointing: */
426 else
427 {
428 /* Search for the machine-view focused now: */
429 int iFocusedView = -1;
430 QList<ulong> screenIds = m_views.keys();
431 for (int i = 0; i < screenIds.size(); ++i)
432 {
433 if (m_views[screenIds[i]]->hasFocus())
434 {
435 iFocusedView = screenIds[i];
436 break;
437 }
438 }
439 /* If there is no focused view but views are present we will use the first one: */
440 if (iFocusedView == -1 && !screenIds.isEmpty())
441 iFocusedView = screenIds[0];
442 /* Capture mouse using that view: */
443 if (iFocusedView != -1)
444 captureMouse(iFocusedView);
445 }
446#else /* but just to switch the guest mouse into relative mode! */
447 /* If mouse-integration deactivated or mouse doesn't supports absolute pointing: */
448 else
449 {
450 /* Switch guest mouse to the relative mode: */
451 uimachine()->putMouseEvent(0, 0, 0, 0, 0);
452 }
453#endif
454
455 /* Notify user whether mouse supports absolute pointing
456 * if that method was called by corresponding signal: */
457 if (sender())
458 {
459 /* Do not annoy user while restoring VM: */
460 if (uimachine()->machineState() != KMachineState_Restoring)
461 UINotificationMessage::remindAboutMouseIntegration(uimachine()->isMouseSupportsAbsolute());
462 }
463
464 /* Notify all the listeners: */
465 emit sigStateChange(state());
466}
467
468/* Mouse pointer-shape-change handler: */
469void UIMouseHandler::sltMousePointerShapeChanged()
470{
471 /* First of all, we should check if the host pointer should be visible.
472 * We should hide host pointer in case of:
473 * 1. mouse is 'captured' or
474 * 2. machine is NOT 'paused' and mouse is NOT 'captured' and 'integrated' and 'absolute' but host pointer is 'hidden' by the guest. */
475 if (uimachine()->isMouseCaptured() ||
476 (!uimachine()->isPaused() &&
477 uimachine()->isMouseIntegrated() &&
478 uimachine()->isMouseSupportsAbsolute() &&
479 uimachine()->isHidingHostPointer()))
480 {
481 QList<ulong> screenIds = m_viewports.keys();
482 for (int i = 0; i < screenIds.size(); ++i)
483 m_viewports[screenIds[i]]->setCursor(Qt::BlankCursor);
484 }
485
486 else
487
488 /* Otherwise we should show host pointer with guest shape assigned to it if:
489 * machine is NOT 'paused', mouse is 'integrated' and 'absolute' and valid pointer shape is present. */
490 if (!uimachine()->isPaused() &&
491 uimachine()->isMouseIntegrated() &&
492 uimachine()->isMouseSupportsAbsolute() &&
493 uimachine()->isValidPointerShapePresent())
494 {
495 QList<ulong> screenIds = m_viewports.keys();
496 for (int i = 0; i < screenIds.size(); ++i)
497 m_viewports[screenIds[i]]->setCursor(m_views[screenIds[i]]->cursor());
498 }
499
500 else
501
502 /* There could be other states covering such situations as:
503 * 1. machine is 'paused' or
504 * 2. mouse is NOT 'captured' and 'integrated' but NOT 'absolute' or
505 * 3. mouse is NOT 'captured' and 'absolute' but NOT 'integrated'.
506 * We have nothing to do with that except just unset the cursor. */
507 {
508 QList<ulong> screenIds = m_viewports.keys();
509 for (int i = 0; i < screenIds.size(); ++i)
510 m_viewports[screenIds[i]]->unsetCursor();
511 }
512}
513
514void UIMouseHandler::sltMaybeActivateHoveredWindow()
515{
516 /* Are we still have hovered window to activate? */
517 if (m_pHoveredWindow && !m_pHoveredWindow->isActiveWindow())
518 {
519 /* Activate it: */
520 m_pHoveredWindow->activateWindow();
521#ifdef VBOX_WS_NIX
522 /* On X11 its not enough to just activate window if you
523 * want to raise it also, so we will make it separately: */
524 m_pHoveredWindow->raise();
525#endif /* VBOX_WS_NIX */
526 }
527}
528
529/* Mouse-handler constructor: */
530UIMouseHandler::UIMouseHandler(UIMachineLogic *pMachineLogic)
531 : QObject(pMachineLogic)
532 , m_pMachineLogic(pMachineLogic)
533 , m_iLastMouseWheelDelta(0)
534 , m_iMouseCaptureViewIndex(-1)
535#ifdef VBOX_WS_WIN
536 , m_fCursorPositionReseted(false)
537#endif
538{
539 /* Machine state-change updater: */
540 connect(uimachine(), &UIMachine::sigMachineStateChange, this, &UIMouseHandler::sltMachineStateChanged);
541
542 /* Mouse capability state-change updater: */
543 connect(uimachine(), &UIMachine::sigMouseCapabilityChange, this, &UIMouseHandler::sltMouseCapabilityChanged);
544 /* Mouse cursor position state-change updater: */
545 connect(uimachine(), &UIMachine::sigCursorPositionChange, this, &UIMouseHandler::sltMousePointerShapeChanged);
546
547 /* Mouse pointer shape state-change updater: */
548 connect(this, &UIMouseHandler::sigStateChange, this, &UIMouseHandler::sltMousePointerShapeChanged);
549
550 /* Initialize: */
551 sltMachineStateChanged();
552 sltMousePointerShapeChanged();
553 sltMouseCapabilityChanged();
554}
555
556/* Mouse-handler destructor: */
557UIMouseHandler::~UIMouseHandler()
558{
559}
560
561UIMachine *UIMouseHandler::uimachine() const
562{
563 return machineLogic()->uimachine();
564}
565
566/* Event handler for registered machine-view(s): */
567bool UIMouseHandler::eventFilter(QObject *pWatched, QEvent *pEvent)
568{
569 /* If that object is of QWidget type: */
570 if (QWidget *pWatchedWidget = qobject_cast<QWidget*>(pWatched))
571 {
572 /* Check if that widget is in windows list: */
573 if (m_windows.values().contains(pWatchedWidget))
574 {
575#ifdef VBOX_WS_WIN
576 /* Handle window events: */
577 switch (pEvent->type())
578 {
579 case QEvent::Move:
580 {
581 /* Update mouse clipping if window was moved
582 * by Operating System desktop manager: */
583 updateMouseCursorClipping();
584 break;
585 }
586 default:
587 break;
588 }
589#endif /* VBOX_WS_WIN */
590 }
591
592 else
593
594 /* Check if that widget is of UIMachineView type: */
595 if (UIMachineView *pWatchedMachineView = qobject_cast<UIMachineView*>(pWatchedWidget))
596 {
597 /* Check if that widget is in views list: */
598 if (m_views.values().contains(pWatchedMachineView))
599 {
600 /* Handle view events: */
601 switch (pEvent->type())
602 {
603 case QEvent::FocusOut:
604 {
605 /* Release the mouse: */
606 releaseMouse();
607 break;
608 }
609 default:
610 break;
611 }
612 }
613 }
614
615 else
616
617 /* Check if that widget is in viewports list: */
618 if (m_viewports.values().contains(pWatchedWidget))
619 {
620 /* Get current watched widget screen id: */
621 ulong uScreenId = m_viewports.key(pWatchedWidget);
622 /* Handle viewport events: */
623 switch (pEvent->type())
624 {
625#ifdef VBOX_WS_MAC
626 case UIGrabMouseEvent::GrabMouseEvent:
627 {
628 UIGrabMouseEvent *pDeltaEvent = static_cast<UIGrabMouseEvent*>(pEvent);
629 QPoint p = QPoint(pDeltaEvent->xDelta() + m_lastMousePos.x(),
630 pDeltaEvent->yDelta() + m_lastMousePos.y());
631 if (mouseEvent(pDeltaEvent->mouseEventType(), uScreenId,
632 m_viewports[uScreenId]->mapFromGlobal(p), p,
633 pDeltaEvent->buttons(),
634 pDeltaEvent->wheelDelta(), pDeltaEvent->orientation()))
635 return true;
636 break;
637 }
638#endif /* VBOX_WS_MAC */
639 case QEvent::MouseMove:
640 {
641#ifdef VBOX_WS_MAC
642 // WORKAROUND:
643 // Since we are handling mouse move/drag events in the same thread
644 // where we are painting guest content changes corresponding to those
645 // events, we can have input event queue overloaded with the move/drag
646 // events, so we should discard current one if there is subsequent already.
647 EventTypeSpec list[2];
648 list[0].eventClass = kEventClassMouse;
649 list[0].eventKind = kEventMouseMoved;
650 list[1].eventClass = kEventClassMouse;
651 list[1].eventKind = kEventMouseDragged;
652 if (AcquireFirstMatchingEventInQueue(GetCurrentEventQueue(), 2, list,
653 kEventQueueOptionsNone) != NULL)
654 return true;
655#endif /* VBOX_WS_MAC */
656
657 /* This event should be also processed using next 'case': */
658 }
659 RT_FALL_THRU();
660 case QEvent::MouseButtonRelease:
661 {
662 /* Get mouse-event: */
663 QMouseEvent *pOldMouseEvent = static_cast<QMouseEvent*>(pEvent);
664 const QPoint gPos = pOldMouseEvent->globalPosition().toPoint();
665
666 /* Check which viewport(s) we *probably* hover: */
667 QWidgetList probablyHoveredViewports;
668 foreach (QWidget *pViewport, m_viewports)
669 {
670 QPoint posInViewport = pViewport->mapFromGlobal(gPos);
671 if (pViewport->geometry().adjusted(0, 0, 1, 1).contains(posInViewport))
672 probablyHoveredViewports << pViewport;
673 }
674 /* Determine actually hovered viewport: */
675 QWidget *pHoveredWidget = probablyHoveredViewports.isEmpty() ? 0 :
676 probablyHoveredViewports.contains(pWatchedWidget) ? pWatchedWidget :
677 probablyHoveredViewports.first();
678
679 /* Check if we should propagate this event to another window: */
680 if (pHoveredWidget && pHoveredWidget != pWatchedWidget && m_viewports.values().contains(pHoveredWidget))
681 {
682 /* Prepare redirected mouse-move event: */
683 QMouseEvent *pNewMouseEvent = new QMouseEvent(pOldMouseEvent->type(),
684 pHoveredWidget->mapFromGlobal(gPos),
685 gPos,
686 pOldMouseEvent->button(),
687 pOldMouseEvent->buttons(),
688 pOldMouseEvent->modifiers());
689
690 /* Send that event to real destination: */
691 QApplication::postEvent(pHoveredWidget, pNewMouseEvent);
692
693 /* Filter out that event: */
694 return true;
695 }
696
697#ifdef VBOX_WS_NIX
698 /* Make sure that we are focused after a click. Rather
699 * ugly, but works around a problem with GNOME
700 * screensaver, which sometimes removes our input focus
701 * and gives us no way to get it back. */
702 if (pEvent->type() == QEvent::MouseButtonRelease)
703 pWatchedWidget->window()->activateWindow();
704#endif /* VBOX_WS_NIX */
705 /* Check if we should activate window under cursor: */
706 if (gEDataManager->activateHoveredMachineWindow() &&
707 !uimachine()->isMouseCaptured() &&
708 QApplication::activeWindow() &&
709 m_windows.values().contains(QApplication::activeWindow()) &&
710 m_windows.values().contains(pWatchedWidget->window()) &&
711 QApplication::activeWindow() != pWatchedWidget->window())
712 {
713 /* Put request for hovered window activation in 300msec: */
714 m_pHoveredWindow = pWatchedWidget->window();
715 QTimer::singleShot(300, this, SLOT(sltMaybeActivateHoveredWindow()));
716 }
717 else
718 {
719 /* Revoke request for hovered window activation: */
720 m_pHoveredWindow = 0;
721 }
722
723 /* This event should be also processed using next 'case': */
724 }
725 RT_FALL_THRU();
726 case QEvent::MouseButtonPress:
727 case QEvent::MouseButtonDblClick:
728 {
729 QMouseEvent *pMouseEvent = static_cast<QMouseEvent*>(pEvent);
730#ifdef VBOX_WS_NIX
731 /* When the keyboard is captured, we also capture mouse button
732 * events, and release the keyboard and re-capture it delayed
733 * on every mouse click. When the click is inside our window
734 * area though the delay is not needed or wanted. Calling
735 * finaliseCaptureKeyboard() skips the delay if a delayed
736 * capture is in progress and has no effect if not: */
737 if (pEvent->type() == QEvent::MouseButtonPress)
738 machineLogic()->keyboardHandler()->finaliseCaptureKeyboard();
739#endif /* VBOX_WS_NIX */
740
741 /* For various mouse click related events
742 * we also reset last mouse wheel delta: */
743 if (pEvent->type() != QEvent::MouseMove)
744 m_iLastMouseWheelDelta = 0;
745
746 if (mouseEvent(pMouseEvent->type(), uScreenId,
747 pMouseEvent->position().toPoint(), pMouseEvent->globalPosition().toPoint(),
748 pMouseEvent->buttons(), 0, Qt::Horizontal))
749 return true;
750 break;
751 }
752 case QEvent::TouchBegin:
753 case QEvent::TouchUpdate:
754 case QEvent::TouchEnd:
755 {
756 if (uimachine()->isMouseSupportsTouchScreen() || uimachine()->isMouseSupportsTouchPad())
757 return multiTouchEvent(static_cast<QTouchEvent*>(pEvent), uScreenId);
758 break;
759 }
760 case QEvent::Wheel:
761 {
762 QWheelEvent *pWheelEvent = static_cast<QWheelEvent*>(pEvent);
763 /* There are pointing devices which send smaller values for the delta than 120.
764 * Here we sum them up until we are greater than 120. This allows to have finer control
765 * over the speed acceleration & enables such devices to send a valid wheel event to our
766 * guest mouse device at all: */
767 int iDelta = 0;
768 const Qt::Orientation enmOrientation = qFabs(pWheelEvent->angleDelta().x())
769 > qFabs(pWheelEvent->angleDelta().y())
770 ? Qt::Horizontal
771 : Qt::Vertical;
772 m_iLastMouseWheelDelta += enmOrientation == Qt::Horizontal
773 ? pWheelEvent->angleDelta().x()
774 : pWheelEvent->angleDelta().y();
775 if (qAbs(m_iLastMouseWheelDelta) >= 120)
776 {
777 /* Rounding iDelta to the nearest multiple of 120: */
778 iDelta = m_iLastMouseWheelDelta / 120;
779 iDelta *= 120;
780 m_iLastMouseWheelDelta = m_iLastMouseWheelDelta % 120;
781 }
782 if (mouseEvent(pWheelEvent->type(),
783 uScreenId,
784 pWheelEvent->position().toPoint(),
785 pWheelEvent->globalPosition().toPoint(),
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.

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