[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/D8748icc-effect-5.14.5
parent
965c30f4d4
commit
68698b4201
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue