kwin/libinput/connection.cpp
Martin Gräßlin cc2f4e3240 Support for relative pointer motion events
Summary:
If KWin interacts with Libinput the RelativePointerManager interface
gets created on the Wayland server. The ForwardInputEventFilter does
forward the relative motion events in addition to the normal motion
events.

In order to properly support the relative motion events as they are
expected by the Wayland protocol the handling of pointer motion events
got slightly adjusted:
* Libinput Pointer event extended by the additional data points
* Libinput Pointer event carries the delta as a QSizeF instead of
QPointF
* PointerInputRedirection adjusted to take a pointer motion event with
more arguments
* Custom QMouseEvent subclass adjusted to carry the additional members

The DebugConsole is adjusted to show the relative motion events in
addition to the global position.

Test Plan:
Verified the manager object is created and verified the
events in DebugConsole. Unfortunately not aware of any test application.

Reviewers: #kwin, #plasma_on_wayland

Subscribers: plasma-devel, kwin

Tags: #plasma_on_wayland, #kwin

Differential Revision: https://phabricator.kde.org/D2979
2016-10-12 11:26:46 +02:00

521 lines
19 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 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 "connection.h"
#include "context.h"
#include "device.h"
#include "events.h"
#include "../logind.h"
#include "../udev.h"
#include "libinput_logging.h"
#include <KConfigGroup>
#include <KGlobalAccel>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusPendingCall>
#include <QMutexLocker>
#include <QSocketNotifier>
#include <QThread>
#include <libinput.h>
namespace KWin
{
namespace LibInput
{
Connection *Connection::s_self = nullptr;
QThread *Connection::s_thread = nullptr;
static Context *s_context = nullptr;
Connection::Connection(QObject *parent)
: Connection(nullptr, parent)
{
// only here to fix build, using will crash, BUG 343529
}
Connection *Connection::create(QObject *parent)
{
Q_ASSERT(!s_self);
static Udev s_udev;
if (!s_udev.isValid()) {
qCWarning(KWIN_LIBINPUT) << "Failed to initialize udev";
return nullptr;
}
if (!s_context) {
s_context = new Context(s_udev);
if (!s_context->isValid()) {
qCWarning(KWIN_LIBINPUT) << "Failed to create context from udev";
delete s_context;
s_context = nullptr;
return nullptr;
}
// TODO: don't hardcode seat name
if (!s_context->assignSeat("seat0")) {
qCWarning(KWIN_LIBINPUT) << "Failed to assign seat seat0";
delete s_context;
s_context = nullptr;
return nullptr;
}
}
s_thread = new QThread();
s_self = new Connection(s_context);
s_self->moveToThread(s_thread);
s_thread->start();
QObject::connect(s_thread, &QThread::finished, s_self, &QObject::deleteLater);
QObject::connect(s_thread, &QThread::finished, s_thread, &QObject::deleteLater);
QObject::connect(parent, &QObject::destroyed, s_thread, &QThread::quit);
return s_self;
}
static const QString s_touchpadComponent = QStringLiteral("kcm_touchpad");
static const QString s_serviceName = QStringLiteral("org.kde.KWin.InputDevice");
Connection::Connection(Context *input, QObject *parent)
: QObject(parent)
, m_input(input)
, m_notifier(nullptr)
, m_mutex(QMutex::Recursive)
{
Q_ASSERT(m_input);
// steal touchpad shortcuts
QAction *touchpadToggleAction = new QAction(this);
QAction *touchpadOnAction = new QAction(this);
QAction *touchpadOffAction = new QAction(this);
touchpadToggleAction->setObjectName(QStringLiteral("Toggle Touchpad"));
touchpadToggleAction->setProperty("componentName", s_touchpadComponent);
touchpadOnAction->setObjectName(QStringLiteral("Enable Touchpad"));
touchpadOnAction->setProperty("componentName", s_touchpadComponent);
touchpadOffAction->setObjectName(QStringLiteral("Disable Touchpad"));
touchpadOffAction->setProperty("componentName", s_touchpadComponent);
KGlobalAccel::self()->setDefaultShortcut(touchpadToggleAction, QList<QKeySequence>{Qt::Key_TouchpadToggle});
KGlobalAccel::self()->setShortcut(touchpadToggleAction, QList<QKeySequence>{Qt::Key_TouchpadToggle});
KGlobalAccel::self()->setDefaultShortcut(touchpadOnAction, QList<QKeySequence>{Qt::Key_TouchpadOn});
KGlobalAccel::self()->setShortcut(touchpadOnAction, QList<QKeySequence>{Qt::Key_TouchpadOn});
KGlobalAccel::self()->setDefaultShortcut(touchpadOffAction, QList<QKeySequence>{Qt::Key_TouchpadOff});
KGlobalAccel::self()->setShortcut(touchpadOffAction, QList<QKeySequence>{Qt::Key_TouchpadOff});
#ifndef KWIN_BUILD_TESTING
InputRedirection::self()->registerShortcut(Qt::Key_TouchpadToggle, touchpadToggleAction);
InputRedirection::self()->registerShortcut(Qt::Key_TouchpadOn, touchpadOnAction);
InputRedirection::self()->registerShortcut(Qt::Key_TouchpadOff, touchpadOffAction);
#endif
connect(touchpadToggleAction, &QAction::triggered, this, &Connection::toggleTouchpads);
connect(touchpadOnAction, &QAction::triggered, this,
[this] {
if (m_touchpadsEnabled) {
return;
}
toggleTouchpads();
}
);
connect(touchpadOffAction, &QAction::triggered, this,
[this] {
if (!m_touchpadsEnabled) {
return;
}
toggleTouchpads();
}
);
// need to connect to KGlobalSettings as the mouse KCM does not emit a dedicated signal
QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"),
QStringLiteral("notifyChange"), this, SLOT(slotKGlobalSettingsNotifyChange(int,int)));
QDBusConnection::sessionBus().registerService(s_serviceName);
}
Connection::~Connection()
{
QDBusConnection::sessionBus().unregisterService(s_serviceName);
s_self = nullptr;
delete s_context;
s_context = nullptr;
}
void Connection::setup()
{
QMetaObject::invokeMethod(this, "doSetup", Qt::QueuedConnection);
}
void Connection::doSetup()
{
Q_ASSERT(!m_notifier);
m_notifier = new QSocketNotifier(m_input->fileDescriptor(), QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, &Connection::handleEvent);
LogindIntegration *logind = LogindIntegration::self();
connect(logind, &LogindIntegration::sessionActiveChanged, this,
[this](bool active) {
if (active) {
if (!m_input->isSuspended()) {
return;
}
m_input->resume();
wasSuspended = true;
} else {
deactivate();
}
}
);
handleEvent();
}
void Connection::deactivate()
{
if (m_input->isSuspended()) {
return;
}
m_keyboardBeforeSuspend = hasKeyboard();
m_alphaNumericKeyboardBeforeSuspend = hasAlphaNumericKeyboard();
m_pointerBeforeSuspend = hasPointer();
m_touchBeforeSuspend = hasTouch();
m_input->suspend();
handleEvent();
}
void Connection::handleEvent()
{
QMutexLocker locker(&m_mutex);
const bool wasEmpty = m_eventQueue.isEmpty();
do {
m_input->dispatch();
Event *event = m_input->event();
if (!event) {
break;
}
m_eventQueue << event;
} while (true);
if (wasEmpty && !m_eventQueue.isEmpty()) {
emit eventsRead();
}
}
void Connection::processEvents()
{
QMutexLocker locker(&m_mutex);
while (!m_eventQueue.isEmpty()) {
QScopedPointer<Event> event(m_eventQueue.takeFirst());
switch (event->type()) {
case LIBINPUT_EVENT_DEVICE_ADDED: {
auto device = new Device(event->nativeDevice());
device->moveToThread(s_thread);
m_devices << device;
if (device->isKeyboard()) {
m_keyboard++;
if (device->isAlphaNumericKeyboard()) {
m_alphaNumericKeyboard++;
if (m_alphaNumericKeyboard == 1) {
emit hasAlphaNumericKeyboardChanged(true);
}
}
if (m_keyboard == 1) {
emit hasKeyboardChanged(true);
}
}
if (device->isPointer()) {
m_pointer++;
if (m_pointer == 1) {
emit hasPointerChanged(true);
}
}
if (device->isTouch()) {
m_touch++;
if (m_touch == 1) {
emit hasTouchChanged(true);
}
}
applyDeviceConfig(device);
emit deviceAdded(device);
break;
}
case LIBINPUT_EVENT_DEVICE_REMOVED: {
auto it = std::find_if(m_devices.begin(), m_devices.end(), [&event] (Device *d) { return event->device() == d; } );
if (it == m_devices.end()) {
// we don't know this device
break;
}
auto device = *it;
m_devices.erase(it);
emit deviceRemoved(device);
if (device->isKeyboard()) {
m_keyboard--;
if (device->isAlphaNumericKeyboard()) {
m_alphaNumericKeyboard--;
if (m_alphaNumericKeyboard == 0) {
emit hasAlphaNumericKeyboardChanged(false);
}
}
if (m_keyboard == 0) {
emit hasKeyboardChanged(false);
}
}
if (device->isPointer()) {
m_pointer--;
if (m_pointer == 0) {
emit hasPointerChanged(false);
}
}
if (device->isTouch()) {
m_touch--;
if (m_touch == 0) {
emit hasTouchChanged(false);
}
}
device->deleteLater();
break;
}
case LIBINPUT_EVENT_KEYBOARD_KEY: {
KeyEvent *ke = static_cast<KeyEvent*>(event.data());
emit keyChanged(ke->key(), ke->state(), ke->time(), ke->device());
break;
}
case LIBINPUT_EVENT_POINTER_AXIS: {
PointerEvent *pe = static_cast<PointerEvent*>(event.data());
struct Axis {
qreal delta = 0.0;
quint32 time = 0;
};
QMap<InputRedirection::PointerAxis, Axis> deltas;
auto update = [&deltas] (PointerEvent *pe) {
const auto axis = pe->axis();
for (auto it = axis.begin(); it != axis.end(); ++it) {
deltas[*it].delta += pe->axisValue(*it);
deltas[*it].time = pe->time();
}
};
update(pe);
auto it = m_eventQueue.begin();
while (it != m_eventQueue.end()) {
if ((*it)->type() == LIBINPUT_EVENT_POINTER_AXIS) {
QScopedPointer<PointerEvent> p(static_cast<PointerEvent*>(*it));
update(p.data());
it = m_eventQueue.erase(it);
} else {
break;
}
}
for (auto it = deltas.constBegin(); it != deltas.constEnd(); ++it) {
emit pointerAxisChanged(it.key(), it.value().delta, it.value().time, pe->device());
}
break;
}
case LIBINPUT_EVENT_POINTER_BUTTON: {
PointerEvent *pe = static_cast<PointerEvent*>(event.data());
emit pointerButtonChanged(pe->button(), pe->buttonState(), pe->time(), pe->device());
break;
}
case LIBINPUT_EVENT_POINTER_MOTION: {
PointerEvent *pe = static_cast<PointerEvent*>(event.data());
auto delta = pe->delta();
auto deltaNonAccel = pe->deltaUnaccelerated();
quint32 latestTime = pe->time();
quint64 latestTimeUsec = pe->timeMicroseconds();
auto it = m_eventQueue.begin();
while (it != m_eventQueue.end()) {
if ((*it)->type() == LIBINPUT_EVENT_POINTER_MOTION) {
QScopedPointer<PointerEvent> p(static_cast<PointerEvent*>(*it));
delta += p->delta();
deltaNonAccel += p->deltaUnaccelerated();
latestTime = p->time();
latestTimeUsec = p->timeMicroseconds();
it = m_eventQueue.erase(it);
} else {
break;
}
}
emit pointerMotion(delta, deltaNonAccel, latestTime, latestTimeUsec, pe->device());
break;
}
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: {
PointerEvent *pe = static_cast<PointerEvent*>(event.data());
emit pointerMotionAbsolute(pe->absolutePos(), pe->absolutePos(m_size), pe->time(), pe->device());
break;
}
case LIBINPUT_EVENT_TOUCH_DOWN: {
TouchEvent *te = static_cast<TouchEvent*>(event.data());
emit touchDown(te->id(), te->absolutePos(m_size), te->time(), te->device());
break;
}
case LIBINPUT_EVENT_TOUCH_UP: {
TouchEvent *te = static_cast<TouchEvent*>(event.data());
emit touchUp(te->id(), te->time(), te->device());
break;
}
case LIBINPUT_EVENT_TOUCH_MOTION: {
TouchEvent *te = static_cast<TouchEvent*>(event.data());
emit touchMotion(te->id(), te->absolutePos(m_size), te->time(), te->device());
break;
}
case LIBINPUT_EVENT_TOUCH_CANCEL: {
emit touchCanceled(event->device());
break;
}
case LIBINPUT_EVENT_TOUCH_FRAME: {
emit touchFrame(event->device());
break;
}
case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: {
PinchGestureEvent *pe = static_cast<PinchGestureEvent*>(event.data());
emit pinchGestureBegin(pe->fingerCount(), pe->time(), pe->device());
break;
}
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: {
PinchGestureEvent *pe = static_cast<PinchGestureEvent*>(event.data());
emit pinchGestureUpdate(pe->scale(), pe->angleDelta(), pe->delta(), pe->time(), pe->device());
break;
}
case LIBINPUT_EVENT_GESTURE_PINCH_END: {
PinchGestureEvent *pe = static_cast<PinchGestureEvent*>(event.data());
if (pe->isCancelled()) {
emit pinchGestureCancelled(pe->time(), pe->device());
} else {
emit pinchGestureEnd(pe->time(), pe->device());
}
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: {
SwipeGestureEvent *se = static_cast<SwipeGestureEvent*>(event.data());
emit swipeGestureBegin(se->fingerCount(), se->time(), se->device());
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: {
SwipeGestureEvent *se = static_cast<SwipeGestureEvent*>(event.data());
emit swipeGestureUpdate(se->delta(), se->time(), se->device());
break;
}
case LIBINPUT_EVENT_GESTURE_SWIPE_END: {
SwipeGestureEvent *se = static_cast<SwipeGestureEvent*>(event.data());
if (se->isCancelled()) {
emit swipeGestureCancelled(se->time(), se->device());
} else {
emit swipeGestureEnd(se->time(), se->device());
}
break;
}
default:
// nothing
break;
}
}
if (wasSuspended) {
if (m_keyboardBeforeSuspend && !m_keyboard) {
emit hasKeyboardChanged(false);
}
if (m_alphaNumericKeyboardBeforeSuspend && !m_alphaNumericKeyboard) {
emit hasAlphaNumericKeyboardChanged(false);
}
if (m_pointerBeforeSuspend && !m_pointer) {
emit hasPointerChanged(false);
}
if (m_touchBeforeSuspend && !m_touch) {
emit hasTouchChanged(false);
}
wasSuspended = false;
}
}
void Connection::setScreenSize(const QSize &size)
{
m_size = size;
}
bool Connection::isSuspended() const
{
if (!s_context) {
return false;
}
return s_context->isSuspended();
}
void Connection::applyDeviceConfig(Device *device)
{
if (device->isPointer()) {
const KConfigGroup group = m_config->group("Mouse");
device->setLeftHanded(group.readEntry("MouseButtonMapping", "RightHanded") == QLatin1String("LeftHanded"));
qreal accel = group.readEntry("Acceleration", -1.0);
if (qFuzzyCompare(accel, -1.0) || qFuzzyCompare(accel, 1.0)) {
// default value
device->setPointerAcceleration(0.0);
} else {
// the X11-based config is mapped in [0.1,20.0] with 1.0 being the "normal" setting - we assume that's the default
if (accel < 1.0) {
device->setPointerAcceleration(-1.0 + ((accel * 10.0) - 1.0) / 9.0);
} else {
device->setPointerAcceleration((accel -1.0)/19.0);
}
}
}
}
void Connection::slotKGlobalSettingsNotifyChange(int type, int arg)
{
if (type == 3 /**SettingsChanged**/ && arg == 0 /** SETTINGS_MOUSE **/) {
m_config->reparseConfiguration();
for (auto it = m_devices.constBegin(), end = m_devices.constEnd(); it != end; ++it) {
if ((*it)->isPointer()) {
applyDeviceConfig(*it);
}
}
}
}
void Connection::toggleTouchpads()
{
bool changed = false;
m_touchpadsEnabled = !m_touchpadsEnabled;
for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) {
auto device = *it;
if (!device->isPointer()) {
continue;
}
if (device->isKeyboard() || device->isTouch() || device->isTabletPad() || device->isTabletTool()) {
// ignore all combined devices. E.g. a touchpad on a keyboard we don't want to toggle
// as that would result in the keyboard going off as well
continue;
}
// is this a touch pad? We don't really know, let's do some assumptions
if (device->tapFingerCount() > 0 || device->supportsDisableWhileTyping() || device->supportsDisableEventsOnExternalMouse()) {
const bool old = device->isEnabled();
device->setEnabled(m_touchpadsEnabled);
if (old != device->isEnabled()) {
changed = true;
}
}
}
if (changed) {
// send OSD message
QDBusMessage msg = QDBusMessage::createMethodCall(
QStringLiteral("org.kde.plasmashell"),
QStringLiteral("/org/kde/osdService"),
QStringLiteral("org.kde.osdService"),
QStringLiteral("touchpadEnabledChanged")
);
msg.setArguments({m_touchpadsEnabled});
QDBusConnection::sessionBus().asyncCall(msg);
}
}
}
}