Split out pointer related handling from InputRedirection

All pointer related code is moved into a new class called
PointerInputRedirection.

The main idea is to simplify the code and make it easier to maintain.
Therefore also a few changes in the setup were performed:
* before init() is called, no processing is performed
* init() is only called on Wayland and after Workspace is created
* init property is set to false once Workspace or WaylandServer is
  destroyed

Thus code can operate on the following assumptions:
* Workspace is valid
* WaylandServer is valid
* ScreenLocker integration is used

The various checks whether there is a waylandServer() and whether
there is a seat are no longer needed.

Some of the checks have been reordered to be faster in the most common
use case of using libinput. E.g. whether warping is supported is first
evaluated by the variable bound to whether we have libinput and only if
that is false the backend is checked.

The new class doesn't have signals but invokes the signals provided
by InputRedirection. I didn't want to add new signals as I consider
them as not needed. The areas in KWin needing those signals should
be ported to InputEventFilters.
icc-effect-5.14.5
Martin Gräßlin 2016-02-12 13:30:00 +01:00
parent 768f1be939
commit c044ad98be
7 changed files with 578 additions and 387 deletions

View File

@ -332,6 +332,7 @@ set(kwin_KDEINIT_SRCS
focuschain.cpp
globalshortcuts.cpp
input.cpp
pointer_input.cpp
netinfo.cpp
placement.cpp
atoms.cpp

384
input.cpp
View File

@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "input.h"
#include "pointer_input.h"
#include "client.h"
#include "effects.h"
#include "globalshortcuts.h"
@ -358,7 +359,7 @@ public:
if (event->type() == QEvent::MouseMove) {
if (event->buttons() == Qt::NoButton) {
// update pointer window only if no button is pressed
input()->updatePointerWindow();
input()->pointer()->update();
}
if (pointerSurfaceAllowed()) {
seat->setPointerPos(event->screenPos().toPoint());
@ -567,7 +568,7 @@ public:
class InternalWindowEventFilter : public InputEventFilter {
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton)
auto internal = input()->m_pointerInternalWindow;
auto internal = input()->pointer()->internalWindow();
if (!internal) {
return false;
}
@ -580,7 +581,7 @@ class InternalWindowEventFilter : public InputEventFilter {
return e.isAccepted();
}
bool wheelEvent(QWheelEvent *event) override {
auto internal = input()->m_pointerInternalWindow;
auto internal = input()->pointer()->internalWindow();
if (!internal) {
return false;
}
@ -598,7 +599,7 @@ class InternalWindowEventFilter : public InputEventFilter {
return e.isAccepted();
}
bool keyEvent(QKeyEvent *event) override {
auto internal = input()->m_pointerInternalWindow;
auto internal = input()->pointer()->internalWindow();
if (!internal) {
return false;
}
@ -612,7 +613,7 @@ class DecorationEventFilter : public InputEventFilter {
public:
bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override {
Q_UNUSED(nativeButton)
auto decoration = input()->m_pointerDecoration;
auto decoration = input()->pointer()->decoration();
if (!decoration) {
return false;
}
@ -638,7 +639,7 @@ public:
if (event->type() == QEvent::MouseButtonRelease) {
decoration->client()->processDecorationButtonRelease(&e);
}
input()->installCursorFromDecoration();
input()->pointer()->installCursorFromDecoration();
return true;
}
default:
@ -647,7 +648,7 @@ public:
return false;
}
bool wheelEvent(QWheelEvent *event) override {
auto decoration = input()->m_pointerDecoration;
auto decoration = input()->pointer()->decoration();
if (!decoration) {
return false;
}
@ -711,13 +712,13 @@ public:
case QEvent::MouseMove:
if (event->buttons() == Qt::NoButton) {
// update pointer window only if no button is pressed
input()->updatePointerWindow();
input()->pointer()->update();
}
seat->setPointerPos(event->globalPos());
break;
case QEvent::MouseButtonPress: {
bool passThrough = true;
if (AbstractClient *c = dynamic_cast<AbstractClient*>(input()->m_pointerWindow.data())) {
if (AbstractClient *c = dynamic_cast<AbstractClient*>(input()->pointer()->window().data())) {
bool wasAction = false;
const Options::MouseCommand command = c->getMouseCommand(event->button(), &wasAction);
if (wasAction) {
@ -732,7 +733,7 @@ public:
case QEvent::MouseButtonRelease:
seat->pointerButtonReleased(nativeButton);
if (event->buttons() == Qt::NoButton) {
input()->updatePointerWindow();
input()->pointer()->update();
}
break;
default:
@ -820,8 +821,8 @@ KWIN_SINGLETON_FACTORY(InputRedirection)
InputRedirection::InputRedirection(QObject *parent)
: QObject(parent)
, m_pointer(new PointerInputRedirection(this))
, m_xkb(new Xkb(this))
, m_pointerWindow()
, m_shortcuts(new GlobalShortcutsManager(this))
{
qRegisterMetaType<KWin::InputRedirection::KeyboardKeyState>();
@ -879,19 +880,19 @@ void InputRedirection::setupWorkspace()
connect(device, &FakeInputDevice::pointerMotionRequested, this,
[this] (const QSizeF &delta) {
// TODO: Fix time
processPointerMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0);
m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0);
}
);
connect(device, &FakeInputDevice::pointerButtonPressRequested, this,
[this] (quint32 button) {
// TODO: Fix time
processPointerButton(button, InputRedirection::PointerButtonPressed, 0);
m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0);
}
);
connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this,
[this] (quint32 button) {
// TODO: Fix time
processPointerButton(button, InputRedirection::PointerButtonReleased, 0);
m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0);
}
);
connect(device, &FakeInputDevice::pointerAxisRequested, this,
@ -910,7 +911,7 @@ void InputRedirection::setupWorkspace()
break;
}
// TODO: Fix time
processPointerAxis(axis, delta, 0);
m_pointer->processAxis(axis, delta, 0);
}
);
}
@ -927,9 +928,9 @@ void InputRedirection::setupWorkspace()
}
);
connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure);
connect(screens(), &Screens::changed, this, &InputRedirection::updatePointerAfterScreenChange);
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &InputRedirection::updatePointerWindow);
m_pointer->init();
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &InputRedirection::updateKeyboardWindow);
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this,
[this] {
@ -1002,24 +1003,23 @@ void InputRedirection::setupLibInput()
m_libInput = conn;
if (conn) {
conn->setup();
m_pointerWarping = true;
connect(conn, &LibInput::Connection::eventsRead, this,
[this] {
m_libInput->processEvents();
}, Qt::QueuedConnection
);
connect(conn, &LibInput::Connection::pointerButtonChanged, this, &InputRedirection::processPointerButton);
connect(conn, &LibInput::Connection::pointerAxisChanged, this, &InputRedirection::processPointerAxis);
connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton);
connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis);
connect(conn, &LibInput::Connection::keyChanged, this, &InputRedirection::processKeyboardKey);
connect(conn, &LibInput::Connection::pointerMotion, this,
[this] (QPointF delta, uint32_t time) {
processPointerMotion(m_globalPointer + delta, time);
m_pointer->processMotion(m_pointer->pos() + delta, time);
}
);
connect(conn, &LibInput::Connection::pointerMotionAbsolute, this,
[this] (QPointF orig, QPointF screen, uint32_t time) {
Q_UNUSED(orig)
processPointerMotion(screen, time);
m_pointer->processMotion(screen, time);
}
);
connect(conn, &LibInput::Connection::touchDown, this, &InputRedirection::processTouchDown);
@ -1084,189 +1084,9 @@ void InputRedirection::setupLibInputWithScreens()
m_libInput->setScreenSize(screens()->size());
}
);
// set pos to center of all screens
m_globalPointer = screens()->geometry().center();
emit globalPointerChanged(m_globalPointer);
// sanitize
updatePointerAfterScreenChange();
#endif
}
void InputRedirection::updatePointerWindow()
{
// TODO: handle pointer grab aka popups
Toplevel *t = findToplevel(m_globalPointer.toPoint());
updatePointerInternalWindow();
if (!m_pointerInternalWindow) {
updatePointerDecoration(t);
} else {
m_pointerDecoration.clear();
}
if (m_pointerDecoration || m_pointerInternalWindow) {
t = nullptr;
}
auto oldWindow = m_pointerWindow;
if (!oldWindow.isNull() && t == m_pointerWindow.data()) {
return;
}
if (auto seat = findSeat()) {
// disconnect old surface
if (oldWindow) {
disconnect(oldWindow.data(), &Toplevel::geometryChanged, this, &InputRedirection::updateFocusedPointerPosition);
if (AbstractBackend *b = waylandServer()->backend()) {
if (auto p = seat->focusedPointer()) {
if (auto c = p->cursor()) {
disconnect(c, &KWayland::Server::Cursor::changed, b, &AbstractBackend::installCursorFromServer);
}
}
}
}
if (t && t->surface()) {
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
connect(t, &Toplevel::geometryChanged, this, &InputRedirection::updateFocusedPointerPosition);
if (AbstractBackend *b = waylandServer()->backend()) {
b->installCursorFromServer();
if (auto p = seat->focusedPointer()) {
if (auto c = p->cursor()) {
connect(c, &KWayland::Server::Cursor::changed, b, &AbstractBackend::installCursorFromServer);
}
}
}
} else {
seat->setFocusedPointerSurface(nullptr);
t = nullptr;
}
}
if (!t) {
m_pointerWindow.clear();
return;
}
m_pointerWindow = QWeakPointer<Toplevel>(t);
}
void InputRedirection::updatePointerDecoration(Toplevel *t)
{
const auto oldDeco = m_pointerDecoration;
bool needsReset = waylandServer() && waylandServer()->isScreenLocked();
if (AbstractClient *c = dynamic_cast<AbstractClient*>(t)) {
// check whether it's on a Decoration
if (c->decoratedClient()) {
const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos());
if (!clientRect.contains(m_globalPointer.toPoint())) {
m_pointerDecoration = c->decoratedClient();
} else {
needsReset = true;
}
} else {
needsReset = true;
}
} else {
needsReset = true;
}
if (needsReset) {
m_pointerDecoration.clear();
}
if (oldDeco && oldDeco != m_pointerDecoration) {
// send leave
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event);
if (!m_pointerDecoration && waylandServer()) {
waylandServer()->backend()->installCursorImage(Qt::ArrowCursor);
}
}
if (m_pointerDecoration) {
const QPointF p = m_globalPointer - t->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(m_pointerDecoration->decoration(), &event);
m_pointerDecoration->client()->processDecorationMove();
installCursorFromDecoration();
}
}
void InputRedirection::updatePointerInternalWindow()
{
const auto oldInternalWindow = m_pointerInternalWindow;
if (waylandServer()) {
bool found = false;
bool needsReset = waylandServer()->isScreenLocked();
const auto &internalClients = waylandServer()->internalClients();
const bool change = m_pointerInternalWindow.isNull() || !(m_pointerInternalWindow->flags().testFlag(Qt::Popup) && m_pointerInternalWindow->isVisible());
if (!internalClients.isEmpty() && change) {
auto it = internalClients.end();
do {
it--;
if (QWindow *w = (*it)->internalWindow()) {
if (!w->isVisible()) {
continue;
}
if (w->geometry().contains(m_globalPointer.toPoint())) {
m_pointerInternalWindow = QPointer<QWindow>(w);
found = true;
break;
}
}
} while (it != internalClients.begin());
if (!found) {
needsReset = true;
}
}
if (needsReset) {
m_pointerInternalWindow.clear();
}
}
if (oldInternalWindow != m_pointerInternalWindow) {
// changed
if (oldInternalWindow) {
disconnect(oldInternalWindow.data(), &QWindow::visibleChanged, this, &InputRedirection::pointerInternalWindowVisibilityChanged);
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
}
if (m_pointerInternalWindow) {
connect(oldInternalWindow.data(), &QWindow::visibleChanged, this, &InputRedirection::pointerInternalWindowVisibilityChanged);
QEnterEvent event(m_globalPointer - m_pointerInternalWindow->position(),
m_globalPointer - m_pointerInternalWindow->position(),
m_globalPointer);
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
return;
}
}
}
void InputRedirection::pointerInternalWindowVisibilityChanged(bool visible)
{
if (!visible) {
updatePointerWindow();
}
}
void InputRedirection::installCursorFromDecoration()
{
if (waylandServer() && m_pointerDecoration) {
waylandServer()->backend()->installCursorImage(m_pointerDecoration->client()->cursor());
}
}
void InputRedirection::updateFocusedPointerPosition()
{
if (!workspace()) {
return;
}
if (m_pointerWindow.isNull()) {
return;
}
if (workspace()->getMovingClient()) {
// don't update while moving
return;
}
if (auto seat = findSeat()) {
if (m_pointerWindow.data()->surface() != seat->focusedPointerSurface()) {
return;
}
seat->setFocusedPointerSurfaceTransformation(m_pointerWindow.data()->inputTransformation());
}
}
void InputRedirection::updateFocusedTouchPosition()
{
if (m_touchWindow.isNull()) {
@ -1282,66 +1102,17 @@ void InputRedirection::updateFocusedTouchPosition()
void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time)
{
if (!workspace()) {
return;
}
// first update to new mouse position
// const QPointF oldPos = m_globalPointer;
updatePointerPosition(pos);
// TODO: check which part of KWin would like to intercept the event
QMouseEvent event(QEvent::MouseMove, m_globalPointer.toPoint(), m_globalPointer.toPoint(),
Qt::NoButton, qtButtonStates(), keyboardModifiers());
event.setTimestamp(time);
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->pointerEvent(&event, 0)) {
return;
}
}
m_pointer->processMotion(pos, time);
}
void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time)
{
if (!workspace()) {
return;
}
m_pointerButtons[button] = state;
emit pointerButtonStateChanged(button, state);
QMouseEvent event(buttonStateToEvent(state), m_globalPointer.toPoint(), m_globalPointer.toPoint(),
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
event.setTimestamp(time);
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->pointerEvent(&event, button)) {
return;
}
}
m_pointer->processButton(button, state, time);
}
void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time)
{
if (delta == 0) {
return;
}
emit pointerAxisChanged(axis, delta);
QWheelEvent wheelEvent(m_globalPointer, m_globalPointer, QPoint(),
(axis == PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta),
delta,
(axis == PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
qtButtonStates(),
m_xkb->modifiers());
wheelEvent.setTimestamp(time);
for (auto it = m_filters.constBegin(), end = m_filters.constEnd(); it != end; it++) {
if ((*it)->wheelEvent(&wheelEvent)) {
return;
}
}
m_pointer->processAxis(axis, delta, time);
}
void InputRedirection::updateKeyboardWindow()
@ -1517,47 +1288,9 @@ void InputRedirection::removeTouchId(quint32 internalId)
m_touchIdMapper.remove(internalId);
}
QEvent::Type InputRedirection::buttonStateToEvent(InputRedirection::PointerButtonState state)
{
switch (state) {
case KWin::InputRedirection::PointerButtonReleased:
return QEvent::MouseButtonRelease;
case KWin::InputRedirection::PointerButtonPressed:
return QEvent::MouseButtonPress;
}
return QEvent::None;
}
Qt::MouseButton InputRedirection::buttonToQtMouseButton(uint32_t button)
{
switch (button) {
case BTN_LEFT:
return Qt::LeftButton;
case BTN_MIDDLE:
return Qt::MiddleButton;
case BTN_RIGHT:
return Qt::RightButton;
case BTN_BACK:
return Qt::XButton1;
case BTN_FORWARD:
return Qt::XButton2;
}
return Qt::NoButton;
}
Qt::MouseButtons InputRedirection::qtButtonStates() const
{
Qt::MouseButtons buttons;
for (auto it = m_pointerButtons.constBegin(); it != m_pointerButtons.constEnd(); ++it) {
if (it.value() == KWin::InputRedirection::PointerButtonReleased) {
continue;
}
Qt::MouseButton button = buttonToQtMouseButton(it.key());
if (button != Qt::NoButton) {
buttons |= button;
}
}
return buttons;
return m_pointer->buttons();
}
static bool acceptsInput(Toplevel *t, const QPoint &pos)
@ -1654,69 +1387,20 @@ void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action)
});
}
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;
}
void InputRedirection::updatePointerPosition(const QPointF &pos)
{
// verify that at least one screen contains the pointer position
QPointF p = pos;
if (!screenContainsPos(p)) {
// allow either x or y to pass
p = QPointF(m_globalPointer.x(), pos.y());
if (!screenContainsPos(p)) {
p = QPointF(pos.x(), m_globalPointer.y());
if (!screenContainsPos(p)) {
return;
}
}
}
m_globalPointer = p;
emit globalPointerChanged(m_globalPointer);
}
void InputRedirection::updatePointerAfterScreenChange()
{
if (screenContainsPos(m_globalPointer)) {
// pointer still on a screen
return;
}
// pointer no longer on a screen, reposition to closes screen
const QPointF pos = screens()->geometry(screens()->number(m_globalPointer.toPoint())).center();
quint32 timestamp = 0;
if (auto seat = findSeat()) {
timestamp = seat->timestamp();
}
// TODO: better way to get timestamps
processPointerMotion(pos, timestamp);
}
void InputRedirection::warpPointer(const QPointF &pos)
{
if (supportsPointerWarping()) {
quint32 timestamp = 0;
if (waylandServer()) {
waylandServer()->backend()->warpPointer(pos);
timestamp = waylandServer()->seat()->timestamp();
}
// TODO: better way to get timestamps
processPointerMotion(pos, timestamp);
}
m_pointer->warp(pos);
}
bool InputRedirection::supportsPointerWarping() const
{
if (waylandServer() && waylandServer()->backend()->supportsPointerWarping()) {
return true;
}
return m_pointerWarping;
return m_pointer->supportsWarping();
}
QPointF InputRedirection::globalPointer() const
{
return m_pointer->pos();
}
} // namespace

