44bb78c193
The mapping is slightly inspired by the mapping in QtWayland. But the mapping in QtWayland seems wrong. E.g. there is a linux kernel button called BTN_BACK which is not mapped to Qt::BackButton. Anyway we are not really interested in the mapping being 100 % correct for the case in KWin. KWin internally uses only very few mouse buttons and all others are only relevant to figure out whether buttons are pressed. The button code itself is passed to the seat with the native code.
442 lines
14 KiB
C++
442 lines
14 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_SIDE:
|
|
// in QtWayland mapped like that
|
|
return Qt::ExtraButton1;
|
|
case BTN_EXTRA:
|
|
// in QtWayland mapped like that
|
|
return Qt::ExtraButton2;
|
|
case BTN_BACK:
|
|
return Qt::BackButton;
|
|
case BTN_FORWARD:
|
|
return Qt::ForwardButton;
|
|
case BTN_TASK:
|
|
return Qt::TaskButton;
|
|
// mapped like that in QtWayland
|
|
case 0x118:
|
|
return Qt::ExtraButton6;
|
|
case 0x119:
|
|
return Qt::ExtraButton7;
|
|
case 0x11a:
|
|
return Qt::ExtraButton8;
|
|
case 0x11b:
|
|
return Qt::ExtraButton9;
|
|
case 0x11c:
|
|
return Qt::ExtraButton10;
|
|
case 0x11d:
|
|
return Qt::ExtraButton11;
|
|
case 0x11e:
|
|
return Qt::ExtraButton12;
|
|
case 0x11f:
|
|
return Qt::ExtraButton13;
|
|
}
|
|
// 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 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
m_qtButtons |= buttonToQtMouseButton(it.key());
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
}
|