kwin/pointer_input.cpp

1470 lines
47 KiB
C++
Raw Normal View History

2020-08-02 22:22:19 +00:00
/*
KWin - the KDE window manager
This file is part of the KDE project.
2020-08-02 22:22:19 +00:00
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>
2020-08-02 22:22:19 +00:00
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pointer_input.h"
#include "platform.h"
#include "x11client.h"
#include "effects.h"
#include "input_event.h"
#include "input_event_spy.h"
#include "osd.h"
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
#include "decorations/decoratedclient.h"
// KDecoration
#include <KDecoration2/Decoration>
// KWayland
#include <KWaylandServer/buffer_interface.h>
#include <KWaylandServer/datadevice_interface.h>
#include <KWaylandServer/display.h>
#include <KWaylandServer/pointerconstraints_interface.h>
#include <KWaylandServer/seat_interface.h>
#include <KWaylandServer/surface_interface.h>
// screenlocker
#include <KScreenLocker/KsldApp>
#include <KLocalizedString>
#include <QHoverEvent>
#include <QWindow>
#include <QPainter>
// Wayland
#include <wayland-cursor.h>
#include <linux/input.h>
namespace KWin
{
static const QHash<uint32_t, Qt::MouseButton> s_buttonToQtMouseButton = {
{ BTN_LEFT , Qt::LeftButton },
{ BTN_MIDDLE , Qt::MiddleButton },
{ BTN_RIGHT , Qt::RightButton },
// in QtWayland mapped like that
{ BTN_SIDE , Qt::ExtraButton1 },
// in QtWayland mapped like that
{ BTN_EXTRA , Qt::ExtraButton2 },
{ BTN_BACK , Qt::BackButton },
{ BTN_FORWARD , Qt::ForwardButton },
{ BTN_TASK , Qt::TaskButton },
// mapped like that in QtWayland
{ 0x118 , Qt::ExtraButton6 },
{ 0x119 , Qt::ExtraButton7 },
{ 0x11a , Qt::ExtraButton8 },
{ 0x11b , Qt::ExtraButton9 },
{ 0x11c , Qt::ExtraButton10 },
{ 0x11d , Qt::ExtraButton11 },
{ 0x11e , Qt::ExtraButton12 },
{ 0x11f , Qt::ExtraButton13 },
};
uint32_t qtMouseButtonToButton(Qt::MouseButton button)
{
return s_buttonToQtMouseButton.key(button);
}
static Qt::MouseButton buttonToQtMouseButton(uint32_t button)
{
// all other values get mapped to ExtraButton24
// this is actually incorrect but doesn't matter in our usage
// KWin internally doesn't use these high extra buttons anyway
// it's only needed for recognizing whether buttons are pressed
// if multiple buttons are mapped to the value the evaluation whether
// buttons are pressed is correct and that's all we care about.
return s_buttonToQtMouseButton.value(button, Qt::ExtraButton24);
}
static bool screenContainsPos(const QPointF &pos)
{
for (int i = 0; i < screens()->count(); ++i) {
if (screens()->geometry(i).contains(pos.toPoint())) {
return true;
}
}
return false;
}
static QPointF confineToBoundingBox(const QPointF &pos, const QRectF &boundingBox)
{
return QPointF(
qBound(boundingBox.left(), pos.x(), boundingBox.right() - 1.0),
qBound(boundingBox.top(), pos.y(), boundingBox.bottom() - 1.0)
);
}
PointerInputRedirection::PointerInputRedirection(InputRedirection* parent)
: InputDeviceHandler(parent)
, m_cursor(nullptr)
, m_supportsWarping(Application::usesLibinput())
{
}
PointerInputRedirection::~PointerInputRedirection() = default;
void PointerInputRedirection::init()
{
Q_ASSERT(!inited());
m_cursor = new CursorImage(this);
setInited(true);
InputDeviceHandler::init();
connect(m_cursor, &CursorImage::changed, Cursors::self()->mouse(), [this] {
auto cursor = Cursors::self()->mouse();
cursor->updateCursor(m_cursor->image(), m_cursor->hotSpot());
});
emit m_cursor->changed();
connect(Cursors::self()->mouse(), &Cursor::rendered, m_cursor, &CursorImage::markAsRendered);
connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange);
if (waylandServer()->hasScreenLockerIntegration()) {
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this,
[this] {
waylandServer()->seat()->cancelPointerPinchGesture();
waylandServer()->seat()->cancelPointerSwipeGesture();
update();
}
);
}
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
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
setFocus(nullptr);
update();
}
);
// connect the move resize of all window
auto setupMoveResizeConnection = [this] (AbstractClient *c) {
connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize);
connect(c, &AbstractClient::clientFinishUserMovedResized, this, &PointerInputRedirection::update);
};
const auto clients = workspace()->allClientList();
std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection);
// warp the cursor to center of screen
warp(screens()->geometry().center());
updateAfterScreenChange();
}
void PointerInputRedirection::updateOnStartMoveResize()
{
breakPointerConstraints(focus() ? focus()->surface() : nullptr);
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
disconnectPointerConstraintsConnection();
setFocus(nullptr);
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
}
void PointerInputRedirection::updateToReset()
{
if (internalWindow()) {
disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection();
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(internalWindow().data(), &event);
setInternalWindow(nullptr);
}
if (decoration()) {
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
setDecoration(nullptr);
}
if (focus()) {
if (AbstractClient *c = qobject_cast<AbstractClient*>(focus().data())) {
c->leaveEvent();
}
disconnect(m_focusGeometryConnection);
m_focusGeometryConnection = QMetaObject::Connection();
breakPointerConstraints(focus()->surface());
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
disconnectPointerConstraintsConnection();
setFocus(nullptr);
}
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
}
void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time, LibInput::Device *device)
{
processMotion(pos, QSizeF(), QSizeF(), time, 0, device);
}
Ensure PointerInputRedirection::processMotion finishes prior to warping Summary: Consider the following situation: we have three InputEventFilter linked in the sequence A - B - C. The input filters are processing pointer motion events. The expected behavior is that the new motion is processed in the sequence A -> B -> C So far this did not work correctly if the pointer gets warped during the processing. If e.g. filter B warps the pointer we get a motion sequence: A (1) -> B (1) -> A (2) -> B (2) -> C (2) -> C (1) The filters following the one warping the pointer get first the newer than the older position. This is obviously wrong. Unfortunately it is not just a theoretical condition, but a condition happening when interacting with the screenedges, which warp the pointer. This change introduces a PositionUpdateBlocker in PointerInputRedirection::processMotion to ensure that a processMotion call finishes prior to the next update. If the PositionUpdateBlocker is blocked the new position gets scheduled and processed once the PositionUpdateBlocker gets destroyed. With this we get the expected sequence for B warping pointer: A (1) -> B (1) -> C (1) -> A (2) -> B (2) -> C (2) This should hopefully improve the interaction with screen edges on Wayland. CCBUG: 374867 Test Plan: Added an auto test demonstrating the issue of incorrect ordering caused by screenedges. Prior to the change the test is failing. Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D5182
2017-03-26 13:53:09 +00:00
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->processMotion(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, pos.timeUsec, nullptr);
}
}
}
static bool isPositionBlocked() {
return s_counter > 0;
}
static void schedulePosition(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec) {
s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time, timeUsec});
}
private:
static int s_counter;
struct ScheduledPosition {
QPointF pos;
QSizeF delta;
QSizeF deltaNonAccelerated;
quint32 time;
quint64 timeUsec;
};
static QVector<ScheduledPosition> s_scheduledPositions;
PointerInputRedirection *m_pointer;
};
int PositionUpdateBlocker::s_counter = 0;
QVector<PositionUpdateBlocker::ScheduledPosition> PositionUpdateBlocker::s_scheduledPositions;
void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device)
{
if (!inited()) {
return;
}
Ensure PointerInputRedirection::processMotion finishes prior to warping Summary: Consider the following situation: we have three InputEventFilter linked in the sequence A - B - C. The input filters are processing pointer motion events. The expected behavior is that the new motion is processed in the sequence A -> B -> C So far this did not work correctly if the pointer gets warped during the processing. If e.g. filter B warps the pointer we get a motion sequence: A (1) -> B (1) -> A (2) -> B (2) -> C (2) -> C (1) The filters following the one warping the pointer get first the newer than the older position. This is obviously wrong. Unfortunately it is not just a theoretical condition, but a condition happening when interacting with the screenedges, which warp the pointer. This change introduces a PositionUpdateBlocker in PointerInputRedirection::processMotion to ensure that a processMotion call finishes prior to the next update. If the PositionUpdateBlocker is blocked the new position gets scheduled and processed once the PositionUpdateBlocker gets destroyed. With this we get the expected sequence for B warping pointer: A (1) -> B (1) -> C (1) -> A (2) -> B (2) -> C (2) This should hopefully improve the interaction with screen edges on Wayland. CCBUG: 374867 Test Plan: Added an auto test demonstrating the issue of incorrect ordering caused by screenedges. Prior to the change the test is failing. Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D5182
2017-03-26 13:53:09 +00:00
if (PositionUpdateBlocker::isPositionBlocked()) {
PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time, timeUsec);
return;
}
PositionUpdateBlocker blocker(this);
updatePosition(pos);
MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons,
input()->keyboardModifiers(), time,
delta, deltaNonAccelerated, timeUsec, 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, uint32_t time, LibInput::Device *device)
{
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, QSizeF(), QSizeF(), 0, 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 discreteDelta,
InputRedirection::PointerAxisSource source, uint32_t time, LibInput::Device *device)
{
update();
emit input()->pointerAxisChanged(axis, delta);
WheelEvent wheelEvent(m_pos, delta, discreteDelta,
(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, quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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 QSizeF &delta, quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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(quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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(quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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, quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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 QSizeF &delta, quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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(quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
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(quint32 time, KWin::LibInput::Device *device)
{
Q_UNUSED(device)
if (!inited()) {
return;
}
update();
input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time));
input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time));
}
bool PointerInputRedirection::areButtonsPressed() const
{
for (auto state : m_buttons) {
if (state == InputRedirection::PointerButtonPressed) {
return true;
}
}
return false;
}
bool PointerInputRedirection::focusUpdatesBlocked()
{
if (!inited()) {
return true;
}
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::cleanupInternalWindow(QWindow *old, QWindow *now)
{
disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection();
if (old) {
// leave internal window
QEvent leaveEvent(QEvent::Leave);
QCoreApplication::sendEvent(old, &leaveEvent);
}
if (now) {
m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this,
[this] (bool visible) {
if (!visible) {
update();
}
}
);
}
}
void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now)
{
disconnect(m_decorationGeometryConnection);
m_decorationGeometryConnection = QMetaObject::Connection();
workspace()->updateFocusMousePosition(position().toPoint());
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;
}
waylandServer()->seat()->setFocusedPointerSurface(nullptr);
auto pos = m_pos - now->client()->pos();
QHoverEvent event(QEvent::HoverEnter, pos, pos);
QCoreApplication::instance()->sendEvent(now->decoration(), &event);
now->client()->processDecorationMove(pos.toPoint(), m_pos.toPoint());
m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::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()->client()->isMove() &&
!decoration()->client()->isResize() &&
!areButtonsPressed()) {
// position of window did not change, we need to send HoverMotion manually
const QPointF p = m_pos - decoration()->client()->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event);
}
}, Qt::QueuedConnection);
}
static bool s_cursorUpdateBlocking = false;
void PointerInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow)
{
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusOld)) {
ac->leaveEvent();
breakPointerConstraints(ac->surface());
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
disconnectPointerConstraintsConnection();
}
disconnect(m_focusGeometryConnection);
m_focusGeometryConnection = QMetaObject::Connection();
if (AbstractClient *ac = qobject_cast<AbstractClient*>(focusNow)) {
ac->enterEvent(m_pos.toPoint());
workspace()->updateFocusMousePosition(m_pos.toPoint());
}
if (internalWindow()) {
// enter internal window
const auto pos = at()->pos();
QEnterEvent enterEvent(pos, pos, m_pos);
QCoreApplication::sendEvent(internalWindow().data(), &enterEvent);
}
auto seat = waylandServer()->seat();
if (!focusNow || !focusNow->surface() || decoration()) {
// Clean up focused pointer surface if there's no client to take focus,
// or the pointer is on a client without surface or on a decoration.
warpXcbOnSurfaceLeft(nullptr);
seat->setFocusedPointerSurface(nullptr);
return;
}
// TODO: add convenient API to update global pos together with updating focused surface
warpXcbOnSurfaceLeft(focusNow->surface());
// TODO: why? in order to reset the cursor icon?
s_cursorUpdateBlocking = true;
seat->setFocusedPointerSurface(nullptr);
s_cursorUpdateBlocking = false;
seat->setPointerPos(m_pos.toPoint());
seat->setFocusedPointerSurface(focusNow->surface(), focusNow->inputTransformation());
m_focusGeometryConnection = connect(focusNow, &Toplevel::inputTransformationChanged, this,
[this] {
// TODO: why no assert possible?
if (!focus()) {
return;
}
// TODO: can we check on the client instead?
if (workspace()->moveResizeClient()) {
// 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::clientActivated,
this, &PointerInputRedirection::updatePointerConstraints);
updatePointerConstraints();
}
void PointerInputRedirection::breakPointerConstraints(KWaylandServer::SurfaceInterface *surface)
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
{
// 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();
}
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
void PointerInputRedirection::disconnectPointerConstraintsConnection()
{
disconnect(m_constraintsConnection);
m_constraintsConnection = QMetaObject::Connection();
disconnect(m_constraintsActivatedConnection);
m_constraintsActivatedConnection = QMetaObject::Connection();
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
}
template <typename T>
static QRegion getConstraintRegion(Toplevel *t, T *constraint)
{
const QRegion windowShape = t->inputShape();
const QRegion windowRegion = windowShape.isEmpty() ? QRegion(0, 0, t->clientSize().width(), t->clientSize().height()) : windowShape;
const QRegion intersected = constraint->region().isEmpty() ? windowRegion : windowRegion.intersected(constraint->region());
return intersected.translated(t->pos() + t->clientPos());
}
void PointerInputRedirection::setEnableConstraints(bool set)
{
if (m_enableConstraints == set) {
return;
}
m_enableConstraints = set;
updatePointerConstraints();
}
void PointerInputRedirection::updatePointerConstraints()
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
{
if (focus().isNull()) {
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
return;
}
const auto s = focus()->surface();
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
if (!s) {
return;
}
if (s != waylandServer()->seat()->focusedPointerSurface()) {
return;
}
if (!supportsWarping()) {
return;
}
const bool canConstrain = m_enableConstraints && focus() == workspace()->activeClient();
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
const auto cf = s->confinedPointer();
if (cf) {
if (cf->isConfined()) {
if (!canConstrain) {
cf->setConfined(false);
m_confined = false;
disconnectConfinedPointerRegionConnection();
}
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
return;
}
const QRegion r = getConstraintRegion(focus().data(), cf.data());
if (canConstrain && r.contains(m_pos.toPoint())) {
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
cf->setConfined(true);
m_confined = true;
m_confinedPointerRegionConnection = connect(cf.data(), &KWaylandServer::ConfinedPointerInterface::regionChanged, this,
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
[this] {
if (!focus()) {
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
return;
}
const auto s = focus()->surface();
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
if (!s) {
return;
}
const auto cf = s->confinedPointer();
if (!getConstraintRegion(focus().data(), cf.data()).contains(m_pos.toPoint())) {
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
// 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;
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
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()) {
processMotion(focus()->pos() - focus()->clientContentPos() + hint, waylandServer()->seat()->timestamp());
}
}
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
return;
}
const QRegion r = getConstraintRegion(focus().data(), lock.data());
if (canConstrain && r.contains(m_pos.toPoint())) {
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
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.data(), &KWaylandServer::LockedPointerInterface::aboutToBeUnbound, this,
[this, lock]() {
const auto hint = lock->cursorPositionHint();
if (hint.x() < 0 || hint.y() < 0 || !focus()) {
return;
}
auto globalHint = focus()->pos() - focus()->clientContentPos() + hint;
// When the resource finally goes away, reposition the cursor according to the hint
connect(lock.data(), &KWaylandServer::LockedPointerInterface::unbound, this,
[this, globalHint]() {
processMotion(globalHint, waylandServer()->seat()->timestamp());
});
}
);
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
// 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();
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
}
}
void PointerInputRedirection::warpXcbOnSurfaceLeft(KWaylandServer::SurfaceInterface *newSurface)
Warp the xcb pointer whenever pointer leaves an X11 surface Summary: For Xwayland windows we observed that passing pointer focus to another window does not trigger proper leave events on X. Which results in e.g. tooltip windows to show after the pointer moved to a completely different position on a completely different surface. This is a bug in Xwayland which will be fixed in 1.19 (already fixed in master). Given that there is a runtime version check. Although it's fixed in Xwayland master it's worth to carry a workaround. To circumvent this problem KWin warps the xcb pointer to 0/0 whever an X window loses pointer focus. That way the X window gets a proper leave through the X protocol. This created a problem though: when giving focus back to the X window it started to warp the pointer for maximized windows as KWin got pointer motion events through the X11 event filter for positions on the window decoration. These are passed into the screen edge filter which pushes the pointer back and warps our Wayland pointer. To solve this problem KWin no longer performs any actions for pointer motion in the X11 event filter if not on X11. The event filter needs to be reworked and most of it should be moved into the Platform API, if possible. Test Plan: Reproduced situations where one could see that pointer updates don't trigger leave. E.g. going from a highlighted window to the decoration. Reviewers: #kwin, #plasma_on_wayland, bshah Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D2531
2016-08-22 17:20:57 +00:00
{
auto xc = waylandServer()->xWaylandConnection();
if (!xc) {
// No XWayland, no point in warping the x cursor
return;
}
const auto c = kwinApp()->x11Connection();
if (!c) {
Warp the xcb pointer whenever pointer leaves an X11 surface Summary: For Xwayland windows we observed that passing pointer focus to another window does not trigger proper leave events on X. Which results in e.g. tooltip windows to show after the pointer moved to a completely different position on a completely different surface. This is a bug in Xwayland which will be fixed in 1.19 (already fixed in master). Given that there is a runtime version check. Although it's fixed in Xwayland master it's worth to carry a workaround. To circumvent this problem KWin warps the xcb pointer to 0/0 whever an X window loses pointer focus. That way the X window gets a proper leave through the X protocol. This created a problem though: when giving focus back to the X window it started to warp the pointer for maximized windows as KWin got pointer motion events through the X11 event filter for positions on the window decoration. These are passed into the screen edge filter which pushes the pointer back and warps our Wayland pointer. To solve this problem KWin no longer performs any actions for pointer motion in the X11 event filter if not on X11. The event filter needs to be reworked and most of it should be moved into the Platform API, if possible. Test Plan: Reproduced situations where one could see that pointer updates don't trigger leave. E.g. going from a highlighted window to the decoration. Reviewers: #kwin, #plasma_on_wayland, bshah Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D2531
2016-08-22 17:20:57 +00:00
return;
}
static bool s_hasXWayland119 = xcb_get_setup(c)->release_number >= 11900000;
Warp the xcb pointer whenever pointer leaves an X11 surface Summary: For Xwayland windows we observed that passing pointer focus to another window does not trigger proper leave events on X. Which results in e.g. tooltip windows to show after the pointer moved to a completely different position on a completely different surface. This is a bug in Xwayland which will be fixed in 1.19 (already fixed in master). Given that there is a runtime version check. Although it's fixed in Xwayland master it's worth to carry a workaround. To circumvent this problem KWin warps the xcb pointer to 0/0 whever an X window loses pointer focus. That way the X window gets a proper leave through the X protocol. This created a problem though: when giving focus back to the X window it started to warp the pointer for maximized windows as KWin got pointer motion events through the X11 event filter for positions on the window decoration. These are passed into the screen edge filter which pushes the pointer back and warps our Wayland pointer. To solve this problem KWin no longer performs any actions for pointer motion in the X11 event filter if not on X11. The event filter needs to be reworked and most of it should be moved into the Platform API, if possible. Test Plan: Reproduced situations where one could see that pointer updates don't trigger leave. E.g. going from a highlighted window to the decoration. Reviewers: #kwin, #plasma_on_wayland, bshah Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D2531
2016-08-22 17:20:57 +00:00
if (s_hasXWayland119) {
return;
}
if (newSurface && newSurface->client() == xc) {
// new window is an X window
return;
}
auto s = waylandServer()->seat()->focusedPointerSurface();
if (!s || s->client() != xc) {
// pointer was not on an X window
return;
}
// warp pointer to 0/0 to trigger leave events on previously focused X window
xcb_warp_pointer(c, XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 0, 0),
xcb_flush(c);
Warp the xcb pointer whenever pointer leaves an X11 surface Summary: For Xwayland windows we observed that passing pointer focus to another window does not trigger proper leave events on X. Which results in e.g. tooltip windows to show after the pointer moved to a completely different position on a completely different surface. This is a bug in Xwayland which will be fixed in 1.19 (already fixed in master). Given that there is a runtime version check. Although it's fixed in Xwayland master it's worth to carry a workaround. To circumvent this problem KWin warps the xcb pointer to 0/0 whever an X window loses pointer focus. That way the X window gets a proper leave through the X protocol. This created a problem though: when giving focus back to the X window it started to warp the pointer for maximized windows as KWin got pointer motion events through the X11 event filter for positions on the window decoration. These are passed into the screen edge filter which pushes the pointer back and warps our Wayland pointer. To solve this problem KWin no longer performs any actions for pointer motion in the X11 event filter if not on X11. The event filter needs to be reworked and most of it should be moved into the Platform API, if possible. Test Plan: Reproduced situations where one could see that pointer updates don't trigger leave. E.g. going from a highlighted window to the decoration. Reviewers: #kwin, #plasma_on_wayland, bshah Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D2531
2016-08-22 17:20:57 +00:00
}
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const
{
if (!focus()) {
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
return pos;
}
auto s = focus()->surface();
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
if (!s) {
return pos;
}
auto cf = s->confinedPointer();
if (!cf) {
return pos;
}
if (!cf->isConfined()) {
return pos;
}
const QRegion confinementRegion = getConstraintRegion(focus().data(), cf.data());
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
if (confinementRegion.contains(pos.toPoint())) {
return pos;
}
QPointF p = pos;
// allow either x or y to pass
p = QPointF(m_pos.x(), pos.y());
if (confinementRegion.contains(p.toPoint())) {
return p;
}
p = QPointF(pos.x(), m_pos.y());
if (confinementRegion.contains(p.toPoint())) {
return p;
}
return m_pos;
}
void PointerInputRedirection::updatePosition(const QPointF &pos)
{
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
if (m_locked) {
// locked pointer should not move
return;
}
// verify that at least one screen contains the pointer position
QPointF p = pos;
if (!screenContainsPos(p)) {
const QRectF unitedScreensGeometry = screens()->geometry();
p = confineToBoundingBox(p, unitedScreensGeometry);
if (!screenContainsPos(p)) {
const QRectF currentScreenGeometry = screens()->geometry(screens()->number(m_pos.toPoint()));
p = confineToBoundingBox(p, currentScreenGeometry);
}
}
Implement support for pointer constraints Summary: There are two types of constraints supported: 1. Pointer confinement 2. Pointer locking In the case of confinement the pointer is confined to a given region of the surface. This is comparable to general operation where the pointer is confined to the screen region. In the second case the pointer gets locked. That means it cannot move at all. No further position updates are provided, only relative motion events can go to the application. There is a hint about cursor position update on unlock which is not yet implemented in KWayland::Server, thus also not in this change. The implementation in KWin grants the requests for pointer constraints when the pointer enters the constrained region, either by pointer movement or by e.g. stacking order changes. There is no confirmation from user required to enter that mode. But we want to show an OSD when the pointer gets constrained, this is not yet implemented, though. Breaking an active constraint is relatively easy. E.g. changing the stacking order will break the constraint if another surface is under the cursor. Also (in case of confinement) moving the pointer to an overlapping window breaks the confinement. But as soon as one moves the pointer back to the window a constraint might get honoured again. To properly break there is a dedicated event filter. It listens for a long press of the Escape key. If hold for 3sec the pointer constraint is broken and not activated again till the pointer got moved out of the window. Afterward when moving in the pointer might activate again. The escape filter ensures that the key press is forwarded to the application if it's a short press or if another key gets pressed during the three seconds. If the three seconds way fires, the later escape release is not sent to the application. This basic interaction is also ensured through an added auto test. This change implements T4605. Test Plan: Added auto test and nested KWin Wayland with D3488 Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3506
2016-11-25 06:17:43 +00:00
p = applyPointerConfinement(p);
if (p == m_pos) {
// didn't change due to confinement
return;
}
// verify screen confinement
if (!screenContainsPos(p)) {
return;
}
m_pos = p;
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());
}
emit input()->pointerButtonStateChanged(button, state);
}
void PointerInputRedirection::warp(const QPointF &pos)
{
if (supportsWarping()) {
kwinApp()->platform()->warpPointer(pos);
processMotion(pos, waylandServer()->seat()->timestamp());
}
}
bool PointerInputRedirection::supportsWarping() const
{
if (!inited()) {
return false;
}
if (m_supportsWarping) {
return true;
}
if (kwinApp()->platform()->supportsPointerWarping()) {
return true;
}
return false;
}
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 QPointF pos = screens()->geometry(screens()->number(m_pos.toPoint())).center();
// TODO: better way to get timestamps
processMotion(pos, waylandServer()->seat()->timestamp());
}
QPointF PointerInputRedirection::position() const
{
return m_pos.toPoint();
}
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)
{
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::focusedPointerChanged, this, &CursorImage::update);
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragStarted, this, &CursorImage::updateDrag);
connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragEnded, this,
[this] {
disconnect(m_drag.connection);
reevaluteSource();
}
);
if (waylandServer()->hasScreenLockerIntegration()) {
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource);
}
connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration);
// connect the move resize of all window
auto setupMoveResizeConnection = [this] (AbstractClient *c) {
connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize);
connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateMoveResize);
};
const auto clients = workspace()->allClientList();
std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection);
connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection);
loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor);
m_surfaceRenderedTimer.start();
connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] {
loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor);
updateDecorationCursor();
updateMoveResize();
// TODO: update effects
});
}
CursorImage::~CursorImage() = default;
void CursorImage::markAsRendered()
{
if (m_currentSource == CursorSource::DragAndDrop) {
// always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 )
if (auto ddi = waylandServer()->seat()->dragSource()) {
if (auto s = ddi->icon()) {
s->frameRendered(m_surfaceRenderedTimer.elapsed());
}
}
auto p = waylandServer()->seat()->dragPointer();
if (!p) {
return;
}
auto c = p->cursor();
if (!c) {
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
return;
}
cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed());
return;
}
if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) {
return;
}
auto p = waylandServer()->seat()->focusedPointer();
if (!p) {
return;
}
auto c = p->cursor();
if (!c) {
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
return;
}
cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed());
}
void CursorImage::update()
{
if (s_cursorUpdateBlocking) {
return;
}
using namespace KWaylandServer;
disconnect(m_serverCursor.connection);
auto p = waylandServer()->seat()->focusedPointer();
if (p) {
m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor);
} else {
m_serverCursor.connection = QMetaObject::Connection();
reevaluteSource();
}
}
void CursorImage::updateDecoration()
{
disconnect(m_decorationConnection);
auto deco = m_pointer->decoration();
AbstractClient *c = deco.isNull() ? nullptr : deco->client();
if (c) {
m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor);
} else {
m_decorationConnection = QMetaObject::Connection();
}
updateDecorationCursor();
}
void CursorImage::updateDecorationCursor()
{
m_decorationCursor = {};
auto deco = m_pointer->decoration();
if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) {
loadThemeCursor(c->cursor(), &m_decorationCursor);
if (m_currentSource == CursorSource::Decoration) {
emit changed();
}
}
reevaluteSource();
}
void CursorImage::updateMoveResize()
{
m_moveResizeCursor = {};
if (AbstractClient *c = workspace()->moveResizeClient()) {
loadThemeCursor(c->cursor(), &m_moveResizeCursor);
if (m_currentSource == CursorSource::MoveResize) {
emit changed();
}
}
reevaluteSource();
}
void CursorImage::updateServerCursor()
{
m_serverCursor.cursor = {};
reevaluteSource();
const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface;
auto p = waylandServer()->seat()->focusedPointer();
if (!p) {
if (needsEmit) {
emit changed();
}
return;
}
auto c = p->cursor();
if (!c) {
if (needsEmit) {
emit changed();
}
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
if (needsEmit) {
emit changed();
}
return;
}
auto buffer = cursorSurface.data()->buffer();
if (!buffer) {
if (needsEmit) {
emit changed();
}
return;
}
m_serverCursor.cursor.hotspot = c->hotspot();
m_serverCursor.cursor.image = buffer->data().copy();
m_serverCursor.cursor.image.setDevicePixelRatio(cursorSurface->bufferScale());
if (needsEmit) {
emit changed();
}
}
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
{
loadThemeCursor(shape, &m_effectsCursor);
if (m_currentSource == CursorSource::EffectsOverride) {
emit changed();
}
reevaluteSource();
}
void CursorImage::removeEffectsOverrideCursor()
{
reevaluteSource();
}
void CursorImage::setWindowSelectionCursor(const QByteArray &shape)
{
if (shape.isEmpty()) {
loadThemeCursor(Qt::CrossCursor, &m_windowSelectionCursor);
} else {
loadThemeCursor(shape, &m_windowSelectionCursor);
}
if (m_currentSource == CursorSource::WindowSelector) {
emit changed();
}
reevaluteSource();
}
void CursorImage::removeWindowSelectionCursor()
{
reevaluteSource();
}
void CursorImage::updateDrag()
{
using namespace KWaylandServer;
disconnect(m_drag.connection);
m_drag.cursor = {};
reevaluteSource();
if (auto p = waylandServer()->seat()->dragPointer()) {
m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor);
} else {
m_drag.connection = QMetaObject::Connection();
}
updateDragCursor();
}
void CursorImage::updateDragCursor()
{
m_drag.cursor = {};
const bool needsEmit = m_currentSource == CursorSource::DragAndDrop;
QImage additionalIcon;
if (auto ddi = waylandServer()->seat()->dragSource()) {
if (auto dragIcon = ddi->icon()) {
if (auto buffer = dragIcon->buffer()) {
additionalIcon = buffer->data().copy();
additionalIcon.setOffset(dragIcon->offset());
}
}
}
auto p = waylandServer()->seat()->dragPointer();
if (!p) {
if (needsEmit) {
emit changed();
}
return;
}
auto c = p->cursor();
if (!c) {
if (needsEmit) {
emit changed();
}
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
if (needsEmit) {
emit changed();
}
return;
}
auto buffer = cursorSurface.data()->buffer();
if (!buffer) {
if (needsEmit) {
emit changed();
}
return;
}
m_drag.cursor.hotspot = c->hotspot();
if (additionalIcon.isNull()) {
m_drag.cursor.image = buffer->data().copy();
m_drag.cursor.image.setDevicePixelRatio(cursorSurface->bufferScale());
} else {
QRect cursorRect = buffer->data().rect();
QRect iconRect = additionalIcon.rect();
if (-m_drag.cursor.hotspot.x() < additionalIcon.offset().x()) {
iconRect.moveLeft(m_drag.cursor.hotspot.x() - additionalIcon.offset().x());
} else {
cursorRect.moveLeft(-additionalIcon.offset().x() - m_drag.cursor.hotspot.x());
}
if (-m_drag.cursor.hotspot.y() < additionalIcon.offset().y()) {
iconRect.moveTop(m_drag.cursor.hotspot.y() - additionalIcon.offset().y());
} else {
cursorRect.moveTop(-additionalIcon.offset().y() - m_drag.cursor.hotspot.y());
}
m_drag.cursor.image = QImage(cursorRect.united(iconRect).size(), QImage::Format_ARGB32_Premultiplied);
m_drag.cursor.image.setDevicePixelRatio(cursorSurface->bufferScale());
m_drag.cursor.image.fill(Qt::transparent);
QPainter p(&m_drag.cursor.image);
p.drawImage(iconRect, additionalIcon);
p.drawImage(cursorRect, buffer->data());
p.end();
}
if (needsEmit) {
emit changed();
}
// TODO: add the cursor image
}
void CursorImage::loadThemeCursor(CursorShape shape, WaylandCursorImage::Image *image)
{
m_waylandImage.loadThemeCursor(shape, image);
}
void CursorImage::loadThemeCursor(const QByteArray &shape, WaylandCursorImage::Image *image)
{
m_waylandImage.loadThemeCursor(shape, image);
}
WaylandCursorImage::WaylandCursorImage(QObject *parent)
: QObject(parent)
{
Cursor *pointerCursor = Cursors::self()->mouse();
connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::invalidateCursorTheme);
connect(screens(), &Screens::maxScaleChanged, this, &WaylandCursorImage::invalidateCursorTheme);
}
bool WaylandCursorImage::ensureCursorTheme()
{
if (!m_cursorTheme.isEmpty()) {
return true;
}
const Cursor *pointerCursor = Cursors::self()->mouse();
const qreal targetDevicePixelRatio = screens()->maxScale();
m_cursorTheme = KXcursorTheme::fromTheme(pointerCursor->themeName(), pointerCursor->themeSize(),
targetDevicePixelRatio);
if (!m_cursorTheme.isEmpty()) {
return true;
}
m_cursorTheme = KXcursorTheme::fromTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(),
targetDevicePixelRatio);
if (!m_cursorTheme.isEmpty()) {
return true;
}
return false;
}
void WaylandCursorImage::invalidateCursorTheme()
{
m_cursorTheme = KXcursorTheme();
}
void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, Image *cursorImage)
{
loadThemeCursor(shape.name(), cursorImage);
}
void WaylandCursorImage::loadThemeCursor(const QByteArray &name, Image *cursorImage)
{
if (!ensureCursorTheme()) {
return;
}
if (loadThemeCursor_helper(name, cursorImage)) {
return;
}
const auto alternativeNames = Cursor::cursorAlternativeNames(name);
for (const QByteArray &alternativeName : alternativeNames) {
if (loadThemeCursor_helper(alternativeName, cursorImage)) {
return;
}
}
qCWarning(KWIN_CORE) << "Failed to load theme cursor for shape" << name;
}
bool WaylandCursorImage::loadThemeCursor_helper(const QByteArray &name, Image *cursorImage)
{
const QVector<KXcursorSprite> sprites = m_cursorTheme.shape(name);
if (sprites.isEmpty()) {
return false;
}
cursorImage->image = sprites.first().data();
cursorImage->image.setDevicePixelRatio(m_cursorTheme.devicePixelRatio());
cursorImage->hotspot = sprites.first().hotspot();
return true;
}
void CursorImage::reevaluteSource()
{
if (waylandServer()->seat()->isDragPointer()) {
// TODO: touch drag?
setSource(CursorSource::DragAndDrop);
return;
}
if (waylandServer()->isScreenLocked()) {
setSource(CursorSource::LockScreen);
return;
}
if (input()->isSelectingWindow()) {
setSource(CursorSource::WindowSelector);
return;
}
if (effects && static_cast<EffectsHandlerImpl*>(effects)->isMouseInterception()) {
setSource(CursorSource::EffectsOverride);
return;
}
if (workspace() && workspace()->moveResizeClient()) {
setSource(CursorSource::MoveResize);
return;
}
if (!m_pointer->decoration().isNull()) {
setSource(CursorSource::Decoration);
return;
}
if (!m_pointer->focus().isNull() && waylandServer()->seat()->focusedPointer()) {
setSource(CursorSource::PointerSurface);
return;
}
setSource(CursorSource::Fallback);
}
void CursorImage::setSource(CursorSource source)
{
if (m_currentSource == source) {
return;
}
m_currentSource = source;
emit changed();
}
QImage CursorImage::image() const
{
switch (m_currentSource) {
case CursorSource::EffectsOverride:
return m_effectsCursor.image;
case CursorSource::MoveResize:
return m_moveResizeCursor.image;
case CursorSource::LockScreen:
case CursorSource::PointerSurface:
// lockscreen also uses server cursor image
return m_serverCursor.cursor.image;
case CursorSource::Decoration:
return m_decorationCursor.image;
case CursorSource::DragAndDrop:
return m_drag.cursor.image;
case CursorSource::Fallback:
return m_fallbackCursor.image;
case CursorSource::WindowSelector:
return m_windowSelectionCursor.image;
default:
Q_UNREACHABLE();
}
}
QPoint CursorImage::hotSpot() const
{
switch (m_currentSource) {
case CursorSource::EffectsOverride:
return m_effectsCursor.hotspot;
case CursorSource::MoveResize:
return m_moveResizeCursor.hotspot;
case CursorSource::LockScreen:
case CursorSource::PointerSurface:
// lockscreen also uses server cursor image
return m_serverCursor.cursor.hotspot;
case CursorSource::Decoration:
return m_decorationCursor.hotspot;
case CursorSource::DragAndDrop:
return m_drag.cursor.hotspot;
case CursorSource::Fallback:
return m_fallbackCursor.hotspot;
case CursorSource::WindowSelector:
return m_windowSelectionCursor.hotspot;
default:
Q_UNREACHABLE();
}
}
InputRedirectionCursor::InputRedirectionCursor(QObject *parent)
: Cursor(parent)
, m_currentButtons(Qt::NoButton)
{
Cursors::self()->setMouse(this);
connect(input(), SIGNAL(globalPointerChanged(QPointF)), SLOT(slotPosChanged(QPointF)));
connect(input(), SIGNAL(pointerButtonStateChanged(uint32_t,InputRedirection::PointerButtonState)),
SLOT(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());
emit posChanged(currentPos());
}
void InputRedirectionCursor::slotPosChanged(const QPointF &pos)
{
const QPoint oldPos = currentPos();
updatePos(pos.toPoint());
emit mouseChanged(pos.toPoint(), oldPos, m_currentButtons, m_currentButtons,
input()->keyboardModifiers(), input()->keyboardModifiers());
}
void InputRedirectionCursor::slotModifiersChanged(Qt::KeyboardModifiers mods, Qt::KeyboardModifiers oldMods)
{
emit mouseChanged(currentPos(), currentPos(), m_currentButtons, m_currentButtons, mods, oldMods);
}
void InputRedirectionCursor::slotPointerButtonChanged()
{
const Qt::MouseButtons oldButtons = m_currentButtons;
m_currentButtons = input()->qtButtonStates();
const QPoint pos = currentPos();
emit mouseChanged(pos, pos, m_currentButtons, oldButtons, input()->keyboardModifiers(), input()->keyboardModifiers());
}
void InputRedirectionCursor::doStartCursorTracking()
{
#ifndef KCMRULES
// connect(Cursors::self(), &Cursors::currentCursorChanged, this, &Cursor::cursorChanged);
#endif
}
void InputRedirectionCursor::doStopCursorTracking()
{
#ifndef KCMRULES
// disconnect(kwinApp()->platform(), &Platform::cursorChanged, this, &Cursor::cursorChanged);
#endif
}
}