diff --git a/autotests/libinput/input_event_test.cpp b/autotests/libinput/input_event_test.cpp index 2b517877c..f588016f9 100644 --- a/autotests/libinput/input_event_test.cpp +++ b/autotests/libinput/input_event_test.cpp @@ -1,150 +1,153 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "mock_libinput.h" #include "../../libinput/device.h" #include "../input_event.h" #include using namespace KWin; using namespace KWin::LibInput; class InputEventsTest : public QObject { Q_OBJECT private Q_SLOTS: void testInitMouseEvent_data(); void testInitMouseEvent(); void testInitKeyEvent_data(); void testInitKeyEvent(); void testInitWheelEvent_data(); void testInitWheelEvent(); }; void InputEventsTest::testInitMouseEvent_data() { QTest::addColumn("type"); QTest::newRow("Press") << QEvent::MouseButtonPress; QTest::newRow("Release") << QEvent::MouseButtonRelease; QTest::newRow("Move") << QEvent::MouseMove; } void InputEventsTest::testInitMouseEvent() { // this test verifies that a MouseEvent is constructed correctly // first create the test LibInput::Device libinput_device device; Device d(&device); QFETCH(QEvent::Type, type); // now create our own event MouseEvent event(type, QPointF(100, 200), Qt::LeftButton, Qt::LeftButton | Qt::RightButton, - Qt::ShiftModifier | Qt::ControlModifier, 300, &d); + Qt::ShiftModifier | Qt::ControlModifier, 300, QSizeF(1, 2), QSizeF(3, 4), quint64(-1), &d); // and verify the contract of QMouseEvent QCOMPARE(event.type(), type); QCOMPARE(event.globalPos(), QPoint(100, 200)); QCOMPARE(event.screenPos(), QPointF(100, 200)); QCOMPARE(event.localPos(), QPointF(100, 200)); QCOMPARE(event.button(), Qt::LeftButton); QCOMPARE(event.buttons(), Qt::LeftButton | Qt::RightButton); QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); QCOMPARE(event.timestamp(), 300ul); // and our custom argument QCOMPARE(event.device(), &d); + QCOMPARE(event.delta(), QSizeF(1, 2)); + QCOMPARE(event.deltaUnaccelerated(), QSizeF(3, 4)); + QCOMPARE(event.timestampMicroseconds(), quint64(-1)); } void InputEventsTest::testInitKeyEvent_data() { QTest::addColumn("type"); QTest::addColumn("autorepeat"); QTest::newRow("Press") << QEvent::KeyPress << false; QTest::newRow("Repeat") << QEvent::KeyPress << true; QTest::newRow("Release") << QEvent::KeyRelease << false; } void InputEventsTest::testInitKeyEvent() { // this test verifies that a KeyEvent is constructed correctly // first create the test LibInput::Device libinput_device device; Device d(&device); // setup event QFETCH(QEvent::Type, type); QFETCH(bool, autorepeat); KeyEvent event(type, Qt::Key_Space, Qt::ShiftModifier | Qt::ControlModifier, 200, 300, QStringLiteral(" "), autorepeat, 400, &d); // and verify the contract of QKeyEvent QCOMPARE(event.type(), type); QCOMPARE(event.isAutoRepeat(), autorepeat); QCOMPARE(event.key(), int(Qt::Key_Space)); QCOMPARE(event.nativeScanCode(), 200u); QCOMPARE(event.nativeVirtualKey(), 300u); QCOMPARE(event.text(), QStringLiteral(" ")); QCOMPARE(event.count(), 1); QCOMPARE(event.nativeModifiers(), 0u); QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); QCOMPARE(event.timestamp(), 400ul); // and our custom argument QCOMPARE(event.device(), &d); } void InputEventsTest::testInitWheelEvent_data() { QTest::addColumn("orientation"); QTest::addColumn("delta"); QTest::addColumn("expectedAngleDelta"); QTest::newRow("horiz") << Qt::Horizontal << 3.0 << QPoint(3, 0); QTest::newRow("vert") << Qt::Vertical << 2.0 << QPoint(0, 2); } void InputEventsTest::testInitWheelEvent() { // this test verifies that a WheelEvent is constructed correctly // first create the test LibInput::Device libinput_device device; Device d(&device); // setup event QFETCH(Qt::Orientation, orientation); QFETCH(qreal, delta); WheelEvent event(QPointF(100, 200), delta, orientation, Qt::LeftButton | Qt::RightButton, Qt::ShiftModifier | Qt::ControlModifier, 300, &d); // compare QWheelEvent contract QCOMPARE(event.type(), QEvent::Wheel); QCOMPARE(event.posF(), QPointF(100, 200)); QCOMPARE(event.globalPosF(), QPointF(100, 200)); QCOMPARE(event.buttons(), Qt::LeftButton | Qt::RightButton); QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); QCOMPARE(event.timestamp(), 300ul); QTEST(event.angleDelta(), "expectedAngleDelta"); // and our custom argument QCOMPARE(event.device(), &d); } QTEST_GUILESS_MAIN(InputEventsTest) #include "input_event_test.moc" diff --git a/autotests/libinput/mock_libinput.cpp b/autotests/libinput/mock_libinput.cpp index 86d3fc153..271081b1d 100644 --- a/autotests/libinput/mock_libinput.cpp +++ b/autotests/libinput/mock_libinput.cpp @@ -1,589 +1,604 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include #include "mock_libinput.h" #include #include int libinput_device_keyboard_has_key(struct libinput_device *device, uint32_t code) { return device->keys.contains(code); } int libinput_device_has_capability(struct libinput_device *device, enum libinput_device_capability capability) { switch (capability) { case LIBINPUT_DEVICE_CAP_KEYBOARD: return device->keyboard; case LIBINPUT_DEVICE_CAP_POINTER: return device->pointer; case LIBINPUT_DEVICE_CAP_TOUCH: return device->touch; case LIBINPUT_DEVICE_CAP_GESTURE: return device->gestureSupported; case LIBINPUT_DEVICE_CAP_TABLET_TOOL: return device->tabletTool; default: return 0; } } const char *libinput_device_get_name(struct libinput_device *device) { return device->name.constData(); } const char *libinput_device_get_sysname(struct libinput_device *device) { return device->sysName.constData(); } const char *libinput_device_get_output_name(struct libinput_device *device) { return device->outputName.constData(); } unsigned int libinput_device_get_id_product(struct libinput_device *device) { return device->product; } unsigned int libinput_device_get_id_vendor(struct libinput_device *device) { return device->vendor; } int libinput_device_config_tap_get_finger_count(struct libinput_device *device) { return device->tapFingerCount; } enum libinput_config_tap_state libinput_device_config_tap_get_enabled(struct libinput_device *device) { if (device->tapToClick) { return LIBINPUT_CONFIG_TAP_ENABLED; } else { return LIBINPUT_CONFIG_TAP_DISABLED; } } enum libinput_config_status libinput_device_config_tap_set_enabled(struct libinput_device *device, enum libinput_config_tap_state enable) { if (device->setTapToClickReturnValue == 0) { device->tapToClick = (enable == LIBINPUT_CONFIG_TAP_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_tap_state libinput_device_config_tap_get_default_enabled(struct libinput_device *device) { if (device->tapEnabledByDefault) { return LIBINPUT_CONFIG_TAP_ENABLED; } else { return LIBINPUT_CONFIG_TAP_DISABLED; } } enum libinput_config_drag_state libinput_device_config_tap_get_default_drag_enabled(struct libinput_device *device) { if (device->tapAndDragEnabledByDefault) { return LIBINPUT_CONFIG_DRAG_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_DISABLED; } } enum libinput_config_drag_state libinput_device_config_tap_get_drag_enabled(struct libinput_device *device) { if (device->tapAndDrag) { return LIBINPUT_CONFIG_DRAG_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_DISABLED; } } enum libinput_config_status libinput_device_config_tap_set_drag_enabled(struct libinput_device *device, enum libinput_config_drag_state enable) { if (device->setTapAndDragReturnValue == 0) { device->tapAndDrag = (enable == LIBINPUT_CONFIG_DRAG_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_drag_lock_state libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device *device) { if (device->tapDragLockEnabledByDefault) { return LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } } enum libinput_config_drag_lock_state libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device) { if (device->tapDragLock) { return LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; } else { return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; } } enum libinput_config_status libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device, enum libinput_config_drag_lock_state enable) { if (device->setTapDragLockReturnValue == 0) { device->tapDragLock = (enable == LIBINPUT_CONFIG_DRAG_LOCK_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } int libinput_device_config_dwt_is_available(struct libinput_device *device) { return device->supportsDisableWhileTyping; } int libinput_device_config_accel_is_available(struct libinput_device *device) { return device->supportsPointerAcceleration; } int libinput_device_config_calibration_has_matrix(struct libinput_device *device) { return device->supportsCalibrationMatrix; } int libinput_device_config_left_handed_is_available(struct libinput_device *device) { return device->supportsLeftHanded; } uint32_t libinput_device_config_send_events_get_modes(struct libinput_device *device) { uint32_t modes = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; if (device->supportsDisableEvents) { modes |= LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } if (device->supportsDisableEventsOnExternalMouse) { modes |= LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; } return modes; } int libinput_device_config_left_handed_get(struct libinput_device *device) { return device->leftHanded; } double libinput_device_config_accel_get_speed(struct libinput_device *device) { return device->pointerAcceleration; } uint32_t libinput_device_config_send_events_get_mode(struct libinput_device *device) { if (device->enabled) { return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; } else { // TODO: disabled on eternal mouse return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } } struct libinput_device *libinput_device_ref(struct libinput_device *device) { return device; } struct libinput_device *libinput_device_unref(struct libinput_device *device) { return device; } int libinput_device_get_size(struct libinput_device *device, double *width, double *height) { if (device->deviceSizeReturnValue) { return device->deviceSizeReturnValue; } if (width) { *width = device->deviceSize.width(); } if (height) { *height = device->deviceSize.height(); } return device->deviceSizeReturnValue; } int libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code) { switch (code) { case BTN_LEFT: return device->supportedButtons.testFlag(Qt::LeftButton); case BTN_MIDDLE: return device->supportedButtons.testFlag(Qt::MiddleButton); case BTN_RIGHT: return device->supportedButtons.testFlag(Qt::RightButton); case BTN_SIDE: return device->supportedButtons.testFlag(Qt::ExtraButton1); case BTN_EXTRA: return device->supportedButtons.testFlag(Qt::ExtraButton2); case BTN_BACK: return device->supportedButtons.testFlag(Qt::BackButton); case BTN_FORWARD: return device->supportedButtons.testFlag(Qt::ForwardButton); case BTN_TASK: return device->supportedButtons.testFlag(Qt::TaskButton); default: return 0; } } enum libinput_config_status libinput_device_config_left_handed_set(struct libinput_device *device, int left_handed) { if (device->setLeftHandedReturnValue == 0) { device->leftHanded = left_handed; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_status libinput_device_config_accel_set_speed(struct libinput_device *device, double speed) { if (device->setPointerAccelerationReturnValue == 0) { device->pointerAcceleration = speed; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_status libinput_device_config_send_events_set_mode(struct libinput_device *device, uint32_t mode) { if (device->setEnableModeReturnValue == 0) { device->enabled = (mode == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_event_type libinput_event_get_type(struct libinput_event *event) { return event->type; } struct libinput_device *libinput_event_get_device(struct libinput_event *event) { return event->device; } void libinput_event_destroy(struct libinput_event *event) { delete event; } struct libinput_event_keyboard *libinput_event_get_keyboard_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_KEYBOARD_KEY) { return reinterpret_cast(event); } return nullptr; } struct libinput_event_pointer *libinput_event_get_pointer_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_POINTER_MOTION || event->type == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE || event->type == LIBINPUT_EVENT_POINTER_BUTTON || event->type == LIBINPUT_EVENT_POINTER_AXIS) { return reinterpret_cast(event); } return nullptr; } struct libinput_event_touch *libinput_event_get_touch_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_TOUCH_DOWN || event->type == LIBINPUT_EVENT_TOUCH_UP || event->type == LIBINPUT_EVENT_TOUCH_MOTION || event->type == LIBINPUT_EVENT_TOUCH_CANCEL || event->type == LIBINPUT_EVENT_TOUCH_FRAME) { return reinterpret_cast(event); } return nullptr; } struct libinput_event_gesture *libinput_event_get_gesture_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_BEGIN || event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_PINCH_END || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_END) { return reinterpret_cast(event); } return nullptr; } int libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_END || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_END) { return event->cancelled; } return 0; } uint32_t libinput_event_gesture_get_time(struct libinput_event_gesture *event) { return event->time; } int libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event) { return event->fingerCount; } double libinput_event_gesture_get_dx(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE) { return event->delta.width(); } return 0.0; } double libinput_event_gesture_get_dy(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE) { return event->delta.height(); } return 0.0; } double libinput_event_gesture_get_scale(struct libinput_event_gesture *event) { switch (event->type) { case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: return 1.0; case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: case LIBINPUT_EVENT_GESTURE_PINCH_END: return event->scale; default: return 0.0; } } double libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event) { if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE) { return event->angleDelta; } return 0.0; } uint32_t libinput_event_keyboard_get_key(struct libinput_event_keyboard *event) { return event->key; } enum libinput_key_state libinput_event_keyboard_get_key_state(struct libinput_event_keyboard *event) { return event->state; } uint32_t libinput_event_keyboard_get_time(struct libinput_event_keyboard *event) { return event->time; } double libinput_event_pointer_get_absolute_x(struct libinput_event_pointer *event) { return event->absolutePos.x(); } double libinput_event_pointer_get_absolute_y(struct libinput_event_pointer *event) { return event->absolutePos.y(); } double libinput_event_pointer_get_absolute_x_transformed(struct libinput_event_pointer *event, uint32_t width) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.x() / deviceWidth * width; } double libinput_event_pointer_get_absolute_y_transformed(struct libinput_event_pointer *event, uint32_t height) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.y() / deviceHeight * height; } double libinput_event_pointer_get_dx(struct libinput_event_pointer *event) { return event->delta.width(); } double libinput_event_pointer_get_dy(struct libinput_event_pointer *event) { return event->delta.height(); } +double libinput_event_pointer_get_dx_unaccelerated(struct libinput_event_pointer *event) +{ + return event->delta.width(); +} + +double libinput_event_pointer_get_dy_unaccelerated(struct libinput_event_pointer *event) +{ + return event->delta.height(); +} + uint32_t libinput_event_pointer_get_time(struct libinput_event_pointer *event) { return event->time; } +uint64_t libinput_event_pointer_get_time_usec(struct libinput_event_pointer *event) +{ + return quint64(event->time * 1000); +} + uint32_t libinput_event_pointer_get_button(struct libinput_event_pointer *event) { return event->button; } enum libinput_button_state libinput_event_pointer_get_button_state(struct libinput_event_pointer *event) { return event->buttonState; } int libinput_event_pointer_has_axis(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) { if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { return event->verticalAxis; } else { return event->horizontalAxis; } } double libinput_event_pointer_get_axis_value(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) { if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { return event->verticalAxisValue; } else { return event->horizontalAxisValue; } } uint32_t libinput_event_touch_get_time(struct libinput_event_touch *event) { return event->time; } double libinput_event_touch_get_x(struct libinput_event_touch *event) { return event->absolutePos.x(); } double libinput_event_touch_get_y(struct libinput_event_touch *event) { return event->absolutePos.y(); } double libinput_event_touch_get_x_transformed(struct libinput_event_touch *event, uint32_t width) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.x() / deviceWidth * width; } double libinput_event_touch_get_y_transformed(struct libinput_event_touch *event, uint32_t height) { double deviceWidth = 0.0; double deviceHeight = 0.0; libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); return event->absolutePos.y() / deviceHeight * height; } int32_t libinput_event_touch_get_slot(struct libinput_event_touch *event) { return event->slot; } struct libinput *libinput_udev_create_context(const struct libinput_interface *interface, void *user_data, struct udev *udev) { if (!udev) { return nullptr; } Q_UNUSED(interface) Q_UNUSED(user_data) return new libinput; } void libinput_log_set_priority(struct libinput *libinput, enum libinput_log_priority priority) { Q_UNUSED(libinput) Q_UNUSED(priority) } void libinput_log_set_handler(struct libinput *libinput, libinput_log_handler log_handler) { Q_UNUSED(libinput) Q_UNUSED(log_handler) } struct libinput *libinput_unref(struct libinput *libinput) { libinput->refCount--; if (libinput->refCount == 0) { delete libinput; return nullptr; } return libinput; } int libinput_udev_assign_seat(struct libinput *libinput, const char *seat_id) { if (libinput->assignSeatRetVal == 0) { libinput->seat = QByteArray(seat_id); } return libinput->assignSeatRetVal; } int libinput_get_fd(struct libinput *libinput) { Q_UNUSED(libinput) return -1; } int libinput_dispatch(struct libinput *libinput) { Q_UNUSED(libinput) return 0; } struct libinput_event *libinput_get_event(struct libinput *libinput) { Q_UNUSED(libinput) return nullptr; } void libinput_suspend(struct libinput *libinput) { Q_UNUSED(libinput) } int libinput_resume(struct libinput *libinput) { Q_UNUSED(libinput) return 0; } diff --git a/autotests/libinput/pointer_event_test.cpp b/autotests/libinput/pointer_event_test.cpp index b86441da4..03af54da0 100644 --- a/autotests/libinput/pointer_event_test.cpp +++ b/autotests/libinput/pointer_event_test.cpp @@ -1,208 +1,209 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "mock_libinput.h" #include "../../libinput/device.h" #include "../../libinput/events.h" #include #include Q_DECLARE_METATYPE(libinput_event_type) Q_DECLARE_METATYPE(libinput_button_state) using namespace KWin::LibInput; class TestLibinputPointerEvent : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testType_data(); void testType(); void testButton_data(); void testButton(); void testAxis_data(); void testAxis(); void testMotion(); void testAbsoluteMotion(); private: libinput_device *m_nativeDevice = nullptr; Device *m_device = nullptr; }; void TestLibinputPointerEvent::init() { m_nativeDevice = new libinput_device; m_nativeDevice->pointer = true; m_nativeDevice->deviceSize = QSizeF(12.5, 13.8); m_device = new Device(m_nativeDevice); } void TestLibinputPointerEvent::cleanup() { delete m_device; m_device = nullptr; delete m_nativeDevice; m_nativeDevice = nullptr; } void TestLibinputPointerEvent::testType_data() { QTest::addColumn("type"); QTest::newRow("motion") << LIBINPUT_EVENT_POINTER_MOTION; QTest::newRow("absolute motion") << LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE; QTest::newRow("button") << LIBINPUT_EVENT_POINTER_BUTTON; QTest::newRow("axis") << LIBINPUT_EVENT_POINTER_AXIS; } void TestLibinputPointerEvent::testType() { // this test verifies the initialization of a PointerEvent and the parent Event class libinput_event_pointer *pointerEvent = new libinput_event_pointer; QFETCH(libinput_event_type, type); pointerEvent->type = type; pointerEvent->device = m_nativeDevice; QScopedPointer event(Event::create(pointerEvent)); // API of event QCOMPARE(event->type(), type); QCOMPARE(event->device(), m_device); QCOMPARE(event->nativeDevice(), m_nativeDevice); QCOMPARE((libinput_event*)(*event.data()), pointerEvent); // verify it's a pointer event QVERIFY(dynamic_cast(event.data())); QCOMPARE((libinput_event_pointer*)(*dynamic_cast(event.data())), pointerEvent); } void TestLibinputPointerEvent::testButton_data() { QTest::addColumn("buttonState"); QTest::addColumn("expectedButtonState"); QTest::addColumn("button"); QTest::addColumn("time"); QTest::newRow("pressed") << LIBINPUT_BUTTON_STATE_RELEASED << KWin::InputRedirection::PointerButtonReleased << quint32(BTN_RIGHT) << 100u; QTest::newRow("released") << LIBINPUT_BUTTON_STATE_PRESSED << KWin::InputRedirection::PointerButtonPressed << quint32(BTN_LEFT) << 200u; } void TestLibinputPointerEvent::testButton() { // this test verifies the button press/release libinput_event_pointer *pointerEvent = new libinput_event_pointer; pointerEvent->device = m_nativeDevice; pointerEvent->type = LIBINPUT_EVENT_POINTER_BUTTON; QFETCH(libinput_button_state, buttonState); pointerEvent->buttonState = buttonState; QFETCH(quint32, button); pointerEvent->button = button; QFETCH(quint32, time); pointerEvent->time = time; QScopedPointer event(Event::create(pointerEvent)); auto pe = dynamic_cast(event.data()); QVERIFY(pe); QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_BUTTON); QTEST(pe->buttonState(), "expectedButtonState"); QCOMPARE(pe->button(), button); QCOMPARE(pe->time(), time); + QCOMPARE(pe->timeMicroseconds(), quint64(time * 1000)); } void TestLibinputPointerEvent::testAxis_data() { QTest::addColumn("horizontal"); QTest::addColumn("vertical"); QTest::addColumn("value"); QTest::addColumn("time"); QTest::newRow("horizontal") << true << false << QPointF(3.0, 0.0) << 100u; QTest::newRow("vertical") << false << true << QPointF(0.0, 2.5) << 200u; QTest::newRow("both") << true << true << QPointF(1.1, 4.2) << 300u; } void TestLibinputPointerEvent::testAxis() { // this test verifies pointer axis functionality libinput_event_pointer *pointerEvent = new libinput_event_pointer; pointerEvent->device = m_nativeDevice; pointerEvent->type = LIBINPUT_EVENT_POINTER_AXIS; QFETCH(bool, horizontal); QFETCH(bool, vertical); QFETCH(QPointF, value); QFETCH(quint32, time); pointerEvent->horizontalAxis = horizontal; pointerEvent->verticalAxis = vertical; pointerEvent->horizontalAxisValue = value.x(); pointerEvent->verticalAxisValue = value.y(); pointerEvent->time = time; QScopedPointer event(Event::create(pointerEvent)); auto pe = dynamic_cast(event.data()); QVERIFY(pe); QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_AXIS); QCOMPARE(pe->axis().contains(KWin::InputRedirection::PointerAxisHorizontal), horizontal); QCOMPARE(pe->axis().contains(KWin::InputRedirection::PointerAxisVertical), vertical); QCOMPARE(pe->axisValue(KWin::InputRedirection::PointerAxisHorizontal), value.x()); QCOMPARE(pe->axisValue(KWin::InputRedirection::PointerAxisVertical), value.y()); QCOMPARE(pe->time(), time); } void TestLibinputPointerEvent::testMotion() { // this test verifies pointer motion (delta) libinput_event_pointer *pointerEvent = new libinput_event_pointer; pointerEvent->device = m_nativeDevice; pointerEvent->type = LIBINPUT_EVENT_POINTER_MOTION; pointerEvent->delta = QSizeF(2.1, 4.5); pointerEvent->time = 500u; QScopedPointer event(Event::create(pointerEvent)); auto pe = dynamic_cast(event.data()); QVERIFY(pe); QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_MOTION); QCOMPARE(pe->time(), 500u); - QCOMPARE(pe->delta(), QPointF(2.1, 4.5)); + QCOMPARE(pe->delta(), QSizeF(2.1, 4.5)); } void TestLibinputPointerEvent::testAbsoluteMotion() { // this test verifies absolute pointer motion libinput_event_pointer *pointerEvent = new libinput_event_pointer; pointerEvent->device = m_nativeDevice; pointerEvent->type = LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE; pointerEvent->absolutePos = QPointF(6.25, 6.9); pointerEvent->time = 500u; QScopedPointer event(Event::create(pointerEvent)); auto pe = dynamic_cast(event.data()); QVERIFY(pe); QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); QCOMPARE(pe->time(), 500u); QCOMPARE(pe->absolutePos(), QPointF(6.25, 6.9)); QCOMPARE(pe->absolutePos(QSize(1280, 1024)), QPointF(640, 512)); } QTEST_GUILESS_MAIN(TestLibinputPointerEvent) #include "pointer_event_test.moc" diff --git a/debug_console.cpp b/debug_console.cpp index db18569fe..9656754b3 100644 --- a/debug_console.cpp +++ b/debug_console.cpp @@ -1,1463 +1,1481 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "debug_console.h" #include "client.h" #include "input_event.h" #include "main.h" #include "shell_client.h" #include "unmanaged.h" #include "wayland_server.h" #include "workspace.h" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include #include #include "ui_debug_console.h" // KWayland #include #include #include #include // frameworks #include #include // Qt #include #include #include namespace KWin { static QString tableHeaderRow(const QString &title) { return QStringLiteral("%1").arg(title); } template static QString tableRow(const QString &title, const T &argument) { return QStringLiteral("%1%2").arg(title).arg(argument); } static QString timestampRow(quint32 timestamp) { return tableRow(i18n("Timestamp"), timestamp); } +static QString timestampRowUsec(quint64 timestamp) +{ + return tableRow(i18n("Timestamp (µsec)"), timestamp); +} + static QString buttonToString(Qt::MouseButton button) { switch (button) { case Qt::LeftButton: return i18nc("A mouse button", "Left"); case Qt::RightButton: return i18nc("A mouse button", "Right"); case Qt::MiddleButton: return i18nc("A mouse button", "Middle"); case Qt::BackButton: return i18nc("A mouse button", "Back"); case Qt::ForwardButton: return i18nc("A mouse button", "Forward"); case Qt::TaskButton: return i18nc("A mouse button", "Task"); case Qt::ExtraButton4: return i18nc("A mouse button", "Extra Button 4"); case Qt::ExtraButton5: return i18nc("A mouse button", "Extra Button 5"); case Qt::ExtraButton6: return i18nc("A mouse button", "Extra Button 6"); case Qt::ExtraButton7: return i18nc("A mouse button", "Extra Button 7"); case Qt::ExtraButton8: return i18nc("A mouse button", "Extra Button 8"); case Qt::ExtraButton9: return i18nc("A mouse button", "Extra Button 9"); case Qt::ExtraButton10: return i18nc("A mouse button", "Extra Button 10"); case Qt::ExtraButton11: return i18nc("A mouse button", "Extra Button 11"); case Qt::ExtraButton12: return i18nc("A mouse button", "Extra Button 12"); case Qt::ExtraButton13: return i18nc("A mouse button", "Extra Button 13"); case Qt::ExtraButton14: return i18nc("A mouse button", "Extra Button 14"); case Qt::ExtraButton15: return i18nc("A mouse button", "Extra Button 15"); case Qt::ExtraButton16: return i18nc("A mouse button", "Extra Button 16"); case Qt::ExtraButton17: return i18nc("A mouse button", "Extra Button 17"); case Qt::ExtraButton18: return i18nc("A mouse button", "Extra Button 18"); case Qt::ExtraButton19: return i18nc("A mouse button", "Extra Button 19"); case Qt::ExtraButton20: return i18nc("A mouse button", "Extra Button 20"); case Qt::ExtraButton21: return i18nc("A mouse button", "Extra Button 21"); case Qt::ExtraButton22: return i18nc("A mouse button", "Extra Button 22"); case Qt::ExtraButton23: return i18nc("A mouse button", "Extra Button 23"); case Qt::ExtraButton24: return i18nc("A mouse button", "Extra Button 24"); default: return QString(); } } #if HAVE_INPUT static QString deviceRow(LibInput::Device *device) { if (!device) { return tableRow(i18n("Input Device"), i18nc("The input device of the event is not known", "Unknown")); } return tableRow(i18n("Input Device"), QStringLiteral("%1 (%2)").arg(device->name()).arg(device->sysName())); } #endif static QString buttonsToString(Qt::MouseButtons buttons) { QString ret; for (uint i = 1; i < Qt::ExtraButton24; i = i << 1) { if (buttons & i) { ret.append(buttonToString(Qt::MouseButton(uint(buttons) & i))); ret.append(QStringLiteral(" ")); } }; return ret.trimmed(); } static const QString s_hr = QStringLiteral("
"); static const QString s_tableStart = QStringLiteral(""); static const QString s_tableEnd = QStringLiteral("
"); DebugConsoleFilter::DebugConsoleFilter(QTextEdit *textEdit) : InputEventFilter() , m_textEdit(textEdit) { } DebugConsoleFilter::~DebugConsoleFilter() = default; bool DebugConsoleFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { QString text = s_hr; const QString timestamp = timestampRow(event->timestamp()); text.append(s_tableStart); switch (event->type()) { - case QEvent::MouseMove: + case QEvent::MouseMove: { text.append(tableHeaderRow(i18nc("A mouse pointer motion event", "Pointer Motion"))); + auto e = static_cast(event); #if HAVE_INPUT - text.append(deviceRow(static_cast(event)->device())); + text.append(deviceRow(e->device())); #endif text.append(timestamp); + if (e->timestampMicroseconds() != 0) { + text.append(timestampRowUsec(e->timestampMicroseconds())); + } + if (e->delta() != QSizeF()) { + text.append(tableRow(i18nc("The relative mouse movement", "Delta"), + QStringLiteral("%1/%2").arg(e->delta().width()).arg(e->delta().height()))); + } + if (e->deltaUnaccelerated() != QSizeF()) { + text.append(tableRow(i18nc("The relative mouse movement", "Delta (not accelerated)"), + QStringLiteral("%1/%2").arg(e->deltaUnaccelerated().width()).arg(e->deltaUnaccelerated().height()))); + } text.append(tableRow(i18nc("The global mouse pointer position", "Global Position"), QStringLiteral("%1/%2").arg(event->pos().x()).arg(event->pos().y()))); break; + } case QEvent::MouseButtonPress: text.append(tableHeaderRow(i18nc("A mouse pointer button press event", "Pointer Button Press"))); #if HAVE_INPUT text.append(deviceRow(static_cast(event)->device())); #endif text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), nativeButton)); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; case QEvent::MouseButtonRelease: text.append(tableHeaderRow(i18nc("A mouse pointer button release event", "Pointer Button Release"))); #if HAVE_INPUT text.append(deviceRow(static_cast(event)->device())); #endif text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), nativeButton)); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; default: break; } text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::wheelEvent(QWheelEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A mouse pointer axis (wheel) event", "Pointer Axis"))); #if HAVE_INPUT text.append(deviceRow(static_cast(event)->device())); #endif text.append(timestampRow(event->timestamp())); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; text.append(tableRow(i18nc("The orientation of a pointer axis event", "Orientation"), orientation == Qt::Horizontal ? i18nc("An orientation of a pointer axis event", "Horizontal") : i18nc("An orientation of a pointer axis event", "Vertical"))); text.append(tableRow(QStringLiteral("Delta"), orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::keyEvent(QKeyEvent *event) { QString text = s_hr; text.append(s_tableStart); switch (event->type()) { case QEvent::KeyPress: text.append(tableHeaderRow(i18nc("A key press event", "Key Press"))); break; case QEvent::KeyRelease: text.append(tableHeaderRow(i18nc("A key release event", "Key Release"))); break; default: break; } #if HAVE_INPUT text.append(deviceRow(static_cast(event)->device())); #endif auto modifiersToString = [event] { QString ret; if (event->modifiers().testFlag(Qt::ShiftModifier)) { ret.append(i18nc("A keyboard modifier", "Shift")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::ControlModifier)) { ret.append(i18nc("A keyboard modifier", "Control")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::AltModifier)) { ret.append(i18nc("A keyboard modifier", "Alt")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::MetaModifier)) { ret.append(i18nc("A keyboard modifier", "Meta")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::KeypadModifier)) { ret.append(i18nc("A keyboard modifier", "Keypad")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::GroupSwitchModifier)) { ret.append(i18nc("A keyboard modifier", "Group-switch")); ret.append(QStringLiteral(" ")); } return ret; }; text.append(timestampRow(event->timestamp())); text.append(tableRow(i18nc("Whether the event is an automatic key repeat", "Repeat"), event->isAutoRepeat())); text.append(tableRow(i18nc("The code as read from the input device", "Scan code"), event->nativeScanCode())); text.append(tableRow(i18nc("The translated code to an Xkb symbol", "Xkb symbol"), event->nativeVirtualKey())); text.append(tableRow(i18nc("The translated code interpreted as text", "Utf8"), event->text())); text.append(tableRow(i18nc("The currently active modifiers", "Modifiers"), modifiersToString())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::touchDown(quint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch down event", "Touch down"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::touchMotion(quint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch motion event", "Touch Motion"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::touchUp(quint32 id, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch up event", "Touch Up"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::pinchGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is started", "Pinch start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this pinch gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is updated", "Pinch update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current scale in pinch gesture", "Scale"), scale)); text.append(tableRow(i18nc("Current angle in pinch gesture", "Angle delta"), angleDelta)); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::pinchGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture ended", "Pinch end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::pinchGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture got cancelled", "Pinch cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::swipeGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is started", "Swipe start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this swipe gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is updated", "Swipe update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::swipeGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture ended", "Swipe end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } bool DebugConsoleFilter::swipeGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture got cancelled", "Swipe cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); return false; } DebugConsole::DebugConsole() : QWidget() , m_ui(new Ui::DebugConsole) { setAttribute(Qt::WA_ShowWithoutActivating); m_ui->setupUi(this); m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this)); m_ui->windowsView->setModel(new DebugConsoleModel(this)); m_ui->surfacesView->setModel(new SurfaceTreeModel(this)); #if HAVE_INPUT if (kwinApp()->usesLibinput()) { m_ui->inputDevicesView->setModel(new InputDeviceModel(this)); m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this)); } #endif m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree"))); m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree"))); if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) { m_ui->tabWidget->setTabEnabled(1, false); m_ui->tabWidget->setTabEnabled(2, false); } if (!kwinApp()->usesLibinput()) { m_ui->tabWidget->setTabEnabled(3, false); } connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this] (int index) { // delay creation of input event filter until the tab is selected if (index == 2 && m_inputFilter.isNull()) { m_inputFilter.reset(new DebugConsoleFilter(m_ui->inputTextEdit)); input()->prepandInputEventFilter(m_inputFilter.data()); } } ); // for X11 setWindowFlags(Qt::X11BypassWindowManagerHint); initGLTab(); } DebugConsole::~DebugConsole() = default; void DebugConsole::initGLTab() { GLPlatform *gl = GLPlatform::instance(); if (!gl) { m_ui->noOpenGLLabel->setVisible(true); m_ui->glInfoScrollArea->setVisible(false); return; } m_ui->noOpenGLLabel->setVisible(false); m_ui->glInfoScrollArea->setVisible(true); m_ui->glVendorStringLabel->setText(QString::fromLocal8Bit(gl->glVendorString())); m_ui->glRendererStringLabel->setText(QString::fromLocal8Bit(gl->glRendererString())); m_ui->glVersionStringLabel->setText(QString::fromLocal8Bit(gl->glVersionString())); m_ui->glslVersionStringLabel->setText(QString::fromLocal8Bit(gl->glShadingLanguageVersionString())); m_ui->glDriverLabel->setText(GLPlatform::driverToString(gl->driver())); m_ui->glGPULabel->setText(GLPlatform::chipClassToString(gl->chipClass())); m_ui->glVersionLabel->setText(GLPlatform::versionToString(gl->glVersion())); m_ui->glslLabel->setText(GLPlatform::versionToString(gl->glslVersion())); auto extensionsString = [] (const QList &extensions) { QString text = QStringLiteral("
    "); for (auto extension : extensions) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(extension))); } text.append(QStringLiteral("
