We want the platform cursor to have roughly the same lifespan as the application. By using QObject parent mechanism, this gets deleted after the Application destructor in the QObject destructor. This causes an issue that removing an event filter (used by the X11 cursor) calls into the application singleton which is no longer valid. BUG: 465970
1149 lines
38 KiB
C++
1149 lines
38 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
|
|
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
|
|
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include "pointer_input.h"
|
|
|
|
#include <config-kwin.h>
|
|
|
|
#include "core/output.h"
|
|
#include "cursorsource.h"
|
|
#include "decorations/decoratedclient.h"
|
|
#include "effects.h"
|
|
#include "input_event.h"
|
|
#include "input_event_spy.h"
|
|
#include "mousebuttons.h"
|
|
#include "osd.h"
|
|
#include "wayland/display.h"
|
|
#include "wayland/pointer_interface.h"
|
|
#include "wayland/pointerconstraints_v1_interface.h"
|
|
#include "wayland/seat_interface.h"
|
|
#include "wayland/surface_interface.h"
|
|
#include "wayland_server.h"
|
|
#include "workspace.h"
|
|
#include "x11window.h"
|
|
// KDecoration
|
|
#include <KDecoration2/Decoration>
|
|
// screenlocker
|
|
#if KWIN_BUILD_SCREENLOCKER
|
|
#include <KScreenLocker/KsldApp>
|
|
#endif
|
|
|
|
#include <KLocalizedString>
|
|
|
|
#include <QHoverEvent>
|
|
#include <QPainter>
|
|
#include <QWindow>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <cmath>
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
static bool screenContainsPos(const QPointF &pos)
|
|
{
|
|
const auto outputs = workspace()->outputs();
|
|
for (const Output *output : outputs) {
|
|
if (output->geometry().contains(flooredPoint(pos))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static QPointF confineToBoundingBox(const QPointF &pos, const QRectF &boundingBox)
|
|
{
|
|
return QPointF(
|
|
std::clamp(pos.x(), boundingBox.left(), boundingBox.right() - 1.0),
|
|
std::clamp(pos.y(), boundingBox.top(), boundingBox.bottom() - 1.0));
|
|
}
|
|
|
|
PointerInputRedirection::PointerInputRedirection(InputRedirection *parent)
|
|
: InputDeviceHandler(parent)
|
|
, m_cursor(nullptr)
|
|
{
|
|
}
|
|
|
|
PointerInputRedirection::~PointerInputRedirection() = default;
|
|
|
|
void PointerInputRedirection::init()
|
|
{
|
|
Q_ASSERT(!inited());
|
|
waylandServer()->seat()->setHasPointer(input()->hasPointer());
|
|
connect(input(), &InputRedirection::hasPointerChanged,
|
|
waylandServer()->seat(), &KWaylandServer::SeatInterface::setHasPointer);
|
|
|
|
m_cursor = new CursorImage(this);
|
|
setInited(true);
|
|
InputDeviceHandler::init();
|
|
|
|
if (!input()->hasPointer()) {
|
|
Cursors::self()->hideCursor();
|
|
}
|
|
connect(input(), &InputRedirection::hasPointerChanged, this, []() {
|
|
if (input()->hasPointer()) {
|
|
Cursors::self()->showCursor();
|
|
} else {
|
|
Cursors::self()->hideCursor();
|
|
}
|
|
});
|
|
|
|
connect(Cursors::self()->mouse(), &Cursor::rendered, m_cursor, &CursorImage::markAsRendered);
|
|
connect(m_cursor, &CursorImage::changed, Cursors::self()->mouse(), [this] {
|
|
Cursors::self()->mouse()->setSource(m_cursor->source());
|
|
m_cursor->updateCursorOutputs(m_pos);
|
|
});
|
|
Q_EMIT m_cursor->changed();
|
|
|
|
connect(workspace(), &Workspace::outputsChanged, this, &PointerInputRedirection::updateAfterScreenChange);
|
|
#if KWIN_BUILD_SCREENLOCKER
|
|
if (waylandServer()->hasScreenLockerIntegration()) {
|
|
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, [this]() {
|
|
if (waylandServer()->seat()->hasPointer()) {
|
|
waylandServer()->seat()->cancelPointerPinchGesture();
|
|
waylandServer()->seat()->cancelPointerSwipeGesture();
|
|
}
|
|
update();
|
|
});
|
|
}
|
|
#endif
|
|
connect(workspace(), &QObject::destroyed, this, [this] {
|
|
setInited(false);
|
|
});
|
|
connect(waylandServer(), &QObject::destroyed, this, [this] {
|
|
setInited(false);
|
|
});
|
|
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragEnded, this, [this]() {
|
|
// need to force a focused pointer change
|
|
setFocus(nullptr);
|
|
update();
|
|
});
|
|
// connect the move resize of all window
|
|
auto setupMoveResizeConnection = [this](Window *window) {
|
|
connect(window, &Window::interactiveMoveResizeStarted, this, &PointerInputRedirection::updateOnStartMoveResize);
|
|
connect(window, &Window::interactiveMoveResizeFinished, this, &PointerInputRedirection::update);
|
|
};
|
|
const auto clients = workspace()->windows();
|
|
std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
|
|
connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection);
|
|
|
|
// warp the cursor to center of screen containing the workspace center
|
|
if (const Output *output = workspace()->outputAt(workspace()->geometry().center())) {
|
|
warp(output->geometry().center());
|
|
}
|
|
updateAfterScreenChange();
|
|
}
|
|
|
|
void PointerInputRedirection::updateOnStartMoveResize()
|
|
{
|
|
breakPointerConstraints(focus() ? focus()->surface() : nullptr);
|
|
disconnectPointerConstraintsConnection();
|
|
setFocus(nullptr);
|
|
}
|
|
|
|
void PointerInputRedirection::updateToReset()
|
|
{
|
|
if (decoration()) {
|
|
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
|
|
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
|
|
setDecoration(nullptr);
|
|
}
|
|
if (focus()) {
|
|
if (focus()->isClient()) {
|
|
focus()->pointerLeaveEvent();
|
|
}
|
|
disconnect(m_focusGeometryConnection);
|
|
m_focusGeometryConnection = QMetaObject::Connection();
|
|
breakPointerConstraints(focus()->surface());
|
|
disconnectPointerConstraintsConnection();
|
|
setFocus(nullptr);
|
|
}
|
|
}
|
|
|
|
class PositionUpdateBlocker
|
|
{
|
|
public:
|
|
PositionUpdateBlocker(PointerInputRedirection *pointer)
|
|
: m_pointer(pointer)
|
|
{
|
|
s_counter++;
|
|
}
|
|
~PositionUpdateBlocker()
|
|
{
|
|
s_counter--;
|
|
if (s_counter == 0) {
|
|
if (!s_scheduledPositions.isEmpty()) {
|
|
const auto pos = s_scheduledPositions.takeFirst();
|
|
m_pointer->processMotionInternal(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool isPositionBlocked()
|
|
{
|
|
return s_counter > 0;
|
|
}
|
|
|
|
static void schedulePosition(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time)
|
|
{
|
|
s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time});
|
|
}
|
|
|
|
private:
|
|
static int s_counter;
|
|
struct ScheduledPosition
|
|
{
|
|
QPointF pos;
|
|
QPointF delta;
|
|
QPointF deltaNonAccelerated;
|
|
std::chrono::microseconds time;
|
|
};
|
|
static QVector<ScheduledPosition> s_scheduledPositions;
|
|
|
|
PointerInputRedirection *m_pointer;
|
|
};
|
|
|
|
int PositionUpdateBlocker::s_counter = 0;
|
|
QVector<PositionUpdateBlocker::ScheduledPosition> PositionUpdateBlocker::s_scheduledPositions;
|
|
|
|
void PointerInputRedirection::processMotionAbsolute(const QPointF &pos, std::chrono::microseconds time, InputDevice *device)
|
|
{
|
|
processMotionInternal(pos, QPointF(), QPointF(), time, device);
|
|
}
|
|
|
|
void PointerInputRedirection::processMotion(const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device)
|
|
{
|
|
processMotionInternal(m_pos + delta, delta, deltaNonAccelerated, time, device);
|
|
}
|
|
|
|
void PointerInputRedirection::processMotionInternal(const QPointF &pos, const QPointF &delta, const QPointF &deltaNonAccelerated, std::chrono::microseconds time, InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
if (PositionUpdateBlocker::isPositionBlocked()) {
|
|
PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time);
|
|
return;
|
|
}
|
|
|
|
PositionUpdateBlocker blocker(this);
|
|
updatePosition(pos);
|
|
MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons,
|
|
input()->keyboardModifiers(), time,
|
|
delta, deltaNonAccelerated, device);
|
|
event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
|
|
|
|
update();
|
|
input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
|
|
input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0));
|
|
}
|
|
|
|
void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, std::chrono::microseconds time, InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
QEvent::Type type;
|
|
switch (state) {
|
|
case InputRedirection::PointerButtonReleased:
|
|
type = QEvent::MouseButtonRelease;
|
|
break;
|
|
case InputRedirection::PointerButtonPressed:
|
|
type = QEvent::MouseButtonPress;
|
|
update();
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
return;
|
|
}
|
|
|
|
updateButton(button, state);
|
|
|
|
MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons,
|
|
input()->keyboardModifiers(), time, QPointF(), QPointF(), device);
|
|
event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
|
|
event.setNativeButton(button);
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event));
|
|
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
|
|
input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button));
|
|
|
|
if (state == InputRedirection::PointerButtonReleased) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 deltaV120,
|
|
InputRedirection::PointerAxisSource source, std::chrono::microseconds time, InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
update();
|
|
|
|
Q_EMIT input()->pointerAxisChanged(axis, delta);
|
|
|
|
WheelEvent wheelEvent(m_pos, delta, deltaV120,
|
|
(axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
|
|
m_qtButtons, input()->keyboardModifiers(), source, time, device);
|
|
wheelEvent.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts());
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent));
|
|
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent));
|
|
}
|
|
|
|
void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::swipeGestureBegin, std::placeholders::_1, fingerCount, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::swipeGestureBegin, std::placeholders::_1, fingerCount, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processSwipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processSwipeGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processSwipeGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processPinchGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processPinchGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processPinchGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
input()->setLastInputHandler(this);
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processHoldGestureBegin(int fingerCount, std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::holdGestureBegin, std::placeholders::_1, fingerCount, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::holdGestureBegin, std::placeholders::_1, fingerCount, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processHoldGestureEnd(std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::holdGestureEnd, std::placeholders::_1, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::holdGestureEnd, std::placeholders::_1, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processHoldGestureCancelled(std::chrono::microseconds time, KWin::InputDevice *device)
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
|
|
input()->processSpies(std::bind(&InputEventSpy::holdGestureCancelled, std::placeholders::_1, time));
|
|
input()->processFilters(std::bind(&InputEventFilter::holdGestureCancelled, std::placeholders::_1, time));
|
|
}
|
|
|
|
void PointerInputRedirection::processFrame(KWin::InputDevice *device)
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
|
|
input()->processFilters(std::bind(&InputEventFilter::pointerFrame, std::placeholders::_1));
|
|
}
|
|
|
|
bool PointerInputRedirection::areButtonsPressed() const
|
|
{
|
|
for (auto state : m_buttons) {
|
|
if (state == InputRedirection::PointerButtonPressed) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PointerInputRedirection::focusUpdatesBlocked()
|
|
{
|
|
if (waylandServer()->seat()->isDragPointer()) {
|
|
// ignore during drag and drop
|
|
return true;
|
|
}
|
|
if (waylandServer()->seat()->isTouchSequence()) {
|
|
// ignore during touch operations
|
|
return true;
|
|
}
|
|
if (input()->isSelectingWindow()) {
|
|
return true;
|
|
}
|
|
if (areButtonsPressed()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now)
|
|
{
|
|
disconnect(m_decorationGeometryConnection);
|
|
m_decorationGeometryConnection = QMetaObject::Connection();
|
|
|
|
disconnect(m_decorationDestroyedConnection);
|
|
m_decorationDestroyedConnection = QMetaObject::Connection();
|
|
|
|
if (old) {
|
|
// send leave event to old decoration
|
|
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
|
|
QCoreApplication::instance()->sendEvent(old->decoration(), &event);
|
|
}
|
|
if (!now) {
|
|
// left decoration
|
|
return;
|
|
}
|
|
|
|
auto pos = m_pos - now->window()->pos();
|
|
QHoverEvent event(QEvent::HoverEnter, pos, pos);
|
|
QCoreApplication::instance()->sendEvent(now->decoration(), &event);
|
|
now->window()->processDecorationMove(pos, m_pos);
|
|
|
|
m_decorationGeometryConnection = connect(
|
|
decoration()->window(), &Window::frameGeometryChanged, this, [this]() {
|
|
// ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140
|
|
const auto oldDeco = decoration();
|
|
update();
|
|
if (oldDeco && oldDeco == decoration() && !decoration()->window()->isInteractiveMove() && !decoration()->window()->isInteractiveResize() && !areButtonsPressed()) {
|
|
// position of window did not change, we need to send HoverMotion manually
|
|
const QPointF p = m_pos - decoration()->window()->pos();
|
|
QHoverEvent event(QEvent::HoverMove, p, p);
|
|
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
|
|
}
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
// if our decoration gets destroyed whilst it has focus, we pass focus on to the same window
|
|
m_decorationDestroyedConnection = connect(now, &QObject::destroyed, this, &PointerInputRedirection::update, Qt::QueuedConnection);
|
|
}
|
|
|
|
void PointerInputRedirection::focusUpdate(Window *focusOld, Window *focusNow)
|
|
{
|
|
if (focusOld && focusOld->isClient()) {
|
|
focusOld->pointerLeaveEvent();
|
|
breakPointerConstraints(focusOld->surface());
|
|
disconnectPointerConstraintsConnection();
|
|
}
|
|
disconnect(m_focusGeometryConnection);
|
|
m_focusGeometryConnection = QMetaObject::Connection();
|
|
|
|
if (focusNow && focusNow->isClient()) {
|
|
focusNow->pointerEnterEvent(m_pos);
|
|
}
|
|
|
|
auto seat = waylandServer()->seat();
|
|
if (!focusNow || !focusNow->surface()) {
|
|
seat->notifyPointerLeave();
|
|
return;
|
|
}
|
|
|
|
seat->notifyPointerEnter(focusNow->surface(), m_pos, focusNow->inputTransformation());
|
|
|
|
m_focusGeometryConnection = connect(focusNow, &Window::inputTransformationChanged, this, [this]() {
|
|
// TODO: why no assert possible?
|
|
if (!focus()) {
|
|
return;
|
|
}
|
|
// TODO: can we check on the window instead?
|
|
if (workspace()->moveResizeWindow()) {
|
|
// don't update while moving
|
|
return;
|
|
}
|
|
auto seat = waylandServer()->seat();
|
|
if (focus()->surface() != seat->focusedPointerSurface()) {
|
|
return;
|
|
}
|
|
seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation());
|
|
});
|
|
|
|
m_constraintsConnection = connect(focusNow->surface(), &KWaylandServer::SurfaceInterface::pointerConstraintsChanged,
|
|
this, &PointerInputRedirection::updatePointerConstraints);
|
|
m_constraintsActivatedConnection = connect(workspace(), &Workspace::windowActivated,
|
|
this, &PointerInputRedirection::updatePointerConstraints);
|
|
updatePointerConstraints();
|
|
}
|
|
|
|
void PointerInputRedirection::breakPointerConstraints(KWaylandServer::SurfaceInterface *surface)
|
|
{
|
|
// cancel pointer constraints
|
|
if (surface) {
|
|
auto c = surface->confinedPointer();
|
|
if (c && c->isConfined()) {
|
|
c->setConfined(false);
|
|
}
|
|
auto l = surface->lockedPointer();
|
|
if (l && l->isLocked()) {
|
|
l->setLocked(false);
|
|
}
|
|
}
|
|
disconnectConfinedPointerRegionConnection();
|
|
m_confined = false;
|
|
m_locked = false;
|
|
}
|
|
|
|
void PointerInputRedirection::disconnectConfinedPointerRegionConnection()
|
|
{
|
|
disconnect(m_confinedPointerRegionConnection);
|
|
m_confinedPointerRegionConnection = QMetaObject::Connection();
|
|
}
|
|
|
|
void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection()
|
|
{
|
|
disconnect(m_lockedPointerAboutToBeUnboundConnection);
|
|
m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection();
|
|
}
|
|
|
|
void PointerInputRedirection::disconnectPointerConstraintsConnection()
|
|
{
|
|
disconnect(m_constraintsConnection);
|
|
m_constraintsConnection = QMetaObject::Connection();
|
|
|
|
disconnect(m_constraintsActivatedConnection);
|
|
m_constraintsActivatedConnection = QMetaObject::Connection();
|
|
}
|
|
|
|
void PointerInputRedirection::setEnableConstraints(bool set)
|
|
{
|
|
if (m_enableConstraints == set) {
|
|
return;
|
|
}
|
|
m_enableConstraints = set;
|
|
updatePointerConstraints();
|
|
}
|
|
|
|
void PointerInputRedirection::updatePointerConstraints()
|
|
{
|
|
if (!focus()) {
|
|
return;
|
|
}
|
|
const auto s = focus()->surface();
|
|
if (!s) {
|
|
return;
|
|
}
|
|
if (s != waylandServer()->seat()->focusedPointerSurface()) {
|
|
return;
|
|
}
|
|
if (!supportsWarping()) {
|
|
return;
|
|
}
|
|
const bool canConstrain = m_enableConstraints && focus() == workspace()->activeWindow();
|
|
const auto cf = s->confinedPointer();
|
|
if (cf) {
|
|
if (cf->isConfined()) {
|
|
if (!canConstrain) {
|
|
cf->setConfined(false);
|
|
m_confined = false;
|
|
disconnectConfinedPointerRegionConnection();
|
|
}
|
|
return;
|
|
}
|
|
if (canConstrain && cf->region().contains(flooredPoint(focus()->mapToLocal(m_pos)))) {
|
|
cf->setConfined(true);
|
|
m_confined = true;
|
|
m_confinedPointerRegionConnection = connect(cf, &KWaylandServer::ConfinedPointerV1Interface::regionChanged, this, [this]() {
|
|
if (!focus()) {
|
|
return;
|
|
}
|
|
const auto s = focus()->surface();
|
|
if (!s) {
|
|
return;
|
|
}
|
|
const auto cf = s->confinedPointer();
|
|
if (!cf->region().contains(flooredPoint(focus()->mapToLocal(m_pos)))) {
|
|
// pointer no longer in confined region, break the confinement
|
|
cf->setConfined(false);
|
|
m_confined = false;
|
|
} else {
|
|
if (!cf->isConfined()) {
|
|
cf->setConfined(true);
|
|
m_confined = true;
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
} else {
|
|
m_confined = false;
|
|
disconnectConfinedPointerRegionConnection();
|
|
}
|
|
const auto lock = s->lockedPointer();
|
|
if (lock) {
|
|
if (lock->isLocked()) {
|
|
if (!canConstrain) {
|
|
const auto hint = lock->cursorPositionHint();
|
|
lock->setLocked(false);
|
|
m_locked = false;
|
|
disconnectLockedPointerAboutToBeUnboundConnection();
|
|
if (!(hint.x() < 0 || hint.y() < 0) && focus()) {
|
|
processMotionAbsolute(focus()->mapFromLocal(hint), waylandServer()->seat()->timestamp());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (canConstrain && lock->region().contains(flooredPoint(focus()->mapToLocal(m_pos)))) {
|
|
lock->setLocked(true);
|
|
m_locked = true;
|
|
|
|
// The client might cancel pointer locking from its side by unbinding the LockedPointerInterface.
|
|
// In this case the cached cursor position hint must be fetched before the resource goes away
|
|
m_lockedPointerAboutToBeUnboundConnection = connect(lock, &KWaylandServer::LockedPointerV1Interface::aboutToBeDestroyed, this, [this, lock]() {
|
|
const auto hint = lock->cursorPositionHint();
|
|
if (hint.x() < 0 || hint.y() < 0 || !focus()) {
|
|
return;
|
|
}
|
|
auto globalHint = focus()->mapFromLocal(hint);
|
|
|
|
// When the resource finally goes away, reposition the cursor according to the hint
|
|
connect(lock, &KWaylandServer::LockedPointerV1Interface::destroyed, this, [this, globalHint]() {
|
|
processMotionAbsolute(globalHint, waylandServer()->seat()->timestamp());
|
|
});
|
|
});
|
|
// TODO: connect to region change - is it needed at all? If the pointer is locked it's always in the region
|
|
}
|
|
} else {
|
|
m_locked = false;
|
|
disconnectLockedPointerAboutToBeUnboundConnection();
|
|
}
|
|
}
|
|
|
|
QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const
|
|
{
|
|
if (!focus()) {
|
|
return pos;
|
|
}
|
|
auto s = focus()->surface();
|
|
if (!s) {
|
|
return pos;
|
|
}
|
|
auto cf = s->confinedPointer();
|
|
if (!cf) {
|
|
return pos;
|
|
}
|
|
if (!cf->isConfined()) {
|
|
return pos;
|
|
}
|
|
|
|
const QPointF localPos = focus()->mapToLocal(pos);
|
|
if (cf->region().contains(flooredPoint(localPos))) {
|
|
return pos;
|
|
}
|
|
|
|
const QPointF currentPos = focus()->mapToLocal(m_pos);
|
|
|
|
// allow either x or y to pass
|
|
QPointF p(currentPos.x(), localPos.y());
|
|
if (cf->region().contains(flooredPoint(p))) {
|
|
return focus()->mapFromLocal(p);
|
|
}
|
|
|
|
p = QPointF(localPos.x(), currentPos.y());
|
|
if (cf->region().contains(flooredPoint(p))) {
|
|
return focus()->mapFromLocal(p);
|
|
}
|
|
|
|
return m_pos;
|
|
}
|
|
|
|
void PointerInputRedirection::updatePosition(const QPointF &pos)
|
|
{
|
|
if (m_locked) {
|
|
// locked pointer should not move
|
|
return;
|
|
}
|
|
// verify that at least one screen contains the pointer position
|
|
const Output *currentOutput = workspace()->outputAt(pos);
|
|
QPointF p = confineToBoundingBox(pos, currentOutput->geometry());
|
|
p = applyPointerConfinement(p);
|
|
if (p == m_pos) {
|
|
// didn't change due to confinement
|
|
return;
|
|
}
|
|
// verify screen confinement
|
|
if (!screenContainsPos(p)) {
|
|
return;
|
|
}
|
|
|
|
m_pos = p;
|
|
|
|
workspace()->setActiveCursorOutput(m_pos);
|
|
m_cursor->updateCursorOutputs(m_pos);
|
|
|
|
Q_EMIT input()->globalPointerChanged(m_pos);
|
|
}
|
|
|
|
void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state)
|
|
{
|
|
m_buttons[button] = state;
|
|
|
|
// update Qt buttons
|
|
m_qtButtons = Qt::NoButton;
|
|
for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) {
|
|
if (it.value() == InputRedirection::PointerButtonReleased) {
|
|
continue;
|
|
}
|
|
m_qtButtons |= buttonToQtMouseButton(it.key());
|
|
}
|
|
|
|
Q_EMIT input()->pointerButtonStateChanged(button, state);
|
|
}
|
|
|
|
void PointerInputRedirection::warp(const QPointF &pos)
|
|
{
|
|
if (supportsWarping()) {
|
|
processMotionAbsolute(pos, waylandServer()->seat()->timestamp());
|
|
}
|
|
}
|
|
|
|
bool PointerInputRedirection::supportsWarping() const
|
|
{
|
|
return inited();
|
|
}
|
|
|
|
void PointerInputRedirection::updateAfterScreenChange()
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
if (screenContainsPos(m_pos)) {
|
|
// pointer still on a screen
|
|
return;
|
|
}
|
|
// pointer no longer on a screen, reposition to closes screen
|
|
const Output *output = workspace()->outputAt(m_pos);
|
|
// TODO: better way to get timestamps
|
|
processMotionAbsolute(output->geometry().center(), waylandServer()->seat()->timestamp());
|
|
}
|
|
|
|
QPointF PointerInputRedirection::position() const
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape)
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
// current pointer focus window should get a leave event
|
|
update();
|
|
m_cursor->setEffectsOverrideCursor(shape);
|
|
}
|
|
|
|
void PointerInputRedirection::removeEffectsOverrideCursor()
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
// cursor position might have changed while there was an effect in place
|
|
update();
|
|
m_cursor->removeEffectsOverrideCursor();
|
|
}
|
|
|
|
void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape)
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
// send leave to current pointer focus window
|
|
updateToReset();
|
|
m_cursor->setWindowSelectionCursor(shape);
|
|
}
|
|
|
|
void PointerInputRedirection::removeWindowSelectionCursor()
|
|
{
|
|
if (!inited()) {
|
|
return;
|
|
}
|
|
update();
|
|
m_cursor->removeWindowSelectionCursor();
|
|
}
|
|
|
|
CursorImage::CursorImage(PointerInputRedirection *parent)
|
|
: QObject(parent)
|
|
, m_pointer(parent)
|
|
{
|
|
m_effectsCursor = std::make_unique<ShapeCursorSource>();
|
|
m_fallbackCursor = std::make_unique<ShapeCursorSource>();
|
|
m_moveResizeCursor = std::make_unique<ShapeCursorSource>();
|
|
m_windowSelectionCursor = std::make_unique<ShapeCursorSource>();
|
|
m_decoration.cursor = std::make_unique<ShapeCursorSource>();
|
|
m_serverCursor.surface = std::make_unique<SurfaceCursorSource>();
|
|
m_serverCursor.shape = std::make_unique<ShapeCursorSource>();
|
|
|
|
#if KWIN_BUILD_SCREENLOCKER
|
|
if (waylandServer()->hasScreenLockerIntegration()) {
|
|
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource);
|
|
}
|
|
#endif
|
|
connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration);
|
|
// connect the move resize of all window
|
|
auto setupMoveResizeConnection = [this](Window *window) {
|
|
connect(window, &Window::moveResizedChanged, this, &CursorImage::updateMoveResize);
|
|
connect(window, &Window::moveResizeCursorChanged, this, &CursorImage::updateMoveResize);
|
|
};
|
|
const auto clients = workspace()->windows();
|
|
std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
|
|
connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection);
|
|
|
|
m_fallbackCursor->setShape(Qt::ArrowCursor);
|
|
|
|
m_effectsCursor->setTheme(m_waylandImage.theme());
|
|
m_fallbackCursor->setTheme(m_waylandImage.theme());
|
|
m_moveResizeCursor->setTheme(m_waylandImage.theme());
|
|
m_windowSelectionCursor->setTheme(m_waylandImage.theme());
|
|
m_decoration.cursor->setTheme(m_waylandImage.theme());
|
|
m_serverCursor.shape->setTheme(m_waylandImage.theme());
|
|
|
|
connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] {
|
|
m_effectsCursor->setTheme(m_waylandImage.theme());
|
|
m_fallbackCursor->setTheme(m_waylandImage.theme());
|
|
m_moveResizeCursor->setTheme(m_waylandImage.theme());
|
|
m_windowSelectionCursor->setTheme(m_waylandImage.theme());
|
|
m_decoration.cursor->setTheme(m_waylandImage.theme());
|
|
m_serverCursor.shape->setTheme(m_waylandImage.theme());
|
|
});
|
|
|
|
KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer();
|
|
|
|
connect(pointer, &KWaylandServer::PointerInterface::focusedSurfaceChanged,
|
|
this, &CursorImage::handleFocusedSurfaceChanged);
|
|
|
|
reevaluteSource();
|
|
}
|
|
|
|
CursorImage::~CursorImage() = default;
|
|
|
|
void CursorImage::updateCursorOutputs(const QPointF &pos)
|
|
{
|
|
if (m_currentSource == m_serverCursor.surface.get()) {
|
|
auto cursorSurface = m_serverCursor.surface->surface();
|
|
if (cursorSurface) {
|
|
const QRectF cursorGeometry(pos - m_currentSource->hotspot(), m_currentSource->size());
|
|
cursorSurface->setOutputs(waylandServer()->display()->outputsIntersecting(cursorGeometry.toAlignedRect()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CursorImage::markAsRendered(std::chrono::milliseconds timestamp)
|
|
{
|
|
if (m_currentSource == m_serverCursor.surface.get()) {
|
|
auto cursorSurface = m_serverCursor.surface->surface();
|
|
if (cursorSurface) {
|
|
cursorSurface->frameRendered(timestamp.count());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CursorImage::handleFocusedSurfaceChanged()
|
|
{
|
|
KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer();
|
|
disconnect(m_serverCursor.connection);
|
|
|
|
if (pointer->focusedSurface()) {
|
|
m_serverCursor.connection = connect(pointer, &KWaylandServer::PointerInterface::cursorChanged,
|
|
this, &CursorImage::updateServerCursor);
|
|
} else {
|
|
m_serverCursor.connection = QMetaObject::Connection();
|
|
reevaluteSource();
|
|
}
|
|
}
|
|
|
|
void CursorImage::updateDecoration()
|
|
{
|
|
disconnect(m_decoration.connection);
|
|
auto deco = m_pointer->decoration();
|
|
Window *window = deco ? deco->window() : nullptr;
|
|
if (window) {
|
|
m_decoration.connection = connect(window, &Window::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor);
|
|
} else {
|
|
m_decoration.connection = QMetaObject::Connection();
|
|
}
|
|
updateDecorationCursor();
|
|
}
|
|
|
|
void CursorImage::updateDecorationCursor()
|
|
{
|
|
auto deco = m_pointer->decoration();
|
|
if (Window *window = deco ? deco->window() : nullptr) {
|
|
m_decoration.cursor->setShape(window->cursor().name());
|
|
}
|
|
reevaluteSource();
|
|
}
|
|
|
|
void CursorImage::updateMoveResize()
|
|
{
|
|
if (Window *window = workspace()->moveResizeWindow()) {
|
|
m_moveResizeCursor->setShape(window->cursor().name());
|
|
}
|
|
reevaluteSource();
|
|
}
|
|
|
|
void CursorImage::updateServerCursor(const KWaylandServer::PointerCursor &cursor)
|
|
{
|
|
if (auto surfaceCursor = std::get_if<KWaylandServer::Cursor *>(&cursor)) {
|
|
m_serverCursor.surface->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot());
|
|
m_serverCursor.cursor = m_serverCursor.surface.get();
|
|
} else if (auto shapeCursor = std::get_if<QByteArray>(&cursor)) {
|
|
m_serverCursor.shape->setShape(*shapeCursor);
|
|
m_serverCursor.cursor = m_serverCursor.shape.get();
|
|
}
|
|
reevaluteSource();
|
|
}
|
|
|
|
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
|
|
{
|
|
m_effectsCursor->setShape(shape);
|
|
reevaluteSource();
|
|
}
|
|
|
|
void CursorImage::removeEffectsOverrideCursor()
|
|
{
|
|
reevaluteSource();
|
|
}
|
|
|
|
void CursorImage::setWindowSelectionCursor(const QByteArray &shape)
|
|
{
|
|
if (shape.isEmpty()) {
|
|
m_windowSelectionCursor->setShape(Qt::CrossCursor);
|
|
} else {
|
|
m_windowSelectionCursor->setShape(shape);
|
|
}
|
|
reevaluteSource();
|
|
}
|
|
|
|
void CursorImage::removeWindowSelectionCursor()
|
|
{
|
|
reevaluteSource();
|
|
}
|
|
|
|
WaylandCursorImage::WaylandCursorImage(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
Cursor *pointerCursor = Cursors::self()->mouse();
|
|
updateCursorTheme();
|
|
|
|
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::updateCursorTheme);
|
|
connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::updateCursorTheme);
|
|
}
|
|
|
|
KXcursorTheme WaylandCursorImage::theme() const
|
|
{
|
|
return m_cursorTheme;
|
|
}
|
|
|
|
void WaylandCursorImage::updateCursorTheme()
|
|
{
|
|
const Cursor *pointerCursor = Cursors::self()->mouse();
|
|
qreal targetDevicePixelRatio = 1;
|
|
|
|
const auto outputs = workspace()->outputs();
|
|
for (const Output *output : outputs) {
|
|
if (output->scale() > targetDevicePixelRatio) {
|
|
targetDevicePixelRatio = output->scale();
|
|
}
|
|
}
|
|
|
|
m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio);
|
|
if (m_cursorTheme.isEmpty()) {
|
|
m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio);
|
|
}
|
|
|
|
Q_EMIT themeChanged();
|
|
}
|
|
|
|
void CursorImage::reevaluteSource()
|
|
{
|
|
if (waylandServer()->isScreenLocked()) {
|
|
setSource(m_serverCursor.cursor);
|
|
return;
|
|
}
|
|
if (input()->isSelectingWindow()) {
|
|
setSource(m_windowSelectionCursor.get());
|
|
return;
|
|
}
|
|
if (effects && static_cast<EffectsHandlerImpl *>(effects)->isMouseInterception()) {
|
|
setSource(m_effectsCursor.get());
|
|
return;
|
|
}
|
|
if (workspace() && workspace()->moveResizeWindow()) {
|
|
setSource(m_moveResizeCursor.get());
|
|
return;
|
|
}
|
|
if (m_pointer->decoration()) {
|
|
setSource(m_decoration.cursor.get());
|
|
return;
|
|
}
|
|
const KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer();
|
|
if (pointer && pointer->focusedSurface()) {
|
|
setSource(m_serverCursor.cursor);
|
|
return;
|
|
}
|
|
setSource(m_fallbackCursor.get());
|
|
}
|
|
|
|
CursorSource *CursorImage::source() const
|
|
{
|
|
return m_currentSource;
|
|
}
|
|
|
|
void CursorImage::setSource(CursorSource *source)
|
|
{
|
|
if (m_currentSource == source) {
|
|
return;
|
|
}
|
|
m_currentSource = source;
|
|
Q_EMIT changed();
|
|
}
|
|
|
|
KXcursorTheme CursorImage::theme() const
|
|
{
|
|
return m_waylandImage.theme();
|
|
}
|
|
|
|
InputRedirectionCursor::InputRedirectionCursor()
|
|
: Cursor()
|
|
, m_currentButtons(Qt::NoButton)
|
|
{
|
|
Cursors::self()->setMouse(this);
|
|
connect(input(), &InputRedirection::globalPointerChanged,
|
|
this, &InputRedirectionCursor::slotPosChanged);
|
|
connect(input(), &InputRedirection::pointerButtonStateChanged,
|
|
this, &InputRedirectionCursor::slotPointerButtonChanged);
|
|
#ifndef KCMRULES
|
|
connect(input(), &InputRedirection::keyboardModifiersChanged,
|
|
this, &InputRedirectionCursor::slotModifiersChanged);
|
|
#endif
|
|
}
|
|
|
|
InputRedirectionCursor::~InputRedirectionCursor()
|
|
{
|
|
}
|
|
|
|
void InputRedirectionCursor::doSetPos()
|
|
{
|
|
if (input()->supportsPointerWarping()) {
|
|
input()->warpPointer(currentPos());
|
|
}
|
|
slotPosChanged(input()->globalPointer());
|
|
Q_EMIT posChanged(currentPos());
|
|
}
|
|
|
|
void InputRedirectionCursor::slotPosChanged(const QPointF &pos)
|
|
{
|
|
const QPointF oldPos = currentPos();
|
|
updatePos(pos);
|
|
Q_EMIT mouseChanged(pos, oldPos, m_currentButtons, m_currentButtons,
|
|
input()->keyboardModifiers(), input()->keyboardModifiers());
|
|
}
|
|
|
|
void InputRedirectionCursor::slotModifiersChanged(Qt::KeyboardModifiers mods, Qt::KeyboardModifiers oldMods)
|
|
{
|
|
Q_EMIT mouseChanged(currentPos(), currentPos(), m_currentButtons, m_currentButtons, mods, oldMods);
|
|
}
|
|
|
|
void InputRedirectionCursor::slotPointerButtonChanged()
|
|
{
|
|
const Qt::MouseButtons oldButtons = m_currentButtons;
|
|
m_currentButtons = input()->qtButtonStates();
|
|
const QPointF pos = currentPos();
|
|
Q_EMIT mouseChanged(pos, pos, m_currentButtons, oldButtons, input()->keyboardModifiers(), input()->keyboardModifiers());
|
|
}
|
|
|
|
}
|
|
|
|
#include "moc_pointer_input.cpp"
|