Warning, file /plasma/kwin/src/pointer_input.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org> 0006 SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com> 0007 SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 #include "pointer_input.h" 0012 0013 #include <config-kwin.h> 0014 0015 #include "core/output.h" 0016 #include "cursorsource.h" 0017 #include "decorations/decoratedclient.h" 0018 #include "effects.h" 0019 #include "input_event.h" 0020 #include "input_event_spy.h" 0021 #include "mousebuttons.h" 0022 #include "osd.h" 0023 #include "wayland/display.h" 0024 #include "wayland/pointer_interface.h" 0025 #include "wayland/pointerconstraints_v1_interface.h" 0026 #include "wayland/seat_interface.h" 0027 #include "wayland/surface_interface.h" 0028 #include "wayland_server.h" 0029 #include "workspace.h" 0030 #include "x11window.h" 0031 // KDecoration 0032 #include <KDecoration2/Decoration> 0033 // screenlocker 0034 #if KWIN_BUILD_SCREENLOCKER 0035 #include <KScreenLocker/KsldApp> 0036 #endif 0037 0038 #include <KLocalizedString> 0039 0040 #include <QHoverEvent> 0041 #include <QPainter> 0042 #include <QWindow> 0043 0044 #include <linux/input.h> 0045 0046 #include <cmath> 0047 0048 namespace KWin 0049 { 0050 0051 static bool screenContainsPos(const QPointF &pos) 0052 { 0053 const auto outputs = workspace()->outputs(); 0054 for (const Output *output : outputs) { 0055 if (output->geometry().contains(flooredPoint(pos))) { 0056 return true; 0057 } 0058 } 0059 return false; 0060 } 0061 0062 static QPointF confineToBoundingBox(const QPointF &pos, const QRectF &boundingBox) 0063 { 0064 return QPointF( 0065 std::clamp(pos.x(), boundingBox.left(), boundingBox.right() - 1.0), 0066 std::clamp(pos.y(), boundingBox.top(), boundingBox.bottom() - 1.0)); 0067 } 0068 0069 PointerInputRedirection::PointerInputRedirection(InputRedirection *parent) 0070 : InputDeviceHandler(parent) 0071 , m_cursor(nullptr) 0072 { 0073 } 0074 0075 PointerInputRedirection::~PointerInputRedirection() = default; 0076 0077 void PointerInputRedirection::init() 0078 { 0079 Q_ASSERT(!inited()); 0080 waylandServer()->seat()->setHasPointer(input()->hasPointer()); 0081 connect(input(), &InputRedirection::hasPointerChanged, 0082 waylandServer()->seat(), &KWaylandServer::SeatInterface::setHasPointer); 0083 0084 m_cursor = new CursorImage(this); 0085 setInited(true); 0086 InputDeviceHandler::init(); 0087 0088 if (!input()->hasPointer()) { 0089 Cursors::self()->hideCursor(); 0090 } 0091 connect(input(), &InputRedirection::hasPointerChanged, this, []() { 0092 if (input()->hasPointer()) { 0093 Cursors::self()->showCursor(); 0094 } else { 0095 Cursors::self()->hideCursor(); 0096 } 0097 }); 0098 0099 connect(Cursors::self()->mouse(), &Cursor::rendered, m_cursor, &CursorImage::markAsRendered); 0100 connect(m_cursor, &CursorImage::changed, Cursors::self()->mouse(), [this] { 0101 Cursors::self()->mouse()->setSource(m_cursor->source()); 0102 updateCursorOutputs(); 0103 }); 0104 Q_EMIT m_cursor->changed(); 0105 0106 connect(workspace(), &Workspace::outputsChanged, this, &PointerInputRedirection::updateAfterScreenChange); 0107 #if KWIN_BUILD_SCREENLOCKER 0108 if (waylandServer()->hasScreenLockerIntegration()) { 0109 connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, [this]() { 0110 if (waylandServer()->seat()->hasPointer()) { 0111 waylandServer()->seat()->cancelPointerPinchGesture(); 0112 waylandServer()->seat()->cancelPointerSwipeGesture(); 0113 } 0114 update(); 0115 }); 0116 } 0117 #endif 0118 connect(workspace(), &QObject::destroyed, this, [this] { 0119 setInited(false); 0120 }); 0121 connect(waylandServer(), &QObject::destroyed, this, [this] { 0122 setInited(false); 0123 }); 0124 connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragEnded, this, [this]() { 0125 // need to force a focused pointer change 0126 setFocus(nullptr); 0127 update(); 0128 }); 0129 // connect the move resize of all window 0130 auto setupMoveResizeConnection = [this](Window *window) { 0131 connect(window, &Window::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize); 0132 connect(window, &Window::clientFinishUserMovedResized, this, &PointerInputRedirection::update); 0133 }; 0134 const auto clients = workspace()->allClientList(); 0135 std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); 0136 connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection); 0137 0138 // warp the cursor to center of screen containing the workspace center 0139 if (const Output *output = workspace()->outputAt(workspace()->geometry().center())) { 0140 warp(output->geometry().center()); 0141 } 0142 updateAfterScreenChange(); 0143 } 0144 0145 void PointerInputRedirection::updateOnStartMoveResize() 0146 { 0147 breakPointerConstraints(focus() ? focus()->surface() : nullptr); 0148 disconnectPointerConstraintsConnection(); 0149 setFocus(nullptr); 0150 } 0151 0152 void PointerInputRedirection::updateToReset() 0153 { 0154 if (decoration()) { 0155 QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); 0156 QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); 0157 setDecoration(nullptr); 0158 } 0159 if (focus()) { 0160 if (focus()->isClient()) { 0161 focus()->pointerLeaveEvent(); 0162 } 0163 disconnect(m_focusGeometryConnection); 0164 m_focusGeometryConnection = QMetaObject::Connection(); 0165 breakPointerConstraints(focus()->surface()); 0166 disconnectPointerConstraintsConnection(); 0167 setFocus(nullptr); 0168 } 0169 } 0170 0171 class PositionUpdateBlocker 0172 { 0173 public: 0174 PositionUpdateBlocker(PointerInputRedirection *pointer) 0175 : m_pointer(pointer) 0176 { 0177 s_counter++; 0178 } 0179 ~PositionUpdateBlocker() 0180 { 0181 s_counter--; 0182 if (s_counter == 0) { 0183 if (!s_scheduledPositions.isEmpty()) { 0184 const auto pos = s_scheduledPositions.takeFirst(); 0185 m_pointer->processMotionInternal(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, nullptr); 0186 } 0187 } 0188 } 0189 0190 static bool isPositionBlocked() 0191 { 0192 return s_counter > 0; 0193 } 0194 0195 static void schedulePosition(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time) 0196 { 0197 s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time}); 0198 } 0199 0200 private: 0201 static int s_counter; 0202 struct ScheduledPosition 0203 { 0204 QPointF pos; 0205 QPointF delta; 0206 QPointF deltaNonAccelerated; 0207 std::chrono::microseconds time; 0208 }; 0209 static QVector<ScheduledPosition> s_scheduledPositions; 0210 0211 PointerInputRedirection *m_pointer; 0212 }; 0213 0214 int PositionUpdateBlocker::s_counter = 0; 0215 QVector<PositionUpdateBlocker::ScheduledPosition> PositionUpdateBlocker::s_scheduledPositions; 0216 0217 void PointerInputRedirection::processMotionAbsolute(const QPointF &pos, std::chrono::microseconds time, InputDevice *device) 0218 { 0219 processMotionInternal(pos, QPointF(), QPointF(), time, device); 0220 } 0221 0222 void PointerInputRedirection::processMotion(const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device) 0223 { 0224 processMotionInternal(m_pos + delta, delta, deltaNonAccelerated, time, device); 0225 } 0226 0227 void PointerInputRedirection::processMotionInternal(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device) 0228 { 0229 input()->setLastInputHandler(this); 0230 if (!inited()) { 0231 return; 0232 } 0233 if (PositionUpdateBlocker::isPositionBlocked()) { 0234 PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time); 0235 return; 0236 } 0237 0238 PositionUpdateBlocker blocker(this); 0239 updatePosition(pos); 0240 MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons, 0241 input()->keyboardModifiers(), time, 0242 delta, deltaNonAccelerated, device); 0243 event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); 0244 0245 update(); 0246 input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); 0247 input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0)); 0248 } 0249 0250 void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, std::chrono::microseconds time, InputDevice *device) 0251 { 0252 input()->setLastInputHandler(this); 0253 QEvent::Type type; 0254 switch (state) { 0255 case InputRedirection::PointerButtonReleased: 0256 type = QEvent::MouseButtonRelease; 0257 break; 0258 case InputRedirection::PointerButtonPressed: 0259 type = QEvent::MouseButtonPress; 0260 update(); 0261 break; 0262 default: 0263 Q_UNREACHABLE(); 0264 return; 0265 } 0266 0267 updateButton(button, state); 0268 0269 MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons, 0270 input()->keyboardModifiers(), time, QPointF(), QPointF(), device); 0271 event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); 0272 event.setNativeButton(button); 0273 0274 input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); 0275 0276 if (!inited()) { 0277 return; 0278 } 0279 0280 input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button)); 0281 0282 if (state == InputRedirection::PointerButtonReleased) { 0283 update(); 0284 } 0285 } 0286 0287 void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 deltaV120, 0288 InputRedirection::PointerAxisSource source, std::chrono::microseconds time, InputDevice *device) 0289 { 0290 input()->setLastInputHandler(this); 0291 update(); 0292 0293 Q_EMIT input()->pointerAxisChanged(axis, delta); 0294 0295 WheelEvent wheelEvent(m_pos, delta, deltaV120, 0296 (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, 0297 m_qtButtons, input()->keyboardModifiers(), source, time, device); 0298 wheelEvent.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); 0299 0300 input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent)); 0301 0302 if (!inited()) { 0303 return; 0304 } 0305 input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent)); 0306 } 0307 0308 void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device) 0309 { 0310 input()->setLastInputHandler(this); 0311 if (!inited()) { 0312 return; 0313 } 0314 0315 input()->processSpies(std::bind(&InputEventSpy::swipeGestureBegin, std::placeholders::_1, fingerCount, time)); 0316 input()->processFilters(std::bind(&InputEventFilter::swipeGestureBegin, std::placeholders::_1, fingerCount, time)); 0317 } 0318 0319 void PointerInputRedirection::processSwipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device) 0320 { 0321 input()->setLastInputHandler(this); 0322 if (!inited()) { 0323 return; 0324 } 0325 update(); 0326 0327 input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time)); 0328 input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time)); 0329 } 0330 0331 void PointerInputRedirection::processSwipeGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device) 0332 { 0333 input()->setLastInputHandler(this); 0334 if (!inited()) { 0335 return; 0336 } 0337 update(); 0338 0339 input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time)); 0340 input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time)); 0341 } 0342 0343 void PointerInputRedirection::processSwipeGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device) 0344 { 0345 input()->setLastInputHandler(this); 0346 if (!inited()) { 0347 return; 0348 } 0349 update(); 0350 0351 input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time)); 0352 input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time)); 0353 } 0354 0355 void PointerInputRedirection::processPinchGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device) 0356 { 0357 input()->setLastInputHandler(this); 0358 if (!inited()) { 0359 return; 0360 } 0361 update(); 0362 0363 input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); 0364 input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); 0365 } 0366 0367 void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device) 0368 { 0369 input()->setLastInputHandler(this); 0370 if (!inited()) { 0371 return; 0372 } 0373 update(); 0374 0375 input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); 0376 input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); 0377 } 0378 0379 void PointerInputRedirection::processPinchGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device) 0380 { 0381 input()->setLastInputHandler(this); 0382 if (!inited()) { 0383 return; 0384 } 0385 update(); 0386 0387 input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time)); 0388 input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time)); 0389 } 0390 0391 void PointerInputRedirection::processPinchGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device) 0392 { 0393 input()->setLastInputHandler(this); 0394 if (!inited()) { 0395 return; 0396 } 0397 update(); 0398 0399 input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time)); 0400 input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time)); 0401 } 0402 0403 void PointerInputRedirection::processHoldGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device) 0404 { 0405 if (!inited()) { 0406 return; 0407 } 0408 update(); 0409 0410 input()->processSpies(std::bind(&InputEventSpy::holdGestureBegin, std::placeholders::_1, fingerCount, time)); 0411 input()->processFilters(std::bind(&InputEventFilter::holdGestureBegin, std::placeholders::_1, fingerCount, time)); 0412 } 0413 0414 void PointerInputRedirection::processHoldGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device) 0415 { 0416 if (!inited()) { 0417 return; 0418 } 0419 update(); 0420 0421 input()->processSpies(std::bind(&InputEventSpy::holdGestureEnd, std::placeholders::_1, time)); 0422 input()->processFilters(std::bind(&InputEventFilter::holdGestureEnd, std::placeholders::_1, time)); 0423 } 0424 0425 void PointerInputRedirection::processHoldGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device) 0426 { 0427 if (!inited()) { 0428 return; 0429 } 0430 update(); 0431 0432 input()->processSpies(std::bind(&InputEventSpy::holdGestureCancelled, std::placeholders::_1, time)); 0433 input()->processFilters(std::bind(&InputEventFilter::holdGestureCancelled, std::placeholders::_1, time)); 0434 } 0435 0436 bool PointerInputRedirection::areButtonsPressed() const 0437 { 0438 for (auto state : m_buttons) { 0439 if (state == InputRedirection::PointerButtonPressed) { 0440 return true; 0441 } 0442 } 0443 return false; 0444 } 0445 0446 bool PointerInputRedirection::focusUpdatesBlocked() 0447 { 0448 if (waylandServer()->seat()->isDragPointer()) { 0449 // ignore during drag and drop 0450 return true; 0451 } 0452 if (waylandServer()->seat()->isTouchSequence()) { 0453 // ignore during touch operations 0454 return true; 0455 } 0456 if (input()->isSelectingWindow()) { 0457 return true; 0458 } 0459 if (areButtonsPressed()) { 0460 return true; 0461 } 0462 return false; 0463 } 0464 0465 void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) 0466 { 0467 disconnect(m_decorationGeometryConnection); 0468 m_decorationGeometryConnection = QMetaObject::Connection(); 0469 0470 disconnect(m_decorationDestroyedConnection); 0471 m_decorationDestroyedConnection = QMetaObject::Connection(); 0472 0473 if (old) { 0474 // send leave event to old decoration 0475 QHoverEvent event(QEvent::HoverLeave, QPointF(-1, -1), QPointF()); 0476 QCoreApplication::instance()->sendEvent(old->decoration(), &event); 0477 } 0478 if (!now) { 0479 // left decoration 0480 return; 0481 } 0482 0483 auto pos = m_pos - now->window()->pos(); 0484 QHoverEvent event(QEvent::HoverEnter, pos, QPointF(-1, -1)); 0485 QCoreApplication::instance()->sendEvent(now->decoration(), &event); 0486 now->window()->processDecorationMove(pos, m_pos); 0487 0488 m_decorationGeometryConnection = connect( 0489 decoration()->window(), &Window::frameGeometryChanged, this, [this]() { 0490 // ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140 0491 const auto oldDeco = decoration(); 0492 update(); 0493 if (oldDeco && oldDeco == decoration() && !decoration()->window()->isInteractiveMove() && !decoration()->window()->isInteractiveResize() && !areButtonsPressed()) { 0494 // position of window did not change, we need to send HoverMotion manually 0495 const QPointF p = m_pos - decoration()->window()->pos(); 0496 QHoverEvent event(QEvent::HoverMove, p, p); 0497 QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); 0498 } 0499 }, 0500 Qt::QueuedConnection); 0501 0502 // if our decoration gets destroyed whilst it has focus, we pass focus on to the same window 0503 m_decorationDestroyedConnection = connect(now, &QObject::destroyed, this, &PointerInputRedirection::update, Qt::QueuedConnection); 0504 } 0505 0506 void PointerInputRedirection::focusUpdate(Window *focusOld, Window *focusNow) 0507 { 0508 if (focusOld && focusOld->isClient()) { 0509 focusOld->pointerLeaveEvent(); 0510 breakPointerConstraints(focusOld->surface()); 0511 disconnectPointerConstraintsConnection(); 0512 } 0513 disconnect(m_focusGeometryConnection); 0514 m_focusGeometryConnection = QMetaObject::Connection(); 0515 0516 if (focusNow && focusNow->isClient()) { 0517 focusNow->pointerEnterEvent(m_pos); 0518 } 0519 0520 auto seat = waylandServer()->seat(); 0521 if (!focusNow || !focusNow->surface()) { 0522 seat->notifyPointerLeave(); 0523 return; 0524 } 0525 0526 seat->notifyPointerEnter(focusNow->surface(), m_pos, focusNow->inputTransformation()); 0527 0528 m_focusGeometryConnection = connect(focusNow, &Window::inputTransformationChanged, this, [this]() { 0529 // TODO: why no assert possible? 0530 if (!focus()) { 0531 return; 0532 } 0533 // TODO: can we check on the window instead? 0534 if (workspace()->moveResizeWindow()) { 0535 // don't update while moving 0536 return; 0537 } 0538 auto seat = waylandServer()->seat(); 0539 if (focus()->surface() != seat->focusedPointerSurface()) { 0540 return; 0541 } 0542 seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation()); 0543 }); 0544 0545 m_constraintsConnection = connect(focusNow->surface(), &KWaylandServer::SurfaceInterface::pointerConstraintsChanged, 0546 this, &PointerInputRedirection::updatePointerConstraints); 0547 m_constraintsActivatedConnection = connect(workspace(), &Workspace::windowActivated, 0548 this, &PointerInputRedirection::updatePointerConstraints); 0549 updatePointerConstraints(); 0550 } 0551 0552 void PointerInputRedirection::breakPointerConstraints(KWaylandServer::SurfaceInterface *surface) 0553 { 0554 // cancel pointer constraints 0555 if (surface) { 0556 auto c = surface->confinedPointer(); 0557 if (c && c->isConfined()) { 0558 c->setConfined(false); 0559 } 0560 auto l = surface->lockedPointer(); 0561 if (l && l->isLocked()) { 0562 l->setLocked(false); 0563 } 0564 } 0565 disconnectConfinedPointerRegionConnection(); 0566 m_confined = false; 0567 m_locked = false; 0568 } 0569 0570 void PointerInputRedirection::disconnectConfinedPointerRegionConnection() 0571 { 0572 disconnect(m_confinedPointerRegionConnection); 0573 m_confinedPointerRegionConnection = QMetaObject::Connection(); 0574 } 0575 0576 void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection() 0577 { 0578 disconnect(m_lockedPointerAboutToBeUnboundConnection); 0579 m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection(); 0580 } 0581 0582 void PointerInputRedirection::disconnectPointerConstraintsConnection() 0583 { 0584 disconnect(m_constraintsConnection); 0585 m_constraintsConnection = QMetaObject::Connection(); 0586 0587 disconnect(m_constraintsActivatedConnection); 0588 m_constraintsActivatedConnection = QMetaObject::Connection(); 0589 } 0590 0591 template<typename T> 0592 static QRegion getConstraintRegion(Window *window, T *constraint) 0593 { 0594 const QRegion windowShape = window->inputShape(); 0595 const QRegion intersected = constraint->region().isEmpty() ? windowShape : windowShape.intersected(constraint->region()); 0596 return intersected.translated(window->mapFromLocal(QPointF(0, 0)).toPoint()); 0597 } 0598 0599 void PointerInputRedirection::setEnableConstraints(bool set) 0600 { 0601 if (m_enableConstraints == set) { 0602 return; 0603 } 0604 m_enableConstraints = set; 0605 updatePointerConstraints(); 0606 } 0607 0608 void PointerInputRedirection::updatePointerConstraints() 0609 { 0610 if (!focus()) { 0611 return; 0612 } 0613 const auto s = focus()->surface(); 0614 if (!s) { 0615 return; 0616 } 0617 if (s != waylandServer()->seat()->focusedPointerSurface()) { 0618 return; 0619 } 0620 if (!supportsWarping()) { 0621 return; 0622 } 0623 const bool canConstrain = m_enableConstraints && focus() == workspace()->activeWindow(); 0624 const auto cf = s->confinedPointer(); 0625 if (cf) { 0626 if (cf->isConfined()) { 0627 if (!canConstrain) { 0628 cf->setConfined(false); 0629 m_confined = false; 0630 disconnectConfinedPointerRegionConnection(); 0631 } 0632 return; 0633 } 0634 const QRegion r = getConstraintRegion(focus(), cf); 0635 if (canConstrain && r.contains(m_pos.toPoint())) { 0636 cf->setConfined(true); 0637 m_confined = true; 0638 m_confinedPointerRegionConnection = connect(cf, &KWaylandServer::ConfinedPointerV1Interface::regionChanged, this, [this]() { 0639 if (!focus()) { 0640 return; 0641 } 0642 const auto s = focus()->surface(); 0643 if (!s) { 0644 return; 0645 } 0646 const auto cf = s->confinedPointer(); 0647 if (!getConstraintRegion(focus(), cf).contains(m_pos.toPoint())) { 0648 // pointer no longer in confined region, break the confinement 0649 cf->setConfined(false); 0650 m_confined = false; 0651 } else { 0652 if (!cf->isConfined()) { 0653 cf->setConfined(true); 0654 m_confined = true; 0655 } 0656 } 0657 }); 0658 return; 0659 } 0660 } else { 0661 m_confined = false; 0662 disconnectConfinedPointerRegionConnection(); 0663 } 0664 const auto lock = s->lockedPointer(); 0665 if (lock) { 0666 if (lock->isLocked()) { 0667 if (!canConstrain) { 0668 const auto hint = lock->cursorPositionHint(); 0669 lock->setLocked(false); 0670 m_locked = false; 0671 disconnectLockedPointerAboutToBeUnboundConnection(); 0672 if (!(hint.x() < 0 || hint.y() < 0) && focus()) { 0673 processMotionAbsolute(focus()->mapFromLocal(hint), waylandServer()->seat()->timestamp()); 0674 } 0675 } 0676 return; 0677 } 0678 const QRegion r = getConstraintRegion(focus(), lock); 0679 if (canConstrain && r.contains(m_pos.toPoint())) { 0680 lock->setLocked(true); 0681 m_locked = true; 0682 0683 // The client might cancel pointer locking from its side by unbinding the LockedPointerInterface. 0684 // In this case the cached cursor position hint must be fetched before the resource goes away 0685 m_lockedPointerAboutToBeUnboundConnection = connect(lock, &KWaylandServer::LockedPointerV1Interface::aboutToBeDestroyed, this, [this, lock]() { 0686 const auto hint = lock->cursorPositionHint(); 0687 if (hint.x() < 0 || hint.y() < 0 || !focus()) { 0688 return; 0689 } 0690 auto globalHint = focus()->mapFromLocal(hint); 0691 0692 // When the resource finally goes away, reposition the cursor according to the hint 0693 connect(lock, &KWaylandServer::LockedPointerV1Interface::destroyed, this, [this, globalHint]() { 0694 processMotionAbsolute(globalHint, waylandServer()->seat()->timestamp()); 0695 }); 0696 }); 0697 // TODO: connect to region change - is it needed at all? If the pointer is locked it's always in the region 0698 } 0699 } else { 0700 m_locked = false; 0701 disconnectLockedPointerAboutToBeUnboundConnection(); 0702 } 0703 } 0704 0705 QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const 0706 { 0707 if (!focus()) { 0708 return pos; 0709 } 0710 auto s = focus()->surface(); 0711 if (!s) { 0712 return pos; 0713 } 0714 auto cf = s->confinedPointer(); 0715 if (!cf) { 0716 return pos; 0717 } 0718 if (!cf->isConfined()) { 0719 return pos; 0720 } 0721 0722 const QRegion confinementRegion = getConstraintRegion(focus(), cf); 0723 if (confinementRegion.contains(flooredPoint(pos))) { 0724 return pos; 0725 } 0726 QPointF p = pos; 0727 // allow either x or y to pass 0728 p = QPointF(m_pos.x(), pos.y()); 0729 0730 if (confinementRegion.contains(flooredPoint(p))) { 0731 return p; 0732 } 0733 p = QPointF(pos.x(), m_pos.y()); 0734 if (confinementRegion.contains(flooredPoint(p))) { 0735 return p; 0736 } 0737 0738 return m_pos; 0739 } 0740 0741 void PointerInputRedirection::updatePosition(const QPointF &pos) 0742 { 0743 if (m_locked) { 0744 // locked pointer should not move 0745 return; 0746 } 0747 // verify that at least one screen contains the pointer position 0748 const Output *currentOutput = workspace()->outputAt(pos); 0749 QPointF p = confineToBoundingBox(pos, currentOutput->geometry()); 0750 p = applyPointerConfinement(p); 0751 if (p == m_pos) { 0752 // didn't change due to confinement 0753 return; 0754 } 0755 // verify screen confinement 0756 if (!screenContainsPos(p)) { 0757 return; 0758 } 0759 0760 m_pos = p; 0761 0762 workspace()->setActiveCursorOutput(m_pos); 0763 updateCursorOutputs(); 0764 0765 Q_EMIT input()->globalPointerChanged(m_pos); 0766 } 0767 0768 void PointerInputRedirection::updateCursorOutputs() 0769 { 0770 KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer(); 0771 if (!pointer) { 0772 return; 0773 } 0774 0775 KWaylandServer::Cursor *cursor = pointer->cursor(); 0776 if (!cursor) { 0777 return; 0778 } 0779 0780 KWaylandServer::SurfaceInterface *surface = cursor->surface(); 0781 if (!surface) { 0782 return; 0783 } 0784 0785 const QRectF cursorGeometry(m_pos - m_cursor->source()->hotspot(), surface->size()); 0786 surface->setOutputs(waylandServer()->display()->outputsIntersecting(cursorGeometry.toAlignedRect())); 0787 } 0788 0789 void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state) 0790 { 0791 m_buttons[button] = state; 0792 0793 // update Qt buttons 0794 m_qtButtons = Qt::NoButton; 0795 for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) { 0796 if (it.value() == InputRedirection::PointerButtonReleased) { 0797 continue; 0798 } 0799 m_qtButtons |= buttonToQtMouseButton(it.key()); 0800 } 0801 0802 Q_EMIT input()->pointerButtonStateChanged(button, state); 0803 } 0804 0805 void PointerInputRedirection::warp(const QPointF &pos) 0806 { 0807 if (supportsWarping()) { 0808 processMotionAbsolute(pos, waylandServer()->seat()->timestamp()); 0809 } 0810 } 0811 0812 bool PointerInputRedirection::supportsWarping() const 0813 { 0814 return inited(); 0815 } 0816 0817 void PointerInputRedirection::updateAfterScreenChange() 0818 { 0819 if (!inited()) { 0820 return; 0821 } 0822 if (screenContainsPos(m_pos)) { 0823 // pointer still on a screen 0824 return; 0825 } 0826 // pointer no longer on a screen, reposition to closes screen 0827 const Output *output = workspace()->outputAt(m_pos); 0828 // TODO: better way to get timestamps 0829 processMotionAbsolute(output->geometry().center(), waylandServer()->seat()->timestamp()); 0830 } 0831 0832 QPointF PointerInputRedirection::position() const 0833 { 0834 return m_pos; 0835 } 0836 0837 void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) 0838 { 0839 if (!inited()) { 0840 return; 0841 } 0842 // current pointer focus window should get a leave event 0843 update(); 0844 m_cursor->setEffectsOverrideCursor(shape); 0845 } 0846 0847 void PointerInputRedirection::removeEffectsOverrideCursor() 0848 { 0849 if (!inited()) { 0850 return; 0851 } 0852 // cursor position might have changed while there was an effect in place 0853 update(); 0854 m_cursor->removeEffectsOverrideCursor(); 0855 } 0856 0857 void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape) 0858 { 0859 if (!inited()) { 0860 return; 0861 } 0862 // send leave to current pointer focus window 0863 updateToReset(); 0864 m_cursor->setWindowSelectionCursor(shape); 0865 } 0866 0867 void PointerInputRedirection::removeWindowSelectionCursor() 0868 { 0869 if (!inited()) { 0870 return; 0871 } 0872 update(); 0873 m_cursor->removeWindowSelectionCursor(); 0874 } 0875 0876 CursorImage::CursorImage(PointerInputRedirection *parent) 0877 : QObject(parent) 0878 , m_pointer(parent) 0879 { 0880 m_effectsCursor = std::make_unique<ShapeCursorSource>(); 0881 m_fallbackCursor = std::make_unique<ShapeCursorSource>(); 0882 m_moveResizeCursor = std::make_unique<ShapeCursorSource>(); 0883 m_windowSelectionCursor = std::make_unique<ShapeCursorSource>(); 0884 m_decoration.cursor = std::make_unique<ShapeCursorSource>(); 0885 m_serverCursor.cursor = std::make_unique<SurfaceCursorSource>(); 0886 0887 #if KWIN_BUILD_SCREENLOCKER 0888 if (waylandServer()->hasScreenLockerIntegration()) { 0889 connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); 0890 } 0891 #endif 0892 connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); 0893 // connect the move resize of all window 0894 auto setupMoveResizeConnection = [this](Window *window) { 0895 connect(window, &Window::moveResizedChanged, this, &CursorImage::updateMoveResize); 0896 connect(window, &Window::moveResizeCursorChanged, this, &CursorImage::updateMoveResize); 0897 }; 0898 const auto clients = workspace()->allClientList(); 0899 std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); 0900 connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection); 0901 0902 m_fallbackCursor->setShape(Qt::ArrowCursor); 0903 0904 m_effectsCursor->setTheme(m_waylandImage.theme()); 0905 m_fallbackCursor->setTheme(m_waylandImage.theme()); 0906 m_moveResizeCursor->setTheme(m_waylandImage.theme()); 0907 m_windowSelectionCursor->setTheme(m_waylandImage.theme()); 0908 m_decoration.cursor->setTheme(m_waylandImage.theme()); 0909 0910 connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] { 0911 m_effectsCursor->setTheme(m_waylandImage.theme()); 0912 m_fallbackCursor->setTheme(m_waylandImage.theme()); 0913 m_moveResizeCursor->setTheme(m_waylandImage.theme()); 0914 m_windowSelectionCursor->setTheme(m_waylandImage.theme()); 0915 m_decoration.cursor->setTheme(m_waylandImage.theme()); 0916 }); 0917 0918 KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer(); 0919 0920 connect(pointer, &KWaylandServer::PointerInterface::focusedSurfaceChanged, 0921 this, &CursorImage::handleFocusedSurfaceChanged); 0922 0923 reevaluteSource(); 0924 } 0925 0926 CursorImage::~CursorImage() = default; 0927 0928 void CursorImage::markAsRendered(std::chrono::milliseconds timestamp) 0929 { 0930 if (m_currentSource != m_serverCursor.cursor.get()) { 0931 return; 0932 } 0933 auto p = waylandServer()->seat()->pointer(); 0934 if (!p) { 0935 return; 0936 } 0937 auto c = p->cursor(); 0938 if (!c) { 0939 return; 0940 } 0941 auto cursorSurface = c->surface(); 0942 if (!cursorSurface) { 0943 return; 0944 } 0945 cursorSurface->frameRendered(timestamp.count()); 0946 } 0947 0948 void CursorImage::handleFocusedSurfaceChanged() 0949 { 0950 KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer(); 0951 disconnect(m_serverCursor.connection); 0952 0953 if (pointer->focusedSurface()) { 0954 m_serverCursor.connection = connect(pointer, &KWaylandServer::PointerInterface::cursorChanged, 0955 this, &CursorImage::updateServerCursor); 0956 } else { 0957 m_serverCursor.connection = QMetaObject::Connection(); 0958 reevaluteSource(); 0959 } 0960 } 0961 0962 void CursorImage::updateDecoration() 0963 { 0964 disconnect(m_decoration.connection); 0965 auto deco = m_pointer->decoration(); 0966 Window *window = deco ? deco->window() : nullptr; 0967 if (window) { 0968 m_decoration.connection = connect(window, &Window::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor); 0969 } else { 0970 m_decoration.connection = QMetaObject::Connection(); 0971 } 0972 updateDecorationCursor(); 0973 } 0974 0975 void CursorImage::updateDecorationCursor() 0976 { 0977 auto deco = m_pointer->decoration(); 0978 if (Window *window = deco ? deco->window() : nullptr) { 0979 m_decoration.cursor->setShape(window->cursor().name()); 0980 } 0981 reevaluteSource(); 0982 } 0983 0984 void CursorImage::updateMoveResize() 0985 { 0986 if (Window *window = workspace()->moveResizeWindow()) { 0987 m_moveResizeCursor->setShape(window->cursor().name()); 0988 } 0989 reevaluteSource(); 0990 } 0991 0992 void CursorImage::updateServerCursor() 0993 { 0994 reevaluteSource(); 0995 auto p = waylandServer()->seat()->pointer(); 0996 if (!p) { 0997 return; 0998 } 0999 auto c = p->cursor(); 1000 if (c) { 1001 m_serverCursor.cursor->update(c->surface(), c->hotspot()); 1002 } 1003 } 1004 1005 void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) 1006 { 1007 m_effectsCursor->setShape(shape); 1008 reevaluteSource(); 1009 } 1010 1011 void CursorImage::removeEffectsOverrideCursor() 1012 { 1013 reevaluteSource(); 1014 } 1015 1016 void CursorImage::setWindowSelectionCursor(const QByteArray &shape) 1017 { 1018 if (shape.isEmpty()) { 1019 m_windowSelectionCursor->setShape(Qt::CrossCursor); 1020 } else { 1021 m_windowSelectionCursor->setShape(shape); 1022 } 1023 reevaluteSource(); 1024 } 1025 1026 void CursorImage::removeWindowSelectionCursor() 1027 { 1028 reevaluteSource(); 1029 } 1030 1031 WaylandCursorImage::WaylandCursorImage(QObject *parent) 1032 : QObject(parent) 1033 { 1034 Cursor *pointerCursor = Cursors::self()->mouse(); 1035 updateCursorTheme(); 1036 1037 connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::updateCursorTheme); 1038 connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::updateCursorTheme); 1039 } 1040 1041 KXcursorTheme WaylandCursorImage::theme() const 1042 { 1043 return m_cursorTheme; 1044 } 1045 1046 void WaylandCursorImage::updateCursorTheme() 1047 { 1048 const Cursor *pointerCursor = Cursors::self()->mouse(); 1049 qreal targetDevicePixelRatio = 1; 1050 1051 const auto outputs = workspace()->outputs(); 1052 for (const Output *output : outputs) { 1053 if (output->scale() > targetDevicePixelRatio) { 1054 targetDevicePixelRatio = output->scale(); 1055 } 1056 } 1057 1058 m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio); 1059 if (m_cursorTheme.isEmpty()) { 1060 m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio); 1061 } 1062 1063 Q_EMIT themeChanged(); 1064 } 1065 1066 void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, ImageCursorSource *source) 1067 { 1068 loadThemeCursor(shape.name(), source); 1069 } 1070 1071 void WaylandCursorImage::loadThemeCursor(const QByteArray &name, ImageCursorSource *source) 1072 { 1073 if (loadThemeCursor_helper(name, source)) { 1074 return; 1075 } 1076 1077 const auto alternativeNames = Cursor::cursorAlternativeNames(name); 1078 for (const QByteArray &alternativeName : alternativeNames) { 1079 if (loadThemeCursor_helper(alternativeName, source)) { 1080 return; 1081 } 1082 } 1083 1084 qCWarning(KWIN_CORE) << "Failed to load theme cursor for shape" << name; 1085 } 1086 1087 bool WaylandCursorImage::loadThemeCursor_helper(const QByteArray &name, ImageCursorSource *source) 1088 { 1089 const QVector<KXcursorSprite> sprites = m_cursorTheme.shape(name); 1090 if (sprites.isEmpty()) { 1091 return false; 1092 } 1093 source->update(sprites.first().data(), sprites.first().hotspot()); 1094 return true; 1095 } 1096 1097 void CursorImage::reevaluteSource() 1098 { 1099 if (waylandServer()->isScreenLocked()) { 1100 setSource(m_serverCursor.cursor.get()); 1101 return; 1102 } 1103 if (input()->isSelectingWindow()) { 1104 setSource(m_windowSelectionCursor.get()); 1105 return; 1106 } 1107 if (effects && static_cast<EffectsHandlerImpl *>(effects)->isMouseInterception()) { 1108 setSource(m_effectsCursor.get()); 1109 return; 1110 } 1111 if (workspace() && workspace()->moveResizeWindow()) { 1112 setSource(m_moveResizeCursor.get()); 1113 return; 1114 } 1115 if (m_pointer->decoration()) { 1116 setSource(m_decoration.cursor.get()); 1117 return; 1118 } 1119 const KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer(); 1120 if (pointer && pointer->focusedSurface()) { 1121 setSource(m_serverCursor.cursor.get()); 1122 return; 1123 } 1124 setSource(m_fallbackCursor.get()); 1125 } 1126 1127 CursorSource *CursorImage::source() const 1128 { 1129 return m_currentSource; 1130 } 1131 1132 void CursorImage::setSource(CursorSource *source) 1133 { 1134 if (m_currentSource == source) { 1135 return; 1136 } 1137 m_currentSource = source; 1138 Q_EMIT changed(); 1139 } 1140 1141 KXcursorTheme CursorImage::theme() const 1142 { 1143 return m_waylandImage.theme(); 1144 } 1145 1146 InputRedirectionCursor::InputRedirectionCursor(QObject *parent) 1147 : Cursor(parent) 1148 , m_currentButtons(Qt::NoButton) 1149 { 1150 Cursors::self()->setMouse(this); 1151 connect(input(), &InputRedirection::globalPointerChanged, 1152 this, &InputRedirectionCursor::slotPosChanged); 1153 connect(input(), &InputRedirection::pointerButtonStateChanged, 1154 this, &InputRedirectionCursor::slotPointerButtonChanged); 1155 #ifndef KCMRULES 1156 connect(input(), &InputRedirection::keyboardModifiersChanged, 1157 this, &InputRedirectionCursor::slotModifiersChanged); 1158 #endif 1159 } 1160 1161 InputRedirectionCursor::~InputRedirectionCursor() 1162 { 1163 } 1164 1165 void InputRedirectionCursor::doSetPos() 1166 { 1167 if (input()->supportsPointerWarping()) { 1168 input()->warpPointer(currentPos()); 1169 } 1170 slotPosChanged(input()->globalPointer()); 1171 Q_EMIT posChanged(currentPos()); 1172 } 1173 1174 void InputRedirectionCursor::slotPosChanged(const QPointF &pos) 1175 { 1176 const QPoint oldPos = currentPos(); 1177 updatePos(pos.toPoint()); 1178 Q_EMIT mouseChanged(pos.toPoint(), oldPos, m_currentButtons, m_currentButtons, 1179 input()->keyboardModifiers(), input()->keyboardModifiers()); 1180 } 1181 1182 void InputRedirectionCursor::slotModifiersChanged(Qt::KeyboardModifiers mods, Qt::KeyboardModifiers oldMods) 1183 { 1184 Q_EMIT mouseChanged(currentPos(), currentPos(), m_currentButtons, m_currentButtons, mods, oldMods); 1185 } 1186 1187 void InputRedirectionCursor::slotPointerButtonChanged() 1188 { 1189 const Qt::MouseButtons oldButtons = m_currentButtons; 1190 m_currentButtons = input()->qtButtonStates(); 1191 const QPoint pos = currentPos(); 1192 Q_EMIT mouseChanged(pos, pos, m_currentButtons, oldButtons, input()->keyboardModifiers(), input()->keyboardModifiers()); 1193 } 1194 1195 }