Commit 6bb8cde9 authored by Roman Gilg's avatar Roman Gilg
Browse files

[Mouse KCM] Add X11 libinput exclusive backend and UI

Summary:
This patch splits up the current X11 backend into one for systems with
installed X libinput driver and systems with only evdev driver.

The evdev backend is used together with the old QWidget based UI. The
libinput backend is based on the KWin Wayland one and controlled by a
very similar QML based UI. One difference is that values are always
propagated to all pointer like devices and can not be set for
individual devices.

As for the evdev backend values are saved to a config file in the user
directory and reapplied on every session start.

Note that the libinput backend always takes precedence to the evdev one.
If an user wants to force the evdev backend, the X libinput driver needs
to be removed.

Depends on D11468

{F5757944}

BUG: 350688

Test Plan: Tested X session with and without libinput driver.

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: davidedmundson, hein, mart, abetts, ngraham, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D11469
parent 4f06b7cc
......@@ -24,6 +24,9 @@ ecm_qt_declare_logging_category(common_SRCS
Critical
)
set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml)
qt5_add_dbus_interface(common_SRCS ${klauncher_xml} klauncher_iface)
include(backends/x11.cmake)
include(backends/kwin_wl.cmake)
......@@ -57,10 +60,7 @@ set(common_SRCS
kcm/xlib/xlib_config.cpp
)
set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml)
ki18n_wrap_ui(common_SRCS kcm/xlib/kcmmouse.ui)
qt5_add_dbus_interface(common_SRCS ${klauncher_xml} klauncher_iface)
qt5_add_resources( common_SRCS kcm/resources.qrc )
......
......@@ -9,8 +9,12 @@ include_directories(
set(backend_SRCS
${backend_SRCS}
backends/x11/evdev_settings.cpp
backends/x11/x11_backend.cpp
backends/x11/x11_evdev_backend.cpp
backends/x11/evdev_settings.cpp
backends/x11/x11_libinput_backend.cpp
backends/x11/x11_libinput_dummydevice.cpp
backends/x11/libinput_settings.cpp
)
set(backend_LIBS
......
......@@ -17,7 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "evdev_settings.h"
#include "x11_backend.h"
#include "x11_evdev_backend.h"
#include <QDBusMessage>
#include <QDBusConnection>
#include <KSharedConfig>
......@@ -25,7 +25,7 @@
#include "../migrationlib/kdelibs4config.h"
void EvdevSettings::apply(X11Backend *backend, bool force)
void EvdevSettings::apply(X11EvdevBackend *backend, bool force)
{
if (!backend) {
return;
......@@ -35,7 +35,7 @@ void EvdevSettings::apply(X11Backend *backend, bool force)
handedNeedsApply = false;
}
void EvdevSettings::load(X11Backend *backend)
void EvdevSettings::load(X11EvdevBackend *backend)
{
KConfig config("kcminputrc");
......@@ -43,7 +43,6 @@ void EvdevSettings::load(X11Backend *backend)
int threshold = 0;
handed = Handed::Right;
double accel = 1.0;
QString profile;
if (backend) {
auto handedOnServer = backend->handed();
handedEnabled = handedOnServer != Handed::NotSupported;
......@@ -52,7 +51,6 @@ void EvdevSettings::load(X11Backend *backend)
}
accel = backend->accelRate();
threshold = backend->threshold();
profile = backend->accelerationProfile();
}
KConfigGroup group = config.group("Mouse");
......@@ -74,10 +72,7 @@ void EvdevSettings::load(X11Backend *backend)
else if (key == "LeftHanded")
handed = Handed::Left;
reverseScrollPolarity = group.readEntry("ReverseScrollPolarity", false);
currentAccelProfile = group.readEntry("AccelerationProfile");
if (currentAccelProfile.isEmpty()) {
currentAccelProfile = profile;
}
handedNeedsApply = false;
// SC/DC/AutoSelect/ChangeCursor
......@@ -124,7 +119,6 @@ void EvdevSettings::save()
kcminputGroup.writeEntry("MouseButtonMapping",QString("LeftHanded"));
}
kcminputGroup.writeEntry("ReverseScrollPolarity", reverseScrollPolarity);
kcminputGroup.writeEntry("AccelerationProfile", currentAccelProfile);
kcminputGroup.sync();
KSharedConfig::Ptr profile = KSharedConfig::openConfig("kdeglobals");
......
......@@ -20,9 +20,7 @@
#ifndef EVDEVSETTINGS_H
#define EVDEVSETTINGS_H
#include <KConfig>
class X11Backend;
class X11EvdevBackend;
enum class Handed {
Right = 0,
......@@ -33,8 +31,8 @@ enum class Handed {
struct EvdevSettings
{
void save();
void load(X11Backend *);
void apply(X11Backend *, bool force = false);
void load(X11EvdevBackend *);
void apply(X11EvdevBackend *, bool force = false);
bool handedEnabled;
bool handedNeedsApply;
......@@ -47,7 +45,6 @@ struct EvdevSettings
bool singleClick;
int wheelScrollLines;
bool reverseScrollPolarity;
QString currentAccelProfile;
};
#endif // EVDEVSETTINGS_H
/*
* Copyright 2018 Roman Gilg <subdiff@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "libinput_settings.h"
#include <KSharedConfig>
#include <KConfigGroup>
template<>
bool LibinputSettings::load(QString key, bool defVal)
{
KSharedConfig::Ptr kcminputPtr = KSharedConfig::openConfig("kcminputrc");
KConfigGroup group(kcminputPtr, "Mouse");
return group.readEntry(key, defVal);
}
template<>
qreal LibinputSettings::load(QString key, qreal defVal)
{
KSharedConfig::Ptr kcminputPtr = KSharedConfig::openConfig("kcminputrc");
KConfigGroup group(kcminputPtr, "Mouse");
return group.readEntry(key, defVal);
}
template<>
void LibinputSettings::save(QString key, bool val)
{
KSharedConfig::Ptr kcminputPtr = KSharedConfig::openConfig("kcminputrc");
KConfigGroup group(kcminputPtr, "Mouse");
group.writeEntry(key, val);
group.sync();
kcminputPtr->sync();
}
template<>
void LibinputSettings::save(QString key, qreal val)
{
KSharedConfig::Ptr kcminputPtr = KSharedConfig::openConfig("kcminputrc");
KConfigGroup group(kcminputPtr, "Mouse");
group.writeEntry(key, val);
group.sync();
kcminputPtr->sync();
}
/*
* Copyright 2018 Roman Gilg <subdiff@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef LIBINPUTSETTINGS_H
#define LIBINPUTSETTINGS_H
#include <QString>
struct LibinputSettings
{
template<class T>
T load(QString key, T defVal);
template<class T>
void save(QString key, T val);
};
#endif // LIBINPUTSETTINGS_H
......@@ -18,14 +18,22 @@
*/
#include "x11_backend.h"
#include "x11_evdev_backend.h"
#include "x11_libinput_backend.h"
#include "logging.h"
#include <config-X11.h>
#include <libinput-properties.h>
#include <QFile>
#include <QX11Info>
#include <KLocalizedString>
#include <evdev-properties.h>
#include <libinput-properties.h>
#include <KConfig>
#include <KConfigGroup>
#include <klauncher_iface.h>
#include <X11/X.h>
#include <X11/Xlib.h>
......@@ -38,56 +46,25 @@
#include <X11/extensions/XInput.h>
#endif
static const char PROFILE_NONE[] = I18N_NOOP("None");
static const char PROFILE_ADAPTIVE[] = I18N_NOOP("Adaptive");
static const char PROFILE_FLAT[] = I18N_NOOP("Flat");
struct ScopedXDeleter {
static inline void cleanup(void* pointer)
{
if (pointer) {
XFree(pointer);
}
}
};
template<typename Callback>
static void XI2ForallPointerDevices(Display* dpy, const Callback& callback)
X11Backend *X11Backend::implementation(QObject *parent)
{
int ndevices_return;
XIDeviceInfo* info = XIQueryDevice(dpy, XIAllDevices, &ndevices_return);
if (!info) {
return;
}
for (int i = 0; i < ndevices_return; ++i) {
if ((info + i)->use == XISlavePointer) {
callback(info + i);
}
}
XIFreeDeviceInfo(info);
}
auto dpy = QX11Info::display();
Atom testAtom = XInternAtom(dpy, LIBINPUT_PROP_ACCEL, True);
template<typename Callback>
static void XIForallPointerDevices(Display* dpy, const Callback& callback)
{
int ndevices_return;
XDeviceInfo* info = XListInputDevices(dpy, &ndevices_return);
if (!info) {
return;
//There are multiple possible drivers
if (testAtom) {
qCDebug(KCM_INPUT) << "Using libinput driver on X11.";
return new X11LibinputBackend(parent);
}
for (int i = 0; i < ndevices_return; ++i) {
if (info[i].use == IsXPointer || info[i].use == IsXExtensionPointer) {
callback(info + i);
}
else {
qCDebug(KCM_INPUT) << "Using evdev driver on X11.";
return new X11EvdevBackend(parent);
}
XFreeDeviceList(info);
}
X11Backend::X11Backend(QObject* parent)
: InputBackend(parent)
{
m_mode = InputBackendMode::XEvdev;
m_platformX11 = QX11Info::isPlatformX11();
if (m_platformX11) {
m_dpy = QX11Info::display();
......@@ -96,222 +73,13 @@ X11Backend::X11Backend(QObject* parent)
// let's hope we have a compatibility system like Xwayland ready
m_dpy = XOpenDisplay(nullptr);
}
m_settings = new EvdevSettings();
initAtom();
}
void X11Backend::initAtom()
{
if (!m_dpy) {
return;
}
m_libinputAccelProfileAvailableAtom = XInternAtom(m_dpy, LIBINPUT_PROP_ACCEL_PROFILES_AVAILABLE, True);
m_libinputAccelProfileEnabledAtom = XInternAtom(m_dpy, LIBINPUT_PROP_ACCEL_PROFILE_ENABLED, True);
m_libinputNaturalScrollAtom = XInternAtom(m_dpy, LIBINPUT_PROP_NATURAL_SCROLL, True);
m_evdevScrollDistanceAtom = XInternAtom(m_dpy, EVDEV_PROP_SCROLL_DISTANCE, True);
m_evdevWheelEmulationAtom = XInternAtom(m_dpy, EVDEV_PROP_WHEEL, True);
m_evdevWheelEmulationAxesAtom = XInternAtom(m_dpy, EVDEV_PROP_WHEEL_AXES, True);
m_touchpadAtom = XInternAtom(m_dpy, XI_TOUCHPAD, True);
}
X11Backend::~X11Backend()
{
if (!m_platformX11 && m_dpy) {
XCloseDisplay(m_dpy);
}
delete m_settings;
}
bool X11Backend::supportScrollPolarity()
{
return m_numButtons >= 5;
}
QStringList X11Backend::supportedAccelerationProfiles()
{
return m_supportedAccelerationProfiles;
}
QString X11Backend::accelerationProfile()
{
return m_accelerationProfile;
}
double X11Backend::accelRate()
{
return m_accelRate;
}
Handed X11Backend::handed()
{
return m_handed;
}
int X11Backend::threshold()
{
return m_threshold;
}
void X11Backend::load()
{
if (!m_dpy) {
return;
}
m_accelRate = 1.0;
int accel_num, accel_den;
XGetPointerControl(m_dpy, &accel_num, &accel_den, &m_threshold);
m_accelRate = double(accel_num) / double(accel_den);
// get settings from X server
unsigned char map[256];
m_numButtons = XGetPointerMapping(m_dpy, map, 256);
m_middleButton = -1;
m_handed = Handed::NotSupported;
// ## keep this in sync with KGlobalSettings::mouseSettings
if (m_numButtons == 2) {
if (map[0] == 1 && map[1] == 2) {
m_handed = Handed::Right;
} else if (map[0] == 2 && map[1] == 1) {
m_handed = Handed::Left;
}
} else if (m_numButtons >= 3) {
m_middleButton = map[1];
if (map[0] == 1 && map[2] == 3) {
m_handed = Handed::Right;
} else if (map[0] == 3 && map[2] == 1) {
m_handed = Handed::Left;
}
}
m_supportedAccelerationProfiles.clear();
bool adaptiveAvailable = false;
bool flatAvailable = false;
bool adaptiveEnabled = false;
bool flatEnabled = false;
if (m_libinputAccelProfileAvailableAtom != None && m_libinputAccelProfileEnabledAtom != None) {
XI2ForallPointerDevices(m_dpy, [&] (XIDeviceInfo *info) {
int deviceid = info->deviceid;
Status status;
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *_data = nullptr;
//data returned is an 2 byte boolean
status = XIGetProperty(m_dpy, deviceid, m_libinputAccelProfileAvailableAtom, 0, 2,
False, XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return, &_data);
QScopedArrayPointer<unsigned char, ScopedXDeleter> data(_data);
_data = nullptr;
if (status != Success || type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) {
return;
}
adaptiveAvailable = adaptiveAvailable || data[0];
flatAvailable = flatAvailable || data[1];
//data returned is an 2 byte boolean
status = XIGetProperty(m_dpy, deviceid, m_libinputAccelProfileEnabledAtom, 0, 2,
False, XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return, &_data);
data.reset(_data);
_data = nullptr;
if (status != Success || type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) {
return;
}
adaptiveEnabled = adaptiveEnabled || data[0];
flatEnabled = flatEnabled || data[1];
});
}
if (adaptiveAvailable) {
m_supportedAccelerationProfiles << PROFILE_ADAPTIVE;
}
if (flatAvailable) {
m_supportedAccelerationProfiles << PROFILE_FLAT;
}
if (adaptiveAvailable || flatAvailable) {
m_supportedAccelerationProfiles << PROFILE_NONE;
}
m_accelerationProfile = PROFILE_NONE;
if (adaptiveEnabled) {
m_accelerationProfile = PROFILE_ADAPTIVE;
} else if (flatEnabled) {
m_accelerationProfile = PROFILE_FLAT;
}
m_settings->load(this);
}
void X11Backend::apply(bool force)
{
// 256 might seems extreme, but X has already been known to return 32,
// and we don't want to truncate things. Xlib limits the table to 256 bytes,
// so it's a good upper bound..
unsigned char map[256];
XGetPointerMapping(m_dpy, map, 256);
if (m_settings->handedEnabled && (m_settings->handedNeedsApply || force)) {
if (m_numButtons == 1) {
map[0] = (unsigned char) 1;
} else if (m_numButtons == 2) {
if (m_settings->handed == Handed::Right) {
map[0] = (unsigned char) 1;
map[1] = (unsigned char) 3;
} else {
map[0] = (unsigned char) 3;
map[1] = (unsigned char) 1;
}
} else { // 3 buttons and more
if (m_settings->handed == Handed::Right) {
map[0] = (unsigned char) 1;
map[1] = (unsigned char) m_middleButton;
map[2] = (unsigned char) 3;
} else {
map[0] = (unsigned char) 3;
map[1] = (unsigned char) m_middleButton;
map[2] = (unsigned char) 1;
}
}
int retval;
if (m_numButtons >= 1) {
while ((retval = XSetPointerMapping(m_dpy, map,
m_numButtons)) == MappingBusy)
/* keep trying until the pointer is free */
{ };
}
// apply reverseScrollPolarity for all non-touchpad pointer, touchpad
// are belong to kcm touchpad.
XIForallPointerDevices(m_dpy, [this](XDeviceInfo * info) {
int deviceid = info->id;
if (info->type == m_touchpadAtom) {
return;
}
if (libinputApplyReverseScroll(deviceid, m_settings->reverseScrollPolarity)) {
return;
}
evdevApplyReverseScroll(deviceid, m_settings->reverseScrollPolarity);
});
}
XI2ForallPointerDevices(m_dpy, [&] (XIDeviceInfo *info) {
libinputApplyAccelerationProfile(info->deviceid, m_settings->currentAccelProfile);
});
XChangePointerControl(m_dpy,
true, true, int(qRound(m_settings->accelRate * 10)), 10, m_settings->thresholdMove);
XFlush(m_dpy);
}
QString X11Backend::currentCursorTheme()
......@@ -353,146 +121,37 @@ void X11Backend::applyCursorTheme(const QString& theme, int size)
#endif
}
bool X11Backend::evdevApplyReverseScroll(int deviceid, bool reverse)
void X11Backend::kcmInit()
{
// Check atom availability first.
if (m_evdevWheelEmulationAtom == None || m_evdevScrollDistanceAtom == None ||
m_evdevWheelEmulationAxesAtom == None) {
return false;
}
Status status;
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char* _data = nullptr;
//data returned is an 1 byte boolean
status = XIGetProperty(m_dpy, deviceid, m_evdevWheelEmulationAtom, 0, 1,
False, XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return, &_data);
QScopedArrayPointer<unsigned char, ScopedXDeleter> data(_data);
_data = nullptr;
if (status != Success) {
return false;
}
KConfigGroup group = KConfig("kcminputrc", KConfig::NoGlobals).group("Mouse");
QString theme = group.readEntry("cursorTheme", QString());
QString size = group.readEntry("cursorSize", QString());
// pointer device without wheel emulation
if (type_return != XA_INTEGER || data == NULL || *data == False) {
status = XIGetProperty(m_dpy, deviceid, m_evdevScrollDistanceAtom, 0, 3,
False, XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return, &_data);
data.reset(_data);
_data = nullptr;
// negate scroll distance
if (status == Success && type_return == XA_INTEGER &&
format_return == 32 && num_items_return == 3) {
int32_t* vals = (int32_t*)data.data();
for (unsigned long i = 0; i < num_items_return; ++i) {
int32_t val = *(vals + i);
*(vals + i) = (int32_t)(reverse ? -abs(val) : abs(val));
}
XIChangeProperty(m_dpy, deviceid, m_evdevScrollDistanceAtom, XA_INTEGER,
32, XIPropModeReplace, data.data(), 3);
int intSize = -1;
if (size.isEmpty()) {
bool ok;
uint value = size.toUInt(&ok);
if (ok) {
intSize = value;
}
} else { // wheel emulation used, reverse wheel axes
status = XIGetProperty(m_dpy, deviceid, m_evdevWheelEmulationAxesAtom, 0, 4,
False, XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return, &_data);
data.reset(_data);
_data = nullptr;
if (status == Success && type_return == XA_INTEGER &&
format_return == 8 && num_items_return == 4) {
// when scroll direction is not reversed,
// up b