VirtualBox

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

Last change on this file since 104158 was 103803, checked in by vboxsync, 9 months ago

FE/Qt. bugref:10618. Splitting COMEnums.h file into individual enum header files.

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