File indexing completed on 2025-03-16 05:04:47
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([×tamp](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"