41
input.h
View File

@ -44,6 +44,7 @@ class GlobalShortcutsManager;
class Toplevel;
class Xkb;
class InputEventFilter;
class PointerInputRedirection;
namespace Decoration
{
@ -86,7 +87,7 @@ public:
/**
* @return const QPointF& The current global pointer position
*/
const QPointF &globalPointer() const;
QPointF globalPointer() const;
Qt::MouseButtons qtButtonStates() const;
Qt::KeyboardModifiers keyboardModifiers() const;
@ -150,8 +151,12 @@ public:
void updateKeyboardWindow();
void updateTouchWindow(const QPointF &pos);
public Q_SLOTS:
void updatePointerWindow();
QVector<InputEventFilter*> filters() const {
return m_filters;
}
PointerInputRedirection *pointer() const {
return m_pointer;
}
Q_SIGNALS:
/**
@ -193,36 +198,16 @@ Q_SIGNALS:
void keyStateChanged(quint32 keyCode, InputRedirection::KeyboardKeyState state);
private:
static QEvent::Type buttonStateToEvent(PointerButtonState state);
static Qt::MouseButton buttonToQtMouseButton(uint32_t button);
void setupLibInput();
void setupLibInputWithScreens();
void updatePointerPosition(const QPointF &pos);
void updatePointerAfterScreenChange();
void registerShortcutForGlobalAccelTimestamp(QAction *action);
void updateFocusedPointerPosition();
void updateFocusedTouchPosition();
void updatePointerDecoration(Toplevel *t);
void updatePointerInternalWindow();
void pointerInternalWindowVisibilityChanged(bool visible);
void installCursorFromDecoration();
void setupWorkspace();
void reconfigure();
void setupInputFilters();
void installInputEventFilter(InputEventFilter *filter);
QPointF m_globalPointer;
QHash<uint32_t, PointerButtonState> m_pointerButtons;
PointerInputRedirection *m_pointer;
QScopedPointer<Xkb> m_xkb;
/**
* @brief The Toplevel which currently receives pointer events
*/
QWeakPointer<Toplevel> m_pointerWindow;
/**
* @brief The Decoration which currently receives pointer events.
* Decoration belongs to the pointerWindow
**/
QPointer<Decoration::DecoratedClientImpl> m_pointerDecoration;
QPointer<QWindow> m_pointerInternalWindow;
/**
* @brief The Toplevel which currently receives touch events
*/
@ -236,8 +221,6 @@ private:
LibInput::Connection *m_libInput = nullptr;
bool m_pointerWarping = false;
QVector<InputEventFilter*> m_filters;
KWIN_SINGLETON(InputRedirection)
@ -344,12 +327,6 @@ InputRedirection *input()
return InputRedirection::s_self;
}
inline
const QPointF &InputRedirection::globalPointer() const
{
return m_globalPointer;
}
template <typename T>
inline
void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, void (T::*slot)()) {

View File

@ -47,3 +47,8 @@ bool KWin::InputRedirection::supportsPointerWarping() const
{
return false;
}
QPointF KWin::InputRedirection::globalPointer() const
{
return QPointF();
}

415
pointer_input.cpp Normal file
View File

@ -0,0 +1,415 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "pointer_input.h"
#include "abstract_backend.h"
#include "screens.h"
#include "shell_client.h"
#include "wayland_server.h"
#include "workspace.h"
#include "decorations/decoratedclient.h"
// KDecoration
#include <KDecoration2/Decoration>
// KWayland
#include <KWayland/Server/seat_interface.h>
// screenlocker
#include <KScreenLocker/KsldApp>
#include <QHoverEvent>
#include <QWindow>
#include <linux/input.h>
namespace KWin
{
static Qt::MouseButton buttonToQtMouseButton(uint32_t button)
{
switch (button) {
case BTN_LEFT:
return Qt::LeftButton;
case BTN_MIDDLE:
return Qt::MiddleButton;
case BTN_RIGHT:
return Qt::RightButton;
case BTN_BACK:
return Qt::XButton1;
case BTN_FORWARD:
return Qt::XButton2;
}
return Qt::NoButton;
}
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;
}
PointerInputRedirection::PointerInputRedirection(InputRedirection* parent)
: QObject(parent)
, m_input(parent)
, m_supportsWarping(Application::usesLibinput())
{
}
PointerInputRedirection::~PointerInputRedirection() = default;
void PointerInputRedirection::init()
{
Q_ASSERT(!m_inited);
m_inited = true;
connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update);
connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange);
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update);
connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; });
connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
// warp the cursor to center of screen
warp(screens()->geometry().center());
updateAfterScreenChange();
}
void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time)
{
if (!m_inited) {
return;
}
updatePosition(pos);
QMouseEvent event(QEvent::MouseMove, m_pos.toPoint(), m_pos.toPoint(),
Qt::NoButton, m_qtButtons, m_input->keyboardModifiers());
event.setTimestamp(time);
const auto &filters = m_input->filters();
for (auto it = filters.begin(), end = filters.end(); it != end; it++) {
if ((*it)->pointerEvent(&event, 0)) {
return;
}
}
}
void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time)
{
if (!m_inited) {
return;
}
updateButton(button, state);
QEvent::Type type;
switch (state) {
case InputRedirection::PointerButtonReleased:
type = QEvent::MouseButtonRelease;
break;
case InputRedirection::PointerButtonPressed:
type = QEvent::MouseButtonPress;
break;
default:
Q_UNREACHABLE();
return;
}
QMouseEvent event(type, m_pos.toPoint(), m_pos.toPoint(),
buttonToQtMouseButton(button), m_qtButtons, m_input->keyboardModifiers());
event.setTimestamp(time);
const auto &filters = m_input->filters();
for (auto it = filters.begin(), end = filters.end(); it != end; it++) {
if ((*it)->pointerEvent(&event, button)) {
return;
}
}
}
void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time)
{
if (!m_inited) {
return;
}
if (delta == 0) {
return;
}
emit m_input->pointerAxisChanged(axis, delta);
QWheelEvent wheelEvent(m_pos, m_pos, QPoint(),
(axis == InputRedirection::PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta),
delta,
(axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
m_qtButtons,
m_input->keyboardModifiers());
wheelEvent.setTimestamp(time);
const auto &filters = m_input->filters();
for (auto it = filters.begin(), end = filters.end(); it != end; it++) {
if ((*it)->wheelEvent(&wheelEvent)) {
return;
}
}
}
void PointerInputRedirection::update()
{
if (!m_inited) {
return;
}
// TODO: handle pointer grab aka popups
Toplevel *t = m_input->findToplevel(m_pos.toPoint());
updateInternalWindow();
if (!m_internalWindow) {
updateDecoration(t);
} else {
// TODO: send hover leave to decoration
m_decoration.clear();
}
if (m_decoration || m_internalWindow) {
t = nullptr;
}
auto oldWindow = m_window;
if (!oldWindow.isNull() && t == m_window.data()) {
return;
}
auto seat = waylandServer()->seat();
// disconnect old surface
if (oldWindow) {
disconnect(m_windowGeometryConnection);
m_windowGeometryConnection = QMetaObject::Connection();
if (auto p = seat->focusedPointer()) {
if (auto c = p->cursor()) {
disconnect(c, &KWayland::Server::Cursor::changed, waylandServer()->backend(), &AbstractBackend::installCursorFromServer);
}
}
}
if (t && t->surface()) {
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this,
[this] {
if (m_window.isNull()) {
return;
}
// TODO: can we check on the client instead?
if (workspace()->getMovingClient()) {
// don't update while moving
return;
}
auto seat = waylandServer()->seat();
if (m_window.data()->surface() != seat->focusedPointerSurface()) {
return;
}
seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation());
}
);
waylandServer()->backend()->installCursorFromServer();
if (auto p = seat->focusedPointer()) {
if (auto c = p->cursor()) {
connect(c, &KWayland::Server::Cursor::changed, waylandServer()->backend(), &AbstractBackend::installCursorFromServer);
}
}
} else {
seat->setFocusedPointerSurface(nullptr);
t = nullptr;
}
if (!t) {
m_window.clear();
return;
}
m_window = QPointer<Toplevel>(t);
}
void PointerInputRedirection::updateInternalWindow()
{
const auto oldInternalWindow = m_internalWindow;
bool found = false;
// TODO: screen locked check without going through wayland server
bool needsReset = waylandServer()->isScreenLocked();
const auto &internalClients = waylandServer()->internalClients();
const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible());
if (!internalClients.isEmpty() && change) {
auto it = internalClients.end();
do {
it--;
if (QWindow *w = (*it)->internalWindow()) {
if (!w->isVisible()) {
continue;
}
if (w->geometry().contains(m_pos.toPoint())) {
m_internalWindow = QPointer<QWindow>(w);
found = true;
break;
}
}
} while (it != internalClients.begin());
if (!found) {
needsReset = true;
}
}
if (needsReset) {
m_internalWindow.clear();
}
if (oldInternalWindow != m_internalWindow) {
// changed
if (oldInternalWindow) {
disconnect(m_internalWindowConnection);
m_internalWindowConnection = QMetaObject::Connection();
QEvent event(QEvent::Leave);
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
}
if (m_internalWindow) {
m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this,
[this] (bool visible) {
if (!visible) {
update();
}
});
QEnterEvent event(m_pos - m_internalWindow->position(),
m_pos - m_internalWindow->position(),
m_pos);
QCoreApplication::sendEvent(m_internalWindow.data(), &event);
return;
}
}
}
void PointerInputRedirection::updateDecoration(Toplevel *t)
{
const auto oldDeco = m_decoration;
bool needsReset = waylandServer()->isScreenLocked();
if (AbstractClient *c = dynamic_cast<AbstractClient*>(t)) {
// check whether it's on a Decoration
if (c->decoratedClient()) {
const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos());
if (!clientRect.contains(m_pos.toPoint())) {
m_decoration = c->decoratedClient();
} else {
needsReset = true;
}
} else {
needsReset = true;
}
} else {
needsReset = true;
}
if (needsReset) {
m_decoration.clear();
}
if (oldDeco && oldDeco != m_decoration) {
// send leave
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event);
if (!m_decoration) {
waylandServer()->backend()->installCursorImage(Qt::ArrowCursor);
}
}
if (m_decoration) {
const QPointF p = m_pos - t->pos();
QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event);
m_decoration->client()->processDecorationMove();
installCursorFromDecoration();
}
}
void PointerInputRedirection::updatePosition(const QPointF &pos)
{
// verify that at least one screen contains the pointer position
QPointF p = pos;
if (!screenContainsPos(p)) {
// allow either x or y to pass
p = QPointF(m_pos.x(), pos.y());
if (!screenContainsPos(p)) {
p = QPointF(pos.x(), m_pos.y());
if (!screenContainsPos(p)) {
return;
}
}
}
m_pos = p;
emit m_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;
}
Qt::MouseButton button = buttonToQtMouseButton(it.key());
// TODO: we need to map all buttons, otherwise checks for are buttons pressed fail
if (button != Qt::NoButton) {
m_qtButtons |= button;
}
}
emit m_input->pointerButtonStateChanged(button, state);
}
void PointerInputRedirection::warp(const QPointF &pos)
{
if (supportsWarping()) {
waylandServer()->backend()->warpPointer(pos);
processMotion(pos, waylandServer()->seat()->timestamp());
}
}
bool PointerInputRedirection::supportsWarping() const
{
if (!m_inited) {
return false;
}
if (m_supportsWarping) {
return true;
}
if (waylandServer()->backend()->supportsPointerWarping()) {
return true;
}
return false;
}
void PointerInputRedirection::installCursorFromDecoration()
{
if (!m_inited || !m_decoration) {
return;
}
waylandServer()->backend()->installCursorImage(m_decoration->client()->cursor());
}
void PointerInputRedirection::updateAfterScreenChange()
{
if (!m_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());
}
}

