[libinput] Add support for switch devices and events

Summary:
This change adds support for the switch devices introduces with libinput
1.7 (lid) and 1.9 (tablet mode). So far it's not yet used internally in
KWin, but only exposed through the Device and Events.

As KWin currently only requires libinput 1.5 and we are rather late in
the release cycle the new functionality is ifdef'ed. The requirement
will be raised once master is 5.13. It is already available on
build.kde.org, but e.g. Neon only has 1.6.

The switch events are interesting as they report whether the lid is
closed (might be interesting for e.g. powerdevil) and whether a
convertible is in tablet mode (supported for e.g. Lenovo Yogas with
recent kernel). This can be used by KWin internally to enable/disable
the virtual keyboard. And can be exposed globally to switch to Plasma
Mobile shell in future.

Test Plan:
Only through test case as my Lenovo Yoga uses Neon which has a
too old libinput

Reviewers: #kwin, #plasma

Subscribers: plasma-devel, kwin

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D9516
icc-effect-5.14.5
Martin Flöser 2017-12-27 17:57:00 +01:00
parent a252ff9656
commit ac2f41c86d
13 changed files with 348 additions and 8 deletions

View File

@ -185,8 +185,12 @@ set_package_properties(UDev PROPERTIES URL "http://www.freedesktop.org/software
PURPOSE "Required for input handling on Wayland."
)
set(HAVE_INPUT FALSE)
set(HAVE_INPUT_1_9 FALSE)
if (Libinput_FOUND AND UDEV_FOUND)
set(HAVE_INPUT TRUE)
if(Libinput_VERSION VERSION_GREATER_EQUAL "1.9.0")
set(HAVE_INPUT_1_9 TRUE)
endif()
endif()
set(HAVE_UDEV FALSE)
if (UDEV_FOUND)

View File

@ -65,6 +65,22 @@ target_link_libraries( testLibinputGestureEvent Qt5::Test Qt5::DBus Qt5::Widgets
add_test(NAME kwin-testLibinputGestureEvent COMMAND testLibinputGestureEvent)
ecm_mark_as_test(testLibinputGestureEvent)
########################################################
# Test Switch Event
########################################################
if(HAVE_INPUT_1_9)
set( testLibinputSwitchEvent_SRCS
switch_event_test.cpp
mock_libinput.cpp
../../libinput/device.cpp
../../libinput/events.cpp
)
add_executable(testLibinputSwitchEvent ${testLibinputSwitchEvent_SRCS})
target_link_libraries(testLibinputSwitchEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore)
add_test(NAME kwin-testLibinputSwitchEvent COMMAND testLibinputSwitchEvent)
ecm_mark_as_test(testLibinputSwitchEvent)
endif()
########################################################
# Test Context
########################################################

View File

@ -159,6 +159,8 @@ private Q_SLOTS:
void testOrientation_data();
void testOrientation();
void testCalibrationWithDefault();
void testSwitch_data();
void testSwitch();
};
void TestLibinputDevice::testStaticGetter()
@ -209,15 +211,19 @@ void TestLibinputDevice::testDeviceType_data()
QTest::addColumn<bool>("pointer");
QTest::addColumn<bool>("touch");
QTest::addColumn<bool>("tabletTool");
QTest::addColumn<bool>("switchDevice");
QTest::newRow("keyboard") << true << false << false << false;
QTest::newRow("pointer") << false << true << false << false;
QTest::newRow("touch") << false << false << true << false;
QTest::newRow("keyboard/pointer") << true << true << false << false;
QTest::newRow("keyboard/touch") << true << false << true << false;
QTest::newRow("pointer/touch") << false << true << true << false;
QTest::newRow("keyboard/pointer/touch") << true << true << true << false;
QTest::newRow("tabletTool") << false << false << false << true;
QTest::newRow("keyboard") << true << false << false << false << false;
QTest::newRow("pointer") << false << true << false << false << false;
QTest::newRow("touch") << false << false << true << false << false;
QTest::newRow("keyboard/pointer") << true << true << false << false << false;
QTest::newRow("keyboard/touch") << true << false << true << false << false;
QTest::newRow("pointer/touch") << false << true << true << false << false;
QTest::newRow("keyboard/pointer/touch") << true << true << true << false << false;
QTest::newRow("tabletTool") << false << false << false << true << false;
#if HAVE_INPUT_1_9
QTest::newRow("switch") << false << false << false << false << true;
#endif
}
void TestLibinputDevice::testDeviceType()
@ -227,12 +233,14 @@ void TestLibinputDevice::testDeviceType()
QFETCH(bool, pointer);
QFETCH(bool, touch);
QFETCH(bool, tabletTool);
QFETCH(bool, switchDevice);
libinput_device device;
device.keyboard = keyboard;
device.pointer = pointer;
device.touch = touch;
device.tabletTool = tabletTool;
device.switchDevice = switchDevice;
Device d(&device);
QCOMPARE(d.isKeyboard(), keyboard);
@ -245,6 +253,8 @@ void TestLibinputDevice::testDeviceType()
QCOMPARE(d.property("tabletPad").toBool(), false);
QCOMPARE(d.isTabletTool(), tabletTool);
QCOMPARE(d.property("tabletTool").toBool(), tabletTool);
QCOMPARE(d.isSwitch(), switchDevice);
QCOMPARE(d.property("switchDevice").toBool(), switchDevice);
QCOMPARE(d.device(), &device);
}
@ -2160,5 +2170,35 @@ void TestLibinputDevice::testCalibrationWithDefault()
QCOMPARE(device.calibrationMatrix[5], 4.0f);
}
void TestLibinputDevice::testSwitch_data()
{
QTest::addColumn<bool>("lid");
QTest::addColumn<bool>("tablet");
QTest::newRow("lid") << true << false;
QTest::newRow("tablet") << false << true;
}
void TestLibinputDevice::testSwitch()
{
#if HAVE_INPUT_1_9
libinput_device device;
device.switchDevice = true;
QFETCH(bool, lid);
QFETCH(bool, tablet);
device.lidSwitch = lid;
device.tabletModeSwitch = tablet;
Device d(&device);
QCOMPARE(d.isSwitch(), true);
QCOMPARE(d.isLidSwitch(), lid);
QCOMPARE(d.property("lidSwitch").toBool(), lid);
QCOMPARE(d.isTabletModeSwitch(), tablet);
QCOMPARE(d.property("tabletModeSwitch").toBool(), tablet);
#else
QSKIP("Requires libinput 1.9");
#endif
}
QTEST_GUILESS_MAIN(TestLibinputDevice)
#include "device_test.moc"

