diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,11 @@ find_package(Synaptics) set_package_properties(Synaptics PROPERTIES TYPE OPTIONAL) add_feature_info("Synaptics" SYNAPTICS_FOUND "Synaptics libraries needed for touchpad KCM") + +find_package(XorgLibinput) +set_package_properties(XorgLibinput PROPERTIES TYPE OPTIONAL) +add_feature_info("XorgLibinput" XORGLIBINPUT_FOUND "Libinput driver headers needed for input KCM") + include(ConfigureChecks.cmake) if(${Qt5Gui_OPENGL_IMPLEMENTATION} STREQUAL "GL") diff --git a/cmake/modules/FindXorgLibinput.cmake b/cmake/modules/FindXorgLibinput.cmake new file mode 100644 --- /dev/null +++ b/cmake/modules/FindXorgLibinput.cmake @@ -0,0 +1,45 @@ +# - Find xorg libinput's libraries and headers. +# This module defines the following variables: +# +# XORGLIBINPUT_FOUND - true if libinput was found +# XORGLIBINPUT_INCLUDE_DIRS - include path for synaptics +# There are no libraries, just a header file +# +# Copyright (c) 2017 Weng Xuetian +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +find_package(PkgConfig) +pkg_check_modules(PC_XORGLIBINPUT xorg-libinput) + +find_path(XORGLIBINPUT_INCLUDE_DIRS + NAMES libinput-properties.h + HINTS ${PC_XORGLIBINPUT_INCLUDE_DIRS} ${PC_XORGLIBINPUT_INCLUDEDIR} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(XorgLibinput REQUIRED_VARS XORGLIBINPUT_INCLUDE_DIRS) + +mark_as_advanced(XORGLIBINPUT_INCLUDE_DIRS) diff --git a/kcms/CMakeLists.txt b/kcms/CMakeLists.txt --- a/kcms/CMakeLists.txt +++ b/kcms/CMakeLists.txt @@ -14,7 +14,7 @@ add_subdirectory( keyboard ) endif() -if (EVDEV_FOUND AND X11_Xinput_FOUND) +if (EVDEV_FOUND AND XORGLIBINPUT_FOUND AND X11_Xinput_FOUND) add_subdirectory( input ) endif() diff --git a/kcms/input/backends/x11/x11mousebackend.h b/kcms/input/backends/x11/x11mousebackend.h --- a/kcms/input/backends/x11/x11mousebackend.h +++ b/kcms/input/backends/x11/x11mousebackend.h @@ -35,6 +35,8 @@ void load() override; bool supportScrollPolarity() override; + QStringList supportedAccelerationProfiles() override; + QString accelerationProfile() override; double accelRate() override; MouseHanded handed() override; int threshold() override; @@ -46,17 +48,28 @@ private: void initAtom(); bool evdevApplyReverseScroll(int deviceid, bool reverse); + bool libinputApplyReverseScroll(int deviceid, bool reverse); + void libinputApplyAccelerationProfile(int deviceid, QString profile); + + Atom m_libinputAccelProfileAvailableAtom; + Atom m_libinputAccelProfileEnabledAtom; + Atom m_libinputNaturalScrollAtom; + Atom m_evdevWheelEmulationAtom; Atom m_evdevScrollDistanceAtom; Atom m_evdevWheelEmulationAxesAtom; + + Atom m_touchpadAtom; // We may still need to do something on non-X11 platform due to Xwayland. Display* m_dpy; bool m_platformX11; int m_numButtons = 1; MouseHanded m_handed = MouseHanded::NotSupported; double m_accelRate = 1.0; int m_threshold = 0; int m_middleButton = -1; + QStringList m_supportedAccelerationProfiles; + QString m_accelerationProfile; }; #endif // XLIBMOUSEBACKEND_H diff --git a/kcms/input/backends/x11/x11mousebackend.cpp b/kcms/input/backends/x11/x11mousebackend.cpp --- a/kcms/input/backends/x11/x11mousebackend.cpp +++ b/kcms/input/backends/x11/x11mousebackend.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,10 @@ #include #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) { @@ -95,9 +100,15 @@ 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); } @@ -113,6 +124,16 @@ return m_numButtons >= 5; } +QStringList X11MouseBackend::supportedAccelerationProfiles() +{ + return m_supportedAccelerationProfiles; +} + +QString X11MouseBackend::accelerationProfile() +{ + return m_accelerationProfile; +} + double X11MouseBackend::accelRate() { return m_accelRate; @@ -160,6 +181,62 @@ m_handed = MouseHanded::Left; } } + + m_supportedAccelerationProfiles.clear(); + bool adaptiveAvailable = false; + bool flatAvailable = false; + bool adaptiveEnabled = false; + bool flatEnabled = false; + 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 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; + } } void X11MouseBackend::apply(const MouseSettings& settings, bool force) @@ -205,11 +282,21 @@ // are belong to kcm touchpad. XIForallPointerDevices(m_dpy, [this, &settings](XDeviceInfo * info) { int deviceid = info->id; + if (info->type == m_touchpadAtom) { + return; + } + if (libinputApplyReverseScroll(deviceid, settings.reverseScrollPolarity)) { + return; + } evdevApplyReverseScroll(deviceid, settings.reverseScrollPolarity); }); } + XI2ForallPointerDevices(m_dpy, [&] (XIDeviceInfo *info) { + libinputApplyAccelerationProfile(info->deviceid, settings.currentAccelProfile); + }); + XChangePointerControl(m_dpy, true, true, int(qRound(settings.accelRate * 10)), 10, settings.thresholdMove); @@ -324,4 +411,72 @@ return true; } +bool X11MouseBackend::libinputApplyReverseScroll(int deviceid, bool reverse) +{ + // Check atom availability first. + if (m_libinputNaturalScrollAtom == 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_libinputNaturalScrollAtom, 0, 1, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + if (status != Success) { + return false; + } + QScopedArrayPointer data(_data); + _data = nullptr; + if (type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 1) { + return false; + } + unsigned char natural = reverse ? 1 : 0; + XIChangeProperty(m_dpy, deviceid, m_libinputNaturalScrollAtom, XA_INTEGER, + 8, XIPropModeReplace, &natural, 1); + return true; +} + +void X11MouseBackend::libinputApplyAccelerationProfile(int deviceid, QString profile) +{ + unsigned char profileData[2]; + if (profile == PROFILE_NONE) { + profileData[0] = profileData[1] = 0; + } else if (profile == PROFILE_ADAPTIVE) { + profileData[0] = 1; + profileData[1] = 0; + } else if (profile == PROFILE_FLAT) { + profileData[0] = 0; + profileData[1] = 1; + } + 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_libinputAccelProfileAvailableAtom, 0, 2, + False, XA_INTEGER, &type_return, &format_return, + &num_items_return, &bytes_after_return, &_data); + if (status != Success) { + return; + } + QScopedArrayPointer data(_data); + _data = nullptr; + if (type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) { + return; + } + // Check availability for profile. + if (profileData[0] > data[0] || profileData[1] > data[1]) { + return; + } + XIChangeProperty(m_dpy, deviceid, m_libinputAccelProfileEnabledAtom, XA_INTEGER, + 8, XIPropModeReplace, profileData, 2); +} diff --git a/kcms/input/kcmmouse.ui b/kcms/input/kcmmouse.ui --- a/kcms/input/kcmmouse.ui +++ b/kcms/input/kcmmouse.ui @@ -202,7 +202,7 @@ - + Pointer acceleration: @@ -212,7 +212,7 @@ - + Pointer threshold: @@ -222,7 +222,7 @@ - + Double click interval: @@ -232,7 +232,7 @@ - + Drag start time: @@ -242,7 +242,7 @@ - + Drag start distance: @@ -252,7 +252,7 @@ - + Mouse wheel scrolls by: @@ -262,7 +262,7 @@ - + @@ -293,7 +293,7 @@ - + @@ -312,7 +312,7 @@ - + @@ -340,7 +340,7 @@ - + @@ -365,7 +365,7 @@ - + @@ -387,7 +387,7 @@ - + @@ -409,6 +409,26 @@ + + + + Acceleration Profile: + + + accelProfileComboBox + + + + + + + + 0 + 0 + + + + diff --git a/kcms/input/mouse.cpp b/kcms/input/mouse.cpp --- a/kcms/input/mouse.cpp +++ b/kcms/input/mouse.cpp @@ -98,6 +98,7 @@ connect(accel, SIGNAL(valueChanged(double)), this, SLOT(changed())); connect(thresh, SIGNAL(valueChanged(int)), this, SLOT(changed())); connect(thresh, SIGNAL(valueChanged(int)), this, SLOT(slotThreshChanged(int))); + connect(accelProfileComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changed())); slotThreshChanged(thresh->value()); // It would be nice if the user had a test field. @@ -219,6 +220,22 @@ } } + auto accelerationProfiles = backend->supportedAccelerationProfiles(); + accelProfileComboBox->setEnabled(!accelerationProfiles.isEmpty()); + accelProfileComboBox->setVisible(!accelerationProfiles.isEmpty()); + accelerationProfileLabel->setEnabled(!accelerationProfiles.isEmpty()); + accelerationProfileLabel->setVisible(!accelerationProfiles.isEmpty()); + accelProfileComboBox->clear(); + int idx = 0; + for (const auto &profile : accelerationProfiles) { + accelProfileComboBox->addItem(i18n(profile.toUtf8().constData()), profile); + if (profile == settings->currentAccelProfile) { + accelProfileComboBox->setCurrentIndex(idx); + } + idx++; + } + + rightHanded->setEnabled(settings->handedEnabled); leftHanded->setEnabled(settings->handedEnabled); if (cbScrollPolarity->isEnabled()) @@ -279,6 +296,7 @@ settings->wheelScrollLines = wheelScrollLines->value(); settings->singleClick = !doubleClick->isChecked(); settings->reverseScrollPolarity = cbScrollPolarity->isChecked(); + settings->currentAccelProfile = accelProfileComboBox->itemData(accelProfileComboBox->currentIndex()).toString(); settings->apply(backend); KConfig config("kcminputrc"); diff --git a/kcms/input/mousebackend.h b/kcms/input/mousebackend.h --- a/kcms/input/mousebackend.h +++ b/kcms/input/mousebackend.h @@ -40,6 +40,8 @@ // Return the value from display server or compositor if applicable. virtual bool supportScrollPolarity() = 0; + virtual QStringList supportedAccelerationProfiles() = 0; + virtual QString accelerationProfile() = 0; virtual double accelRate() = 0; virtual int threshold() = 0; virtual MouseHanded handed() = 0; diff --git a/kcms/input/mousesettings.h b/kcms/input/mousesettings.h --- a/kcms/input/mousesettings.h +++ b/kcms/input/mousesettings.h @@ -46,6 +46,7 @@ bool singleClick; int wheelScrollLines; bool reverseScrollPolarity; + QString currentAccelProfile; }; #endif // MOUSESETTINGS_H diff --git a/kcms/input/mousesettings.cpp b/kcms/input/mousesettings.cpp --- a/kcms/input/mousesettings.cpp +++ b/kcms/input/mousesettings.cpp @@ -51,6 +51,7 @@ } accel = backend->accelRate(); threshold = backend->threshold(); + profile = backend->accelerationProfile(); } KConfigGroup group = config->group("Mouse"); @@ -72,6 +73,10 @@ else if (key == "LeftHanded") handed = MouseHanded::Left; reverseScrollPolarity = group.readEntry("ReverseScrollPolarity", false); + currentAccelProfile = group.readEntry("AccelerationProfile"); + if (currentAccelProfile.isEmpty()) { + currentAccelProfile = profile; + } handedNeedsApply = false; // SC/DC/AutoSelect/ChangeCursor @@ -96,6 +101,7 @@ kcminputGroup.writeEntry("MouseButtonMapping",QString("LeftHanded")); } kcminputGroup.writeEntry("ReverseScrollPolarity", reverseScrollPolarity); + kcminputGroup.writeEntry("AccelerationProfile", currentAccelProfile); kcminputGroup.sync(); KSharedConfig::Ptr profile = KSharedConfig::openConfig("kdeglobals");