c044ad98be
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.
415 lines
13 KiB
C++
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());
|
|
}
|
|
|
|
}
|