diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,11 @@ set(USING_X_LIBRARIES ${X11_LIBRARIES} ${X11_Xinput_LIB}) endif() +if(${LIBWACOM_VERSION} VERSION_LESS "0.29") + message(STATUS "Button detection with libwacom requires version at least 0.29. Detected version is: " ${LIBWACOM_VERSION}) + add_definitions(-DLIBWACOM_EVDEV_MISSING) +endif() + add_definitions( -DQT_STRICT_ITERATORS ) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory( common/deviceprofileconfigadaptor ) add_subdirectory( common/deviceproperty ) add_subdirectory( common/enum ) +add_subdirectory( common/libwacomdata ) add_subdirectory( common/profilemanager ) add_subdirectory( common/property ) add_subdirectory( common/propertyset ) diff --git a/autotests/common/libwacomdata/CMakeLists.txt b/autotests/common/libwacomdata/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/autotests/common/libwacomdata/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(Test.Common.LibwacomData testlibwacomdata.cpp) + +# This is technically a test but it's very long, verbose and would fail most of the time +# so for now make it for humans only + +#add_test(NAME Test.Common.LibwacomData COMMAND Test.Common.LibwacomData) +#ecm_mark_as_test(Test.Common.LibwacomData) +target_link_libraries(Test.Common.LibwacomData ${WACOM_COMMON_TEST_LIBS}) diff --git a/autotests/common/libwacomdata/testlibwacomdata.cpp b/autotests/common/libwacomdata/testlibwacomdata.cpp new file mode 100644 --- /dev/null +++ b/autotests/common/libwacomdata/testlibwacomdata.cpp @@ -0,0 +1,142 @@ +/* + * This file is part of the KDE wacomtablet project. For copyright + * information and license terms see the AUTHORS and COPYING files + * in the top-level directory of this distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common/libwacomwrapper.h" +#include "common/tabletdatabase.h" +#include "common/tabletinformation.h" + +//#include +#include +#include + +#include + +using namespace Wacom; + +class TestLibwacomData: public QObject +{ + Q_OBJECT + +public: + void testData(); +}; + +static bool compare(TabletInformation &left, TabletInformation &right, const TabletInfo &info) { + + if (info == TabletInfo::NumPadButtons) { + if (left.getInt(info) != right.getInt(info)) { + qWarning() << "MISMATCH: Property" << info.key() << "mismatch for device" << left.get(TabletInfo::TabletName) + << "DB:" << left.getInt(info) << "vs LIB:" << right.getInt(info); + return false; + } + + return true; + } + + if (left.get(info) != right.get(info)) { + qWarning() << "MISMATCH: Property" << info.key() << "mismatch for device" << left.get(TabletInfo::TabletName) + << "DB:" << left.get(info) << "vs LIB:" << right.get(info); + return false; + } + + return true; +} + +void TestLibwacomData::testData() +{ + using namespace Wacom; + + int missingLocal = 0; + int missingLibwacom = 0; + int okay = 0; + int mismatch = 0; + + int max = 0x0FFF; + + for (int deviceId = 0; deviceId < max; ++deviceId) { + TabletInformation localInfo(deviceId); + TabletInformation libwacomInfo(deviceId); + + localInfo.set(TabletInfo::CompanyId, QString::fromLatin1("056A")); + libwacomInfo.set(TabletInfo::CompanyId, QString::fromLatin1("056A")); + + bool inLocal = false; + bool inLibWacom = false; + + if (TabletDatabase::instance().lookupTablet(localInfo.get (TabletInfo::TabletId), localInfo)) { + inLocal = true; + } + + // lookup information in libWacom tablet database + auto tabletId = localInfo.get(TabletInfo::TabletId).toInt(nullptr, 16); + auto vendorId = localInfo.get(TabletInfo::CompanyId).toInt(nullptr, 16); + if (libWacomWrapper::instance().lookupTabletInfo(tabletId, vendorId, libwacomInfo)) { + inLibWacom = true; + } + + if (inLocal && inLibWacom) { + //qDebug() << "Comparing" << deviceId << inLocal << inLibWacom; + bool identical = true; + identical &= compare(localInfo, libwacomInfo, TabletInfo::HasLeftTouchStrip); + identical &= compare(localInfo, libwacomInfo, TabletInfo::HasRightTouchStrip); + identical &= compare(localInfo, libwacomInfo, TabletInfo::HasTouchRing); + identical &= compare(localInfo, libwacomInfo, TabletInfo::HasWheel); + identical &= compare(localInfo, libwacomInfo, TabletInfo::NumPadButtons); + identical &= compare(localInfo, libwacomInfo, TabletInfo::StatusLEDs); + + // We don't really care about button order here, just compare the indices + auto localMap = localInfo.getButtonMap().values(); + auto libwacomMap = libwacomInfo.getButtonMap().values(); + + std::sort(localMap.begin(), localMap.end()); + std::sort(libwacomMap.begin(), libwacomMap.end()); + + bool identicalmapping = localMap.size() == libwacomMap.size() + && std::equal(localMap.begin(), localMap.end(), libwacomMap.begin()); + + if (!identicalmapping) { + qWarning() << "MISMATCH: Mapping for device" << localInfo.get(TabletInfo::TabletName) << "differs." + << "Local:" << localInfo.getButtonMap() << "Libwacom:" << libwacomInfo.getButtonMap(); + } + + identical &= identicalmapping; + + if (identical) { + okay++; + qDebug() << "Device" << localInfo.get(TabletInfo::TabletName) << "is identical"; + } else { + mismatch++; + } + } else if(inLocal) { + qDebug() << "Device" << localInfo.get(TabletInfo::TabletName) << "is missing from LibWacom"; + missingLibwacom++; + } else if(inLibWacom) { + missingLocal++; + } + } + + qDebug() << "OK:" << okay << "MISMATCH:" << mismatch << "ONLYLOCAL" << missingLibwacom << "ONLYWACOM" << missingLocal; +} + +int main() { + TestLibwacomData test; + test.testData(); +} + +#include "testlibwacomdata.moc" diff --git a/cmake/modules/FindLibWacom.cmake b/cmake/modules/FindLibWacom.cmake --- a/cmake/modules/FindLibWacom.cmake +++ b/cmake/modules/FindLibWacom.cmake @@ -43,13 +43,15 @@ NAMES wacom libwacom HINTS ${LIBWACOM_PKGCONF_LIBDIR} ${LIBWACOM_PKGCONF_LIBRARY_DIRS} ) -set(LIBWACOM_LIBRARIES ${LIBWACOM_LIBRARY} ) -set(LIBWACOM_INCLUDE_DIRS ${LIBWACOM_INCLUDE_DIR} ) +set(LIBWACOM_LIBRARIES ${LIBWACOM_LIBRARY}) +set(LIBWACOM_INCLUDE_DIRS ${LIBWACOM_INCLUDE_DIR}) +set(LIBWACOM_VERSION ${LIBWACOM_PKGCONF_VERSION}) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBWACOM_FOUND to TRUE # if all listed variables are TRUE -find_package_handle_standard_args(LIBWACOM DEFAULT_MSG - LIBWACOM_LIBRARY LIBWACOM_INCLUDE_DIR) +find_package_handle_standard_args(LIBWACOM + REQUIRED_VARS LIBWACOM_LIBRARY LIBWACOM_INCLUDE_DIR + VERSION_VAR LIBWACOM_VERSION) -mark_as_advanced(LIBWACOM_INCLUDE_DIR LIBWACOM_LIBRARY ) +mark_as_advanced(LIBWACOM_INCLUDE_DIR LIBWACOM_LIBRARY) diff --git a/src/common/libwacomwrapper.cpp b/src/common/libwacomwrapper.cpp --- a/src/common/libwacomwrapper.cpp +++ b/src/common/libwacomwrapper.cpp @@ -29,6 +29,16 @@ namespace Wacom { +static int skipWheelButtons(int button) { + // skip buttons 4-7, which correspond to vertical/horizontal wheel up/down events + if (button > 3) { + return button + 4; + } else { + return button; + } +} +static int convertEvdevToXsetwacomButton(int evdevCode); + libWacomWrapper::libWacomWrapper() { db = libwacom_database_new(); @@ -74,17 +84,94 @@ tabletInfo.set(TabletInfo::StatusLEDs, QString::number(0)); tabletInfo.set(TabletInfo::TabletName, QString::fromLatin1(libwacom_get_name(device.get()))); - tabletInfo.set(TabletInfo::NumPadButtons, QString::number(libwacom_get_num_buttons(device.get()))); - int numStrips = libwacom_get_num_strips(device.get()); - bool hasLeftStrip = numStrips > 0; - bool hasRightStrip = numStrips > 1; - bool hasRing = libwacom_has_ring(device.get()) != 0; + const int padButtonNumber = libwacom_get_num_buttons(device.get()); + tabletInfo.set(TabletInfo::NumPadButtons, QString::number(padButtonNumber)); + + // Convert button evdev codes to buttonMap + if (libwacom_get_num_buttons(device.get()) > 0) { + QMap buttonMapping; + for (char i = 1; i < padButtonNumber + 1; i++) { +#ifdef LIBWACOM_EVDEV_MISSING + dbgWacom << "Your libwacom version is too old. We will try and guess button mapping, " + << "but it's going to be broken for quirky tablets. Use kde_wacomtablet_finder instead."; + const int buttonIndex = skipWheelButtons(i); + buttonMapping[QString::number(i)] = QString::number(buttonIndex); +#else + const char buttonChar = 'A' + (i - 1); // libwacom marks buttons as 'A', 'B', 'C'... + const int buttonEvdevCode = libwacom_get_button_evdev_code(device.get(), buttonChar); + const int buttonIndex = convertEvdevToXsetwacomButton(buttonEvdevCode); + buttonMapping[QString::number(i)] = QString::number(buttonIndex); + + if (buttonIndex < 1) { + errWacom << "Unrecognized evdev code. " + << "Device:" << tabletId << "Vendor:" << vendorId + << "Button:" << buttonChar << "EvdevCode:" << buttonEvdevCode; + return false; + } +#endif + } + tabletInfo.setButtonMap(buttonMapping); + } + + const int numStrips = libwacom_get_num_strips(device.get()); + const bool hasLeftStrip = numStrips > 0; + const bool hasRightStrip = numStrips > 1; + const bool hasRing = libwacom_has_ring(device.get()) != 0; tabletInfo.set(TabletInfo::HasLeftTouchStrip, hasLeftStrip); tabletInfo.set(TabletInfo::HasRightTouchStrip, hasRightStrip); tabletInfo.set(TabletInfo::HasTouchRing, hasRing); return true; } +static int convertMouseEvdevToXsetwacomButton(int evdevCode) { + // some quirky consumer tablets, e.g. Bamboo/Graphire, use mouse button events + // instead of just numbered express keys. Translate them back to numbers + static const int BTN_LEFT = 0x110; + static const int BTN_RIGHT = 0x111; + static const int BTN_MIDDLE = 0x112; + static const int BTN_FORWARD = 0x115; + static const int BTN_BACK = 0x116; + + switch (evdevCode) { + case BTN_LEFT: + return 1; + case BTN_RIGHT: + return 3; + case BTN_MIDDLE: + return 2; + case BTN_FORWARD: + return 9; + case BTN_BACK: + return 8; + + default: + return 0; + } +} + +static int convertEvdevToXsetwacomButton(int evdevCode) { + // based on set_button_codes_from_heuristics from libwacom/libwacom-database.c + static const int BTN_MISC = 0x100; + static const int BTN_MOUSE = 0x110; + static const int BTN_BASE = 0x126; + static const int BTN_GAMEPAD = 0x130; + + int translatedCode = 0; + if (evdevCode >= BTN_GAMEPAD) { + translatedCode = evdevCode - BTN_GAMEPAD + 10; + } else if (evdevCode >= BTN_BASE) { + translatedCode = evdevCode - BTN_BASE + 16; + } else if (evdevCode >= BTN_MOUSE) { + return convertMouseEvdevToXsetwacomButton(evdevCode); + } else if (evdevCode >= BTN_MISC) { + translatedCode = evdevCode - BTN_MISC; + } else { + return 0; + } + + return skipWheelButtons(translatedCode + 1); +} + }