kwin/pointer_input.cpp
Martin Gräßlin c044ad98be 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.
2016-02-12 13:38:26 +01:00

415 lines
13 KiB
C++

/********************************************************************
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());
}
}