[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
icc-effect-5.14.5
Martin Flöser 2017-11-10 18:07:15 +01:00
parent 965c30f4d4
commit 68698b4201
14 changed files with 291 additions and 3 deletions

View File

@ -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)

View File

@ -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"

View File

@ -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;

View File

@ -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 {

View File

@ -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
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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;
};

View File

@ -127,6 +127,10 @@ public:
return m_internal;
}
Qt::ScreenOrientation orientation() const {
return m_orientation;
}
Q_SIGNALS:
void dpmsChanged();
void modeChanged();

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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