Commit 68698b42 authored by Martin Flöser's avatar Martin Flöser

[libinput] Find screen for touch screen devices and calibrate accordingly

Summary:
This change finds the screen for a touch screen device based on:
 * number available screens
 * output name defined on the touch screen device
 * internal screen
 * and physical size

The id of the screen is stored in the Device allowing to adjust the
touch points accordingly. This means instead of transferring to the
combined display size the touch points are transferred into the output
space and the position of the output is added. Thus in a multi screen
system the touch points are properly mapped to the output.

Furthermore the screen orientation is passed to the Device and a
calibration matrix is set accordingly. Thus a transformed screen has the
touch screen transformed accordingly.

Please note that this only affects libinput on Wayland and not on X11!
The x11 standalone platform needs to gain similar code.

Reviewers: #kwin, #plasma

Subscribers: plasma-devel, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D8748
parent 965c30f4
......@@ -5,7 +5,7 @@ include_directories(${UDEV_INCLUDE_DIR})
########################################################
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)
......
......@@ -155,6 +155,10 @@ private Q_SLOTS:
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::testLoadLmrTapButtonMap()
}
}
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<Qt::ScreenOrientation>("orientation");
QTest::addColumn<float>("m11");
QTest::addColumn<float>("m12");
QTest::addColumn<float>("m13");
QTest::addColumn<float>("m21");
QTest::addColumn<float>("m22");
QTest::addColumn<float>("m23");
QTest::addColumn<bool>("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<float, 6>{{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<float, 6>{{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"
......@@ -194,6 +194,22 @@ int libinput_device_config_calibration_has_matrix(struct libinput_device *device
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;
......
......@@ -26,6 +26,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QSizeF>
#include <QVector>
#include <array>
struct libinput_device {
bool keyboard = false;
bool pointer = false;
......@@ -90,6 +92,11 @@ struct libinput_device {
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<float, 6> defaultCalibrationMatrix{{1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f}};
std::array<float, 6> calibrationMatrix{{1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f}};
bool defaultCalibrationMatrixIsIdentity = true;
};
struct libinput_event {
......
......@@ -1861,11 +1861,13 @@ void InputRedirection::setupLibInputWithScreens()
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
}
......
......@@ -21,6 +21,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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 @@ void Connection::processEvents()
}
}
applyDeviceConfig(device);
applyScreenToDevice(device);
// enable possible leds
libinput_device_led_update(device->device(), static_cast<libinput_led>(toLibinputLEDS(m_leds)));
......@@ -389,9 +393,12 @@ void Connection::processEvents()
break;
}
case LIBINPUT_EVENT_TOUCH_DOWN: {
#ifndef KWIN_BUILD_TESTING
TouchEvent *te = static_cast<TouchEvent*>(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<TouchEvent*>(event.data());
......@@ -399,9 +406,12 @@ void Connection::processEvents()
break;
}
case LIBINPUT_EVENT_TOUCH_MOTION: {
#ifndef KWIN_BUILD_TESTING
TouchEvent *te = static_cast<TouchEvent*>(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 @@ void Connection::setScreenSize(const QSize &size)
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) {
......
......@@ -61,6 +61,8 @@ public:
**/
void setScreenSize(const QSize &size);
void updateScreens();
bool hasKeyboard() const {
return m_keyboard > 0;
}
......@@ -132,6 +134,7 @@ private:
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;
......
......@@ -132,6 +132,23 @@ static const QMap<ConfigKey, ConfigData> s_configData {
{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 @@ Device::Device(libinput_device *device, QObject *parent)
, 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 @@ CONFIG(setMiddleEmulation, m_supportsMiddleEmulation == false, middle_emulation_
#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);
}
}
}
......@@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KConfigGroup>
#include <QObject>
#include <QMatrix4x4>
#include <QSizeF>
#include <QVector>
......@@ -392,6 +393,22 @@ public:
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 @@ private:
KConfigGroup m_config;
bool m_loading = false;
int m_screenId = 0;
Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation;
QMatrix4x4 m_defaultCalibrationMatrix;
static QVector<Device*> s_devices;
};
......
......@@ -127,6 +127,10 @@ public:
return m_internal;
}
Qt::ScreenOrientation orientation() const {
return m_orientation;
}
Q_SIGNALS:
void dpmsChanged();
void modeChanged();
......
......@@ -140,4 +140,13 @@ bool DrmScreens::supportsTransformations(int screen) const
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();
}
}
......@@ -43,6 +43,7 @@ public:
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;
......
......@@ -220,6 +220,12 @@ bool Screens::supportsTransformations(int screen) const
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)
......
......@@ -137,6 +137,8 @@ public:
**/
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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment