Commit 40f8edd7 authored by Xuetian Weng's avatar Xuetian Weng

Refactor kcm_input to enable having multiple backends.

Summary:
Split patch for D8168. This change only refactor the X11-related code into
its own places. KCM itself will only use the backend interface.

Test Plan: Manually test all options.

Reviewers: subdiff, davidedmundson, ngraham, #plasma

Reviewed By: ngraham

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D8460
parent fde9daf3
......@@ -7,55 +7,39 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcminput\")
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} )
......
# // 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})
......@@ -3,6 +3,7 @@
*
* Copyright (c) 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
* Copyright (c) 2005 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2017 Xuetian Weng <wengxt@gmail.com>
*
* Requires the Qt widget libraries, available at no cost at
* http://www.troll.no/
......@@ -21,68 +22,33 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <QGuiApplication>
#include <QFile>
#include <config-X11.h>
#include <stdlib.h>
#include <ctype.h>
#include <X11/Xlib.h>
#ifdef HAVE_XCURSOR
# include <X11/Xcursor/Xcursor.h>
#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';
}
#include "mousebackend.h"
int main( int argc, char* argv[] )
{
{
int ret = 0;
QGuiApplication app(argc, argv);
if( argc != 3 )
return 1;
dpy = XOpenDisplay( NULL );
if( dpy == NULL )
QString theme = QFile::decodeName(argv[ 1 ]);
QString size = QFile::decodeName(argv[ 2 ]);
auto backend = MouseBackend::implementation();
if (!backend || !backend->isValid()) {
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.
// 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( isEmpty( theme )
&& isEmpty( XGetDefault( display(), "Xcursor", "theme" ))
&& isEmpty( XcursorGetTheme( display())))
if(theme.isEmpty() && backend->currentCursorTheme().isEmpty())
{
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 );
backend->applyCursorTheme(theme, size.toInt());
return ret;
}
}
/*
* Copyright 2017 Xuetian Weng <wengxt@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 "x11mousebackend.h"
#include "mousesettings.h"
#include <config-X11.h>
#include <QFile>
#include <QX11Info>
#include <evdev-properties.h>
#include <KLocalizedString>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/XI.h>
#include <X11/Xutil.h>
#ifdef HAVE_XCURSOR
# include <X11/Xcursor/Xcursor.h>
#include <X11/extensions/XInput.h>
#endif
struct ScopedXDeleter {
static inline void cleanup(void* pointer)
{
if (pointer) {
XFree(pointer);
}
}
};
template<typename Callback>
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<typename Callback>
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<unsigned char, ScopedXDeleter> 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;
}
/*
* Copyright 2017 Xuetian Weng <wengxt@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 XLIBMOUSEBACKEND_H
#define XLIBMOUSEBACKEND_H
#include "mousebackend.h"
#include <QX11Info>
#include <X11/Xdefs.h>
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
/*
* Copyright 2017 Xuetian Weng <wengxt@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.