File indexing completed on 2024-04-28 16:48:58

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 }