")); return text; }; if (gl->platformInterface() == EglPlatformInterface) { m_ui->eglExtensionsBox->setVisible(true); m_ui->glxExtensionsBox->setVisible(false); m_ui->eglExtensionsLabel->setText(extensionsString(eglExtensions())); } else { m_ui->eglExtensionsBox->setVisible(false); m_ui->glxExtensionsBox->setVisible(true); m_ui->glxExtensionsLabel->setText(extensionsString(glxExtensions())); } m_ui->openGLExtensionsLabel->setText(extensionsString(openGLExtensions())); } void DebugConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); // delay the connection to the show event as in ctor the windowHandle returns null connect(windowHandle(), &QWindow::visibleChanged, this, [this] (bool visible) { if (visible) { // ignore return; } deleteLater(); } ); } DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent) : QStyledItemDelegate(parent) { } DebugConsoleDelegate::~DebugConsoleDelegate() = default; QString DebugConsoleDelegate::displayText(const QVariant &value, const QLocale &locale) const { switch (value.type()) { case QMetaType::QPoint: { const QPoint p = value.toPoint(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QPointF: { const QPointF p = value.toPointF(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QSize: { const QSize s = value.toSize(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QSizeF: { const QSizeF s = value.toSizeF(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QRect: { const QRect r = value.toRect(); return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()); } default: if (value.userType() == qMetaTypeId()) { if (auto s = value.value()) { return QStringLiteral("KWayland::Server::SurfaceInterface(0x%1)").arg(qulonglong(s), 0, 16); } else { return QStringLiteral("nullptr"); } } if (value.userType() == qMetaTypeId()) { const auto buttons = value.value(); if (buttons == Qt::NoButton) { return i18n("No Mouse Buttons"); } QStringList list; if (buttons.testFlag(Qt::LeftButton)) { list << i18nc("Mouse Button", "left"); } if (buttons.testFlag(Qt::RightButton)) { list << i18nc("Mouse Button", "right"); } if (buttons.testFlag(Qt::MiddleButton)) { list << i18nc("Mouse Button", "middle"); } if (buttons.testFlag(Qt::BackButton)) { list << i18nc("Mouse Button", "back"); } if (buttons.testFlag(Qt::ForwardButton)) { list << i18nc("Mouse Button", "forward"); } if (buttons.testFlag(Qt::ExtraButton1)) { list << i18nc("Mouse Button", "extra 1"); } if (buttons.testFlag(Qt::ExtraButton2)) { list << i18nc("Mouse Button", "extra 2"); } if (buttons.testFlag(Qt::ExtraButton3)) { list << i18nc("Mouse Button", "extra 3"); } if (buttons.testFlag(Qt::ExtraButton4)) { list << i18nc("Mouse Button", "extra 4"); } if (buttons.testFlag(Qt::ExtraButton5)) { list << i18nc("Mouse Button", "extra 5"); } if (buttons.testFlag(Qt::ExtraButton6)) { list << i18nc("Mouse Button", "extra 6"); } if (buttons.testFlag(Qt::ExtraButton7)) { list << i18nc("Mouse Button", "extra 7"); } if (buttons.testFlag(Qt::ExtraButton8)) { list << i18nc("Mouse Button", "extra 8"); } if (buttons.testFlag(Qt::ExtraButton9)) { list << i18nc("Mouse Button", "extra 9"); } if (buttons.testFlag(Qt::ExtraButton10)) { list << i18nc("Mouse Button", "extra 10"); } if (buttons.testFlag(Qt::ExtraButton11)) { list << i18nc("Mouse Button", "extra 11"); } if (buttons.testFlag(Qt::ExtraButton12)) { list << i18nc("Mouse Button", "extra 12"); } if (buttons.testFlag(Qt::ExtraButton13)) { list << i18nc("Mouse Button", "extra 13"); } if (buttons.testFlag(Qt::ExtraButton14)) { list << i18nc("Mouse Button", "extra 14"); } if (buttons.testFlag(Qt::ExtraButton15)) { list << i18nc("Mouse Button", "extra 15"); } if (buttons.testFlag(Qt::ExtraButton16)) { list << i18nc("Mouse Button", "extra 16"); } if (buttons.testFlag(Qt::ExtraButton17)) { list << i18nc("Mouse Button", "extra 17"); } if (buttons.testFlag(Qt::ExtraButton18)) { list << i18nc("Mouse Button", "extra 18"); } if (buttons.testFlag(Qt::ExtraButton19)) { list << i18nc("Mouse Button", "extra 19"); } if (buttons.testFlag(Qt::ExtraButton20)) { list << i18nc("Mouse Button", "extra 20"); } if (buttons.testFlag(Qt::ExtraButton21)) { list << i18nc("Mouse Button", "extra 21"); } if (buttons.testFlag(Qt::ExtraButton22)) { list << i18nc("Mouse Button", "extra 22"); } if (buttons.testFlag(Qt::ExtraButton23)) { list << i18nc("Mouse Button", "extra 23"); } if (buttons.testFlag(Qt::ExtraButton24)) { list << i18nc("Mouse Button", "extra 24"); } if (buttons.testFlag(Qt::TaskButton)) { list << i18nc("Mouse Button", "task"); } return list.join(QStringLiteral(", ")); } break; } return QStyledItemDelegate::displayText(value, locale); } static const int s_x11ClientId = 1; static const int s_x11UnmanagedId = 2; static const int s_waylandClientId = 3; static const int s_waylandInternalId = 4; static const quint32 s_propertyBitMask = 0xFFFF0000; static const quint32 s_clientBitMask = 0x0000FFFF; static const quint32 s_idDistance = 10000; template void DebugConsoleModel::add(int parentRow, QVector &clients, T *client) { beginInsertRows(index(parentRow, 0, QModelIndex()), clients.count(), clients.count()); clients.append(client); endInsertRows(); } template void DebugConsoleModel::remove(int parentRow, QVector &clients, T *client) { const int remove = clients.indexOf(client); if (remove == -1) { return; } beginRemoveRows(index(parentRow, 0, QModelIndex()), remove, remove); clients.removeAt(remove); endRemoveRows(); } DebugConsoleModel::DebugConsoleModel(QObject *parent) : QAbstractItemModel(parent) { if (waylandServer()) { const auto clients = waylandServer()->clients(); for (auto c : clients) { m_shellClients.append(c); } const auto internals = waylandServer()->internalClients(); for (auto c : internals) { m_internalClients.append(c); } // TODO: that only includes windows getting shown, not those which are only created connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { if (c->isInternal()) { add(s_waylandInternalId -1, m_internalClients, c); } else { add(s_waylandClientId -1, m_shellClients, c); } } ); connect(waylandServer(), &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { remove(s_waylandInternalId -1, m_internalClients, c); remove(s_waylandClientId -1, m_shellClients, c); } ); } const auto x11Clients = workspace()->clientList(); for (auto c : x11Clients) { m_x11Clients.append(c); } const auto x11DesktopClients = workspace()->desktopList(); for (auto c : x11DesktopClients) { m_x11Clients.append(c); } connect(workspace(), &Workspace::clientAdded, this, [this] (Client *c) { add(s_x11ClientId -1, m_x11Clients, c); } ); connect(workspace(), &Workspace::clientRemoved, this, [this] (AbstractClient *ac) { Client *c = qobject_cast(ac); if (!c) { return; } remove(s_x11ClientId -1, m_x11Clients, c); } ); const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { m_unmanageds.append(u); } connect(workspace(), &Workspace::unmanagedAdded, this, [this] (Unmanaged *u) { add(s_x11UnmanagedId -1, m_unmanageds, u); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, [this] (Unmanaged *u) { remove(s_x11UnmanagedId -1, m_unmanageds, u); } ); } DebugConsoleModel::~DebugConsoleModel() = default; int DebugConsoleModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } int DebugConsoleModel::topLevelRowCount() const { return kwinApp()->shouldUseWaylandForCompositing() ? 4 : 2; } template int DebugConsoleModel::propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { return t->metaObject()->propertyCount(); } return 0; } int DebugConsoleModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return topLevelRowCount(); } switch (parent.internalId()) { case s_x11ClientId: return m_x11Clients.count(); case s_x11UnmanagedId: return m_unmanageds.count(); case s_waylandClientId: return m_shellClients.count(); case s_waylandInternalId: return m_internalClients.count(); default: break; } if (parent.internalId() & s_propertyBitMask) { // properties do not have children return 0; } if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return propertyCount(parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::shellClient); } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return propertyCount(parent, &DebugConsoleModel::internalClient); } return 0; } template QModelIndex DebugConsoleModel::indexForClient(int row, int column, const QVector &clients, int id) const { if (column != 0) { return QModelIndex(); } if (row >= clients.count()) { return QModelIndex(); } return createIndex(row, column, s_idDistance * id + row); } template QModelIndex DebugConsoleModel::indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { if (row >= t->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } return QModelIndex(); } QModelIndex DebugConsoleModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { // index for a top level item if (column != 0 || row >= topLevelRowCount()) { return QModelIndex(); } return createIndex(row, column, row + 1); } if (column >= 2) { // max of 2 columns return QModelIndex(); } // index for a client (second level) switch (parent.internalId()) { case s_x11ClientId: return indexForClient(row, column, m_x11Clients, s_x11ClientId); case s_x11UnmanagedId: return indexForClient(row, column, m_unmanageds, s_x11UnmanagedId); case s_waylandClientId: return indexForClient(row, column, m_shellClients, s_waylandClientId); case s_waylandInternalId: return indexForClient(row, column, m_internalClients, s_waylandInternalId); default: break; } // index for a property (third level) if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::shellClient); } else if (parent.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::internalClient); } return QModelIndex(); } QModelIndex DebugConsoleModel::parent(const QModelIndex &child) const { if (child.internalId() <= s_waylandInternalId) { return QModelIndex(); } if (child.internalId() & s_propertyBitMask) { // a property const quint32 parentId = child.internalId() & s_clientBitMask; if (parentId < s_idDistance * (s_x11ClientId + 1)) { return createIndex(parentId - (s_idDistance * s_x11ClientId), 0, parentId); } else if (parentId < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(parentId - (s_idDistance * s_x11UnmanagedId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandClientId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandClientId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandInternalId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandInternalId), 0, parentId); } return QModelIndex(); } if (child.internalId() < s_idDistance * (s_x11ClientId + 1)) { return createIndex(s_x11ClientId -1, 0, s_x11ClientId); } else if (child.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(s_x11UnmanagedId -1, 0, s_x11UnmanagedId); } else if (child.internalId() < s_idDistance * (s_waylandClientId + 1)) { return createIndex(s_waylandClientId -1, 0, s_waylandClientId); } else if (child.internalId() < s_idDistance * (s_waylandInternalId + 1)) { return createIndex(s_waylandInternalId -1, 0, s_waylandInternalId); } return QModelIndex(); } QVariant DebugConsoleModel::propertyData(QObject *object, const QModelIndex &index, int role) const { Q_UNUSED(role) const auto property = object->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else { const QVariant value = property.read(object); if (qstrcmp(property.name(), "windowType") == 0) { switch (value.toInt()) { case NET::Normal: return QStringLiteral("NET::Normal"); case NET::Desktop: return QStringLiteral("NET::Desktop"); case NET::Dock: return QStringLiteral("NET::Dock"); case NET::Toolbar: return QStringLiteral("NET::Toolbar"); case NET::Menu: return QStringLiteral("NET::Menu"); case NET::Dialog: return QStringLiteral("NET::Dialog"); case NET::Override: return QStringLiteral("NET::Override"); case NET::TopMenu: return QStringLiteral("NET::TopMenu"); case NET::Utility: return QStringLiteral("NET::Utility"); case NET::Splash: return QStringLiteral("NET::Splash"); case NET::DropdownMenu: return QStringLiteral("NET::DropdownMenu"); case NET::PopupMenu: return QStringLiteral("NET::PopupMenu"); case NET::Tooltip: return QStringLiteral("NET::Tooltip"); case NET::Notification: return QStringLiteral("NET::Notification"); case NET::ComboBox: return QStringLiteral("NET::ComboBox"); case NET::DNDIcon: return QStringLiteral("NET::DNDIcon"); case NET::OnScreenDisplay: return QStringLiteral("NET::OnScreenDisplay"); case NET::Unknown: default: return QStringLiteral("NET::Unknown"); } } return value; } return QVariant(); } template QVariant DebugConsoleModel::clientData(const QModelIndex &index, int role, const QVector clients) const { if (index.row() >= clients.count()) { return QVariant(); } auto c = clients.at(index.row()); if (role == Qt::DisplayRole) { return QStringLiteral("%1: %2").arg(c->window()).arg(c->caption()); } else if (role == Qt::DecorationRole) { return c->icon(); } return QVariant(); } QVariant DebugConsoleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid()) { // one of the top levels if (index.column() != 0 || role != Qt::DisplayRole) { return QVariant(); } switch (index.internalId()) { case s_x11ClientId: return i18n("X11 Client Windows"); case s_x11UnmanagedId: return i18n("X11 Unmanaged Windows"); case s_waylandClientId: return i18n("Wayland Windows"); case s_waylandInternalId: return i18n("Internal Windows"); default: return QVariant(); } } if (index.internalId() & s_propertyBitMask) { if (index.column() >= 2 || role != Qt::DisplayRole) { return QVariant(); } if (ShellClient *c = shellClient(index)) { return propertyData(c, index, role); } else if (ShellClient *c = internalClient(index)) { return propertyData(c, index, role); } else if (Client *c = x11Client(index)) { return propertyData(c, index, role); } else if (Unmanaged *u = unmanaged(index)) { return propertyData(u, index, role); } } else { if (index.column() != 0) { return QVariant(); } switch (index.parent().internalId()) { case s_x11ClientId: return clientData(index, role, m_x11Clients); case s_x11UnmanagedId: { if (index.row() >= m_unmanageds.count()) { return QVariant(); } auto u = m_unmanageds.at(index.row()); if (role == Qt::DisplayRole) { return u->window(); } break; } case s_waylandClientId: return clientData(index, role, m_shellClients); case s_waylandInternalId: return clientData(index, role, m_internalClients); default: break; } } return QVariant(); } template static T *clientForIndex(const QModelIndex &index, const QVector &clients, int id) { const qint32 row = (index.internalId() & s_clientBitMask) - (s_idDistance * id); if (row < 0 || row >= clients.count()) { return nullptr; } return clients.at(row); } ShellClient *DebugConsoleModel::shellClient(const QModelIndex &index) const { return clientForIndex(index, m_shellClients, s_waylandClientId); } ShellClient *DebugConsoleModel::internalClient(const QModelIndex &index) const { return clientForIndex(index, m_internalClients, s_waylandInternalId); } Client *DebugConsoleModel::x11Client(const QModelIndex &index) const { return clientForIndex(index, m_x11Clients, s_x11ClientId); } Unmanaged *DebugConsoleModel::unmanaged(const QModelIndex &index) const { return clientForIndex(index, m_unmanageds, s_x11UnmanagedId); } /////////////////////////////////////// SurfaceTreeModel SurfaceTreeModel::SurfaceTreeModel(QObject *parent) : QAbstractItemModel(parent) { // TODO: it would be nice to not have to reset the model on each change auto reset = [this] { beginResetModel(); endResetModel(); }; using namespace KWayland::Server; const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { if (!u->surface()) { continue; } connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->allClientList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->desktopList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } if (waylandServer()) { for (auto c : waylandServer()->internalClients()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this, reset] (ShellClient *c) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); reset(); } ); } connect(workspace(), &Workspace::clientAdded, this, [this, reset] (AbstractClient *c) { if (c->surface()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::clientRemoved, this, reset); connect(workspace(), &Workspace::unmanagedAdded, this, [this, reset] (Unmanaged *u) { if (u->surface()) { connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, reset); } SurfaceTreeModel::~SurfaceTreeModel() = default; int SurfaceTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int SurfaceTreeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); return children.count(); } return 0; } const int internalClientsCount = waylandServer() ? waylandServer()->internalClients().count() : 0; // toplevel are all windows return workspace()->allClientList().count() + workspace()->desktopList().count() + workspace()->unmanagedList().count() + internalClientsCount; } QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0) { // invalid column return QModelIndex(); } if (parent.isValid()) { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); if (row < children.count()) { return createIndex(row, column, children.at(row)->surface().data()); } } return QModelIndex(); } // a window const auto &allClients = workspace()->allClientList(); if (row < allClients.count()) { // references a client return createIndex(row, column, allClients.at(row)->surface()); } int reference = allClients.count(); const auto &desktopClients = workspace()->desktopList(); if (row < reference + desktopClients.count()) { return createIndex(row, column, desktopClients.at(row-reference)->surface()); } reference += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); if (row < reference + unmanaged.count()) { return createIndex(row, column, unmanaged.at(row-reference)->surface()); } reference += unmanaged.count(); if (waylandServer()) { const auto &internal = waylandServer()->internalClients(); if (row < reference + internal.count()) { return createIndex(row, column, internal.at(row-reference)->surface()); } } // not found return QModelIndex(); } QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const { using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(child.internalPointer())) { const auto &subsurface = surface->subSurface(); if (subsurface.isNull()) { // doesn't reference a subsurface, this is a top-level window return QModelIndex(); } SurfaceInterface *parent = subsurface->parentSurface().data(); if (!parent) { // something is wrong return QModelIndex(); } // is the parent a subsurface itself? if (parent->subSurface()) { auto grandParent = parent->subSurface()->parentSurface(); if (grandParent.isNull()) { // something is wrong return QModelIndex(); } const auto &children = grandParent->childSubSurfaces(); for (int row = 0; row < children.count(); row++) { if (children.at(row).data() == parent->subSurface().data()) { return createIndex(row, 0, parent); } } return QModelIndex(); } // not a subsurface, thus it's a true window int row = 0; const auto &allClients = workspace()->allClientList(); for (; row < allClients.count(); row++) { if (allClients.at(row)->surface() == parent) { return createIndex(row, 0, parent); } } row = allClients.count(); const auto &desktopClients = workspace()->desktopList(); for (int i = 0; i < desktopClients.count(); i++) { if (desktopClients.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); for (int i = 0; i < unmanaged.count(); i++) { if (unmanaged.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += unmanaged.count(); if (waylandServer()) { const auto &internal = waylandServer()->internalClients(); for (int i = 0; i < internal.count(); i++) { if (internal.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } } } return QModelIndex(); } QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } using namespace KWayland::Server; if (SurfaceInterface *surface = static_cast(index.internalPointer())) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()) .arg(surface->client()->processId()) .arg(surface->id()); } else if (role == Qt::DecorationRole) { if (auto buffer = surface->buffer()) { if (buffer->shmBuffer()) { return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); } } } } return QVariant(); } #if HAVE_INPUT InputDeviceModel::InputDeviceModel(QObject *parent) : QAbstractItemModel(parent) , m_devices(LibInput::Connection::self()->devices()) { for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) { setupDeviceConnections(*it); } auto c = LibInput::Connection::self(); connect(c, &LibInput::Connection::deviceAdded, this, [this] (LibInput::Device *d) { beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); m_devices << d; setupDeviceConnections(d); endInsertRows(); } ); connect(c, &LibInput::Connection::deviceRemoved, this, [this] (LibInput::Device *d) { const int index = m_devices.indexOf(d); if (index == -1) { return; } beginRemoveRows(QModelIndex(), index, index); m_devices.removeAt(index); endRemoveRows(); } ); } InputDeviceModel::~InputDeviceModel() = default; int InputDeviceModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } QVariant InputDeviceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid() && index.column() == 0) { const auto devices = LibInput::Connection::self()->devices(); if (index.row() >= devices.count()) { return QVariant(); } if (role == Qt::DisplayRole) { return devices.at(index.row())->name(); } } if (index.parent().isValid()) { if (role == Qt::DisplayRole) { const auto device = LibInput::Connection::self()->devices().at(index.parent().row()); const auto property = device->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else if (index.column() == 1) { return device->property(property.name()); } } } return QVariant(); } QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const { if (column >= 2) { return QModelIndex(); } if (parent.isValid()) { if (parent.internalId() & s_propertyBitMask) { return QModelIndex(); } if (row >= LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } if (row >= LibInput::Connection::self()->devices().count()) { return QModelIndex(); } return createIndex(row, column, row + 1); } int InputDeviceModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return LibInput::Connection::self()->devices().count(); } if (parent.internalId() & s_propertyBitMask) { return 0; } return LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount(); } QModelIndex InputDeviceModel::parent(const QModelIndex &child) const { if (child.internalId() & s_propertyBitMask) { const quintptr parentId = child.internalId() & s_clientBitMask; return createIndex(parentId - 1, 0, parentId); } return QModelIndex(); } void InputDeviceModel::setupDeviceConnections(LibInput::Device *device) { connect(device, &LibInput::Device::enabledChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("enabled"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::leftHandedChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("leftHanded"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::pointerAccelerationChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("pointerAcceleration"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); } #endif } diff --git a/input.cpp b/input.cpp index f9237b58e..5da10dd44 100644 --- a/input.cpp +++ b/input.cpp @@ -1,1625 +1,1638 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin 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 . *********************************************************************/ #include "input.h" +#include "input_event.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "client.h" #include "effects.h" #include "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include #include #include +#include #include #include //screenlocker #include // Qt #include #include namespace KWin { InputEventFilter::InputEventFilter() = default; InputEventFilter::~InputEventFilter() { if (input()) { input()->uninstallInputEventFilter(this); } } bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) return false; } bool InputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::keyEvent(QKeyEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::touchDown(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchMotion(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } void InputEventFilter::passToWaylandServer(QKeyEvent *event) { Q_ASSERT(waylandServer()); if (event->isAutoRepeat()) { return; } switch (event->type()) { case QEvent::KeyPress: waylandServer()->seat()->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: waylandServer()->seat()->keyReleased(event->nativeScanCode()); break; default: break; } } #if HAVE_INPUT class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; #endif class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (pointerSurfaceAllowed()) { seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } private: bool surfaceAllowed(KWayland::Server::SurfaceInterface *(KWayland::Server::SeatInterface::*method)() const) const { if (KWayland::Server::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Toplevel *t = waylandServer()->findClient(s)) { return t->isLockScreen() || t->isInputMethod(); } return false; } return true; } bool pointerSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedPointerSurface); } bool keyboardSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedKeyboardSurface); } bool touchSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedTouchSurface); } }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchDown(id, pos, time); } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchMotion(id, pos, time); } bool touchUp(quint32 id, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchUp(id, time); } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } switch (event->type()) { case QEvent::MouseMove: c->updateMoveResize(event->screenPos().toPoint()); break; case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { c->endMoveResize(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while moving a window return workspace()->getMovingClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } if (event->type() == QEvent::KeyPress) { c->keyPressEvent(event->key() | event->modifiers()); if (c->isMove() || c->isResize()) { // only update if mode didn't end c->updateMoveResize(input()->globalPointer()); } } return true; } }; class GlobalShortcutFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton); if (event->type() == QEvent::MouseButtonPress) { if (input()->shortcuts()->processPointerPressed(event->modifiers(), event->buttons())) { return true; } } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->modifiers() == Qt::NoModifier) { return false; } PointerAxisDirection direction = PointerAxisUp; if (event->angleDelta().x() < 0) { direction = PointerAxisRight; } else if (event->angleDelta().x() > 0) { direction = PointerAxisLeft; } else if (event->angleDelta().y() < 0) { direction = PointerAxisDown; } else if (event->angleDelta().y() > 0) { direction = PointerAxisUp; } return input()->shortcuts()->processAxis(event->modifiers(), direction); } bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress) { return input()->shortcuts()->processKey(input()->keyboard()->xkb()->modifiersRelevantForGlobalShortcuts(), event->nativeVirtualKey(), event->key()); } return false; } }; class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (!internal) { return false; } QMouseEvent e(event->type(), event->pos() - internal->position(), event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool wheelEvent(QWheelEvent *event) override { auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y()); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool keyEvent(QKeyEvent *event) override { const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return false; } QWindow *found = nullptr; auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (!screens()->geometry().contains(w->geometry())) { continue; } if (w->property("_q_showWithoutActivating").toBool()) { continue; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } event->setAccepted(false); if (QCoreApplication::sendEvent(found, event)) { waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); return true; } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { // something else is getting the events return false; } auto touch = input()->touch(); if (touch->internalPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); touch->update(pos); auto internal = touch->internalWindow(); if (!internal) { return false; } touch->setInternalPressId(id); // Qt's touch event API is rather complex, let's do fake mouse events instead m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QCoreApplication::instance()->sendEvent(internal.data(), &e); return true; } bool touchUp(quint32 id, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; class DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { return false; } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) { decoration->client()->processDecorationButtonPress(&e); } if (event->type() == QEvent::MouseButtonRelease) { decoration->client()->processDecorationButtonRelease(&e); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF localPos = event->globalPosF() - decoration->client()->pos(); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration.data(), &e); if (e.isAccepted()) { return true; } if ((orientation == Qt::Vertical) && decoration->client()->titlebarPositionUnderMouse()) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } if (input()->touch()->decorationPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } seat->setTimestamp(time); input()->touch()->update(pos); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationButtonRelease(&e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } auto seat = waylandServer()->seat(); seat->setFocusedKeyboardSurface(nullptr); // pass the key event to the seat, so that it has a proper model of the currently hold keys // this is important for combinations like alt+shift to ensure that shift is not considered pressed passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); } else if (input()->keyboard()->xkb()->modifiersRelevantForGlobalShortcuts() == Qt::NoModifier) { TabBox::TabBox::self()->modifiersReleased(); } return true; } bool wheelEvent(QWheelEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleWheelEvent(event); } }; #endif class ScreenEdgeInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) ScreenEdges::self()->isEntered(event); // always forward return false; } }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event **/ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (event->modifiers() == options->commandAllModifier()) { wasAction = true; switch (event->button()) { case Qt::LeftButton: command = options->commandAll1(); break; case Qt::MiddleButton: command = options->commandAll2(); break; case Qt::RightButton: command = options->commandAll3(); break; default: // nothing break; } } else { command = c->getMouseCommand(event->button(), &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (event->modifiers() == options->commandAllModifier()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } else { command = c->getWheelCommand(Qt::Vertical, &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } input()->touch()->update(pos); AbstractClient *c = dynamic_cast(input()->touch()->window().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows **/ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { - case QEvent::MouseMove: + case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } seat->setPointerPos(event->globalPos()); + MouseEvent *e = static_cast(event); + if (e->delta() != QSizeF()) { + seat->relativePointerMotion(e->delta(), e->deltaUnaccelerated(), e->timestampMicroseconds()); + } break; + } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); if (event->buttons() == Qt::NoButton) { input()->pointer()->update(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); passToWaylandServer(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } return true; } bool touchUp(quint32 id, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (Toplevel *t = input()->findToplevel(event->globalPos())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->raiseClient(c); } seat->setPointerPos(event->globalPos()); seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } seat->setPointerPos(event->globalPos()); break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } }; KWIN_SINGLETON_FACTORY(InputRedirection) InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #if HAVE_INPUT if (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { if (LogindIntegration::self()->isConnected()) { LogindIntegration::self()->takeControl(); } else { connect(LogindIntegration::self(), &LogindIntegration::connectedChanged, LogindIntegration::self(), &LogindIntegration::takeControl); } connect(LogindIntegration::self(), &LogindIntegration::hasSessionControlChanged, this, [this] (bool sessionControl) { if (sessionControl) { setupLibInput(); } } ); } m_inputConfig = KSharedConfig::openConfig(QStringLiteral("kcminputrc")); } #endif connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { m_filters << filter; } void InputRedirection::prepandInputEventFilter(InputEventFilter *filter) { m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeAll(filter); } void InputRedirection::init() { m_shortcuts->init(); } void InputRedirection::setupWorkspace() { if (waylandServer()) { using namespace KWayland::Server; FakeInputInterface *fakeInput = waylandServer()->display()->createFakeInput(this); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { connect(device, &FakeInputDevice::authenticationRequested, this, [this, device] (const QString &application, const QString &reason) { Q_UNUSED(application) Q_UNUSED(reason) // TODO: make secure device->setAuthentication(true); } ); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { // TODO: Fix time m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { // TODO: Fix time m_touch->processUp(id, 0); } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] () { m_touch->cancel(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] () { m_touch->frame(); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { #if HAVE_INPUT if (LogindIntegration::self()->hasSessionControl()) { installInputEventFilter(new VirtualTerminalFilter); } #endif if (waylandServer()) { installInputEventFilter(new TerminateServerFilter); installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); } installInputEventFilter(new ScreenEdgeInputFilter); installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif installInputEventFilter(new GlobalShortcutFilter); installInputEventFilter(new InternalWindowEventFilter); installInputEventFilter(new DecorationEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { #if HAVE_INPUT if (Application::usesLibinput()) { m_inputConfig->reparseConfiguration(); const auto config = m_inputConfig->group(QStringLiteral("keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } #endif } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { #if HAVE_INPUT if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { + + if (waylandServer()) { + // create relative pointer manager + waylandServer()->display()->createRelativePointerManager(KWayland::Server::RelativePointerInterfaceVersion::UnstableV1, waylandServer()->display())->create(); + } + conn->setInputConfig(m_inputConfig); conn->setup(); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::pinchGestureBegin, m_pointer, &PointerInputRedirection::processPinchGestureBegin); connect(conn, &LibInput::Connection::pinchGestureUpdate, m_pointer, &PointerInputRedirection::processPinchGestureUpdate); connect(conn, &LibInput::Connection::pinchGestureEnd, m_pointer, &PointerInputRedirection::processPinchGestureEnd); connect(conn, &LibInput::Connection::pinchGestureCancelled, m_pointer, &PointerInputRedirection::processPinchGestureCancelled); connect(conn, &LibInput::Connection::swipeGestureBegin, m_pointer, &PointerInputRedirection::processSwipeGestureBegin); connect(conn, &LibInput::Connection::swipeGestureUpdate, m_pointer, &PointerInputRedirection::processSwipeGestureUpdate); connect(conn, &LibInput::Connection::swipeGestureEnd, m_pointer, &PointerInputRedirection::processSwipeGestureEnd); connect(conn, &LibInput::Connection::swipeGestureCancelled, m_pointer, &PointerInputRedirection::processSwipeGestureCancelled); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, - [this] (QPointF delta, uint32_t time, LibInput::Device *device) { - m_pointer->processMotion(m_pointer->pos() + delta, time, device); + [this] (const QSizeF &delta, const QSizeF &deltaNonAccel, uint32_t time, quint64 timeMicroseconds, LibInput::Device *device) { + m_pointer->processMotion(m_pointer->pos() + QPointF(delta.width(), delta.height()), delta, deltaNonAccel, time, timeMicroseconds, device); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time, LibInput::Device *device) { Q_UNUSED(orig) m_pointer->processMotion(screen, time, device); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard s->setHasKeyboard(true); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasAlphaNumericKeyboardChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } // TODO: this should update the seat, only workaround for QTBUG-54371 emit hasAlphaNumericKeyboardChanged(set); } ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } #endif } bool InputRedirection::hasAlphaNumericKeyboard() { #if HAVE_INPUT if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } #endif return true; } void InputRedirection::setupLibInputWithScreens() { #if HAVE_INPUT if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); #endif } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { m_pointer->processAxis(axis, delta, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { m_shortcuts->registerShortcut(action, shortcut); registerShortcutForGlobalAccelTimestamp(action); } void InputRedirection::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { m_shortcuts->registerPointerShortcut(action, modifiers, pointerButtons); } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { m_shortcuts->registerAxisShortcut(action, modifiers, axis); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) { connect(action, &QAction::triggered, kwinApp(), [action] { QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { kwinApp()->setX11Time(t); } }); } void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) , m_input(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(pos.toPoint()); workspace()->updateFocusMousePosition(pos.toPoint()); } const QPointF p = pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), pos.toPoint()); } } void InputDeviceHandler::updateInternalWindow(const QPointF &pos) { const auto oldInternalWindow = m_internalWindow; bool found = false; // TODO: screen locked check without going through wayland server bool needsReset = waylandServer()->isScreenLocked(); const auto &internalClients = waylandServer()->internalClients(); const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (!internalClients.isEmpty() && change) { auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if ((*it)->geometry().contains(pos.toPoint())) { // check input mask const QRegion mask = w->mask().translated(w->geometry().topLeft()); if (!mask.isEmpty() && !mask.contains(pos.toPoint())) { continue; } m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { QEnterEvent event(pos - m_internalWindow->position(), pos - m_internalWindow->position(), pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); } emit internalWindowChanged(); } } } // namespace diff --git a/input_event.cpp b/input_event.cpp index 87fdb41a5..8de428459 100644 --- a/input_event.cpp +++ b/input_event.cpp @@ -1,50 +1,54 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "input_event.h" namespace KWin { MouseEvent::MouseEvent(QEvent::Type type, const QPointF &pos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, - quint32 timestamp, LibInput::Device *device) + quint32 timestamp, const QSizeF &delta, const QSizeF &deltaNonAccelerated, + quint64 timestampMicroseconds, LibInput::Device *device) : QMouseEvent(type, pos, pos, button, buttons, modifiers) + , m_delta(delta) + , m_deltaUnccelerated(deltaNonAccelerated) + , m_timestampMicroseconds(timestampMicroseconds) , m_device(device) { setTimestamp(timestamp); } WheelEvent::WheelEvent(const QPointF &pos, qreal delta, Qt::Orientation orientation, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, quint32 timestamp, LibInput::Device *device) : QWheelEvent(pos, pos, QPoint(), (orientation == Qt::Horizontal) ? QPoint(delta, 0) : QPoint(0, delta), delta, orientation, buttons, modifiers) , m_device(device) { setTimestamp(timestamp); } KeyEvent::KeyEvent(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers, quint32 code, quint32 keysym, const QString &text, bool autorepeat, quint32 timestamp, LibInput::Device *device) : QKeyEvent(type, key, modifiers, code, keysym, 0, text, autorepeat) , m_device(device) { setTimestamp(timestamp); } } diff --git a/input_event.h b/input_event.h index 1203a559a..bbb845da8 100644 --- a/input_event.h +++ b/input_event.h @@ -1,76 +1,93 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_INPUT_EVENT_H #define KWIN_INPUT_EVENT_H #include namespace KWin { namespace LibInput { class Device; } class MouseEvent : public QMouseEvent { public: explicit MouseEvent(QEvent::Type type, const QPointF &pos, Qt::MouseButton button, Qt::MouseButtons buttons, - Qt::KeyboardModifiers modifiers, quint32 timestamp, LibInput::Device *device); + Qt::KeyboardModifiers modifiers, quint32 timestamp, + const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestampMicroseconds, + LibInput::Device *device); + + QSizeF delta() const { + return m_delta; + } + + QSizeF deltaUnaccelerated() const { + return m_deltaUnccelerated; + } + + quint64 timestampMicroseconds() const { + return m_timestampMicroseconds; + } LibInput::Device *device() const { return m_device; } private: + QSizeF m_delta; + QSizeF m_deltaUnccelerated; + quint64 m_timestampMicroseconds; LibInput::Device *m_device; }; class WheelEvent : public QWheelEvent { public: explicit WheelEvent(const QPointF &pos, qreal delta, Qt::Orientation orientation, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, quint32 timestamp, LibInput::Device *device); LibInput::Device *device() const { return m_device; } private: LibInput::Device *m_device; }; class KeyEvent : public QKeyEvent { public: explicit KeyEvent(QEvent::Type type, Qt::Key key, Qt::KeyboardModifiers modifiers, quint32 code, quint32 keysym, const QString &text, bool autorepeat, quint32 timestamp, LibInput::Device *device); LibInput::Device *device() const { return m_device; } private: LibInput::Device *m_device; }; } #endif diff --git a/libinput/connection.cpp b/libinput/connection.cpp index 6831736b3..9cda9c169 100644 --- a/libinput/connection.cpp +++ b/libinput/connection.cpp @@ -1,517 +1,521 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin 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 . *********************************************************************/ #include "connection.h" #include "context.h" #include "device.h" #include "events.h" #include "../logind.h" #include "../udev.h" #include "libinput_logging.h" #include #include #include #include #include #include #include #include #include namespace KWin { namespace LibInput { Connection *Connection::s_self = nullptr; QThread *Connection::s_thread = nullptr; static Context *s_context = nullptr; Connection::Connection(QObject *parent) : Connection(nullptr, parent) { // only here to fix build, using will crash, BUG 343529 } Connection *Connection::create(QObject *parent) { Q_ASSERT(!s_self); static Udev s_udev; if (!s_udev.isValid()) { qCWarning(KWIN_LIBINPUT) << "Failed to initialize udev"; return nullptr; } if (!s_context) { s_context = new Context(s_udev); if (!s_context->isValid()) { qCWarning(KWIN_LIBINPUT) << "Failed to create context from udev"; delete s_context; s_context = nullptr; return nullptr; } // TODO: don't hardcode seat name if (!s_context->assignSeat("seat0")) { qCWarning(KWIN_LIBINPUT) << "Failed to assign seat seat0"; delete s_context; s_context = nullptr; return nullptr; } } s_thread = new QThread(); s_self = new Connection(s_context); s_self->moveToThread(s_thread); s_thread->start(); QObject::connect(s_thread, &QThread::finished, s_self, &QObject::deleteLater); QObject::connect(s_thread, &QThread::finished, s_thread, &QObject::deleteLater); QObject::connect(parent, &QObject::destroyed, s_thread, &QThread::quit); return s_self; } static const QString s_touchpadComponent = QStringLiteral("kcm_touchpad"); static const QString s_serviceName = QStringLiteral("org.kde.KWin.InputDevice"); Connection::Connection(Context *input, QObject *parent) : QObject(parent) , m_input(input) , m_notifier(nullptr) , m_mutex(QMutex::Recursive) { Q_ASSERT(m_input); // steal touchpad shortcuts QAction *touchpadToggleAction = new QAction(this); QAction *touchpadOnAction = new QAction(this); QAction *touchpadOffAction = new QAction(this); touchpadToggleAction->setObjectName(QStringLiteral("Toggle Touchpad")); touchpadToggleAction->setProperty("componentName", s_touchpadComponent); touchpadOnAction->setObjectName(QStringLiteral("Enable Touchpad")); touchpadOnAction->setProperty("componentName", s_touchpadComponent); touchpadOffAction->setObjectName(QStringLiteral("Disable Touchpad")); touchpadOffAction->setProperty("componentName", s_touchpadComponent); KGlobalAccel::self()->setDefaultShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setShortcut(touchpadToggleAction, QList{Qt::Key_TouchpadToggle}); KGlobalAccel::self()->setDefaultShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setShortcut(touchpadOnAction, QList{Qt::Key_TouchpadOn}); KGlobalAccel::self()->setDefaultShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); KGlobalAccel::self()->setShortcut(touchpadOffAction, QList{Qt::Key_TouchpadOff}); #ifndef KWIN_BUILD_TESTING InputRedirection::self()->registerShortcut(Qt::Key_TouchpadToggle, touchpadToggleAction); InputRedirection::self()->registerShortcut(Qt::Key_TouchpadOn, touchpadOnAction); InputRedirection::self()->registerShortcut(Qt::Key_TouchpadOff, touchpadOffAction); #endif connect(touchpadToggleAction, &QAction::triggered, this, &Connection::toggleTouchpads); connect(touchpadOnAction, &QAction::triggered, this, [this] { if (m_touchpadsEnabled) { return; } toggleTouchpads(); } ); connect(touchpadOffAction, &QAction::triggered, this, [this] { if (!m_touchpadsEnabled) { return; } toggleTouchpads(); } ); // need to connect to KGlobalSettings as the mouse KCM does not emit a dedicated signal QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"), this, SLOT(slotKGlobalSettingsNotifyChange(int,int))); QDBusConnection::sessionBus().registerService(s_serviceName); } Connection::~Connection() { QDBusConnection::sessionBus().unregisterService(s_serviceName); s_self = nullptr; delete s_context; s_context = nullptr; } void Connection::setup() { QMetaObject::invokeMethod(this, "doSetup", Qt::QueuedConnection); } void Connection::doSetup() { Q_ASSERT(!m_notifier); m_notifier = new QSocketNotifier(m_input->fileDescriptor(), QSocketNotifier::Read, this); connect(m_notifier, &QSocketNotifier::activated, this, &Connection::handleEvent); LogindIntegration *logind = LogindIntegration::self(); connect(logind, &LogindIntegration::sessionActiveChanged, this, [this](bool active) { if (active) { if (!m_input->isSuspended()) { return; } m_input->resume(); wasSuspended = true; } else { deactivate(); } } ); handleEvent(); } void Connection::deactivate() { if (m_input->isSuspended()) { return; } m_keyboardBeforeSuspend = hasKeyboard(); m_alphaNumericKeyboardBeforeSuspend = hasAlphaNumericKeyboard(); m_pointerBeforeSuspend = hasPointer(); m_touchBeforeSuspend = hasTouch(); m_input->suspend(); handleEvent(); } void Connection::handleEvent() { QMutexLocker locker(&m_mutex); const bool wasEmpty = m_eventQueue.isEmpty(); do { m_input->dispatch(); Event *event = m_input->event(); if (!event) { break; } m_eventQueue << event; } while (true); if (wasEmpty && !m_eventQueue.isEmpty()) { emit eventsRead(); } } void Connection::processEvents() { QMutexLocker locker(&m_mutex); while (!m_eventQueue.isEmpty()) { QScopedPointer event(m_eventQueue.takeFirst()); switch (event->type()) { case LIBINPUT_EVENT_DEVICE_ADDED: { auto device = new Device(event->nativeDevice()); device->moveToThread(s_thread); m_devices << device; if (device->isKeyboard()) { m_keyboard++; if (device->isAlphaNumericKeyboard()) { m_alphaNumericKeyboard++; if (m_alphaNumericKeyboard == 1) { emit hasAlphaNumericKeyboardChanged(true); } } if (m_keyboard == 1) { emit hasKeyboardChanged(true); } } if (device->isPointer()) { m_pointer++; if (m_pointer == 1) { emit hasPointerChanged(true); } } if (device->isTouch()) { m_touch++; if (m_touch == 1) { emit hasTouchChanged(true); } } applyDeviceConfig(device); emit deviceAdded(device); break; } case LIBINPUT_EVENT_DEVICE_REMOVED: { auto it = std::find_if(m_devices.begin(), m_devices.end(), [&event] (Device *d) { return event->device() == d; } ); if (it == m_devices.end()) { // we don't know this device break; } auto device = *it; m_devices.erase(it); emit deviceRemoved(device); if (device->isKeyboard()) { m_keyboard--; if (device->isAlphaNumericKeyboard()) { m_alphaNumericKeyboard--; if (m_alphaNumericKeyboard == 0) { emit hasAlphaNumericKeyboardChanged(false); } } if (m_keyboard == 0) { emit hasKeyboardChanged(false); } } if (device->isPointer()) { m_pointer--; if (m_pointer == 0) { emit hasPointerChanged(false); } } if (device->isTouch()) { m_touch--; if (m_touch == 0) { emit hasTouchChanged(false); } } device->deleteLater(); break; } case LIBINPUT_EVENT_KEYBOARD_KEY: { KeyEvent *ke = static_cast(event.data()); emit keyChanged(ke->key(), ke->state(), ke->time(), ke->device()); break; } case LIBINPUT_EVENT_POINTER_AXIS: { PointerEvent *pe = static_cast(event.data()); struct Axis { qreal delta = 0.0; quint32 time = 0; }; QMap deltas; auto update = [&deltas] (PointerEvent *pe) { const auto axis = pe->axis(); for (auto it = axis.begin(); it != axis.end(); ++it) { deltas[*it].delta += pe->axisValue(*it); deltas[*it].time = pe->time(); } }; update(pe); auto it = m_eventQueue.begin(); while (it != m_eventQueue.end()) { if ((*it)->type() == LIBINPUT_EVENT_POINTER_AXIS) { QScopedPointer p(static_cast(*it)); update(p.data()); it = m_eventQueue.erase(it); } else { break; } } for (auto it = deltas.constBegin(); it != deltas.constEnd(); ++it) { emit pointerAxisChanged(it.key(), it.value().delta, it.value().time, pe->device()); } break; } case LIBINPUT_EVENT_POINTER_BUTTON: { PointerEvent *pe = static_cast(event.data()); emit pointerButtonChanged(pe->button(), pe->buttonState(), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_POINTER_MOTION: { PointerEvent *pe = static_cast(event.data()); - QPointF delta = pe->delta(); + auto delta = pe->delta(); + auto deltaNonAccel = pe->deltaUnaccelerated(); quint32 latestTime = pe->time(); + quint64 latestTimeUsec = pe->timeMicroseconds(); auto it = m_eventQueue.begin(); while (it != m_eventQueue.end()) { if ((*it)->type() == LIBINPUT_EVENT_POINTER_MOTION) { QScopedPointer p(static_cast(*it)); delta += p->delta(); + deltaNonAccel += p->deltaUnaccelerated(); latestTime = p->time(); + latestTimeUsec = p->timeMicroseconds(); it = m_eventQueue.erase(it); } else { break; } } - emit pointerMotion(delta, latestTime, pe->device()); + emit pointerMotion(delta, deltaNonAccel, latestTime, latestTimeUsec, pe->device()); break; } case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: { PointerEvent *pe = static_cast(event.data()); emit pointerMotionAbsolute(pe->absolutePos(), pe->absolutePos(m_size), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_TOUCH_DOWN: { TouchEvent *te = static_cast(event.data()); emit touchDown(te->id(), te->absolutePos(m_size), te->time(), te->device()); break; } 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: { TouchEvent *te = static_cast(event.data()); emit touchMotion(te->id(), te->absolutePos(m_size), te->time(), te->device()); break; } case LIBINPUT_EVENT_TOUCH_CANCEL: { emit touchCanceled(event->device()); break; } case LIBINPUT_EVENT_TOUCH_FRAME: { emit touchFrame(event->device()); break; } case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: { PinchGestureEvent *pe = static_cast(event.data()); emit pinchGestureBegin(pe->fingerCount(), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: { PinchGestureEvent *pe = static_cast(event.data()); emit pinchGestureUpdate(pe->scale(), pe->angleDelta(), pe->delta(), pe->time(), pe->device()); break; } case LIBINPUT_EVENT_GESTURE_PINCH_END: { PinchGestureEvent *pe = static_cast(event.data()); if (pe->isCancelled()) { emit pinchGestureCancelled(pe->time(), pe->device()); } else { emit pinchGestureEnd(pe->time(), pe->device()); } break; } case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: { SwipeGestureEvent *se = static_cast(event.data()); emit swipeGestureBegin(se->fingerCount(), se->time(), se->device()); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: { SwipeGestureEvent *se = static_cast(event.data()); emit swipeGestureUpdate(se->delta(), se->time(), se->device()); break; } case LIBINPUT_EVENT_GESTURE_SWIPE_END: { SwipeGestureEvent *se = static_cast(event.data()); if (se->isCancelled()) { emit swipeGestureCancelled(se->time(), se->device()); } else { emit swipeGestureEnd(se->time(), se->device()); } break; } default: // nothing break; } } if (wasSuspended) { if (m_keyboardBeforeSuspend && !m_keyboard) { emit hasKeyboardChanged(false); } if (m_alphaNumericKeyboardBeforeSuspend && !m_alphaNumericKeyboard) { emit hasAlphaNumericKeyboardChanged(false); } if (m_pointerBeforeSuspend && !m_pointer) { emit hasPointerChanged(false); } if (m_touchBeforeSuspend && !m_touch) { emit hasTouchChanged(false); } wasSuspended = false; } } void Connection::setScreenSize(const QSize &size) { m_size = size; } bool Connection::isSuspended() const { if (!s_context) { return false; } return s_context->isSuspended(); } void Connection::applyDeviceConfig(Device *device) { if (device->isPointer()) { const KConfigGroup group = m_config->group("Mouse"); device->setLeftHanded(group.readEntry("MouseButtonMapping", "RightHanded") == QLatin1String("LeftHanded")); qreal accel = group.readEntry("Acceleration", -1.0); if (qFuzzyCompare(accel, -1.0) || qFuzzyCompare(accel, 1.0)) { // default value device->setPointerAcceleration(0.0); } else { // the X11-based config is mapped in [0.1,20.0] with 1.0 being the "normal" setting - we assume that's the default if (accel < 1.0) { device->setPointerAcceleration(-1.0 + ((accel * 10.0) - 1.0) / 9.0); } else { device->setPointerAcceleration((accel -1.0)/19.0); } } } } void Connection::slotKGlobalSettingsNotifyChange(int type, int arg) { if (type == 3 /**SettingsChanged**/ && arg == 0 /** SETTINGS_MOUSE **/) { m_config->reparseConfiguration(); for (auto it = m_devices.constBegin(), end = m_devices.constEnd(); it != end; ++it) { if ((*it)->isPointer()) { applyDeviceConfig(*it); } } } } void Connection::toggleTouchpads() { bool changed = false; m_touchpadsEnabled = !m_touchpadsEnabled; for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) { auto device = *it; if (!device->isPointer()) { continue; } if (device->isKeyboard() || device->isTouch() || device->isTabletPad() || device->isTabletTool()) { // ignore all combined devices. E.g. a touchpad on a keyboard we don't want to toggle // as that would result in the keyboard going off as well continue; } // is this a touch pad? We don't really know, let's do some assumptions if (device->tapFingerCount() > 0 || device->supportsDisableWhileTyping() || device->supportsDisableEventsOnExternalMouse()) { const bool old = device->isEnabled(); device->setEnabled(m_touchpadsEnabled); if (old != device->isEnabled()) { changed = true; } } } if (changed) { // send OSD message QDBusMessage msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.plasmashell"), QStringLiteral("/org/kde/osdService"), QStringLiteral("org.kde.osdService"), QStringLiteral("touchpadEnabledChanged") ); msg.setArguments({m_touchpadsEnabled}); QDBusConnection::sessionBus().asyncCall(msg); } } } } diff --git a/libinput/connection.h b/libinput/connection.h index 03996d3fb..4ad0f464e 100644 --- a/libinput/connection.h +++ b/libinput/connection.h @@ -1,146 +1,146 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_LIBINPUT_CONNECTION_H #define KWIN_LIBINPUT_CONNECTION_H #include "../input.h" #include #include #include #include #include class QSocketNotifier; class QThread; namespace KWin { namespace LibInput { class Event; class Device; class Context; class Connection : public QObject { Q_OBJECT public: ~Connection(); void setInputConfig(const KSharedConfigPtr &config) { m_config = config; } void setup(); /** * Sets the screen @p size. This is needed for mapping absolute pointer events to * the screen data. **/ void setScreenSize(const QSize &size); bool hasKeyboard() const { return m_keyboard > 0; } bool hasAlphaNumericKeyboard() const { return m_alphaNumericKeyboard > 0; } bool hasTouch() const { return m_touch > 0; } bool hasPointer() const { return m_pointer > 0; } bool isSuspended() const; void deactivate(); void processEvents(); void toggleTouchpads(); QVector devices() const { return m_devices; } Q_SIGNALS: void keyChanged(quint32 key, KWin::InputRedirection::KeyboardKeyState, quint32 time, KWin::LibInput::Device *device); void pointerButtonChanged(quint32 button, KWin::InputRedirection::PointerButtonState state, quint32 time, KWin::LibInput::Device *device); void pointerMotionAbsolute(QPointF orig, QPointF screen, quint32 time, KWin::LibInput::Device *device); - void pointerMotion(QPointF delta, quint32 time, KWin::LibInput::Device *device); + void pointerMotion(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device); void pointerAxisChanged(KWin::InputRedirection::PointerAxis axis, qreal delta, quint32 time, KWin::LibInput::Device *device); void touchFrame(KWin::LibInput::Device *device); void touchCanceled(KWin::LibInput::Device *device); void touchDown(qint32 id, const QPointF &absolutePos, quint32 time, KWin::LibInput::Device *device); void touchUp(qint32 id, quint32 time, KWin::LibInput::Device *device); void touchMotion(qint32 id, const QPointF &absolutePos, quint32 time, KWin::LibInput::Device *device); void hasKeyboardChanged(bool); void hasAlphaNumericKeyboardChanged(bool); void hasPointerChanged(bool); void hasTouchChanged(bool); void deviceAdded(KWin::LibInput::Device *); void deviceRemoved(KWin::LibInput::Device *); void swipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device); void swipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device); void swipeGestureEnd(quint32 time, KWin::LibInput::Device *device); void swipeGestureCancelled(quint32 time, KWin::LibInput::Device *device); void pinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device); 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 eventsRead(); private Q_SLOTS: void doSetup(); void slotKGlobalSettingsNotifyChange(int type, int arg); private: Connection(Context *input, QObject *parent = nullptr); void handleEvent(); void applyDeviceConfig(Device *device); Context *m_input; QSocketNotifier *m_notifier; QSize m_size; int m_keyboard = 0; int m_alphaNumericKeyboard = 0; int m_pointer = 0; int m_touch = 0; bool m_keyboardBeforeSuspend = false; bool m_alphaNumericKeyboardBeforeSuspend = false; bool m_pointerBeforeSuspend = false; bool m_touchBeforeSuspend = false; QMutex m_mutex; QVector m_eventQueue; bool wasSuspended = false; QVector m_devices; KSharedConfigPtr m_config; bool m_touchpadsEnabled = true; KWIN_SINGLETON(Connection) static QThread *s_thread; }; } } #endif diff --git a/libinput/events.cpp b/libinput/events.cpp index 1ebb20359..d0539c3d2 100644 --- a/libinput/events.cpp +++ b/libinput/events.cpp @@ -1,274 +1,285 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin 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 . *********************************************************************/ #include "events.h" #include "device.h" #include namespace KWin { namespace LibInput { Event *Event::create(libinput_event *event) { if (!event) { return nullptr; } const auto t = libinput_event_get_type(event); // TODO: add touch events // TODO: add device notify events switch (t) { case LIBINPUT_EVENT_KEYBOARD_KEY: return new KeyEvent(event); case LIBINPUT_EVENT_POINTER_AXIS: case LIBINPUT_EVENT_POINTER_BUTTON: case LIBINPUT_EVENT_POINTER_MOTION: case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: return new PointerEvent(event, t); case LIBINPUT_EVENT_TOUCH_DOWN: case LIBINPUT_EVENT_TOUCH_UP: case LIBINPUT_EVENT_TOUCH_MOTION: case LIBINPUT_EVENT_TOUCH_CANCEL: case LIBINPUT_EVENT_TOUCH_FRAME: return new TouchEvent(event, t); case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: case LIBINPUT_EVENT_GESTURE_SWIPE_END: return new SwipeGestureEvent(event, t); case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: case LIBINPUT_EVENT_GESTURE_PINCH_END: return new PinchGestureEvent(event, t); default: return new Event(event, t); } } Event::Event(libinput_event *event, libinput_event_type type) : m_event(event) , m_type(type) , m_device(Device::getDevice(libinput_event_get_device(m_event))) { } Event::~Event() { libinput_event_destroy(m_event); } libinput_device *Event::nativeDevice() const { if (m_device) { return m_device->device(); } return libinput_event_get_device(m_event); } KeyEvent::KeyEvent(libinput_event *event) : Event(event, LIBINPUT_EVENT_KEYBOARD_KEY) , m_keyboardEvent(libinput_event_get_keyboard_event(event)) { } KeyEvent::~KeyEvent() = default; uint32_t KeyEvent::key() const { return libinput_event_keyboard_get_key(m_keyboardEvent); } InputRedirection::KeyboardKeyState KeyEvent::state() const { switch (libinput_event_keyboard_get_key_state(m_keyboardEvent)) { case LIBINPUT_KEY_STATE_PRESSED: return InputRedirection::KeyboardKeyPressed; case LIBINPUT_KEY_STATE_RELEASED: return InputRedirection::KeyboardKeyReleased; } abort(); } uint32_t KeyEvent::time() const { return libinput_event_keyboard_get_time(m_keyboardEvent); } PointerEvent::PointerEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_pointerEvent(libinput_event_get_pointer_event(event)) { } PointerEvent::~PointerEvent() = default; QPointF PointerEvent::absolutePos() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); return QPointF(libinput_event_pointer_get_absolute_x(m_pointerEvent), libinput_event_pointer_get_absolute_y(m_pointerEvent)); } QPointF PointerEvent::absolutePos(const QSize &size) const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); return QPointF(libinput_event_pointer_get_absolute_x_transformed(m_pointerEvent, size.width()), libinput_event_pointer_get_absolute_y_transformed(m_pointerEvent, size.height())); } -QPointF PointerEvent::delta() const +QSizeF PointerEvent::delta() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION); - return QPointF(libinput_event_pointer_get_dx(m_pointerEvent), libinput_event_pointer_get_dy(m_pointerEvent)); + return QSizeF(libinput_event_pointer_get_dx(m_pointerEvent), libinput_event_pointer_get_dy(m_pointerEvent)); +} + +QSizeF PointerEvent::deltaUnaccelerated() const +{ + Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION); + return QSizeF(libinput_event_pointer_get_dx_unaccelerated(m_pointerEvent), libinput_event_pointer_get_dy_unaccelerated(m_pointerEvent)); } uint32_t PointerEvent::time() const { return libinput_event_pointer_get_time(m_pointerEvent); } +quint64 PointerEvent::timeMicroseconds() const +{ + return libinput_event_pointer_get_time_usec(m_pointerEvent); +} + uint32_t PointerEvent::button() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON); return libinput_event_pointer_get_button(m_pointerEvent); } InputRedirection::PointerButtonState PointerEvent::buttonState() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON); switch (libinput_event_pointer_get_button_state(m_pointerEvent)) { case LIBINPUT_BUTTON_STATE_PRESSED: return InputRedirection::PointerButtonPressed; case LIBINPUT_BUTTON_STATE_RELEASED: return InputRedirection::PointerButtonReleased; } abort(); } QVector PointerEvent::axis() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS); QVector a; if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) { a << InputRedirection::PointerAxisHorizontal; } if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) { a << InputRedirection::PointerAxisVertical; } return a; } qreal PointerEvent::axisValue(InputRedirection::PointerAxis axis) const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS); const libinput_pointer_axis a = axis == InputRedirection::PointerAxisHorizontal ? LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL : LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL; return libinput_event_pointer_get_axis_value(m_pointerEvent, a); } TouchEvent::TouchEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_touchEvent(libinput_event_get_touch_event(event)) { } TouchEvent::~TouchEvent() = default; quint32 TouchEvent::time() const { return libinput_event_touch_get_time(m_touchEvent); } QPointF TouchEvent::absolutePos() const { Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION); return QPointF(libinput_event_touch_get_x(m_touchEvent), libinput_event_touch_get_y(m_touchEvent)); } QPointF TouchEvent::absolutePos(const QSize &size) const { Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION); return QPointF(libinput_event_touch_get_x_transformed(m_touchEvent, size.width()), libinput_event_touch_get_y_transformed(m_touchEvent, size.height())); } qint32 TouchEvent::id() const { Q_ASSERT(type() != LIBINPUT_EVENT_TOUCH_CANCEL && type() != LIBINPUT_EVENT_TOUCH_FRAME); return libinput_event_touch_get_slot(m_touchEvent); } GestureEvent::GestureEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_gestureEvent(libinput_event_get_gesture_event(event)) { } GestureEvent::~GestureEvent() = default; quint32 GestureEvent::time() const { return libinput_event_gesture_get_time(m_gestureEvent); } int GestureEvent::fingerCount() const { return libinput_event_gesture_get_finger_count(m_gestureEvent); } QSizeF GestureEvent::delta() const { return QSizeF(libinput_event_gesture_get_dx(m_gestureEvent), libinput_event_gesture_get_dy(m_gestureEvent)); } bool GestureEvent::isCancelled() const { return libinput_event_gesture_get_cancelled(m_gestureEvent) != 0; } PinchGestureEvent::PinchGestureEvent(libinput_event *event, libinput_event_type type) : GestureEvent(event, type) { } PinchGestureEvent::~PinchGestureEvent() = default; qreal PinchGestureEvent::scale() const { return libinput_event_gesture_get_scale(m_gestureEvent); } qreal PinchGestureEvent::angleDelta() const { return libinput_event_gesture_get_angle_delta(m_gestureEvent); } SwipeGestureEvent::SwipeGestureEvent(libinput_event *event, libinput_event_type type) : GestureEvent(event, type) { } SwipeGestureEvent::~SwipeGestureEvent() = default; } } diff --git a/libinput/events.h b/libinput/events.h index bc8baaa7a..646844cb7 100644 --- a/libinput/events.h +++ b/libinput/events.h @@ -1,182 +1,184 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_LIBINPUT_EVENTS_H #define KWIN_LIBINPUT_EVENTS_H #include "../input.h" #include namespace KWin { namespace LibInput { class Device; class Event { public: virtual ~Event(); libinput_event_type type() const; Device *device() const { return m_device; } libinput_device *nativeDevice() const; operator libinput_event*() { return m_event; } operator libinput_event*() const { return m_event; } static Event *create(libinput_event *event); protected: Event(libinput_event *event, libinput_event_type type); private: libinput_event *m_event; libinput_event_type m_type; Device *m_device; }; class KeyEvent : public Event { public: KeyEvent(libinput_event *event); virtual ~KeyEvent(); uint32_t key() const; InputRedirection::KeyboardKeyState state() const; uint32_t time() const; operator libinput_event_keyboard*() { return m_keyboardEvent; } operator libinput_event_keyboard*() const { return m_keyboardEvent; } private: libinput_event_keyboard *m_keyboardEvent; }; class PointerEvent : public Event { public: PointerEvent(libinput_event* event, libinput_event_type type); virtual ~PointerEvent(); QPointF absolutePos() const; QPointF absolutePos(const QSize &size) const; - QPointF delta() const; + QSizeF delta() const; + QSizeF deltaUnaccelerated() const; uint32_t button() const; InputRedirection::PointerButtonState buttonState() const; uint32_t time() const; + quint64 timeMicroseconds() const; QVector axis() const; qreal axisValue(InputRedirection::PointerAxis a) const; operator libinput_event_pointer*() { return m_pointerEvent; } operator libinput_event_pointer*() const { return m_pointerEvent; } private: libinput_event_pointer *m_pointerEvent; }; class TouchEvent : public Event { public: TouchEvent(libinput_event *event, libinput_event_type type); virtual ~TouchEvent(); quint32 time() const; QPointF absolutePos() const; QPointF absolutePos(const QSize &size) const; qint32 id() const; operator libinput_event_touch*() { return m_touchEvent; } operator libinput_event_touch*() const { return m_touchEvent; } private: libinput_event_touch *m_touchEvent; }; class GestureEvent : public Event { public: virtual ~GestureEvent(); quint32 time() const; int fingerCount() const; QSizeF delta() const; bool isCancelled() const; operator libinput_event_gesture*() { return m_gestureEvent; } operator libinput_event_gesture*() const { return m_gestureEvent; } protected: GestureEvent(libinput_event *event, libinput_event_type type); libinput_event_gesture *m_gestureEvent; }; class PinchGestureEvent : public GestureEvent { public: PinchGestureEvent(libinput_event *event, libinput_event_type type); virtual ~PinchGestureEvent(); qreal scale() const; qreal angleDelta() const; }; class SwipeGestureEvent : public GestureEvent { public: SwipeGestureEvent(libinput_event *event, libinput_event_type type); virtual ~SwipeGestureEvent(); }; inline libinput_event_type Event::type() const { return m_type; } } } #endif diff --git a/pointer_input.cpp b/pointer_input.cpp index 7716a4e61..c8cd4ce2f 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -1,968 +1,974 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin 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 . *********************************************************************/ #include "pointer_input.h" #include "platform.h" #include "effects.h" #include "input_event.h" #include "screens.h" #include "shell_client.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" // KDecoration #include // KWayland #include #include #include #include #include #include #include // screenlocker #include #include #include // Wayland #include #include namespace KWin { static Qt::MouseButton buttonToQtMouseButton(uint32_t button) { switch (button) { case BTN_LEFT: return Qt::LeftButton; case BTN_MIDDLE: return Qt::MiddleButton; case BTN_RIGHT: return Qt::RightButton; case BTN_SIDE: // in QtWayland mapped like that return Qt::ExtraButton1; case BTN_EXTRA: // in QtWayland mapped like that return Qt::ExtraButton2; case BTN_BACK: return Qt::BackButton; case BTN_FORWARD: return Qt::ForwardButton; case BTN_TASK: return Qt::TaskButton; // mapped like that in QtWayland case 0x118: return Qt::ExtraButton6; case 0x119: return Qt::ExtraButton7; case 0x11a: return Qt::ExtraButton8; case 0x11b: return Qt::ExtraButton9; case 0x11c: return Qt::ExtraButton10; case 0x11d: return Qt::ExtraButton11; case 0x11e: return Qt::ExtraButton12; case 0x11f: return Qt::ExtraButton13; } // all other values get mapped to ExtraButton24 // this is actually incorrect but doesn't matter in our usage // KWin internally doesn't use these high extra buttons anyway // it's only needed for recognizing whether buttons are pressed // if multiple buttons are mapped to the value the evaluation whether // buttons are pressed is correct and that's all we care about. return Qt::ExtraButton24; } static bool screenContainsPos(const QPointF &pos) { for (int i = 0; i < screens()->count(); ++i) { if (screens()->geometry(i).contains(pos.toPoint())) { return true; } } return false; } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) : InputDeviceHandler(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { } PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { Q_ASSERT(!m_inited); m_cursor = new CursorImage(this); m_inited = true; connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); } connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); m_window.clear(); update(); } ); connect(this, &PointerInputRedirection::internalWindowChanged, this, [this] { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); if (m_internalWindow) { m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this, [this] (bool visible) { if (!visible) { update(); } } ); } } ); // warp the cursor to center of screen warp(screens()->geometry().center()); updateAfterScreenChange(); } void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time, LibInput::Device *device) +{ + processMotion(pos, QSizeF(), QSizeF(), time, 0, device); +} + +void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device) { if (!m_inited) { return; } updatePosition(pos); MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons, - m_input->keyboardModifiers(), time, device); + m_input->keyboardModifiers(), time, + delta, deltaNonAccelerated, timeUsec, device); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, 0)) { return; } } } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device) { updateButton(button, state); if (!m_inited) { return; } QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: type = QEvent::MouseButtonRelease; break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; break; default: Q_UNREACHABLE(); return; } MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons, - m_input->keyboardModifiers(), time, device); + m_input->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, button)) { return; } } } void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time, LibInput::Device *device) { if (delta == 0) { return; } emit m_input->pointerAxisChanged(axis, delta); if (!m_inited) { return; } WheelEvent wheelEvent(m_pos, delta, (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, m_qtButtons, m_input->keyboardModifiers(), time, device); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->wheelEvent(&wheelEvent)) { return; } } } void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->swipeGestureBegin(fingerCount, time)) { return; } } } void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->swipeGestureUpdate(delta, time)) { return; } } } void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->swipeGestureEnd(time)) { return; } } } void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->swipeGestureCancelled(time)) { return; } } } void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pinchGestureBegin(fingerCount, time)) { return; } } } void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pinchGestureUpdate(scale, angleDelta, delta, time)) { return; } } } void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pinchGestureEnd(time)) { return; } } } void PointerInputRedirection::processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pinchGestureCancelled(time)) { return; } } } void PointerInputRedirection::update() { if (!m_inited) { return; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop return; } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); const auto oldDeco = m_decoration; updateInternalWindow(m_pos); if (!m_internalWindow) { updateDecoration(t, m_pos); } else { updateDecoration(waylandServer()->findClient(m_internalWindow), m_pos); if (m_decoration) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(m_internalWindow.data(), &event); m_internalWindow.clear(); } } if (m_decoration || m_internalWindow) { t = nullptr; } if (m_decoration != oldDeco) { emit decorationChanged(); } auto oldWindow = m_window; if (!oldWindow.isNull() && t == m_window.data()) { return; } auto seat = waylandServer()->seat(); // disconnect old surface if (oldWindow) { if (AbstractClient *c = qobject_cast(oldWindow.data())) { c->leaveEvent(); } disconnect(m_windowGeometryConnection); m_windowGeometryConnection = QMetaObject::Connection(); } if (AbstractClient *c = qobject_cast(t)) { // only send enter if it wasn't on deco for the same client before if (m_decoration.isNull() || m_decoration->client() != c) { c->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } } if (t && t->surface()) { m_window = QPointer(t); // TODO: add convenient API to update global pos together with updating focused surface warpXcbOnSurfaceLeft(t->surface()); seat->setFocusedPointerSurface(nullptr); seat->setPointerPos(m_pos.toPoint()); seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, [this] { if (m_window.isNull()) { return; } // TODO: can we check on the client instead? if (workspace()->getMovingClient()) { // don't update while moving return; } auto seat = waylandServer()->seat(); if (m_window.data()->surface() != seat->focusedPointerSurface()) { return; } seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation()); } ); } else { m_window.clear(); warpXcbOnSurfaceLeft(nullptr); seat->setFocusedPointerSurface(nullptr); t = nullptr; } } void PointerInputRedirection::warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInterface *newSurface) { auto xc = waylandServer()->xWaylandConnection(); if (!xc) { // No XWayland, no point in warping the x cursor return; } if (!kwinApp()->x11Connection()) { return; } static bool s_hasXWayland119 = xcb_get_setup(kwinApp()->x11Connection())->release_number >= 11900000; if (s_hasXWayland119) { return; } if (newSurface && newSurface->client() == xc) { // new window is an X window return; } auto s = waylandServer()->seat()->focusedPointerSurface(); if (!s || s->client() != xc) { // pointer was not on an X window return; } // warp pointer to 0/0 to trigger leave events on previously focused X window xcb_warp_pointer(connection(), XCB_WINDOW_NONE, rootWindow(), 0, 0, 0, 0, 0, 0), xcb_flush(connection()); } void PointerInputRedirection::updatePosition(const QPointF &pos) { // verify that at least one screen contains the pointer position QPointF p = pos; if (!screenContainsPos(p)) { // allow either x or y to pass p = QPointF(m_pos.x(), pos.y()); if (!screenContainsPos(p)) { p = QPointF(pos.x(), m_pos.y()); if (!screenContainsPos(p)) { return; } } } m_pos = p; emit m_input->globalPointerChanged(m_pos); } void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state) { m_buttons[button] = state; // update Qt buttons m_qtButtons = Qt::NoButton; for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) { if (it.value() == InputRedirection::PointerButtonReleased) { continue; } m_qtButtons |= buttonToQtMouseButton(it.key()); } emit m_input->pointerButtonStateChanged(button, state); } void PointerInputRedirection::warp(const QPointF &pos) { if (supportsWarping()) { kwinApp()->platform()->warpPointer(pos); processMotion(pos, waylandServer()->seat()->timestamp()); } } bool PointerInputRedirection::supportsWarping() const { if (!m_inited) { return false; } if (m_supportsWarping) { return true; } if (kwinApp()->platform()->supportsPointerWarping()) { return true; } return false; } void PointerInputRedirection::updateAfterScreenChange() { if (!m_inited) { return; } if (screenContainsPos(m_pos)) { // pointer still on a screen return; } // pointer no longer on a screen, reposition to closes screen const QPointF pos = screens()->geometry(screens()->number(m_pos.toPoint())).center(); // TODO: better way to get timestamps processMotion(pos, waylandServer()->seat()->timestamp()); } QImage PointerInputRedirection::cursorImage() const { if (!m_inited) { return QImage(); } return m_cursor->image(); } QPoint PointerInputRedirection::cursorHotSpot() const { if (!m_inited) { return QPoint(); } return m_cursor->hotSpot(); } void PointerInputRedirection::markCursorAsRendered() { if (!m_inited) { return; } m_cursor->markAsRendered(); } void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { if (!m_inited) { return; } // current pointer focus window should get a leave event update(); m_cursor->setEffectsOverrideCursor(shape); } void PointerInputRedirection::removeEffectsOverrideCursor() { if (!m_inited) { return; } // cursor position might have changed while there was an effect in place update(); m_cursor->removeEffectsOverrideCursor(); } CursorImage::CursorImage(PointerInputRedirection *parent) : QObject(parent) , m_pointer(parent) { connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &CursorImage::updateDrag); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { disconnect(m_drag.connection); reevaluteSource(); } ); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); } connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection); connect(waylandServer(), &WaylandServer::shellClientAdded, this, setupMoveResizeConnection); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); if (m_cursorTheme) { connect(m_cursorTheme, &WaylandCursorTheme::themeChanged, this, [this] { m_cursors.clear(); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); updateDecorationCursor(); updateMoveResize(); // TODO: update effects } ); } m_surfaceRenderedTimer.start(); } CursorImage::~CursorImage() = default; void CursorImage::markAsRendered() { if (m_currentSource == CursorSource::DragAndDrop) { // always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 ) if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto s = ddi->icon()) { s->frameRendered(m_surfaceRenderedTimer.elapsed()); } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); return; } if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) { return; } auto p = waylandServer()->seat()->focusedPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); } void CursorImage::update() { using namespace KWayland::Server; disconnect(m_serverCursor.connection); auto p = waylandServer()->seat()->focusedPointer(); if (p) { m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor); } else { m_serverCursor.connection = QMetaObject::Connection(); } updateServerCursor(); } void CursorImage::updateDecoration() { disconnect(m_decorationConnection); auto deco = m_pointer->decoration(); AbstractClient *c = deco.isNull() ? nullptr : deco->client(); if (c) { m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor); } else { m_decorationConnection = QMetaObject::Connection(); } updateDecorationCursor(); } void CursorImage::updateDecorationCursor() { m_decorationCursor.image = QImage(); m_decorationCursor.hotSpot = QPoint(); auto deco = m_pointer->decoration(); if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) { loadThemeCursor(c->cursor(), &m_decorationCursor); if (m_currentSource == CursorSource::Decoration) { emit changed(); } } reevaluteSource(); } void CursorImage::updateMoveResize() { m_moveResizeCursor.image = QImage(); m_moveResizeCursor.hotSpot = QPoint(); if (AbstractClient *c = workspace()->getMovingClient()) { loadThemeCursor(c->isMove() ? Qt::SizeAllCursor : Qt::SizeBDiagCursor, &m_moveResizeCursor); if (m_currentSource == CursorSource::MoveResize) { emit changed(); } } reevaluteSource(); } void CursorImage::updateServerCursor() { m_serverCursor.image = QImage(); m_serverCursor.hotSpot = QPoint(); reevaluteSource(); const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface; auto p = waylandServer()->seat()->focusedPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_serverCursor.hotSpot = c->hotspot(); m_serverCursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } } void CursorImage::loadTheme() { if (m_cursorTheme) { return; } // check whether we can create it if (waylandServer()->internalShmPool()) { m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this); connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, [this] { delete m_cursorTheme; m_cursorTheme = nullptr; } ); } } void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { loadThemeCursor(shape, &m_effectsCursor); if (m_currentSource == CursorSource::EffectsOverride) { emit changed(); } reevaluteSource(); } void CursorImage::removeEffectsOverrideCursor() { reevaluteSource(); } void CursorImage::updateDrag() { using namespace KWayland::Server; disconnect(m_drag.connection); m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); reevaluteSource(); if (auto p = waylandServer()->seat()->dragPointer()) { m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor); } else { m_drag.connection = QMetaObject::Connection(); } updateDragCursor(); } void CursorImage::updateDragCursor() { m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); const bool needsEmit = m_currentSource == CursorSource::DragAndDrop; QImage additionalIcon; if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto dragIcon = ddi->icon()) { if (auto buffer = dragIcon->buffer()) { additionalIcon = buffer->data().copy(); } } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_drag.cursor.hotSpot = c->hotspot(); m_drag.cursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } // TODO: add the cursor image } void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image) { loadTheme(); if (!m_cursorTheme) { return; } auto it = m_cursors.constFind(shape); if (it == m_cursors.constEnd()) { image->image = QImage(); image->hotSpot = QPoint(); wl_cursor_image *cursor = m_cursorTheme->get(shape); if (!cursor) { return; } wl_buffer *b = wl_cursor_image_get_buffer(cursor); if (!b) { return; } waylandServer()->internalClientConection()->flush(); waylandServer()->dispatch(); auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b))); if (!buffer) { return; } it = decltype(it)(m_cursors.insert(shape, {buffer->data().copy(), QPoint(cursor->hotspot_x, cursor->hotspot_y)})); } image->hotSpot = it.value().hotSpot; image->image = it.value().image; } void CursorImage::reevaluteSource() { if (waylandServer()->seat()->isDragPointer()) { // TODO: touch drag? setSource(CursorSource::DragAndDrop); return; } if (waylandServer()->isScreenLocked()) { setSource(CursorSource::LockScreen); return; } if (effects && static_cast(effects)->isMouseInterception()) { setSource(CursorSource::EffectsOverride); return; } if (workspace() && workspace()->getMovingClient()) { setSource(CursorSource::MoveResize); return; } if (!m_pointer->decoration().isNull()) { setSource(CursorSource::Decoration); return; } if (!m_pointer->window().isNull()) { setSource(CursorSource::PointerSurface); return; } setSource(CursorSource::Fallback); } void CursorImage::setSource(CursorSource source) { if (m_currentSource == source) { return; } m_currentSource = source; emit changed(); } QImage CursorImage::image() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.image; case CursorSource::MoveResize: return m_moveResizeCursor.image; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.image; case CursorSource::Decoration: return m_decorationCursor.image; case CursorSource::DragAndDrop: return m_drag.cursor.image; case CursorSource::Fallback: return m_fallbackCursor.image; default: Q_UNREACHABLE(); } } QPoint CursorImage::hotSpot() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.hotSpot; case CursorSource::MoveResize: return m_moveResizeCursor.hotSpot; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.hotSpot; case CursorSource::Decoration: return m_decorationCursor.hotSpot; case CursorSource::DragAndDrop: return m_drag.cursor.hotSpot; case CursorSource::Fallback: return m_fallbackCursor.hotSpot; default: Q_UNREACHABLE(); } } } diff --git a/pointer_input.h b/pointer_input.h index cd730e9ac..8e4015349 100644 --- a/pointer_input.h +++ b/pointer_input.h @@ -1,212 +1,216 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin 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 . *********************************************************************/ #ifndef KWIN_POINTER_INPUT_H #define KWIN_POINTER_INPUT_H #include "input.h" #include #include #include #include class QWindow; namespace KWayland { namespace Server { class SurfaceInterface; } } namespace KWin { class CursorImage; class InputRedirection; class Toplevel; class WaylandCursorTheme; namespace Decoration { class DecoratedClientImpl; } namespace LibInput { class Device; } class KWIN_EXPORT PointerInputRedirection : public InputDeviceHandler { Q_OBJECT public: explicit PointerInputRedirection(InputRedirection *parent); virtual ~PointerInputRedirection(); void init(); void update(); void updateAfterScreenChange(); bool supportsWarping() const; void warp(const QPointF &pos); QPointF pos() const { return m_pos; } Qt::MouseButtons buttons() const { return m_qtButtons; } QImage cursorImage() const; QPoint cursorHotSpot() const; void markCursorAsRendered(); void setEffectsOverrideCursor(Qt::CursorShape shape); void removeEffectsOverrideCursor(); /** * @internal */ void processMotion(const QPointF &pos, uint32_t time, LibInput::Device *device = nullptr); + /** + * @internal + **/ + void processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device); /** * @internal */ void processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device = nullptr); /** * @internal */ void processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time, LibInput::Device *device = nullptr); /** * @internal */ void processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device = nullptr); /** * @internal */ void processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device = nullptr); private: void updatePosition(const QPointF &pos); void updateButton(uint32_t button, InputRedirection::PointerButtonState state); void warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInterface *surface); CursorImage *m_cursor; bool m_inited = false; bool m_supportsWarping; QPointF m_pos; QHash m_buttons; Qt::MouseButtons m_qtButtons; QMetaObject::Connection m_windowGeometryConnection; QMetaObject::Connection m_internalWindowConnection; }; class CursorImage : public QObject { Q_OBJECT public: explicit CursorImage(PointerInputRedirection *parent = nullptr); virtual ~CursorImage(); void setEffectsOverrideCursor(Qt::CursorShape shape); void removeEffectsOverrideCursor(); QImage image() const; QPoint hotSpot() const; void markAsRendered(); Q_SIGNALS: void changed(); private: void reevaluteSource(); void update(); void updateServerCursor(); void updateDecoration(); void updateDecorationCursor(); void updateMoveResize(); void updateDrag(); void updateDragCursor(); void loadTheme(); struct Image { QImage image; QPoint hotSpot; }; void loadThemeCursor(Qt::CursorShape shape, Image *image); enum class CursorSource { LockScreen, EffectsOverride, MoveResize, PointerSurface, Decoration, DragAndDrop, Fallback }; void setSource(CursorSource source); PointerInputRedirection *m_pointer; CursorSource m_currentSource = CursorSource::Fallback; WaylandCursorTheme *m_cursorTheme = nullptr; struct { QMetaObject::Connection connection; QImage image; QPoint hotSpot; } m_serverCursor; Image m_effectsCursor; Image m_decorationCursor; QMetaObject::Connection m_decorationConnection; Image m_fallbackCursor; Image m_moveResizeCursor; QHash m_cursors; QElapsedTimer m_surfaceRenderedTimer; struct { Image cursor; QMetaObject::Connection connection; } m_drag; }; } #endif diff --git a/tests/libinputtest.cpp b/tests/libinputtest.cpp index 3446dc727..349b6280b 100644 --- a/tests/libinputtest.cpp +++ b/tests/libinputtest.cpp @@ -1,117 +1,117 @@ /* * Copyright 2014 Martin Gräßlin * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 . */ #include "../input.h" #include "../libinput/connection.h" #include "../libinput/device.h" #include "../logind.h" #include #include #include #include Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core") int main(int argc, char **argv) { using namespace KWin::LibInput; QCoreApplication app(argc, argv); KWin::LogindIntegration *logind = KWin::LogindIntegration::create(&app); QObject::connect(logind, &KWin::LogindIntegration::connectedChanged, [logind]() { if (logind->isConnected()) { logind->takeControl(); } } ); QObject::connect(logind, &KWin::LogindIntegration::hasSessionControlChanged, [&app](bool valid) { if (!valid) { return; } Connection *conn = Connection::create(&app); if (!conn) { std::cerr << "Failed to create LibInput integration" << std::endl; ::exit(1); } conn->setScreenSize(QSize(100, 100)); conn->setup(); QObject::connect(conn, &Connection::keyChanged, [](uint32_t key, KWin::InputRedirection::KeyboardKeyState state) { std::cout << "Got key event" << std::endl;; if (key == KEY_Q && state == KWin::InputRedirection::KeyboardKeyReleased) { QCoreApplication::instance()->quit(); } } ); QObject::connect(conn, &Connection::pointerButtonChanged, [](uint32_t button, KWin::InputRedirection::PointerButtonState state) { std::cout << "Got pointer button event" << std::endl; if (button == BTN_MIDDLE && state == KWin::InputRedirection::PointerButtonReleased) { QCoreApplication::instance()->quit(); } } ); QObject::connect(conn, &Connection::pointerMotion, - [](QPointF delta) { - std::cout << "Got pointer motion: " << delta.x() << "/" << delta.y() << std::endl; + [](const QSizeF &delta) { + std::cout << "Got pointer motion: " << delta.width() << "/" << delta.height() << std::endl; } ); QObject::connect(conn, &Connection::pointerAxisChanged, [](KWin::InputRedirection::PointerAxis axis, qreal delta) { std::cout << "Axis: " << axis << " with delta" << delta << std::endl; } ); QObject::connect(conn, &Connection::touchDown, [](qint32 id, const QPointF &position, quint32 time) { std::cout << "Touch down at: " << position.x() << "/" << position.y() << " id " << id << " timestamp: " << time << std::endl; } ); QObject::connect(conn, &Connection::touchMotion, [](qint32 id, const QPointF &position, quint32 time) { std::cout << "Touch motion at: " << position.x() << "/" << position.y() << " id " << id << " timestamp: " << time << std::endl; } ); QObject::connect(conn, &Connection::touchUp, [](qint32 id, quint32 time) { std::cout << "Touch up for id " << id << " timestamp: " << time << std::endl; } ); QObject::connect(conn, &Connection::touchCanceled, []() { std::cout << "Touch canceled" << std::endl; } ); QObject::connect(conn, &Connection::touchFrame, []() { std::cout << "Touch frame " << std::endl; } ); QObject::connect(&app, &QCoreApplication::aboutToQuit, [conn] { delete conn; }); } ); return app.exec(); }