View File

@ -41,6 +41,10 @@ int libinput_device_has_capability(struct libinput_device *device, enum libinput
return device->gestureSupported;
case LIBINPUT_DEVICE_CAP_TABLET_TOOL:
return device->tabletTool;
#if HAVE_INPUT_1_9
case LIBINPUT_DEVICE_CAP_SWITCH:
return device->switchDevice;
#endif
default:
return 0;
}
@ -810,3 +814,51 @@ uint32_t libinput_device_config_scroll_get_default_button(struct libinput_device
{
return device->defaultScrollButton;
}
#if HAVE_INPUT_1_9
int libinput_device_switch_has_switch(struct libinput_device *device, enum libinput_switch sw)
{
switch (sw) {
case LIBINPUT_SWITCH_LID:
return device->lidSwitch;
case LIBINPUT_SWITCH_TABLET_MODE:
return device->tabletModeSwitch;
default:
Q_UNREACHABLE();
}
return 0;
}
struct libinput_event_switch *libinput_event_get_switch_event(struct libinput_event *event)
{
if (event->type == LIBINPUT_EVENT_SWITCH_TOGGLE) {
return reinterpret_cast<libinput_event_switch*>(event);
} else {
return nullptr;
}
}
enum libinput_switch_state libinput_event_switch_get_switch_state(struct libinput_event_switch *event)
{
switch (event->state) {
case libinput_event_switch::State::On:
return LIBINPUT_SWITCH_STATE_ON;
case libinput_event_switch::State::Off:
return LIBINPUT_SWITCH_STATE_OFF;
default:
Q_UNREACHABLE();
}
}
uint32_t libinput_event_switch_get_time(struct libinput_event_switch *event)
{
return event->time;;
}
uint64_t libinput_event_switch_get_time_usec(struct libinput_event_switch *event)
{
return event->timeMicroseconds;
}
#endif

View File

@ -34,6 +34,7 @@ struct libinput_device {
bool touch = false;
bool tabletTool = false;
bool gestureSupported = false;
bool switchDevice = false;
QByteArray name;
QByteArray sysName = QByteArrayLiteral("event0");
QByteArray outputName;
@ -97,6 +98,9 @@ struct libinput_device {
std::array<float, 6> calibrationMatrix{{1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f}};
bool defaultCalibrationMatrixIsIdentity = true;
bool lidSwitch = false;
bool tabletModeSwitch = false;
};
struct libinput_event {
@ -137,6 +141,15 @@ struct libinput_event_gesture : libinput_event {
qreal angleDelta = 0.0;
};
struct libinput_event_switch : libinput_event {
enum class State {
Off,
On
};
State state = State::Off;
quint64 timeMicroseconds = 0;
};
struct libinput {
int refCount = 1;
QByteArray seat;

View File

@ -0,0 +1,99 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2017 Martin Flöser <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "mock_libinput.h"
#include "../../libinput/device.h"
#include "../../libinput/events.h"
#include <QtTest/QtTest>
#include <memory>
Q_DECLARE_METATYPE(KWin::LibInput::SwitchEvent::State)
using namespace KWin::LibInput;
class TestLibinputSwitchEvent : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void cleanup();
void testToggled_data();
void testToggled();
private:
std::unique_ptr<libinput_device> m_nativeDevice;
std::unique_ptr<Device> m_device;
};
void TestLibinputSwitchEvent::init()
{
m_nativeDevice = std::make_unique<libinput_device>();
m_nativeDevice->switchDevice = true;
m_device = std::make_unique<Device>(m_nativeDevice.get());
}
void TestLibinputSwitchEvent::cleanup()
{
m_device.reset();
m_nativeDevice.reset();
}
void TestLibinputSwitchEvent::testToggled_data()
{
QTest::addColumn<KWin::LibInput::SwitchEvent::State>("state");
QTest::newRow("on") << KWin::LibInput::SwitchEvent::State::On;
QTest::newRow("off") << KWin::LibInput::SwitchEvent::State::Off;
}
void TestLibinputSwitchEvent::testToggled()
{
libinput_event_switch *nativeEvent = new libinput_event_switch;
nativeEvent->type = LIBINPUT_EVENT_SWITCH_TOGGLE;
nativeEvent->device = m_nativeDevice.get();
QFETCH(KWin::LibInput::SwitchEvent::State, state);
switch (state) {
case SwitchEvent::State::Off:
nativeEvent->state = libinput_event_switch::State::Off;
break;
case SwitchEvent::State::On:
nativeEvent->state = libinput_event_switch::State::On;
break;
default:
Q_UNREACHABLE();
}
nativeEvent->time = 23;
nativeEvent->timeMicroseconds = 23456789;
QScopedPointer<Event> event(Event::create(nativeEvent));
auto se = dynamic_cast<SwitchEvent*>(event.data());
QVERIFY(se);
QCOMPARE(se->device(), m_device.get());
QCOMPARE(se->nativeDevice(), m_nativeDevice.get());
QCOMPARE(se->type(), LIBINPUT_EVENT_SWITCH_TOGGLE);
QCOMPARE(se->state(), state);
QCOMPARE(se->time(), 23u);
QCOMPARE(se->timeMicroseconds(), 23456789u);
}
QTEST_GUILESS_MAIN(TestLibinputSwitchEvent)
#include "switch_event_test.moc"

View File

@ -10,6 +10,7 @@
#define KWIN_RULES_DIALOG_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kwin_rules_dialog"
#define KWIN_XCLIPBOARD_SYNC_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/org_kde_kwin_xclipboard_syncer"
#cmakedefine01 HAVE_INPUT
#cmakedefine01 HAVE_INPUT_1_9
#cmakedefine01 HAVE_X11_XCB
#cmakedefine01 HAVE_X11_XINPUT
#cmakedefine01 HAVE_DRM

View File

@ -460,6 +460,22 @@ void Connection::processEvents()
}
break;
}
#if HAVE_INPUT_1_9
case LIBINPUT_EVENT_SWITCH_TOGGLE: {
SwitchEvent *se = static_cast<SwitchEvent*>(event.data());
switch (se->state()) {
case SwitchEvent::State::Off:
emit switchToggledOff(se->time(), se->timeMicroseconds(), se->device());
break;
case SwitchEvent::State::On:
emit switchToggledOn(se->time(), se->timeMicroseconds(), se->device());
break;
default:
Q_UNREACHABLE();
}
break;
}
#endif
default:
// nothing
break;

View File

@ -123,6 +123,8 @@ Q_SIGNALS:
void pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device);
void pinchGestureEnd(quint32 time, KWin::LibInput::Device *device);
void pinchGestureCancelled(quint32 time, KWin::LibInput::Device *device);
void switchToggledOn(quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device);
void switchToggledOff(quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device);
void eventsRead();

