File indexing completed on 2024-04-28 05:30:28

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