KWin
Loading...
Searching...
No Matches
pointer_input.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
6 SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
7 SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
9
10 SPDX-License-Identifier: GPL-2.0-or-later
11*/
12#include "pointer_input.h"
13
14#include <config-kwin.h>
15
16#include "core/output.h"
17#include "cursorsource.h"
20#include "input_event.h"
21#include "input_event_spy.h"
22#include "mousebuttons.h"
23#include "osd.h"
24#include "wayland/display.h"
25#include "wayland/pointer.h"
27#include "wayland/seat.h"
28#include "wayland/surface.h"
29#include "wayland_server.h"
30#include "workspace.h"
31#include "x11window.h"
32// KDecoration
33#include <KDecoration2/Decoration>
34// screenlocker
35#if KWIN_BUILD_SCREENLOCKER
36#include <KScreenLocker/KsldApp>
37#endif
38
39#include <KLocalizedString>
40
41#include <QHoverEvent>
42#include <QPainter>
43#include <QWindow>
44
45#include <linux/input.h>
46
47#include <cmath>
48
49namespace KWin
50{
51
52static bool screenContainsPos(const QPointF &pos)
53{
54 const auto outputs = workspace()->outputs();
55 for (const Output *output : outputs) {
56 if (output->geometry().contains(flooredPoint(pos))) {
57 return true;
58 }
59 }
60 return false;
61}
62
63static QPointF confineToBoundingBox(const QPointF &pos, const QRectF &boundingBox)
64{
65 return QPointF(
66 std::clamp(pos.x(), boundingBox.left(), boundingBox.right() - 1.0),
67 std::clamp(pos.y(), boundingBox.top(), boundingBox.bottom() - 1.0));
68}
69
75
77
79{
80 Q_ASSERT(!inited());
81 waylandServer()->seat()->setHasPointer(input()->hasPointer());
84
85 m_cursor = new CursorImage(this);
86 setInited(true);
88
89 if (!input()->hasPointer()) {
91 }
92 connect(input(), &InputRedirection::hasPointerChanged, this, []() {
93 if (input()->hasPointer()) {
95 } else {
97 }
98 });
99
100 connect(Cursors::self()->mouse(), &Cursor::rendered, m_cursor, &CursorImage::markAsRendered);
101 connect(m_cursor, &CursorImage::changed, Cursors::self()->mouse(), [this] {
102 Cursors::self()->mouse()->setSource(m_cursor->source());
103 m_cursor->updateCursorOutputs(m_pos);
104 });
105 Q_EMIT m_cursor->changed();
106
108#if KWIN_BUILD_SCREENLOCKER
109 if (waylandServer()->hasScreenLockerIntegration()) {
110 connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, [this]() {
111 if (waylandServer()->seat()->hasPointer()) {
114 }
115 update();
116 });
117 }
118#endif
119 connect(workspace(), &QObject::destroyed, this, [this] {
120 setInited(false);
121 });
122 connect(waylandServer(), &QObject::destroyed, this, [this] {
123 setInited(false);
124 });
125 connect(waylandServer()->seat(), &SeatInterface::dragEnded, this, [this]() {
126 // need to force a focused pointer change
127 setFocus(nullptr);
128 update();
129 });
130 // connect the move resize of all window
131 auto setupMoveResizeConnection = [this](Window *window) {
132 connect(window, &Window::interactiveMoveResizeStarted, this, &PointerInputRedirection::updateOnStartMoveResize);
134 };
135 const auto clients = workspace()->windows();
136 std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
137 connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection);
138
139 // warp the cursor to center of screen containing the workspace center
140 if (const Output *output = workspace()->outputAt(workspace()->geometry().center())) {
141 warp(output->geometry().center());
142 }
144}
145
146void PointerInputRedirection::updateOnStartMoveResize()
147{
148 breakPointerConstraints(focus() ? focus()->surface() : nullptr);
149 disconnectPointerConstraintsConnection();
150 setFocus(nullptr);
151}
152
153void PointerInputRedirection::updateToReset()
154{
155 if (decoration()) {
156 QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
157 QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
158 setDecoration(nullptr);
159 }
160 if (focus()) {
161 if (focus()->isClient()) {
163 }
164 disconnect(m_focusGeometryConnection);
165 m_focusGeometryConnection = QMetaObject::Connection();
166 breakPointerConstraints(focus()->surface());
167 disconnectPointerConstraintsConnection();
168 setFocus(nullptr);
169 }
170}
171
173{
174public:
176 : m_pointer(pointer)
177 {
178 s_counter++;
179 }
181 {
182 s_counter--;
183 if (s_counter == 0) {
184 if (!s_scheduledPositions.isEmpty()) {
185 const auto pos = s_scheduledPositions.takeFirst();
186 m_pointer->processMotionInternal(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, nullptr);
187 }
188 }
189 }
190
191 static bool isPositionBlocked()
192 {
193 return s_counter > 0;
194 }
195
196 static void schedulePosition(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time)
197 {
198 s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time});
199 }
200
201private:
202 static int s_counter;
203 struct ScheduledPosition
204 {
205 QPointF pos;
206 QPointF delta;
207 QPointF deltaNonAccelerated;
208 std::chrono::microseconds time;
209 };
210 static QList<ScheduledPosition> s_scheduledPositions;
211
212 PointerInputRedirection *m_pointer;
213};
214
215int PositionUpdateBlocker::s_counter = 0;
216QList<PositionUpdateBlocker::ScheduledPosition> PositionUpdateBlocker::s_scheduledPositions;
217
218void PointerInputRedirection::processMotionAbsolute(const QPointF &pos, std::chrono::microseconds time, InputDevice *device)
219{
220 processMotionInternal(pos, QPointF(), QPointF(), time, device);
221}
222
223void PointerInputRedirection::processMotion(const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device)
224{
225 processMotionInternal(m_pos + delta, delta, deltaNonAccelerated, time, device);
226}
227
228void PointerInputRedirection::processMotionInternal(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device)
229{
230 input()->setLastInputHandler(this);
231 if (!inited()) {
232 return;
233 }
235 PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time);
236 return;
237 }
238
239 PositionUpdateBlocker blocker(this);
240 updatePosition(pos);
241 MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons,
242 input()->keyboardModifiers(), time,
243 delta, deltaNonAccelerated, device);
244 event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
245
246 update();
247 input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
248 input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0));
249}
250
251void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, std::chrono::microseconds time, InputDevice *device)
252{
253 input()->setLastInputHandler(this);
254 QEvent::Type type;
255 switch (state) {
257 type = QEvent::MouseButtonRelease;
258 break;
260 type = QEvent::MouseButtonPress;
261 update();
262 break;
263 default:
264 Q_UNREACHABLE();
265 return;
266 }
267
268 updateButton(button, state);
269
270 MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons,
271 input()->keyboardModifiers(), time, QPointF(), QPointF(), device);
272 event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
273 event.setNativeButton(button);
274
275 input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
276
277 if (!inited()) {
278 return;
279 }
280
281 input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button));
282
284 update();
285 }
286}
287
289 InputRedirection::PointerAxisSource source, std::chrono::microseconds time, InputDevice *device)
290{
291 input()->setLastInputHandler(this);
292 update();
293
294 Q_EMIT input()->pointerAxisChanged(axis, delta);
295
296 WheelEvent wheelEvent(m_pos, delta, deltaV120,
297 (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
298 m_qtButtons, input()->keyboardModifiers(), source, time, device);
300
301 input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent));
302
303 if (!inited()) {
304 return;
305 }
306 input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent));
307}
308
309void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device)
310{
311 input()->setLastInputHandler(this);
312 if (!inited()) {
313 return;
314 }
315
316 input()->processSpies(std::bind(&InputEventSpy::swipeGestureBegin, std::placeholders::_1, fingerCount, time));
317 input()->processFilters(std::bind(&InputEventFilter::swipeGestureBegin, std::placeholders::_1, fingerCount, time));
318}
319
320void PointerInputRedirection::processSwipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device)
321{
322 input()->setLastInputHandler(this);
323 if (!inited()) {
324 return;
325 }
326 update();
327
328 input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time));
329 input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time));
330}
331
332void PointerInputRedirection::processSwipeGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device)
333{
334 input()->setLastInputHandler(this);
335 if (!inited()) {
336 return;
337 }
338 update();
339
340 input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time));
341 input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time));
342}
343
345{
346 input()->setLastInputHandler(this);
347 if (!inited()) {
348 return;
349 }
350 update();
351
352 input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time));
353 input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time));
354}
355
356void PointerInputRedirection::processPinchGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device)
357{
358 input()->setLastInputHandler(this);
359 if (!inited()) {
360 return;
361 }
362 update();
363
364 input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time));
365 input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time));
366}
367
368void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device)
369{
370 input()->setLastInputHandler(this);
371 if (!inited()) {
372 return;
373 }
374 update();
375
376 input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time));
377 input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time));
378}
379
380void PointerInputRedirection::processPinchGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device)
381{
382 input()->setLastInputHandler(this);
383 if (!inited()) {
384 return;
385 }
386 update();
387
388 input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time));
389 input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time));
390}
391
393{
394 input()->setLastInputHandler(this);
395 if (!inited()) {
396 return;
397 }
398 update();
399
400 input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time));
401 input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time));
402}
403
404void PointerInputRedirection::processHoldGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device)
405{
406 if (!inited()) {
407 return;
408 }
409 update();
410
411 input()->processSpies(std::bind(&InputEventSpy::holdGestureBegin, std::placeholders::_1, fingerCount, time));
412 input()->processFilters(std::bind(&InputEventFilter::holdGestureBegin, std::placeholders::_1, fingerCount, time));
413}
414
415void PointerInputRedirection::processHoldGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device)
416{
417 if (!inited()) {
418 return;
419 }
420 update();
421
422 input()->processSpies(std::bind(&InputEventSpy::holdGestureEnd, std::placeholders::_1, time));
423 input()->processFilters(std::bind(&InputEventFilter::holdGestureEnd, std::placeholders::_1, time));
424}
425
427{
428 if (!inited()) {
429 return;
430 }
431 update();
432
433 input()->processSpies(std::bind(&InputEventSpy::holdGestureCancelled, std::placeholders::_1, time));
434 input()->processFilters(std::bind(&InputEventFilter::holdGestureCancelled, std::placeholders::_1, time));
435}
436
438{
439 if (!inited()) {
440 return;
441 }
442
443 input()->processFilters(std::bind(&InputEventFilter::pointerFrame, std::placeholders::_1));
444}
445
447{
448 for (auto state : m_buttons) {
450 return true;
451 }
452 }
453 return false;
454}
455
457{
458 if (waylandServer()->seat()->isDragPointer()) {
459 // ignore during drag and drop
460 return true;
461 }
462 if (waylandServer()->seat()->isTouchSequence()) {
463 // ignore during touch operations
464 return true;
465 }
466 if (input()->isSelectingWindow()) {
467 return true;
468 }
469 if (areButtonsPressed()) {
470 return true;
471 }
472 return false;
473}
474
475void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now)
476{
477 disconnect(m_decorationGeometryConnection);
478 m_decorationGeometryConnection = QMetaObject::Connection();
479
480 disconnect(m_decorationDestroyedConnection);
481 m_decorationDestroyedConnection = QMetaObject::Connection();
482
483 disconnect(m_decorationClosedConnection);
484 m_decorationClosedConnection = QMetaObject::Connection();
485
486 if (old) {
487 // send leave event to old decoration
488 QHoverEvent event(QEvent::HoverLeave, QPointF(-1, -1), QPointF());
489 QCoreApplication::instance()->sendEvent(old->decoration(), &event);
490 }
491 if (!now) {
492 // left decoration
493 return;
494 }
495
496 auto pos = m_pos - now->window()->pos();
497 QHoverEvent event(QEvent::HoverEnter, pos, QPointF(-1, -1));
498 QCoreApplication::instance()->sendEvent(now->decoration(), &event);
499 now->window()->processDecorationMove(pos, m_pos);
500
501 m_decorationGeometryConnection = connect(decoration()->window(), &Window::frameGeometryChanged, this, [this]() {
502 // ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140
503 const auto oldDeco = decoration();
504 update();
505 if (oldDeco && oldDeco == decoration() && !decoration()->window()->isInteractiveMove() && !decoration()->window()->isInteractiveResize() && !areButtonsPressed()) {
506 // position of window did not change, we need to send HoverMotion manually
507 const QPointF p = m_pos - decoration()->window()->pos();
508 QHoverEvent event(QEvent::HoverMove, p, p);
509 QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
510 }
511 });
512
513 auto resetDecoration = [this]() {
514 setDecoration(nullptr); // explicitly reset decoration if focus updates are blocked
515 update();
516 };
517
518 m_decorationClosedConnection = connect(decoration()->window(), &Window::closed, this, resetDecoration);
519 m_decorationDestroyedConnection = connect(now, &QObject::destroyed, this, resetDecoration);
520}
521
522void PointerInputRedirection::focusUpdate(Window *focusOld, Window *focusNow)
523{
524 if (focusOld && focusOld->isClient()) {
525 focusOld->pointerLeaveEvent();
526 breakPointerConstraints(focusOld->surface());
527 disconnectPointerConstraintsConnection();
528 }
529 disconnect(m_focusGeometryConnection);
530 m_focusGeometryConnection = QMetaObject::Connection();
531
532 if (focusNow && focusNow->isClient()) {
533 focusNow->pointerEnterEvent(m_pos);
534 }
535
536 auto seat = waylandServer()->seat();
537 if (!focusNow || !focusNow->surface()) {
538 seat->notifyPointerLeave();
539 return;
540 }
541
542 seat->notifyPointerEnter(focusNow->surface(), m_pos, focusNow->inputTransformation());
543
544 m_focusGeometryConnection = connect(focusNow, &Window::inputTransformationChanged, this, [this]() {
545 // TODO: why no assert possible?
546 if (!focus()) {
547 return;
548 }
549 // TODO: can we check on the window instead?
550 if (workspace()->moveResizeWindow()) {
551 // don't update while moving
552 return;
553 }
554 auto seat = waylandServer()->seat();
555 if (focus()->surface() != seat->focusedPointerSurface()) {
556 return;
557 }
558 seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation());
559 });
560
561 m_constraintsConnection = connect(focusNow->surface(), &SurfaceInterface::pointerConstraintsChanged,
563 m_constraintsActivatedConnection = connect(workspace(), &Workspace::windowActivated,
566}
567
568void PointerInputRedirection::breakPointerConstraints(SurfaceInterface *surface)
569{
570 // cancel pointer constraints
571 if (surface) {
572 auto c = surface->confinedPointer();
573 if (c && c->isConfined()) {
574 c->setConfined(false);
575 }
576 auto l = surface->lockedPointer();
577 if (l && l->isLocked()) {
578 l->setLocked(false);
579 }
580 }
581 disconnectConfinedPointerRegionConnection();
582 m_confined = false;
583 m_locked = false;
584}
585
586void PointerInputRedirection::disconnectConfinedPointerRegionConnection()
587{
588 disconnect(m_confinedPointerRegionConnection);
589 m_confinedPointerRegionConnection = QMetaObject::Connection();
590}
591
592void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection()
593{
594 disconnect(m_lockedPointerAboutToBeUnboundConnection);
595 m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection();
596}
597
598void PointerInputRedirection::disconnectPointerConstraintsConnection()
599{
600 disconnect(m_constraintsConnection);
601 m_constraintsConnection = QMetaObject::Connection();
602
603 disconnect(m_constraintsActivatedConnection);
604 m_constraintsActivatedConnection = QMetaObject::Connection();
605}
606
608{
609 if (m_enableConstraints == set) {
610 return;
611 }
612 m_enableConstraints = set;
614}
615
617{
618 if (!focus()) {
619 return;
620 }
621 const auto s = focus()->surface();
622 if (!s) {
623 return;
624 }
625 if (s != waylandServer()->seat()->focusedPointerSurface()) {
626 return;
627 }
628 if (!supportsWarping()) {
629 return;
630 }
631 const bool canConstrain = m_enableConstraints && focus() == workspace()->activeWindow();
632 const auto cf = s->confinedPointer();
633 if (cf) {
634 if (cf->isConfined()) {
635 if (!canConstrain) {
636 cf->setConfined(false);
637 m_confined = false;
638 disconnectConfinedPointerRegionConnection();
639 }
640 return;
641 }
642 if (canConstrain && cf->region().contains(flooredPoint(focus()->mapToLocal(m_pos)))) {
643 cf->setConfined(true);
644 m_confined = true;
645 m_confinedPointerRegionConnection = connect(cf, &ConfinedPointerV1Interface::regionChanged, this, [this]() {
646 if (!focus()) {
647 return;
648 }
649 const auto s = focus()->surface();
650 if (!s) {
651 return;
652 }
653 const auto cf = s->confinedPointer();
654 if (!cf->region().contains(flooredPoint(focus()->mapToLocal(m_pos)))) {
655 // pointer no longer in confined region, break the confinement
656 cf->setConfined(false);
657 m_confined = false;
658 } else {
659 if (!cf->isConfined()) {
660 cf->setConfined(true);
661 m_confined = true;
662 }
663 }
664 });
665 return;
666 }
667 } else {
668 m_confined = false;
669 disconnectConfinedPointerRegionConnection();
670 }
671 const auto lock = s->lockedPointer();
672 if (lock) {
673 if (lock->isLocked()) {
674 if (!canConstrain) {
675 const auto hint = lock->cursorPositionHint();
676 lock->setLocked(false);
677 m_locked = false;
678 disconnectLockedPointerAboutToBeUnboundConnection();
679 if (!(hint.x() < 0 || hint.y() < 0) && focus()) {
680 processMotionAbsolute(focus()->mapFromLocal(hint), waylandServer()->seat()->timestamp());
681 }
682 }
683 return;
684 }
685 if (canConstrain && lock->region().contains(flooredPoint(focus()->mapToLocal(m_pos)))) {
686 lock->setLocked(true);
687 m_locked = true;
688
689 // The client might cancel pointer locking from its side by unbinding the LockedPointerInterface.
690 // In this case the cached cursor position hint must be fetched before the resource goes away
691 m_lockedPointerAboutToBeUnboundConnection = connect(lock, &LockedPointerV1Interface::aboutToBeDestroyed, this, [this, lock]() {
692 const auto hint = lock->cursorPositionHint();
693 if (hint.x() < 0 || hint.y() < 0 || !focus()) {
694 return;
695 }
696 auto globalHint = focus()->mapFromLocal(hint);
697
698 // When the resource finally goes away, reposition the cursor according to the hint
699 connect(lock, &LockedPointerV1Interface::destroyed, this, [this, globalHint]() {
700 processMotionAbsolute(globalHint, waylandServer()->seat()->timestamp());
701 });
702 });
703 // TODO: connect to region change - is it needed at all? If the pointer is locked it's always in the region
704 }
705 } else {
706 m_locked = false;
707 disconnectLockedPointerAboutToBeUnboundConnection();
708 }
709}
710
711QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const
712{
713 if (!focus()) {
714 return pos;
715 }
716 auto s = focus()->surface();
717 if (!s) {
718 return pos;
719 }
720 auto cf = s->confinedPointer();
721 if (!cf) {
722 return pos;
723 }
724 if (!cf->isConfined()) {
725 return pos;
726 }
727
728 const QPointF localPos = focus()->mapToLocal(pos);
729 if (cf->region().contains(flooredPoint(localPos))) {
730 return pos;
731 }
732
733 const QPointF currentPos = focus()->mapToLocal(m_pos);
734
735 // allow either x or y to pass
736 QPointF p(currentPos.x(), localPos.y());
737 if (cf->region().contains(flooredPoint(p))) {
738 return focus()->mapFromLocal(p);
739 }
740
741 p = QPointF(localPos.x(), currentPos.y());
742 if (cf->region().contains(flooredPoint(p))) {
743 return focus()->mapFromLocal(p);
744 }
745
746 return m_pos;
747}
748
749void PointerInputRedirection::updatePosition(const QPointF &pos)
750{
751 if (m_locked) {
752 // locked pointer should not move
753 return;
754 }
755 // verify that at least one screen contains the pointer position
756 const Output *currentOutput = workspace()->outputAt(pos);
757 QPointF p = confineToBoundingBox(pos, currentOutput->geometry());
758 p = applyPointerConfinement(p);
759 if (p == m_pos) {
760 // didn't change due to confinement
761 return;
762 }
763 // verify screen confinement
764 if (!screenContainsPos(p)) {
765 return;
766 }
767
768 m_pos = p;
769
771 m_cursor->updateCursorOutputs(m_pos);
772
773 Q_EMIT input()->globalPointerChanged(m_pos);
774}
775
776void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state)
777{
778 m_buttons[button] = state;
779
780 // update Qt buttons
781 m_qtButtons = Qt::NoButton;
782 for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) {
783 if (it.value() == InputRedirection::PointerButtonReleased) {
784 continue;
785 }
786 m_qtButtons |= buttonToQtMouseButton(it.key());
787 }
788
789 Q_EMIT input()->pointerButtonStateChanged(button, state);
790}
791
792void PointerInputRedirection::warp(const QPointF &pos)
793{
794 if (supportsWarping()) {
795 processMotionAbsolute(pos, waylandServer()->seat()->timestamp());
796 }
797}
798
800{
801 return inited();
802}
803
805{
806 if (!inited()) {
807 return;
808 }
809
810 Output *output = nullptr;
811 if (m_lastOutputWasPlaceholder) {
812 // previously we've positioned our pointer on a placeholder screen, try
813 // to get us onto the real "primary" screen instead.
814 output = workspace()->outputOrder().at(0);
815 } else {
816 if (screenContainsPos(m_pos)) {
817 // pointer still on a screen
818 return;
819 }
820
821 // pointer no longer on a screen, reposition to closes screen
822 output = workspace()->outputAt(m_pos);
823 }
824
825 m_lastOutputWasPlaceholder = output->isPlaceholder();
826 // TODO: better way to get timestamps
827 processMotionAbsolute(output->geometry().center(), waylandServer()->seat()->timestamp());
828}
829
830QPointF PointerInputRedirection::position() const
831{
832 return m_pos;
833}
834
836{
837 if (!inited()) {
838 return;
839 }
840 // current pointer focus window should get a leave event
841 update();
842 m_cursor->setEffectsOverrideCursor(shape);
843}
844
846{
847 if (!inited()) {
848 return;
849 }
850 // cursor position might have changed while there was an effect in place
851 update();
852 m_cursor->removeEffectsOverrideCursor();
853}
854
856{
857 if (!inited()) {
858 return;
859 }
860 // send leave to current pointer focus window
861 updateToReset();
862 m_cursor->setWindowSelectionCursor(shape);
863}
864
866{
867 if (!inited()) {
868 return;
869 }
870 update();
871 m_cursor->removeWindowSelectionCursor();
872}
873
875 : QObject(parent)
876 , m_pointer(parent)
877{
878 m_effectsCursor = std::make_unique<ShapeCursorSource>();
879 m_fallbackCursor = std::make_unique<ShapeCursorSource>();
880 m_moveResizeCursor = std::make_unique<ShapeCursorSource>();
881 m_windowSelectionCursor = std::make_unique<ShapeCursorSource>();
882 m_decoration.cursor = std::make_unique<ShapeCursorSource>();
883 m_serverCursor.surface = std::make_unique<SurfaceCursorSource>();
884 m_serverCursor.shape = std::make_unique<ShapeCursorSource>();
885
886#if KWIN_BUILD_SCREENLOCKER
887 if (waylandServer()->hasScreenLockerIntegration()) {
888 connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource);
889 }
890#endif
891 connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration);
892 // connect the move resize of all window
893 auto setupMoveResizeConnection = [this](Window *window) {
894 connect(window, &Window::moveResizedChanged, this, &CursorImage::updateMoveResize);
895 connect(window, &Window::moveResizeCursorChanged, this, &CursorImage::updateMoveResize);
896 };
897 const auto clients = workspace()->windows();
898 std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
899 connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection);
900
901 m_fallbackCursor->setShape(Qt::ArrowCursor);
902
903 m_effectsCursor->setTheme(m_waylandImage.theme());
904 m_fallbackCursor->setTheme(m_waylandImage.theme());
905 m_moveResizeCursor->setTheme(m_waylandImage.theme());
906 m_windowSelectionCursor->setTheme(m_waylandImage.theme());
907 m_decoration.cursor->setTheme(m_waylandImage.theme());
908 m_serverCursor.shape->setTheme(m_waylandImage.theme());
909
910 connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] {
911 m_effectsCursor->setTheme(m_waylandImage.theme());
912 m_fallbackCursor->setTheme(m_waylandImage.theme());
913 m_moveResizeCursor->setTheme(m_waylandImage.theme());
914 m_windowSelectionCursor->setTheme(m_waylandImage.theme());
915 m_decoration.cursor->setTheme(m_waylandImage.theme());
916 m_serverCursor.shape->setTheme(m_waylandImage.theme());
917 });
918
919 PointerInterface *pointer = waylandServer()->seat()->pointer();
920
922 this, &CursorImage::handleFocusedSurfaceChanged);
923
924 reevaluteSource();
925}
926
927CursorImage::~CursorImage() = default;
928
929void CursorImage::updateCursorOutputs(const QPointF &pos)
930{
931 if (m_currentSource == m_serverCursor.surface.get()) {
932 auto cursorSurface = m_serverCursor.surface->surface();
933 if (cursorSurface) {
934 const QRectF cursorGeometry(pos - m_currentSource->hotspot(), m_currentSource->size());
935 cursorSurface->setOutputs(waylandServer()->display()->outputsIntersecting(cursorGeometry.toAlignedRect()),
936 waylandServer()->display()->largestIntersectingOutput(cursorGeometry.toAlignedRect()));
937 }
938 }
939}
940
941void CursorImage::markAsRendered(std::chrono::milliseconds timestamp)
942{
943 if (m_currentSource == m_serverCursor.surface.get()) {
944 if (auto cursorSurface = m_serverCursor.surface->surface()) {
945 cursorSurface->traverseTree([&timestamp](SurfaceInterface *surface) {
946 surface->frameRendered(timestamp.count());
947 });
948 }
949 }
950}
951
952void CursorImage::handleFocusedSurfaceChanged()
953{
954 PointerInterface *pointer = waylandServer()->seat()->pointer();
955 disconnect(m_serverCursor.connection);
956
957 if (pointer->focusedSurface()) {
958 m_serverCursor.connection = connect(pointer, &PointerInterface::cursorChanged,
959 this, &CursorImage::updateServerCursor);
960 } else {
961 m_serverCursor.connection = QMetaObject::Connection();
962 reevaluteSource();
963 }
964}
965
966void CursorImage::updateDecoration()
967{
968 disconnect(m_decoration.connection);
969 auto deco = m_pointer->decoration();
970 Window *window = deco ? deco->window() : nullptr;
971 if (window) {
972 m_decoration.connection = connect(window, &Window::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor);
973 } else {
974 m_decoration.connection = QMetaObject::Connection();
975 }
976 updateDecorationCursor();
977}
978
979void CursorImage::updateDecorationCursor()
980{
981 auto deco = m_pointer->decoration();
982 if (Window *window = deco ? deco->window() : nullptr) {
983 m_decoration.cursor->setShape(window->cursor().name());
984 }
985 reevaluteSource();
986}
987
988void CursorImage::updateMoveResize()
989{
990 if (Window *window = workspace()->moveResizeWindow()) {
991 m_moveResizeCursor->setShape(window->cursor().name());
992 }
993 reevaluteSource();
994}
995
996void CursorImage::updateServerCursor(const PointerCursor &cursor)
997{
998 if (auto surfaceCursor = std::get_if<PointerSurfaceCursor *>(&cursor)) {
999 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
1008void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
1009{
1010 m_effectsCursor->setShape(shape);
1011 reevaluteSource();
1012}
1013
1015{
1016 reevaluteSource();
1017}
1018
1019void 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
1030{
1031 reevaluteSource();
1032}
1033
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
1045{
1046 return m_cursorTheme;
1047}
1048
1049void 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
1069void 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 }
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
1100{
1101 return m_currentSource;
1102}
1103
1105{
1106 if (m_currentSource == source) {
1107 return;
1108 }
1109 m_currentSource = source;
1110 Q_EMIT changed();
1111}
1112
1114{
1115 return m_waylandImage.theme();
1116}
1117
1119 : Cursor()
1120 , m_currentButtons(Qt::NoButton)
1121{
1122 Cursors::self()->setMouse(this);
1124 this, &InputRedirectionCursor::slotPosChanged);
1126 this, &InputRedirectionCursor::slotPointerButtonChanged);
1127#ifndef KCMRULES
1129 this, &InputRedirectionCursor::slotModifiersChanged);
1130#endif
1131}
1132
1136
1138{
1139 if (input()->supportsPointerWarping()) {
1141 }
1142 slotPosChanged(input()->globalPointer());
1143 Q_EMIT posChanged(currentPos());
1144}
1145
1146void 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
1154void InputRedirectionCursor::slotModifiersChanged(Qt::KeyboardModifiers mods, Qt::KeyboardModifiers oldMods)
1155{
1156 Q_EMIT mouseChanged(currentPos(), currentPos(), m_currentButtons, m_currentButtons, mods, oldMods);
1157}
1158
1159void 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"
Replacement for QCursor.
Definition cursor.h:102
QPointF pos()
Definition cursor.cpp:204
void mouseChanged(const QPointF &pos, const QPointF &oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers)
static int defaultThemeSize()
Definition cursor.cpp:338
void setSource(CursorSource *source)
Definition cursor.cpp:703
int themeSize() const
The size of the currently used Cursor theme.
Definition cursor.h:322
static QString defaultThemeName()
Definition cursor.cpp:333
void posChanged(const QPointF &pos)
const QString & themeName() const
The name of the currently used Cursor theme.
Definition cursor.h:317
void themeChanged()
void updatePos(const QPointF &pos)
Definition cursor.cpp:274
const QPointF & currentPos() const
Definition cursor.h:312
void rendered(std::chrono::milliseconds timestamp)
std::unique_ptr< ShapeCursorSource > shape
CursorSource * source() const
void setEffectsOverrideCursor(Qt::CursorShape shape)
KXcursorTheme theme() const
CursorImage(PointerInputRedirection *parent=nullptr)
std::unique_ptr< SurfaceCursorSource > surface
void markAsRendered(std::chrono::milliseconds timestamp)
std::unique_ptr< ShapeCursorSource > cursor
void updateCursorOutputs(const QPointF &pos)
~CursorImage() override
void setWindowSelectionCursor(const QByteArray &shape)
void setSource(CursorSource *source)
QSizeF size() const
QPointF hotspot() const
void hideCursor()
Definition cursor.cpp:69
static Cursors * self()
Definition cursor.cpp:35
void showCursor()
Definition cursor.cpp:77
void setMouse(Cursor *mouse)
Definition cursor.h:271
Cursor * mouse() const
Definition cursor.h:266
bool isMouseInterception() const
QPointer< Window > window
Definition input.h:536
bool inited() const
Definition input.h:520
void setDecoration(Decoration::DecoratedClientImpl *decoration)
Definition input.cpp:3449
void setFocus(Window *window)
Definition input.cpp:3430
Window * focus() const
Window currently having pointer input focus (this might be different from the Window at the position ...
Definition input.cpp:3521
void setInited(bool set)
Definition input.h:524
virtual void init()
Definition input.cpp:3410
QPointer< Decoration::DecoratedClientImpl > decoration
Definition input.h:543
virtual bool pinchGestureEnd(std::chrono::microseconds time)
Definition input.cpp:165
virtual bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time)
Definition input.cpp:160
virtual bool swipeGestureCancelled(std::chrono::microseconds time)
Definition input.cpp:190
virtual bool pointerEvent(MouseEvent *event, quint32 nativeButton)
Definition input.cpp:110
virtual bool pointerFrame()
Definition input.cpp:115
virtual bool swipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time)
Definition input.cpp:180
virtual bool wheelEvent(WheelEvent *event)
Definition input.cpp:120
virtual bool holdGestureEnd(std::chrono::microseconds time)
Definition input.cpp:200
virtual bool swipeGestureEnd(std::chrono::microseconds time)
Definition input.cpp:185
virtual bool pinchGestureCancelled(std::chrono::microseconds time)
Definition input.cpp:170
virtual bool holdGestureBegin(int fingerCount, std::chrono::microseconds time)
Definition input.cpp:195
virtual bool pinchGestureBegin(int fingerCount, std::chrono::microseconds time)
Definition input.cpp:155
virtual bool holdGestureCancelled(std::chrono::microseconds time)
Definition input.cpp:205
virtual bool swipeGestureBegin(int fingerCount, std::chrono::microseconds time)
Definition input.cpp:175
virtual void swipeGestureEnd(std::chrono::microseconds time)
virtual void holdGestureCancelled(std::chrono::microseconds time)
virtual void holdGestureEnd(std::chrono::microseconds time)
virtual void wheelEvent(WheelEvent *event)
virtual void swipeGestureBegin(int fingerCount, std::chrono::microseconds time)
virtual void holdGestureBegin(int fingerCount, std::chrono::microseconds time)
virtual void swipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time)
virtual void pinchGestureBegin(int fingerCount, std::chrono::microseconds time)
virtual void swipeGestureCancelled(std::chrono::microseconds time)
virtual void pinchGestureCancelled(std::chrono::microseconds time)
virtual void pinchGestureEnd(std::chrono::microseconds time)
virtual void pointerEvent(MouseEvent *event)
virtual void pinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time)
This class is responsible for redirecting incoming input to the surface which currently has input or ...
Definition input.h:70
void keyboardModifiersChanged(Qt::KeyboardModifiers newMods, Qt::KeyboardModifiers oldMods)
Emitted when the modifiers changes.
void globalPointerChanged(const QPointF &pos)
Emitted when the global pointer position changed.
void processSpies(UnaryFunction function)
Definition input.h:211
void hasPointerChanged(bool set)
void processFilters(UnaryPredicate function)
Definition input.h:192
Qt::MouseButtons qtButtonStates() const
Definition input.cpp:3214
void pointerButtonStateChanged(uint32_t button, InputRedirection::PointerButtonState state)
Emitted when the state of a pointer button changed.
Qt::KeyboardModifiers keyboardModifiers() const
Definition input.cpp:3304
void setLastInputHandler(QObject *device)
Definition input.cpp:2739
void warpPointer(const QPointF &pos)
Definition input.cpp:3363
void pointerAxisChanged(InputRedirection::PointerAxis axis, qreal delta)
Emitted when a pointer axis changed.
Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const
Definition input.cpp:3309
bool isPlaceholder() const
Definition output.cpp:662
QRect geometry
Definition output.h:134
PointerInputRedirection(InputRedirection *parent)
void processPinchGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processSwipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processPinchGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processPinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processHoldGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processHoldGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processMotionAbsolute(const QPointF &pos, std::chrono::microseconds time, InputDevice *device=nullptr)
void setWindowSelectionCursor(const QByteArray &shape)
void setEffectsOverrideCursor(Qt::CursorShape shape)
void processSwipeGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processSwipeGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 deltaV120, InputRedirection::PointerAxisSource source, std::chrono::microseconds time, InputDevice *device=nullptr)
void processHoldGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processPinchGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processSwipeGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device=nullptr)
void processMotion(const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device)
void warp(const QPointF &pos)
void processFrame(KWin::InputDevice *device=nullptr)
void processButton(uint32_t button, InputRedirection::PointerButtonState state, std::chrono::microseconds time, InputDevice *device=nullptr)
SurfaceInterface * focusedSurface() const
Definition pointer.cpp:158
void cursorChanged(const PointerCursor &cursor)
PositionUpdateBlocker(PointerInputRedirection *pointer)
static void schedulePosition(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time)
void cancelPointerSwipeGesture()
Definition seat.cpp:814
void setHasPointer(bool has)
Definition seat.cpp:357
PointerInterface * pointer() const
Definition seat.cpp:648
void cancelPointerPinchGesture()
Definition seat.cpp:862
Resource representing a wl_surface.
Definition surface.h:80
ConfinedPointerV1Interface * confinedPointer() const
Definition surface.cpp:1019
KXcursorTheme theme() const
WaylandCursorImage(QObject *parent=nullptr)
SeatInterface * seat() const
void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods)
void interactiveMoveResizeStarted()
QPointF pos
Definition window.h:79
void inputTransformationChanged()
QPointF mapToLocal(const QPointF &point) const
Definition window.cpp:399
SurfaceInterface * surface() const
Definition window.cpp:342
void processDecorationMove(const QPointF &localPos, const QPointF &globalPos)
Definition window.cpp:2706
void interactiveMoveResizeFinished()
virtual void pointerLeaveEvent()
Definition window.cpp:2847
QPointF mapFromLocal(const QPointF &point) const
Definition window.cpp:404
void moveResizedChanged()
void frameGeometryChanged(const QRectF &oldGeometry)
void moveResizeCursorChanged(CursorShape)
Window * activeWindow() const
Definition workspace.h:767
QList< Output * > outputOrder() const
void windowActivated(KWin::Window *)
void windowAdded(KWin::Window *)
QList< Output * > outputs() const
Definition workspace.h:762
const QList< Window * > windows() const
Definition workspace.h:248
Output * outputAt(const QPointF &pos) const
void setActiveCursorOutput(Output *output)
void outputsChanged()
KWayland::Client::Seat * seat
QList< KWayland::Client::Output * > outputs
std::variant< PointerSurfaceCursor *, QByteArray > PointerCursor
Definition pointer.h:29
Session::Type type
Definition session.cpp:17
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
InputRedirection * input()
Definition input.h:549
KWIN_EXPORT QPoint flooredPoint(const QPointF &point)
Definition globals.h:262
EffectsHandler * effects
Qt::MouseButton buttonToQtMouseButton(uint32_t button)