View File

@ -163,6 +163,11 @@ Device::Device(libinput_device *device, QObject *parent)
, m_tabletPad(false)
#endif
, m_supportsGesture(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_GESTURE))
#if HAVE_INPUT_1_9
, m_switch(libinput_device_has_capability(m_device, LIBINPUT_DEVICE_CAP_SWITCH))
, m_lidSwitch(m_switch ? libinput_device_switch_has_switch(m_device, LIBINPUT_SWITCH_LID) : false)
, m_tabletSwitch(m_switch ? libinput_device_switch_has_switch(m_device, LIBINPUT_SWITCH_TABLET_MODE) : false)
#endif
, 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)))

View File

@ -124,6 +124,11 @@ class Device : public QObject
Q_PROPERTY(bool scrollOnButtonDown READ isScrollOnButtonDown WRITE setScrollOnButtonDown NOTIFY scrollMethodChanged)
Q_PROPERTY(quint32 scrollButton READ scrollButton WRITE setScrollButton NOTIFY scrollButtonChanged)
// switches
Q_PROPERTY(bool switchDevice READ isSwitch CONSTANT)
Q_PROPERTY(bool lidSwitch READ isLidSwitch CONSTANT)
Q_PROPERTY(bool tabletModeSwitch READ isTabletModeSwitch CONSTANT)
public:
explicit Device(libinput_device *device, QObject *parent = nullptr);
@ -414,6 +419,18 @@ public:
**/
void loadConfiguration();
bool isSwitch() const {
return m_switch;
}
bool isLidSwitch() const {
return m_lidSwitch;
}
bool isTabletModeSwitch() const {
return m_tabletSwitch;
}
/**
* All created Devices
**/
@ -453,6 +470,9 @@ private:
bool m_tabletTool;
bool m_tabletPad;
bool m_supportsGesture;
bool m_switch = false;
bool m_lidSwitch = false;
bool m_tabletSwitch = false;
QString m_name;
QString m_sysName;
QString m_outputName;

