diff --git a/kcms/input/CMakeLists.txt b/kcms/input/CMakeLists.txt --- a/kcms/input/CMakeLists.txt +++ b/kcms/input/CMakeLists.txt @@ -7,55 +7,39 @@ add_subdirectory( pics ) - -########### next target ############### - -set(kapplymousetheme_SRCS kapplymousetheme.cpp ) - - -add_executable(kapplymousetheme ${kapplymousetheme_SRCS}) - -target_link_libraries(kapplymousetheme ${X11_Xrender_LIB} ${X11_X11_LIB}) -if (X11_Xcursor_FOUND) - target_link_libraries(kapplymousetheme ${X11_Xcursor_LIB}) - target_include_directories(kapplymousetheme PRIVATE ${X11_Xcursor_INCLUDE_PATH}) -endif () - -install(TARGETS kapplymousetheme ${INSTALL_TARGETS_DEFAULT_ARGS}) +## Add common files here. +set(kcminput_backend_SRCS + mousebackend.cpp + mousesettings.cpp + logging.cpp) +set(kcminput_backend_LIBS) +include(backends/x11.cmake) ########### next target ############### -set(kcm_input_PART_SRCS mouse.cpp main.cpp) +set(kcm_input_PART_SRCS + mouse.cpp + main.cpp + ${kcminput_backend_SRCS} +) set(klauncher_xml ${KINIT_DBUS_INTERFACES_DIR}/kf5_org.kde.KLauncher.xml) ki18n_wrap_ui(kcm_input_PART_SRCS kcmmouse.ui) qt5_add_dbus_interface(kcm_input_PART_SRCS ${klauncher_xml} klauncher_iface) -add_library(kcm_input MODULE ${kcm_input_PART_SRCS}) - -include_directories(${X11_X11_INCLUDE_PATH} - ${X11_Xinput_INCLUDE_PATH} - ${Evdev_INCLUDE_DIRS}) +add_library(kcm_input MODULE ${kcm_input_PART_SRCS} ${kcminput_backend_SRCS}) target_link_libraries(kcm_input Qt5::DBus - Qt5::X11Extras KF5::KCMUtils KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::KDELibs4Support - ${X11_X11_LIB} - ${X11_Xinput_LIB} + ${kcminput_backend_LIBS} ) -if (X11_Xcursor_FOUND) - target_link_libraries(kcm_input ${X11_Xcursor_LIB}) -endif () -if (X11_Xfixes_FOUND) - target_link_libraries(kcm_input ${X11_Xfixes_LIB}) -endif () install(TARGETS kcm_input DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/kcms/input/backends/x11.cmake b/kcms/input/backends/x11.cmake new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11.cmake @@ -0,0 +1,42 @@ +# // krazy:excludeall=copyright,license + +set(kcminput_backend_SRCS + ${kcminput_backend_SRCS} + backends/x11/x11mousebackend.cpp +) + +set(kcminput_backend_LIBS + Qt5::X11Extras + ${X11_X11_LIB} + ${X11_Xinput_LIB} + ${kcminput_backend_LIBS} +) + +include_directories(${X11_X11_INCLUDE_PATH} + ${X11_Xinput_INCLUDE_PATH} + ${Evdev_INCLUDE_DIRS} + ${XORGLIBINPUT_INCLUDE_DIRS}) + +if (X11_Xcursor_FOUND) + set(kcminput_backend_LIBS + ${X11_Xcursor_LIB} + ${kcminput_backend_LIBS} + ) + include_directories(${X11_Xcursor_INCLUDE_PATH}) +endif () + + +set(kapplymousetheme_SRCS + backends/x11/kapplymousetheme.cpp) + +add_executable(kapplymousetheme ${kapplymousetheme_SRCS} ${kcminput_backend_SRCS}) + +target_link_libraries(kapplymousetheme + Qt5::Gui + KF5::I18n + KF5::ConfigCore + KF5::KDELibs4Support + ${kcminput_backend_LIBS} +) + +install(TARGETS kapplymousetheme ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/kcms/input/backends/x11/kapplymousetheme.cpp b/kcms/input/backends/x11/kapplymousetheme.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11/kapplymousetheme.cpp @@ -0,0 +1,54 @@ +/* + * main.cpp + * + * Copyright (c) 1999 Matthias Hoelzer-Kluepfel + * Copyright (c) 2005 Lubos Lunak + * Copyright (c) 2017 Xuetian Weng + * + * Requires the Qt widget libraries, available at no cost at + * http://www.troll.no/ + * + * 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 +#include + +#include "mousebackend.h" + +int main( int argc, char* argv[] ) +{ + int ret = 0; + QGuiApplication app(argc, argv); + if( argc != 3 ) + return 1; + QString theme = QFile::decodeName(argv[ 1 ]); + QString size = QFile::decodeName(argv[ 2 ]); + auto backend = MouseBackend::implementation(); + if (!backend || !backend->isValid()) { + return 2; + } + + // Note: If you update this code, update main.cpp as well. + + // use a default value for theme only if it's not configured at all, not even in X resources + if(theme.isEmpty() && backend->currentCursorTheme().isEmpty()) + { + theme = "breeze_cursors"; + ret = 10; // means to switch to default + } + + backend->applyCursorTheme(theme, size.toInt()); + return ret; +} diff --git a/kcms/input/backends/x11/x11mousebackend.h b/kcms/input/backends/x11/x11mousebackend.h new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11/x11mousebackend.h @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 XLIBMOUSEBACKEND_H +#define XLIBMOUSEBACKEND_H + +#include "mousebackend.h" + +#include +#include + +class X11MouseBackend : public MouseBackend +{ + Q_OBJECT +public: + X11MouseBackend(QObject *parent = nullptr); + ~X11MouseBackend(); + + bool isValid() const override { return m_dpy != nullptr; } + + void load() override; + bool supportScrollPolarity() override; + double accelRate() override; + MouseHanded handed() override; + int threshold() override; + void apply(const MouseSettings & settings, bool force) override; + + QString currentCursorTheme() override; + void applyCursorTheme(const QString &name, int size) override; + +private: + void initAtom(); + bool evdevApplyReverseScroll(int deviceid, bool reverse); + Atom m_evdevWheelEmulationAtom; + Atom m_evdevScrollDistanceAtom; + Atom m_evdevWheelEmulationAxesAtom; + // 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; +}; + +#endif // XLIBMOUSEBACKEND_H diff --git a/kcms/input/backends/x11/x11mousebackend.cpp b/kcms/input/backends/x11/x11mousebackend.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/backends/x11/x11mousebackend.cpp @@ -0,0 +1,327 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 "x11mousebackend.h" +#include "mousesettings.h" +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_XCURSOR +# include +#include +#endif + +struct ScopedXDeleter { + static inline void cleanup(void* pointer) + { + if (pointer) { + XFree(pointer); + } + } +}; + +template +static void XI2ForallPointerDevices(Display* dpy, const Callback& callback) +{ + 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); +} + +template +static void XIForallPointerDevices(Display* dpy, const Callback& callback) +{ + int ndevices_return; + XDeviceInfo* info = XListInputDevices(dpy, &ndevices_return); + if (!info) { + return; + } + for (int i = 0; i < ndevices_return; ++i) { + if (info[i].use == IsXPointer || info[i].use == IsXExtensionPointer) { + callback(info + i); + } + } + XFreeDeviceList(info); +} + +X11MouseBackend::X11MouseBackend(QObject* parent) : MouseBackend(parent), m_dpy(nullptr) +{ + m_platformX11 = QX11Info::isPlatformX11(); + if (m_platformX11) { + m_dpy = QX11Info::display(); + } else { + // let's hope we have a compatibility system like Xwayland ready + m_dpy = XOpenDisplay(nullptr); + } + initAtom(); +} + +void X11MouseBackend::initAtom() +{ + if (!m_dpy) { + return; + } + + 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); +} + + +X11MouseBackend::~X11MouseBackend() +{ + if (!m_platformX11 && m_dpy) { + XCloseDisplay(m_dpy); + } +} + +bool X11MouseBackend::supportScrollPolarity() +{ + return m_numButtons >= 5; +} + +double X11MouseBackend::accelRate() +{ + return m_accelRate; +} + +MouseHanded X11MouseBackend::handed() +{ + return m_handed; +} + +int X11MouseBackend::threshold() +{ + return m_threshold; +} + +void X11MouseBackend::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 = MouseHanded::NotSupported; + // ## keep this in sync with KGlobalSettings::mouseSettings + if (m_numButtons == 2) { + if (map[0] == 1 && map[1] == 2) { + m_handed = MouseHanded::Right; + } else if (map[0] == 2 && map[1] == 1) { + m_handed = MouseHanded::Left; + } + } else if (m_numButtons >= 3) { + m_middleButton = map[1]; + if (map[0] == 1 && map[2] == 3) { + m_handed = MouseHanded::Right; + } else if (map[0] == 3 && map[2] == 1) { + m_handed = MouseHanded::Left; + } + } +} + +void X11MouseBackend::apply(const MouseSettings& settings, 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 (settings.handedEnabled && (settings.handedNeedsApply || force)) { + if (m_numButtons == 1) { + map[0] = (unsigned char) 1; + } else if (m_numButtons == 2) { + if (settings.handed == MouseHanded::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 (settings.handed == MouseHanded::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, &settings](XDeviceInfo * info) { + int deviceid = info->id; + evdevApplyReverseScroll(deviceid, settings.reverseScrollPolarity); + }); + + } + + XChangePointerControl(m_dpy, + true, true, int(qRound(settings.accelRate * 10)), 10, settings.thresholdMove); + + XFlush(m_dpy); +} + +QString X11MouseBackend::currentCursorTheme() +{ + if (!m_dpy) { + return QString(); + } + + QByteArray name = XGetDefault(m_dpy, "Xcursor", "theme"); +#ifdef HAVE_XCURSOR + if (name.isEmpty()) { + name = QByteArray(XcursorGetTheme(m_dpy)); + } +#endif + return QFile::decodeName(name); +} + +void X11MouseBackend::applyCursorTheme(const QString& theme, int size) +{ +#ifdef HAVE_XCURSOR + + // Apply the KDE cursor theme to ourselves + if (m_dpy) { + return; + } + if (!theme.isEmpty()) { + XcursorSetTheme(m_dpy, QFile::encodeName(theme)); + } + + if (size >= 0) { + XcursorSetDefaultSize(m_dpy, size); + } + + // Load the default cursor from the theme and apply it to the root window. + Cursor handle = XcursorLibraryLoadCursor(m_dpy, "left_ptr"); + XDefineCursor(m_dpy, DefaultRootWindow(m_dpy), handle); + XFreeCursor(m_dpy, handle); // Don't leak the cursor + XFlush(m_dpy); +#endif +} + +bool X11MouseBackend::evdevApplyReverseScroll(int deviceid, bool reverse) +{ + // 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 data(_data); + _data = nullptr; + if (status != Success) { + return false; + } + + // 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); + } + } 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 button id should be smaller than down button id, + // up/left are odd elements, down/right are even elements + for (int i = 0; i < 2; ++i) { + unsigned char odd = data[i * 2]; + unsigned char even = data[i * 2 + 1]; + unsigned char max_elem = std::max(odd, even); + unsigned char min_elem = std::min(odd, even); + data[i * 2] = reverse ? max_elem : min_elem; + data[i * 2 + 1] = reverse ? min_elem : max_elem; + } + XIChangeProperty(m_dpy, deviceid, m_evdevWheelEmulationAxesAtom, XA_INTEGER, + 8, XIPropModeReplace, data.data(), 4); + } + } + + return true; +} + + diff --git a/kcms/input/kapplymousetheme.cpp b/kcms/input/kapplymousetheme.cpp deleted file mode 100644 --- a/kcms/input/kapplymousetheme.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * main.cpp - * - * Copyright (c) 1999 Matthias Hoelzer-Kluepfel - * Copyright (c) 2005 Lubos Lunak - * - * Requires the Qt widget libraries, available at no cost at - * http://www.troll.no/ - * - * 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 - -#include -#include -#include - -#ifdef HAVE_XCURSOR -# include -#endif - -static Display* dpy; -Display* display() { return dpy; } -Window appRootWindow() { return DefaultRootWindow( dpy ); } - -static bool isEmpty( const char* str ) - { - if( str == NULL ) - return true; - while( isspace( *str )) - ++str; - return *str == '\0'; - } - -int main( int argc, char* argv[] ) - { - if( argc != 3 ) - return 1; - dpy = XOpenDisplay( NULL ); - if( dpy == NULL ) - return 2; - int ret = 0; -#ifdef HAVE_XCURSOR - const char* theme = argv[ 1 ]; - const char* size = argv[ 2 ]; - - // Note: If you update this code, update kapplymousetheme as well. - - // use a default value for theme only if it's not configured at all, not even in X resources - if( isEmpty( theme ) - && isEmpty( XGetDefault( display(), "Xcursor", "theme" )) - && isEmpty( XcursorGetTheme( display()))) - { - theme = "breeze_cursors"; - ret = 10; // means to switch to default - } - - // Apply the KDE cursor theme to ourselves - if( !isEmpty( theme )) - XcursorSetTheme(display(), theme ); - - if (!isEmpty( size )) - XcursorSetDefaultSize(display(), atoi( size )); - - // Load the default cursor from the theme and apply it to the root window. - Cursor handle = XcursorLibraryLoadCursor(display(), "left_ptr"); - XDefineCursor(display(), appRootWindow(), handle); - XFreeCursor(display(), handle); // Don't leak the cursor - -#else - ( void ) argv; -#endif - XCloseDisplay( dpy ); - return ret; - } diff --git a/kcms/input/logging.h b/kcms/input/logging.h new file mode 100644 --- /dev/null +++ b/kcms/input/logging.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 KCM_INPUT_LOGGING_H +#define KCM_INPUT_LOGGING_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(KCM_INPUT) +#endif diff --git a/kcms/input/logging.cpp b/kcms/input/logging.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/logging.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 "logging.h" + +Q_LOGGING_CATEGORY(KCM_INPUT, "kcm_input") diff --git a/kcms/input/main.cpp b/kcms/input/main.cpp --- a/kcms/input/main.cpp +++ b/kcms/input/main.cpp @@ -21,89 +21,60 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include - -#include +#include +#include #include -#include "mouse.h" -#include +#include "mousesettings.h" +#include "mousebackend.h" #include -#include -#ifdef HAVE_XCURSOR -# include -#endif - extern "C" { - Q_DECL_EXPORT void kcminit_mouse() - { - KConfig *config = new KConfig("kcminputrc", KConfig::NoGlobals ); - - Display *dpy = nullptr; - const bool platformX11 = QX11Info::isPlatformX11(); - if (platformX11) { - dpy = QX11Info::display(); - } else { - // let's hope we have a compatibility system like Xwayland ready - dpy = XOpenDisplay(nullptr); - } - - MouseSettings settings; - settings.load(config, dpy); - settings.apply(true); // force - -#ifdef HAVE_XCURSOR - KConfigGroup group = config->group("Mouse"); - QString theme = group.readEntry("cursorTheme", QString()); - QString size = group.readEntry("cursorSize", QString()); - - // Note: If you update this code, update kapplymousetheme as well. - - // use a default value for theme only if it's not configured at all, not even in X resources - if( theme.isEmpty() - && (!dpy || - (QByteArray( XGetDefault( dpy, "Xcursor", "theme" )).isEmpty() - && QByteArray( XcursorGetTheme( dpy)).isEmpty()))) + Q_DECL_EXPORT void kcminit_mouse() { - theme = "breeze_cursors"; + KConfig* config = new KConfig("kcminputrc", KConfig::NoGlobals); + + auto backend = MouseBackend::implementation(); + + MouseSettings settings; + settings.load(config, backend); + settings.apply(backend, true); // force + + KConfigGroup group = config->group("Mouse"); + QString theme = group.readEntry("cursorTheme", QString()); + QString size = group.readEntry("cursorSize", QString()); + if (backend) { + int intSize = -1; + if (size.isEmpty()) { + bool ok; + uint value = size.toUInt(&ok); + if (ok) { + intSize = value; + } + } + // Note: If you update this code, update kapplymousetheme as well. + + // use a default value for theme only if it's not configured at all, not even in X resources + if (theme.isEmpty() && backend->currentCursorTheme().isEmpty()) { + theme = "breeze_cursors"; + } + backend->applyCursorTheme(theme, intSize); + } + + // Tell klauncher to set the XCURSOR_THEME and XCURSOR_SIZE environment + // variables when launching applications. + OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"), + QStringLiteral("/KLauncher"), + QDBusConnection::sessionBus()); + if (!theme.isEmpty()) { + klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), theme); + } + if (!size.isEmpty()) { + klauncher.setLaunchEnv(QStringLiteral("XCURSOR_SIZE"), size); + } + + delete config; } - - // Apply the KDE cursor theme to ourselves - if (dpy) { - if( !theme.isEmpty()) - XcursorSetTheme(dpy, QFile::encodeName(theme)); - - if (!size.isEmpty()) - XcursorSetDefaultSize(dpy, size.toUInt()); - - // Load the default cursor from the theme and apply it to the root window. - Cursor handle = XcursorLibraryLoadCursor(dpy, "left_ptr"); - XDefineCursor(dpy, QX11Info::appRootWindow(), handle); - XFreeCursor(dpy, handle); // Don't leak the cursor - } - - // Tell klauncher to set the XCURSOR_THEME and XCURSOR_SIZE environment - // variables when launching applications. - OrgKdeKLauncherInterface klauncher(QStringLiteral("org.kde.klauncher5"), - QStringLiteral("/KLauncher"), - QDBusConnection::sessionBus()); - if(!theme.isEmpty()) - klauncher.setLaunchEnv(QStringLiteral("XCURSOR_THEME"), theme); - if( !size.isEmpty()) - klauncher.setLaunchEnv(QStringLiteral("XCURSOR_SIZE"), size); - -#endif - - if (!platformX11) { - XFlush(dpy); - XCloseDisplay(dpy); - } - - delete config; - } } - - diff --git a/kcms/input/mouse.h b/kcms/input/mouse.h --- a/kcms/input/mouse.h +++ b/kcms/input/mouse.h @@ -31,45 +31,20 @@ #ifndef __MOUSECONFIG_H__ #define __MOUSECONFIG_H__ -#include - #include #include -#include #include "ui_kcmmouse.h" - -#define RIGHT_HANDED 0 -#define LEFT_HANDED 1 +#include "mousesettings.h" class QCheckBox; class QDoubleSpinBox; class QSlider; class QSpinBox; class QTabWidget; -class MouseSettings -{ -public: - void save(KConfig *); - void load(KConfig *, Display *dpy = QX11Info::display()); - void apply(bool force=false); - -public: - int num_buttons; - int middle_button; - bool handedEnabled; - bool m_handedNeedsApply; - int handed; - double accelRate; - int thresholdMove; - int doubleClickInterval; - int dragStartTime; - int dragStartDist; - bool singleClick; - int wheelScrollLines; - bool reverseScrollPolarity; -}; +class MouseSettings; +class MouseBackend; class MouseConfig : public KCModule, public Ui::KCMMouse { @@ -93,14 +68,15 @@ private: double getAccel(); int getThreshold(); - int getHandedness(); + MouseHanded getHandedness(); void setAccel(double); void setThreshold(int); - void setHandedness(int); + void setHandedness(MouseHanded); MouseSettings *settings; + + MouseBackend *backend; }; #endif - diff --git a/kcms/input/mouse.cpp b/kcms/input/mouse.cpp --- a/kcms/input/mouse.cpp +++ b/kcms/input/mouse.cpp @@ -65,50 +65,33 @@ #include #include "mouse.h" +#include "mousebackend.h" +#include "mousesettings.h" -#include -#include -#include -#include -#include #include -#include -#include #undef Below #include "../migrationlib/kdelibs4config.h" K_PLUGIN_FACTORY(MouseConfigFactory, registerPlugin(); // mouse ) -K_EXPORT_PLUGIN(MouseConfigFactory("kcminput")) MouseConfig::MouseConfig(QWidget *parent, const QVariantList &args) - : KCModule(parent, args) + : KCModule(parent, args), + backend(MouseBackend::implementation()) { setupUi(this); - handedGroup->setId(rightHanded, 0); - handedGroup->setId(leftHanded, 1); + handedGroup->setId(rightHanded, static_cast(MouseHanded::Right)); + handedGroup->setId(leftHanded, static_cast(MouseHanded::Left)); connect(handedGroup, SIGNAL(buttonClicked(int)), this, SLOT(changed())); connect(handedGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotHandedChanged(int))); connect(doubleClick, SIGNAL(clicked()), SLOT(changed())); - connect(singleClick, SIGNAL(clicked()), this, SLOT(changed())); - // Only allow setting reversing scroll polarity if we have scroll buttons - unsigned char map[20]; - if (QX11Info::isPlatformX11() && XGetPointerMapping(QX11Info::display(), map, 20) >= 5) - { - cbScrollPolarity->setEnabled(true); - cbScrollPolarity->show(); - } - else - { - cbScrollPolarity->setEnabled(false); - cbScrollPolarity->hide(); - } + connect(singleClick, SIGNAL(clicked()), this, SLOT(changed())); connect(cbScrollPolarity, SIGNAL(clicked()), this, SLOT(changed())); connect(cbScrollPolarity, SIGNAL(clicked()), this, SLOT(slotScrollPolarityChanged())); @@ -192,33 +175,49 @@ } -int MouseConfig::getHandedness() +MouseHanded MouseConfig::getHandedness() { if (rightHanded->isChecked()) - return RIGHT_HANDED; + return MouseHanded::Right; else - return LEFT_HANDED; + return MouseHanded::Left; } -void MouseConfig::setHandedness(int val) +void MouseConfig::setHandedness(MouseHanded val) { rightHanded->setChecked(false); leftHanded->setChecked(false); - if (val == RIGHT_HANDED) { + if (val == MouseHanded::Right) { rightHanded->setChecked(true); mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_rh.png")); } else { leftHanded->setChecked(true); mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_lh.png")); } - settings->m_handedNeedsApply = true; + settings->handedNeedsApply = true; } void MouseConfig::load() { KConfig config("kcminputrc"); - settings->load(&config); + settings->load(&config, backend); + + // settings->load will trigger backend->load so information will be avaialbe + // here. + // Only allow setting reversing scroll polarity if we have scroll buttons + if (backend) { + if (backend->supportScrollPolarity()) + { + cbScrollPolarity->setEnabled(true); + cbScrollPolarity->show(); + } + else + { + cbScrollPolarity->setEnabled(false); + cbScrollPolarity->hide(); + } + } rightHanded->setEnabled(settings->handedEnabled); leftHanded->setEnabled(settings->handedEnabled); @@ -281,7 +280,7 @@ settings->singleClick = !doubleClick->isChecked(); settings->reverseScrollPolarity = cbScrollPolarity->isChecked(); - settings->apply(); + settings->apply(backend); KConfig config("kcminputrc"); settings->save(&config); @@ -310,7 +309,7 @@ { setThreshold(2); setAccel(2); - setHandedness(RIGHT_HANDED); + setHandedness(MouseHanded::Right); cbScrollPolarity->setChecked(false); doubleClickInterval->setValue(400); dragStartTime->setValue(500); @@ -325,107 +324,19 @@ mk_time_to_max->setValue(5000); mk_max_speed->setValue(1000); mk_curve->setValue(0); - + checkAccess(); changed(); } /** No descriptions */ void MouseConfig::slotHandedChanged(int val) { - if (val==RIGHT_HANDED) + if (val == static_cast(MouseHanded::Right)) mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_rh.png")); else mousePix->setPixmap(KStandardDirs::locate("data", "kcminput/pics/mouse_lh.png")); - settings->m_handedNeedsApply = true; -} - -void MouseSettings::load(KConfig *config, Display *dpy) -{ - // TODO: what's a good threshold default value - int threshold = 0; - int h = RIGHT_HANDED; - double accel = 1.0; - if (QX11Info::isPlatformX11()) { - int accel_num, accel_den; - - XGetPointerControl(dpy, &accel_num, &accel_den, &threshold); - accel = float(accel_num) / float(accel_den); - - // get settings from X server - unsigned char map[20]; - num_buttons = XGetPointerMapping(dpy, map, 20); - - handedEnabled = true; - - // ## keep this in sync with KGlobalSettings::mouseSettings - if (num_buttons == 1) - { - /* disable button remapping */ - handedEnabled = false; - } - else if (num_buttons == 2) - { - if ((int)map[0] == 1 && (int)map[1] == 2) - h = RIGHT_HANDED; - else if ((int)map[0] == 2 && (int)map[1] == 1) - h = LEFT_HANDED; - else - /* custom button setup: disable button remapping */ - handedEnabled = false; - } - else - { - middle_button = (int)map[1]; - if ((int)map[0] == 1 && (int)map[2] == 3) - h = RIGHT_HANDED; - else if ((int)map[0] == 3 && (int)map[2] == 1) - h = LEFT_HANDED; - else - { - /* custom button setup: disable button remapping */ - handedEnabled = false; - } - } - } else { - // other platforms - handedEnabled = true; - } - - KConfigGroup group = config->group("Mouse"); - double a = group.readEntry("Acceleration", -1.0); - if (a == -1) - accelRate = accel; - else - accelRate = a; - - int t = group.readEntry("Threshold", -1); - if (t == -1) - thresholdMove = threshold; - else - thresholdMove = t; - - QString key = group.readEntry("MouseButtonMapping"); - if (key == "RightHanded") - handed = RIGHT_HANDED; - else if (key == "LeftHanded") - handed = LEFT_HANDED; -#ifdef __GNUC__ -#warning was key == NULL how was this working? is key.isNull() what the coder meant? -#endif - else if (key.isNull()) - handed = h; - reverseScrollPolarity = group.readEntry("ReverseScrollPolarity", false); - m_handedNeedsApply = false; - - // SC/DC/AutoSelect/ChangeCursor - group = config->group("KDE"); - doubleClickInterval = group.readEntry("DoubleClickInterval", 400); - dragStartTime = group.readEntry("StartDragTime", 500); - dragStartDist = group.readEntry("StartDragDist", 4); - wheelScrollLines = group.readEntry("WheelScrollLines", 3); - - singleClick = group.readEntry("SingleClick", KDE_DEFAULT_SINGLECLICK); + settings->handedNeedsApply = true; } void MouseConfig::slotThreshChanged(int value) @@ -443,172 +354,9 @@ wheelScrollLines->setSuffix(i18np(" line", " lines", value)); } -void MouseSettings::apply(bool force) -{ - if (!QX11Info::isPlatformX11()) { - return; - } - XChangePointerControl(QX11Info::display(), - true, true, int(qRound(accelRate*10)), 10, thresholdMove); - - // 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]; - num_buttons = XGetPointerMapping(QX11Info::display(), map, 256); - - int remap=(num_buttons>=1); - if (handedEnabled && (m_handedNeedsApply || force)) { - if (num_buttons == 1) - { - map[0] = (unsigned char) 1; - } - else if (num_buttons == 2) - { - if (handed == RIGHT_HANDED) - { - 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 (handed == RIGHT_HANDED) - { - map[0] = (unsigned char) 1; - map[1] = (unsigned char) middle_button; - map[2] = (unsigned char) 3; - } - else - { - map[0] = (unsigned char) 3; - map[1] = (unsigned char) middle_button; - map[2] = (unsigned char) 1; - } - } - - int retval; - if (remap) { - while ((retval=XSetPointerMapping(QX11Info::display(), map, - num_buttons)) == MappingBusy) - /* keep trying until the pointer is free */ - { }; - } - - // apply reverseScrollPolarity - Display *dpy = QX11Info::display(); - Atom prop_wheel_emulation = XInternAtom(dpy, EVDEV_PROP_WHEEL, True); - Atom prop_scroll_distance = XInternAtom(dpy, EVDEV_PROP_SCROLL_DISTANCE, True); - Atom prop_wheel_emulation_axes = XInternAtom(dpy, EVDEV_PROP_WHEEL_AXES, True); - 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) { - int deviceid = (info + i)->deviceid; - Status status; - Atom type_return; - int format_return; - unsigned long num_items_return; - unsigned long bytes_after_return; - - unsigned char *data = nullptr; - unsigned char *data2 = nullptr; - //data returned is an 1 byte boolean - status = XIGetProperty(dpy, deviceid, prop_wheel_emulation, 0, 1, - False, XA_INTEGER, &type_return, &format_return, - &num_items_return, &bytes_after_return, &data); - if (status != Success) { - continue; - } - - // pointer device without wheel emulation - if (type_return != XA_INTEGER || data == NULL || *data == False) { - status = XIGetProperty(dpy, deviceid, prop_scroll_distance, 0, 3, - False, XA_INTEGER, &type_return, &format_return, - &num_items_return, &bytes_after_return, &data2); - // negate scroll distance - if (status == Success && type_return == XA_INTEGER && - format_return == 32 && num_items_return == 3) { - int32_t *vals = (int32_t*)data2; - for (unsigned long i=0; isync(); - - Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("Mouse"), "kcminputrc"); - Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("KDE"), "kdeglobals"); - - KGlobalSettings::self()->emitChange(KGlobalSettings::SettingsChanged, KGlobalSettings::SETTINGS_MOUSE); -} - void MouseConfig::slotScrollPolarityChanged() { - settings->m_handedNeedsApply = true; + settings->handedNeedsApply = true; } #include "mouse.moc" diff --git a/kcms/input/mousebackend.h b/kcms/input/mousebackend.h new file mode 100644 --- /dev/null +++ b/kcms/input/mousebackend.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 MOUSEBACKEND_H +#define MOUSEBACKEND_H + +#include +#include "mousesettings.h" + +class MouseBackend : public QObject +{ + Q_OBJECT +protected: + explicit MouseBackend(QObject *parent) : QObject(parent) {} + +public: + static MouseBackend *implementation(); + + virtual bool isValid() const = 0; + + // This function will be called before query any property below, thus it + // can be used to save some round trip. + virtual void load() = 0; + virtual void apply(const MouseSettings &settings, bool force) = 0; + + // Return the value from display server or compositor if applicable. + virtual bool supportScrollPolarity() = 0; + virtual double accelRate() = 0; + virtual int threshold() = 0; + virtual MouseHanded handed() = 0; + + virtual QString currentCursorTheme() = 0; + virtual void applyCursorTheme(const QString &name, int size) = 0; +}; + +#endif // MOUSEBACKEND_H diff --git a/kcms/input/mousebackend.cpp b/kcms/input/mousebackend.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/mousebackend.cpp @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 "mousebackend.h" + +#include "backends/x11/x11mousebackend.h" +#include "logging.h" + +#include +#include + +#include + +MouseBackend *MouseBackend::implementation() +{ + //There are multiple possible backends, always use X11 backend for now. + static QThreadStorage> backend; + if (!backend.hasLocalData()) { + qCDebug(KCM_INPUT) << "Using X11 backend"; + backend.setLocalData(QSharedPointer(new X11MouseBackend)); + } + return backend.localData().data(); + +#if 0 + qCCritical(KCM_INPUT) << "Not able to select appropriate backend."; + return nullptr; +#endif +} diff --git a/kcms/input/mousesettings.h b/kcms/input/mousesettings.h new file mode 100644 --- /dev/null +++ b/kcms/input/mousesettings.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 MOUSESETTINGS_H +#define MOUSESETTINGS_H + +#include + +class MouseBackend; + +enum class MouseHanded { + Right = 0, + Left = 1, + NotSupported = -1 +}; + +struct MouseSettings +{ + void save(KConfig *); + void load(KConfig *, MouseBackend*); + void apply(MouseBackend*, bool force = false); + + bool handedEnabled; + bool handedNeedsApply; + MouseHanded handed; + double accelRate; + int thresholdMove; + int doubleClickInterval; + int dragStartTime; + int dragStartDist; + bool singleClick; + int wheelScrollLines; + bool reverseScrollPolarity; +}; + +#endif // MOUSESETTINGS_H diff --git a/kcms/input/mousesettings.cpp b/kcms/input/mousesettings.cpp new file mode 100644 --- /dev/null +++ b/kcms/input/mousesettings.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2017 Xuetian Weng + * + * 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 "mousesettings.h" + +#include "mousebackend.h" +#include +#include +#include + +#include "../migrationlib/kdelibs4config.h" + +void MouseSettings::apply(MouseBackend* backend, bool force) +{ + if (!backend) { + return; + } + + backend->apply(*this, force); + handedNeedsApply = false; +} + +void MouseSettings::load(KConfig *config, MouseBackend *backend) +{ + // TODO: what's a good threshold default value + int threshold = 0; + handed = MouseHanded::Right; + double accel = 1.0; + QString profile; + if (backend) { + backend->load(); + auto handedOnServer = backend->handed(); + handedEnabled = handedOnServer != MouseHanded::NotSupported; + if (handedEnabled) { + handed = handedOnServer; + } + accel = backend->accelRate(); + threshold = backend->threshold(); + } + + KConfigGroup group = config->group("Mouse"); + double a = group.readEntry("Acceleration", -1.0); + if (a == -1) + accelRate = accel; + else + accelRate = a; + + int t = group.readEntry("Threshold", -1); + if (t == -1) + thresholdMove = threshold; + else + thresholdMove = t; + + QString key = group.readEntry("MouseButtonMapping"); + if (key == "RightHanded") + handed = MouseHanded::Right; + else if (key == "LeftHanded") + handed = MouseHanded::Left; + reverseScrollPolarity = group.readEntry("ReverseScrollPolarity", false); + handedNeedsApply = false; + + // SC/DC/AutoSelect/ChangeCursor + group = config->group("KDE"); + doubleClickInterval = group.readEntry("DoubleClickInterval", 400); + dragStartTime = group.readEntry("StartDragTime", 500); + dragStartDist = group.readEntry("StartDragDist", 4); + wheelScrollLines = group.readEntry("WheelScrollLines", 3); + + singleClick = group.readEntry("SingleClick", KDE_DEFAULT_SINGLECLICK); +} + +void MouseSettings::save(KConfig *config) +{ + KSharedConfig::Ptr kcminputProfile = KSharedConfig::openConfig("kcminputrc"); + KConfigGroup kcminputGroup(kcminputProfile, "Mouse"); + kcminputGroup.writeEntry("Acceleration",accelRate); + kcminputGroup.writeEntry("Threshold",thresholdMove); + if (handed == MouseHanded::Right) { + kcminputGroup.writeEntry("MouseButtonMapping",QString("RightHanded")); + } else { + kcminputGroup.writeEntry("MouseButtonMapping",QString("LeftHanded")); + } + kcminputGroup.writeEntry("ReverseScrollPolarity", reverseScrollPolarity); + kcminputGroup.sync(); + + KSharedConfig::Ptr profile = KSharedConfig::openConfig("kdeglobals"); + KConfigGroup group(profile, "KDE"); + group.writeEntry("DoubleClickInterval", doubleClickInterval, KConfig::Persistent); + group.writeEntry("StartDragTime", dragStartTime, KConfig::Persistent); + group.writeEntry("StartDragDist", dragStartDist, KConfig::Persistent); + group.writeEntry("WheelScrollLines", wheelScrollLines, KConfig::Persistent); + group.writeEntry("SingleClick", singleClick, KConfig::Persistent); + + group.sync(); + config->sync(); + + Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("Mouse"), "kcminputrc"); + Kdelibs4SharedConfig::syncConfigGroup(QLatin1String("KDE"), "kdeglobals"); + + KGlobalSettings::self()->emitChange(KGlobalSettings::SettingsChanged, KGlobalSettings::SETTINGS_MOUSE); +}