VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 weeks ago

Copyright year updates by scm.

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