diff --git a/autotests/libinput/CMakeLists.txt b/autotests/libinput/CMakeLists.txt --- a/autotests/libinput/CMakeLists.txt +++ b/autotests/libinput/CMakeLists.txt @@ -5,7 +5,7 @@ ######################################################## set( testLibinputDevice_SRCS device_test.cpp mock_libinput.cpp ../../libinput/device.cpp ) add_executable(testLibinputDevice ${testLibinputDevice_SRCS}) -target_link_libraries( testLibinputDevice Qt5::Test Qt5::DBus KF5::ConfigCore) +target_link_libraries( testLibinputDevice Qt5::Test Qt5::DBus Qt5::Gui KF5::ConfigCore) add_test(NAME kwin-testLibinputDevice COMMAND testLibinputDevice) ecm_mark_as_test(testLibinputDevice) diff --git a/autotests/libinput/device_test.cpp b/autotests/libinput/device_test.cpp --- a/autotests/libinput/device_test.cpp +++ b/autotests/libinput/device_test.cpp @@ -155,6 +155,10 @@ void testLoadLmrTapButtonMap(); void testLoadLeftHanded_data(); void testLoadLeftHanded(); + void testScreenId(); + void testOrientation_data(); + void testOrientation(); + void testCalibrationWithDefault(); }; void TestLibinputDevice::testStaticGetter() @@ -2095,5 +2099,66 @@ } } +void TestLibinputDevice::testScreenId() +{ + libinput_device device; + Device d(&device); + QCOMPARE(d.screenId(), 0); + d.setScreenId(1); + QCOMPARE(d.screenId(), 1); +} + +void TestLibinputDevice::testOrientation_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("m11"); + QTest::addColumn("m12"); + QTest::addColumn("m13"); + QTest::addColumn("m21"); + QTest::addColumn("m22"); + QTest::addColumn("m23"); + QTest::addColumn("defaultIsIdentity"); + + QTest::newRow("Primary") << Qt::PrimaryOrientation << 1.0f << 2.0f << 3.0f << 4.0f << 5.0f << 6.0f << false; + QTest::newRow("Landscape") << Qt::LandscapeOrientation << 1.0f << 2.0f << 3.0f << 4.0f << 5.0f << 6.0f << false; + QTest::newRow("Portrait") << Qt::PortraitOrientation << 0.0f << -1.0f << 1.0f << 1.0f << 0.0f << 0.0f << true; + QTest::newRow("InvertedLandscape") << Qt::InvertedLandscapeOrientation << -1.0f << 0.0f << 1.0f << 0.0f << -1.0f << 1.0f << true; + QTest::newRow("InvertedPortrait") << Qt::InvertedPortraitOrientation << 0.0f << 1.0f << 0.0f << -1.0f << 0.0f << 1.0f << true; +} + +void TestLibinputDevice::testOrientation() +{ + libinput_device device; + device.supportsCalibrationMatrix = true; + device.defaultCalibrationMatrix = std::array{{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}}; + QFETCH(bool, defaultIsIdentity); + device.defaultCalibrationMatrixIsIdentity = defaultIsIdentity; + Device d(&device); + QFETCH(Qt::ScreenOrientation, orientation); + d.setOrientation(orientation); + QTEST(device.calibrationMatrix[0], "m11"); + QTEST(device.calibrationMatrix[1], "m12"); + QTEST(device.calibrationMatrix[2], "m13"); + QTEST(device.calibrationMatrix[3], "m21"); + QTEST(device.calibrationMatrix[4], "m22"); + QTEST(device.calibrationMatrix[5], "m23"); +} + +void TestLibinputDevice::testCalibrationWithDefault() +{ + libinput_device device; + device.supportsCalibrationMatrix = true; + device.defaultCalibrationMatrix = std::array{{2.0, 3.0, 0.0, 4.0, 5.0, 0.0}}; + device.defaultCalibrationMatrixIsIdentity = false; + Device d(&device); + d.setOrientation(Qt::PortraitOrientation); + QCOMPARE(device.calibrationMatrix[0], 3.0f); + QCOMPARE(device.calibrationMatrix[1], -2.0f); + QCOMPARE(device.calibrationMatrix[2], 2.0f); + QCOMPARE(device.calibrationMatrix[3], 5.0f); + QCOMPARE(device.calibrationMatrix[4], -4.0f); + QCOMPARE(device.calibrationMatrix[5], 4.0f); +} + QTEST_GUILESS_MAIN(TestLibinputDevice) #include "device_test.moc" diff --git a/autotests/libinput/mock_libinput.h b/autotests/libinput/mock_libinput.h --- a/autotests/libinput/mock_libinput.h +++ b/autotests/libinput/mock_libinput.h @@ -26,6 +26,8 @@ #include #include +#include + struct libinput_device { bool keyboard = false; bool pointer = false; @@ -90,6 +92,11 @@ enum libinput_config_accel_profile defaultPointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; enum libinput_config_accel_profile pointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; bool setPointerAccelerationProfileReturnValue = 0; + std::array defaultCalibrationMatrix{{1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f}}; + std::array calibrationMatrix{{1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f}}; + bool defaultCalibrationMatrixIsIdentity = true; }; struct libinput_event { diff --git a/autotests/libinput/mock_libinput.cpp b/autotests/libinput/mock_libinput.cpp --- a/autotests/libinput/mock_libinput.cpp +++ b/autotests/libinput/mock_libinput.cpp @@ -194,6 +194,22 @@ return device->supportsCalibrationMatrix; } +enum libinput_config_status libinput_device_config_calibration_set_matrix(struct libinput_device *device, const float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + device->calibrationMatrix[i] = matrix[i]; + } + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +int libinput_device_config_calibration_get_default_matrix(struct libinput_device *device, float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + matrix[i] = device->defaultCalibrationMatrix[i]; + } + return device->defaultCalibrationMatrixIsIdentity ? 0 : 1; +} + int libinput_device_config_left_handed_is_available(struct libinput_device *device) { return device->supportsLeftHanded; diff --git a/input.cpp b/input.cpp --- a/input.cpp +++ b/input.cpp @@ -1861,11 +1861,13 @@ return; } m_libInput->setScreenSize(screens()->size()); + m_libInput->updateScreens(); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); + connect(screens(), &Screens::changed, m_libInput, &LibInput::Connection::updateScreens); #endif } diff --git a/libinput/connection.h b/libinput/connection.h --- a/libinput/connection.h +++ b/libinput/connection.h @@ -61,6 +61,8 @@ **/ void setScreenSize(const QSize &size); + void updateScreens(); + bool hasKeyboard() const { return m_keyboard > 0; } @@ -132,6 +134,7 @@ Connection(Context *input, QObject *parent = nullptr); void handleEvent(); void applyDeviceConfig(Device *device); + void applyScreenToDevice(Device *device); Context *m_input; QSocketNotifier *m_notifier; QSize m_size; diff --git a/libinput/connection.cpp b/libinput/connection.cpp --- a/libinput/connection.cpp +++ b/libinput/connection.cpp @@ -21,6 +21,9 @@ #include "context.h" #include "device.h" #include "events.h" +#ifndef KWIN_BUILD_TESTING +#include "../screens.h" +#endif #include "../logind.h" #include "../udev.h" #include "libinput_logging.h" @@ -277,6 +280,7 @@ } } applyDeviceConfig(device); + applyScreenToDevice(device); // enable possible leds libinput_device_led_update(device->device(), static_cast(toLibinputLEDS(m_leds))); @@ -389,19 +393,25 @@ break; } case LIBINPUT_EVENT_TOUCH_DOWN: { +#ifndef KWIN_BUILD_TESTING TouchEvent *te = static_cast(event.data()); - emit touchDown(te->id(), te->absolutePos(m_size), te->time(), te->device()); + const auto &geo = screens()->geometry(te->device()->screenId()); + emit touchDown(te->id(), geo.topLeft() + te->absolutePos(geo.size()), te->time(), te->device()); break; +#endif } case LIBINPUT_EVENT_TOUCH_UP: { TouchEvent *te = static_cast(event.data()); emit touchUp(te->id(), te->time(), te->device()); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { +#ifndef KWIN_BUILD_TESTING TouchEvent *te = static_cast(event.data()); - emit touchMotion(te->id(), te->absolutePos(m_size), te->time(), te->device()); + const auto &geo = screens()->geometry(te->device()->screenId()); + emit touchMotion(te->id(), geo.topLeft() + te->absolutePos(geo.size()), te->time(), te->device()); break; +#endif } case LIBINPUT_EVENT_TOUCH_CANCEL: { emit touchCanceled(event->device()); @@ -476,6 +486,79 @@ m_size = size; } +void Connection::updateScreens() +{ + QMutexLocker locker(&m_mutex); + for (auto device: qAsConst(m_devices)) { + applyScreenToDevice(device); + } +} + + +void Connection::applyScreenToDevice(Device *device) +{ +#ifndef KWIN_BUILD_TESTING + QMutexLocker locker(&m_mutex); + if (!device->isTouch()) { + return; + } + int id = -1; + // let's try to find a screen for it + if (screens()->count() == 1) { + id = 0; + } + if (id == -1 && !device->outputName().isEmpty()) { + // we have an output name, try to find a screen with matching name + for (int i = 0; i < screens()->count(); i++) { + if (screens()->name(i) == device->outputName()) { + id = i; + break; + } + } + } + if (id == -1) { + // do we have an internal screen? + int internalId = -1; + for (int i = 0; i < screens()->count(); i++) { + if (screens()->isInternal(i)) { + internalId = i; + break; + } + } + auto testScreenMatches = [device] (int id) { + const auto &size = device->size(); + const auto &screenSize = screens()->physicalSize(id); + return std::round(size.width()) == std::round(screenSize.width()) + && std::round(size.height()) == std::round(screenSize.height()); + }; + if (internalId != -1 && testScreenMatches(internalId)) { + id = internalId; + } + // let's compare all screens for size + for (int i = 0; i < screens()->count(); i++) { + if (testScreenMatches(i)) { + id = i; + break; + } + } + if (id == -1) { + // still not found + if (internalId != -1) { + // we have an internal id, so let's use that + id = internalId; + } else { + // just take first screen, we have no clue + id = 0; + } + } + } + device->setScreenId(id); + device->setOrientation(screens()->orientation(id)); +#else + Q_UNUSED(device) +#endif +} + bool Connection::isSuspended() const { if (!s_context) { diff --git a/libinput/device.h b/libinput/device.h --- a/libinput/device.h +++ b/libinput/device.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -392,6 +393,22 @@ m_config = config; } + /** + * The id of the screen in KWin identifiers. Set from KWin through @link setScreenId. + **/ + int screenId() const { + return m_screenId; + } + + /** + * Sets the KWin screen id for the device + **/ + void setScreenId(int screenId) { + m_screenId = screenId; + } + + void setOrientation(Qt::ScreenOrientation orientation); + /** * Loads the configuration and applies it to the Device **/ @@ -485,6 +502,10 @@ KConfigGroup m_config; bool m_loading = false; + int m_screenId = 0; + Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; + QMatrix4x4 m_defaultCalibrationMatrix; + static QVector s_devices; }; diff --git a/libinput/device.cpp b/libinput/device.cpp --- a/libinput/device.cpp +++ b/libinput/device.cpp @@ -132,6 +132,23 @@ {ConfigKey::ScrollButton, ConfigData(QByteArrayLiteral("ScrollButton"), &Device::setScrollButton, &Device::defaultScrollButton)} }; +namespace { +QMatrix4x4 defaultCalibrationMatrix(libinput_device *device) +{ + float matrix[6]; + const int ret = libinput_device_config_calibration_get_default_matrix(device, matrix); + if (ret == 0) { + return QMatrix4x4(); + } + return QMatrix4x4{ + matrix[0], matrix[1], matrix[2], 0.0f, + matrix[3], matrix[4], matrix[5], 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; +} +} + Device::Device(libinput_device *device, QObject *parent) : QObject(parent) , m_device(device) @@ -188,6 +205,7 @@ , m_pointerAccelerationProfile(libinput_device_config_accel_get_profile(m_device)) , m_enabled(m_supportsDisableEvents ? libinput_device_config_send_events_get_mode(m_device) == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : true) , m_config() + , m_defaultCalibrationMatrix(m_supportsCalibrationMatrix ? defaultCalibrationMatrix(m_device) : QMatrix4x4{}) { libinput_device_ref(m_device); @@ -424,5 +442,56 @@ #undef CONFIG +void Device::setOrientation(Qt::ScreenOrientation orientation) +{ + if (!m_supportsCalibrationMatrix) { + return; + } + // 90 deg cw: + static const QMatrix4x4 portraitMatrix{ + 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + // 180 deg cw: + static const QMatrix4x4 invertedLandscapeMatrix{ + -1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, -1.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + // 270 deg cw + static const QMatrix4x4 invertedPortraitMatrix{ + 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + QMatrix4x4 matrix; + switch (orientation) { + case Qt::PortraitOrientation: + matrix = portraitMatrix; + break; + case Qt::InvertedLandscapeOrientation: + matrix = invertedLandscapeMatrix; + break; + case Qt::InvertedPortraitOrientation: + matrix = invertedPortraitMatrix; + break; + case Qt::PrimaryOrientation: + case Qt::LandscapeOrientation: + default: + break; + } + const auto combined = m_defaultCalibrationMatrix * matrix; + const auto columnOrder = combined.constData(); + float m[6] = { + columnOrder[0], columnOrder[4], columnOrder[8], + columnOrder[1], columnOrder[5], columnOrder[9] + }; + libinput_device_config_calibration_set_matrix(m_device, m); +} + } } diff --git a/plugins/platforms/drm/drm_output.h b/plugins/platforms/drm/drm_output.h --- a/plugins/platforms/drm/drm_output.h +++ b/plugins/platforms/drm/drm_output.h @@ -127,6 +127,10 @@ return m_internal; } + Qt::ScreenOrientation orientation() const { + return m_orientation; + } + Q_SIGNALS: void dpmsChanged(); void modeChanged(); diff --git a/plugins/platforms/drm/screens_drm.h b/plugins/platforms/drm/screens_drm.h --- a/plugins/platforms/drm/screens_drm.h +++ b/plugins/platforms/drm/screens_drm.h @@ -43,6 +43,7 @@ QSizeF physicalSize(int screen) const override; bool isInternal(int screen) const override; bool supportsTransformations(int screen) const override; + Qt::ScreenOrientation orientation(int screen) const override; private: DrmBackend *m_backend; diff --git a/plugins/platforms/drm/screens_drm.cpp b/plugins/platforms/drm/screens_drm.cpp --- a/plugins/platforms/drm/screens_drm.cpp +++ b/plugins/platforms/drm/screens_drm.cpp @@ -140,4 +140,13 @@ return outputs.at(screen)->supportsTransformations(); } +Qt::ScreenOrientation DrmScreens::orientation(int screen) const +{ + const auto outputs = m_backend->outputs(); + if (screen >= outputs.size()) { + return Qt::PrimaryOrientation; + } + return outputs.at(screen)->orientation(); +} + } diff --git a/screens.h b/screens.h --- a/screens.h +++ b/screens.h @@ -137,6 +137,8 @@ **/ virtual bool supportsTransformations(int screen) const; + virtual Qt::ScreenOrientation orientation(int screen) const; + /** * Provides access to the OrientationSensor. The OrientationSensor is controlled by the * base implementation. The implementing subclass can use this to get notifications about diff --git a/screens.cpp b/screens.cpp --- a/screens.cpp +++ b/screens.cpp @@ -220,6 +220,12 @@ return false; } +Qt::ScreenOrientation Screens::orientation(int screen) const +{ + Q_UNUSED(screen) + return Qt::PrimaryOrientation; +} + BasicScreens::BasicScreens(Platform *backend, QObject *parent) : Screens(parent) , m_backend(backend)