diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,6 +443,7 @@ ${kwin_KDEINIT_SRCS} libinput/context.cpp libinput/connection.cpp + libinput/device.cpp libinput/events.cpp libinput/libinput_logging.cpp virtual_terminal.cpp diff --git a/debug_console.h b/debug_console.h --- a/debug_console.h +++ b/debug_console.h @@ -21,6 +21,7 @@ #define KWIN_DEBUG_CONSOLE_H #include +#include #include "input.h" #include @@ -136,6 +137,22 @@ QTextEdit *m_textEdit; }; +#if HAVE_INPUT +class InputDeviceModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit InputDeviceModel(QObject *parent = nullptr); + virtual ~InputDeviceModel(); + + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex & parent) const override; + int rowCount(const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &child) const override; +}; +#endif + } #endif diff --git a/debug_console.cpp b/debug_console.cpp --- a/debug_console.cpp +++ b/debug_console.cpp @@ -24,6 +24,10 @@ #include "unmanaged.h" #include "wayland_server.h" #include "workspace.h" +#if HAVE_INPUT +#include "libinput/connection.h" +#include "libinput/device.h" +#endif #include "ui_debug_console.h" @@ -312,14 +316,23 @@ m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this)); m_ui->windowsView->setModel(new DebugConsoleModel(this)); m_ui->surfacesView->setModel(new SurfaceTreeModel(this)); +#if HAVE_INPUT + if (kwinApp()->usesLibinput()) { + m_ui->inputDevicesView->setModel(new InputDeviceModel(this)); + m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this)); + } +#endif m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree"))); m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree"))); if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) { m_ui->tabWidget->setTabEnabled(1, false); m_ui->tabWidget->setTabEnabled(2, false); } + if (!kwinApp()->usesLibinput()) { + m_ui->tabWidget->setTabEnabled(3, false); + } connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, @@ -376,6 +389,104 @@ return QStringLiteral("nullptr"); } } + if (value.userType() == qMetaTypeId()) { + const auto buttons = value.value(); + if (buttons == Qt::NoButton) { + return i18n("No Mouse Buttons"); + } + QStringList list; + if (buttons.testFlag(Qt::LeftButton)) { + list << i18nc("Mouse Button", "left"); + } + if (buttons.testFlag(Qt::RightButton)) { + list << i18nc("Mouse Button", "right"); + } + if (buttons.testFlag(Qt::MiddleButton)) { + list << i18nc("Mouse Button", "middle"); + } + if (buttons.testFlag(Qt::BackButton)) { + list << i18nc("Mouse Button", "back"); + } + if (buttons.testFlag(Qt::ForwardButton)) { + list << i18nc("Mouse Button", "forward"); + } + if (buttons.testFlag(Qt::ExtraButton1)) { + list << i18nc("Mouse Button", "extra 1"); + } + if (buttons.testFlag(Qt::ExtraButton2)) { + list << i18nc("Mouse Button", "extra 2"); + } + if (buttons.testFlag(Qt::ExtraButton3)) { + list << i18nc("Mouse Button", "extra 3"); + } + if (buttons.testFlag(Qt::ExtraButton4)) { + list << i18nc("Mouse Button", "extra 4"); + } + if (buttons.testFlag(Qt::ExtraButton5)) { + list << i18nc("Mouse Button", "extra 5"); + } + if (buttons.testFlag(Qt::ExtraButton6)) { + list << i18nc("Mouse Button", "extra 6"); + } + if (buttons.testFlag(Qt::ExtraButton7)) { + list << i18nc("Mouse Button", "extra 7"); + } + if (buttons.testFlag(Qt::ExtraButton8)) { + list << i18nc("Mouse Button", "extra 8"); + } + if (buttons.testFlag(Qt::ExtraButton9)) { + list << i18nc("Mouse Button", "extra 9"); + } + if (buttons.testFlag(Qt::ExtraButton10)) { + list << i18nc("Mouse Button", "extra 10"); + } + if (buttons.testFlag(Qt::ExtraButton11)) { + list << i18nc("Mouse Button", "extra 11"); + } + if (buttons.testFlag(Qt::ExtraButton12)) { + list << i18nc("Mouse Button", "extra 12"); + } + if (buttons.testFlag(Qt::ExtraButton13)) { + list << i18nc("Mouse Button", "extra 13"); + } + if (buttons.testFlag(Qt::ExtraButton14)) { + list << i18nc("Mouse Button", "extra 14"); + } + if (buttons.testFlag(Qt::ExtraButton15)) { + list << i18nc("Mouse Button", "extra 15"); + } + if (buttons.testFlag(Qt::ExtraButton16)) { + list << i18nc("Mouse Button", "extra 16"); + } + if (buttons.testFlag(Qt::ExtraButton17)) { + list << i18nc("Mouse Button", "extra 17"); + } + if (buttons.testFlag(Qt::ExtraButton18)) { + list << i18nc("Mouse Button", "extra 18"); + } + if (buttons.testFlag(Qt::ExtraButton19)) { + list << i18nc("Mouse Button", "extra 19"); + } + if (buttons.testFlag(Qt::ExtraButton20)) { + list << i18nc("Mouse Button", "extra 20"); + } + if (buttons.testFlag(Qt::ExtraButton21)) { + list << i18nc("Mouse Button", "extra 21"); + } + if (buttons.testFlag(Qt::ExtraButton22)) { + list << i18nc("Mouse Button", "extra 22"); + } + if (buttons.testFlag(Qt::ExtraButton23)) { + list << i18nc("Mouse Button", "extra 23"); + } + if (buttons.testFlag(Qt::ExtraButton24)) { + list << i18nc("Mouse Button", "extra 24"); + } + if (buttons.testFlag(Qt::TaskButton)) { + list << i18nc("Mouse Button", "task"); + } + return list.join(QStringLiteral(", ")); + } break; } return QStyledItemDelegate::displayText(value, locale); @@ -1013,4 +1124,97 @@ return QVariant(); } +#if HAVE_INPUT +InputDeviceModel::InputDeviceModel(QObject *parent) + : QAbstractItemModel(parent) +{ + auto c = LibInput::Connection::self(); + auto resetModel = [this] { + beginResetModel(); + endResetModel(); + }; + connect(c, &LibInput::Connection::deviceAdded, this, resetModel); + connect(c, &LibInput::Connection::deviceRemoved, this, resetModel); +} + +InputDeviceModel::~InputDeviceModel() = default; + + +int InputDeviceModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 2; +} + +QVariant InputDeviceModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + if (!index.parent().isValid() && index.column() == 0) { + const auto devices = LibInput::Connection::self()->devices(); + if (index.row() >= devices.count()) { + return QVariant(); + } + if (role == Qt::DisplayRole) { + return devices.at(index.row())->name(); + } + } + if (index.parent().isValid()) { + if (role == Qt::DisplayRole) { + const auto device = LibInput::Connection::self()->devices().at(index.parent().row()); + const auto property = device->metaObject()->property(index.row()); + if (index.column() == 0) { + return property.name(); + } else if (index.column() == 1) { + return device->property(property.name()); + } + } + } + return QVariant(); +} + +QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column >= 2) { + return QModelIndex(); + } + if (parent.isValid()) { + if (parent.internalId() & s_propertyBitMask) { + return QModelIndex(); + } + if (row >= LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount()) { + return QModelIndex(); + } + return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); + } + if (row >= LibInput::Connection::self()->devices().count()) { + return QModelIndex(); + } + return createIndex(row, column, row + 1); +} + +int InputDeviceModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return LibInput::Connection::self()->devices().count(); + } + if (parent.internalId() & s_propertyBitMask) { + return 0; + } + + return LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount(); +} + +QModelIndex InputDeviceModel::parent(const QModelIndex &child) const +{ + if (child.internalId() & s_propertyBitMask) { + const quintptr parentId = child.internalId() & s_clientBitMask; + return createIndex(parentId - 1, 0, parentId); + } + return QModelIndex(); +} + +#endif + } diff --git a/debug_console.ui b/debug_console.ui --- a/debug_console.ui +++ b/debug_console.ui @@ -84,6 +84,16 @@ + + + Input Devices + + + + + + + diff --git a/libinput/connection.h b/libinput/connection.h --- a/libinput/connection.h +++ b/libinput/connection.h @@ -37,6 +37,7 @@ { class Event; +class Device; class Context; class Connection : public QObject @@ -68,6 +69,10 @@ void processEvents(); + QVector devices() const { + return m_devices; + } + Q_SIGNALS: void keyChanged(quint32 key, KWin::InputRedirection::KeyboardKeyState, quint32 time); void pointerButtonChanged(quint32 button, KWin::InputRedirection::PointerButtonState state, quint32 time); @@ -82,6 +87,8 @@ void hasKeyboardChanged(bool); void hasPointerChanged(bool); void hasTouchChanged(bool); + void deviceAdded(KWin::LibInput::Device *); + void deviceRemoved(KWin::LibInput::Device *); void eventsRead(); @@ -103,6 +110,7 @@ QMutex m_mutex; QVector m_eventQueue; bool wasSuspended = false; + QVector m_devices; KWIN_SINGLETON(Connection) static QThread *s_thread; diff --git a/libinput/connection.cpp b/libinput/connection.cpp --- a/libinput/connection.cpp +++ b/libinput/connection.cpp @@ -19,6 +19,7 @@ *********************************************************************/ #include "connection.h" #include "context.h" +#include "device.h" #include "events.h" #include "../logind.h" #include "../udev.h" @@ -159,46 +160,61 @@ while (!m_eventQueue.isEmpty()) { QScopedPointer event(m_eventQueue.takeFirst()); switch (event->type()) { - case LIBINPUT_EVENT_DEVICE_ADDED: - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_KEYBOARD)) { + case LIBINPUT_EVENT_DEVICE_ADDED: { + auto device = new Device(event->device(), this); + m_devices << device; + if (device->isKeyboard()) { m_keyboard++; if (m_keyboard == 1) { emit hasKeyboardChanged(true); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_POINTER)) { + if (device->isPointer()) { m_pointer++; if (m_pointer == 1) { emit hasPointerChanged(true); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_TOUCH)) { + if (device->isTouch()) { m_touch++; if (m_touch == 1) { emit hasTouchChanged(true); } } + emit deviceAdded(device); break; - case LIBINPUT_EVENT_DEVICE_REMOVED: - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_KEYBOARD)) { + } + case LIBINPUT_EVENT_DEVICE_REMOVED: { + auto it = std::find_if(m_devices.begin(), m_devices.end(), [&event] (Device *d) { return event->device() == d->device(); } ); + if (it == m_devices.end()) { + // we don't know this device + break; + } + auto device = *it; + m_devices.erase(it); + emit deviceRemoved(device); + + if (device->isKeyboard()) { m_keyboard--; if (m_keyboard == 0) { emit hasKeyboardChanged(false); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_POINTER)) { + if (device->isPointer()) { m_pointer--; if (m_pointer == 0) { emit hasPointerChanged(false); } } - if (libinput_device_has_capability(event->device(), LIBINPUT_DEVICE_CAP_TOUCH)) { + if (device->isTouch()) { m_touch--; if (m_touch == 0) { emit hasTouchChanged(false); } } + delete device; break; + } case LIBINPUT_EVENT_KEYBOARD_KEY: { KeyEvent *ke = static_cast(event.data()); emit keyChanged(ke->key(), ke->state(), ke->time()); diff --git a/libinput/device.h b/libinput/device.h new file mode 100644 --- /dev/null +++ b/libinput/device.h @@ -0,0 +1,164 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 . +*********************************************************************/ +#ifndef KWIN_LIBINPUT_DEVICE_H +#define KWIN_LIBINPUT_DEVICE_H + +#include +#include + +struct libinput_device; + +namespace KWin +{ +namespace LibInput +{ + +class Device : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool keyboard READ isKeyboard CONSTANT) + Q_PROPERTY(bool alphaNumericKeyboard READ isAlphaNumericKeyboard CONSTANT) + Q_PROPERTY(bool pointer READ isPointer CONSTANT) + Q_PROPERTY(bool touch READ isTouch CONSTANT) + Q_PROPERTY(bool tabletTool READ isTabletTool CONSTANT) + Q_PROPERTY(bool tabletPad READ isTabletPad CONSTANT) + Q_PROPERTY(bool gestureSupport READ supportsGesture CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString sysName READ sysName CONSTANT) + Q_PROPERTY(QString outputName READ outputName CONSTANT) + Q_PROPERTY(QSizeF size READ size CONSTANT) + Q_PROPERTY(quint32 product READ product CONSTANT) + Q_PROPERTY(quint32 vendor READ vendor CONSTANT) + Q_PROPERTY(Qt::MouseButtons supportedButtons READ supportedButtons CONSTANT) + Q_PROPERTY(int tapFingerCount READ tapFingerCount CONSTANT) + Q_PROPERTY(bool tapEnabledByDefault READ tapEnabledByDefault CONSTANT) + Q_PROPERTY(bool supportsDisableWhileTyping READ supportsDisableWhileTyping CONSTANT) + Q_PROPERTY(bool supportsPointerAcceleration READ supportsPointerAcceleration CONSTANT) + Q_PROPERTY(bool supportsLeftHanded READ supportsLeftHanded CONSTANT) + Q_PROPERTY(bool supportsCalibrationMatrix READ supportsCalibrationMatrix CONSTANT) + Q_PROPERTY(bool supportsDisableEvents READ supportsDisableEvents CONSTANT) + Q_PROPERTY(bool supportsDisableEventsOnExternalMouse READ supportsDisableEventsOnExternalMouse CONSTANT) +public: + explicit Device(libinput_device *device, QObject *parent = nullptr); + virtual ~Device(); + + bool isKeyboard() const { + return m_keyboard; + } + bool isAlphaNumericKeyboard() const { + return m_alphaNumericKeyboard; + } + bool isPointer() const { + return m_pointer; + } + bool isTouch() const { + return m_touch; + } + bool isTabletTool() const { + return m_tabletTool; + } + bool isTabletPad() const { + return m_tabletPad; + } + bool supportsGesture() const { + return m_supportsGesture; + } + QString name() const { + return m_name; + } + QString sysName() const { + return m_sysName; + } + QString outputName() const { + return m_outputName; + } + QSizeF size() const { + return m_size; + } + quint32 product() const { + return m_product; + } + quint32 vendor() const { + return m_vendor; + } + Qt::MouseButtons supportedButtons() const { + return m_supportedButtons; + } + int tapFingerCount() const { + return m_tapFingerCount; + } + bool tapEnabledByDefault() const { + return m_tapEnabledByDefault; + } + bool supportsDisableWhileTyping() const { + return m_supportsDisableWhileTyping; + } + bool supportsPointerAcceleration() const { + return m_supportsPointerAcceleration; + } + bool supportsLeftHanded() const { + return m_supportsLeftHanded; + } + bool supportsCalibrationMatrix() const { + return m_supportsCalibrationMatrix; + } + bool supportsDisableEvents() const { + return m_supportsDisableEvents; + } + bool supportsDisableEventsOnExternalMouse() const { + return m_supportsDisableEventsOnExternalMouse; + } + + libinput_device *device() const { + return m_device; + } + +private: + libinput_device *m_device; + bool m_keyboard; + bool m_alphaNumericKeyboard = false; + bool m_pointer; + bool m_touch; + bool m_tabletTool; + bool m_tabletPad; + bool m_supportsGesture; + QString m_name; + QString m_sysName; + QString m_outputName; + QSizeF m_size; + quint32 m_product; + quint32 m_vendor; + Qt::MouseButtons m_supportedButtons = Qt::NoButton; + int m_tapFingerCount; + bool m_tapEnabledByDefault; + bool m_supportsDisableWhileTyping; + bool m_supportsPointerAcceleration; + bool m_supportsLeftHanded; + bool m_supportsCalibrationMatrix; + bool m_supportsDisableEvents; + bool m_supportsDisableEventsOnExternalMouse; +}; + +} +} + +Q_DECLARE_METATYPE(KWin::LibInput::Device*) + +#endif diff --git a/libinput/device.cpp b/libinput/device.cpp new file mode 100644 --- /dev/null +++ b/libinput/device.cpp @@ -0,0 +1,127 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 "device.h" +#include + +#include + +namespace KWin +{ +namespace LibInput +{ + +static bool checkAlphaNumericKeyboard(libinput_device *device) +{ + for (uint i = KEY_1; i <= KEY_0; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + for (uint i = KEY_Q; i <= KEY_P; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + for (uint i = KEY_A; i <= KEY_L; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + for (uint i = KEY_Z; i <= KEY_M; i++) { + if (libinput_device_keyboard_has_key(device, i) == 0) { + return false; + } + } + return true; +} + +Device::Device(libinput_device *device, QObject *parent) + : QObject(parent) + , m_device(device) + , m_keyboard(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_KEYBOARD)) + , m_pointer(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_POINTER)) + , m_touch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TOUCH)) + , m_tabletTool(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_TOOL)) +#if 0 + // next libinput version + , m_tabletPad(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_TABLET_PAD)) +#else + , m_tabletPad(false) +#endif + , m_supportsGesture(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_GESTURE)) + , m_name(QString::fromLocal8Bit(libinput_device_get_name(m_device))) + , m_sysName(QString::fromLocal8Bit(libinput_device_get_sysname(m_device))) + , m_outputName(QString::fromLocal8Bit(libinput_device_get_output_name(m_device))) + , m_product(libinput_device_get_id_product(m_device)) + , m_vendor(libinput_device_get_id_vendor(m_device)) + , m_tapFingerCount(libinput_device_config_tap_get_finger_count(m_device)) + , m_tapEnabledByDefault(libinput_device_config_tap_get_default_enabled(m_device) == LIBINPUT_CONFIG_TAP_ENABLED) + , m_supportsDisableWhileTyping(libinput_device_config_dwt_is_available(m_device)) + , m_supportsPointerAcceleration(libinput_device_config_accel_is_available(m_device)) + , m_supportsLeftHanded(libinput_device_config_left_handed_is_available(m_device)) + , m_supportsCalibrationMatrix(libinput_device_config_calibration_has_matrix(m_device)) + , m_supportsDisableEvents(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED) + , m_supportsDisableEventsOnExternalMouse(libinput_device_config_send_events_get_modes(m_device) & LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) +{ + libinput_device_ref(m_device); + + qreal width = 0; + qreal height = 0; + if (libinput_device_get_size(m_device, &width, &height) == 0) { + m_size = QSizeF(width, height); + } + if (m_pointer) { + if (libinput_device_pointer_has_button(m_device, BTN_LEFT) == 1) { + m_supportedButtons |= Qt::LeftButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_MIDDLE) == 1) { + m_supportedButtons |= Qt::MiddleButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_RIGHT) == 1) { + m_supportedButtons |= Qt::RightButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_SIDE) == 1) { + m_supportedButtons |= Qt::ExtraButton1; + } + if (libinput_device_pointer_has_button(m_device, BTN_EXTRA) == 1) { + m_supportedButtons |= Qt::ExtraButton2; + } + if (libinput_device_pointer_has_button(m_device, BTN_BACK) == 1) { + m_supportedButtons |= Qt::BackButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_FORWARD) == 1) { + m_supportedButtons |= Qt::ForwardButton; + } + if (libinput_device_pointer_has_button(m_device, BTN_TASK) == 1) { + m_supportedButtons |= Qt::TaskButton; + } + } + if (m_keyboard) { + m_alphaNumericKeyboard = checkAlphaNumericKeyboard(m_device); + } +} + +Device::~Device() +{ + libinput_device_unref(m_device); +} + +} +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,7 @@ libinputtest.cpp ${KWIN_SOURCE_DIR}/libinput/context.cpp ${KWIN_SOURCE_DIR}/libinput/connection.cpp + ${KWIN_SOURCE_DIR}/libinput/device.cpp ${KWIN_SOURCE_DIR}/libinput/events.cpp ${KWIN_SOURCE_DIR}/libinput/libinput_logging.cpp ${KWIN_SOURCE_DIR}/logind.cpp