114
pointer_input.h Normal file
View File

@ -0,0 +1,114 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_POINTER_INPUT_H
#define KWIN_POINTER_INPUT_H
#include "input.h"
#include <QObject>
#include <QPointer>
#include <QPointF>
class QWindow;
namespace KWin
{
class InputRedirection;
class Toplevel;
namespace Decoration
{
class DecoratedClientImpl;
}
class PointerInputRedirection : public QObject
{
Q_OBJECT
public:
explicit PointerInputRedirection(InputRedirection *parent);
virtual ~PointerInputRedirection();
void init();
void update();
void updateAfterScreenChange();
bool supportsWarping() const;
void warp(const QPointF &pos);
QPointF pos() const {
return m_pos;
}
Qt::MouseButtons buttons() const {
return m_qtButtons;
}
QPointer<Toplevel> window() const {
return m_window;
}
QPointer<Decoration::DecoratedClientImpl> decoration() const {
return m_decoration;
}
QPointer<QWindow> internalWindow() const {
return m_internalWindow;
}
void installCursorFromDecoration();
/**
* @internal
*/
void processMotion(const QPointF &pos, uint32_t time);
/**
* @internal
*/
void processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time);
/**
* @internal
*/
void processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time);
private:
void updatePosition(const QPointF &pos);
void updateButton(uint32_t button, InputRedirection::PointerButtonState state);
void updateInternalWindow();
void updateDecoration(Toplevel *t);
InputRedirection *m_input;
bool m_inited = false;
bool m_supportsWarping;
QPointF m_pos;
QHash<uint32_t, InputRedirection::PointerButtonState> m_buttons;
Qt::MouseButtons m_qtButtons;
/**
* @brief The Toplevel which currently receives pointer events
*/
QPointer<Toplevel> m_window;
/**
* @brief The Decoration which currently receives pointer events.
* Decoration belongs to the pointerWindow
**/
QPointer<Decoration::DecoratedClientImpl> m_decoration;
QPointer<QWindow> m_internalWindow;
QMetaObject::Connection m_windowGeometryConnection;
QMetaObject::Connection m_internalWindowConnection;
};
}
#endif

View File

@ -133,11 +133,6 @@ Workspace::Workspace(const QString &sessionKey)
// first initialize the extensions
Xcb::Extensions::self();
// start the Wayland Backend - will only be created if WAYLAND_DISPLAY is present
if (kwinApp()->operationMode() != Application::OperationModeX11) {
connect(this, SIGNAL(stackingOrderChanged()), input(), SLOT(updatePointerWindow()));
}
#ifdef KWIN_BUILD_ACTIVITIES
Activities *activities = nullptr;
if (kwinApp()->usesKActivities()) {