View File

@ -57,6 +57,10 @@ Event *Event::create(libinput_event *event)
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
case LIBINPUT_EVENT_GESTURE_PINCH_END:
return new PinchGestureEvent(event, t);
#if HAVE_INPUT_1_9
case LIBINPUT_EVENT_SWITCH_TOGGLE:
return new SwitchEvent(event, t);
#endif
default:
return new Event(event, t);
}
@ -284,5 +288,49 @@ SwipeGestureEvent::SwipeGestureEvent(libinput_event *event, libinput_event_type
SwipeGestureEvent::~SwipeGestureEvent() = default;
SwitchEvent::SwitchEvent(libinput_event *event, libinput_event_type type)
: Event(event, type)
#if HAVE_INPUT_1_9
, m_switchEvent(libinput_event_get_switch_event(event))
#else
, m_switchEvent(nullptr)
#endif
{
}
SwitchEvent::~SwitchEvent() = default;
SwitchEvent::State SwitchEvent::state() const
{
#if HAVE_INPUT_1_9
switch (libinput_event_switch_get_switch_state(m_switchEvent))
{
case LIBINPUT_SWITCH_STATE_OFF:
return State::Off;
case LIBINPUT_SWITCH_STATE_ON:
return State::On;
default:
Q_UNREACHABLE();
}
#endif
return State::Off;
}
quint32 SwitchEvent::time() const
{
#if HAVE_INPUT_1_9
return libinput_event_switch_get_time(m_switchEvent);
#endif
return 0;
}
quint64 SwitchEvent::timeMicroseconds() const
{
#if HAVE_INPUT_1_9
return libinput_event_switch_get_time_usec(m_switchEvent);
#endif
return 0;
}
}
}

View File

@ -24,6 +24,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <libinput.h>
#include <config-kwin.h>
#if !(HAVE_INPUT_1_9)
struct libinput_event_switch;
#endif
namespace KWin
{
namespace LibInput
@ -172,6 +177,25 @@ public:
virtual ~SwipeGestureEvent();
};
class SwitchEvent : public Event
{
public:
SwitchEvent(libinput_event *event, libinput_event_type type);
~SwitchEvent() override;
enum class State {
Off,
On
};
State state() const;
quint32 time() const;
quint64 timeMicroseconds() const;
private:
libinput_event_switch *m_switchEvent;
};
inline
libinput_event_type Event::type() const
{