diff --git a/autotests/libinput/CMakeLists.txt b/autotests/libinput/CMakeLists.txt index bc0fff699..b34db02de 100644 --- a/autotests/libinput/CMakeLists.txt +++ b/autotests/libinput/CMakeLists.txt @@ -1,113 +1,113 @@ include_directories(${Libinput_INCLUDE_DIRS}) include_directories(${UDEV_INCLUDE_DIR}) ######################################################## # Test Devices ######################################################## set( testLibinputDevice_SRCS device_test.cpp mock_libinput.cpp ../../libinput/device.cpp ) add_executable(testLibinputDevice ${testLibinputDevice_SRCS}) target_link_libraries( testLibinputDevice Qt5::Test Qt5::DBus Qt5::Gui KF5::ConfigCore) add_test(NAME kwin-testLibinputDevice COMMAND testLibinputDevice) ecm_mark_as_test(testLibinputDevice) ######################################################## # Test Key Event ######################################################## set( testLibinputKeyEvent_SRCS key_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputKeyEvent ${testLibinputKeyEvent_SRCS}) target_link_libraries( testLibinputKeyEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) add_test(NAME kwin-testLibinputKeyEvent COMMAND testLibinputKeyEvent) ecm_mark_as_test(testLibinputKeyEvent) ######################################################## # Test Pointer Event ######################################################## set( testLibinputPointerEvent_SRCS pointer_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputPointerEvent ${testLibinputPointerEvent_SRCS}) target_link_libraries( testLibinputPointerEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) add_test(NAME kwin-testLibinputPointerEvent COMMAND testLibinputPointerEvent) ecm_mark_as_test(testLibinputPointerEvent) ######################################################## # Test Touch Event ######################################################## set( testLibinputTouchEvent_SRCS touch_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputTouchEvent ${testLibinputTouchEvent_SRCS}) target_link_libraries( testLibinputTouchEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) add_test(NAME kwin-testLibinputTouchEvent COMMAND testLibinputTouchEvent) ecm_mark_as_test(testLibinputTouchEvent) ######################################################## # Test Gesture Event ######################################################## set( testLibinputGestureEvent_SRCS gesture_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputGestureEvent ${testLibinputGestureEvent_SRCS}) target_link_libraries( testLibinputGestureEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) add_test(NAME kwin-testLibinputGestureEvent COMMAND testLibinputGestureEvent) ecm_mark_as_test(testLibinputGestureEvent) ######################################################## # Test Switch Event ######################################################## set( testLibinputSwitchEvent_SRCS switch_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../libinput/events.cpp ) add_executable(testLibinputSwitchEvent ${testLibinputSwitchEvent_SRCS}) target_link_libraries(testLibinputSwitchEvent Qt5::Test Qt5::DBus Qt5::Widgets KF5::ConfigCore) add_test(NAME kwin-testLibinputSwitchEvent COMMAND testLibinputSwitchEvent) ecm_mark_as_test(testLibinputSwitchEvent) ######################################################## # Test Context ######################################################## set( testLibinputContext_SRCS context_test.cpp mock_libinput.cpp mock_udev.cpp ../../libinput/context.cpp ../../libinput/device.cpp ../../libinput/events.cpp ../../libinput/libinput_logging.cpp ../../logind.cpp ) add_executable(testLibinputContext ${testLibinputContext_SRCS}) target_link_libraries( testLibinputContext Qt5::DBus Qt5::Test Qt5::Widgets KF5::ConfigCore KF5::WindowSystem ) add_test(NAME kwin-testLibinputContext COMMAND testLibinputContext) ecm_mark_as_test(testLibinputContext) ######################################################## # Test Input Events ######################################################## set( testInputEvents_SRCS input_event_test.cpp mock_libinput.cpp ../../libinput/device.cpp ../../input_event.cpp ) add_executable(testInputEvents ${testInputEvents_SRCS}) -target_link_libraries( testInputEvents Qt5::Test Qt5::DBus Qt5::Gui KF5::ConfigCore) +target_link_libraries( testInputEvents Qt5::Test Qt5::DBus Qt5::Gui Qt5::Widgets KF5::ConfigCore) add_test(NAME kwin-testInputEvents COMMAND testInputEvents) ecm_mark_as_test(testInputEvents) diff --git a/autotests/libinput/input_event_test.cpp b/autotests/libinput/input_event_test.cpp index b1a83aa63..3414a2cd1 100644 --- a/autotests/libinput/input_event_test.cpp +++ b/autotests/libinput/input_event_test.cpp @@ -1,184 +1,190 @@ /******************************************************************** 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 Q_DECLARE_METATYPE(KWin::SwitchEvent::State); 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 testInitSwitchEvent_data(); void testInitSwitchEvent(); }; 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, 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("discreteDelta"); QTest::addColumn("expectedAngleDelta"); - QTest::newRow("horiz") << Qt::Horizontal << 3.0 << QPoint(3, 0); - QTest::newRow("vert") << Qt::Vertical << 2.0 << QPoint(0, 2); + QTest::newRow("horiz") << Qt::Horizontal << 3.3 << 1 << QPoint(3, 0); + QTest::newRow("vert") << Qt::Vertical << 2.4 << 2 << 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); + QFETCH(qint32, discreteDelta); + WheelEvent event(QPointF(100, 200), delta, discreteDelta, orientation, Qt::LeftButton | Qt::RightButton, + Qt::ShiftModifier | Qt::ControlModifier, InputRedirection::PointerAxisSourceWheel, 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"); + QTEST(event.orientation(), "orientation"); + QTEST(event.delta(), "delta"); + QTEST(event.discreteDelta(), "discreteDelta"); + QCOMPARE(event.axisSource(), InputRedirection::PointerAxisSourceWheel); // and our custom argument QCOMPARE(event.device(), &d); } void InputEventsTest::testInitSwitchEvent_data() { QTest::addColumn("state"); QTest::addColumn("timestamp"); QTest::addColumn("micro"); QTest::newRow("on") << SwitchEvent::State::On << 23u << quint64{23456790}; QTest::newRow("off") << SwitchEvent::State::Off << 456892u << quint64{45689235987}; } void InputEventsTest::testInitSwitchEvent() { // this test verifies that a SwitchEvent is constructed correctly libinput_device device; Device d(&device); QFETCH(SwitchEvent::State, state); QFETCH(quint32, timestamp); QFETCH(quint64, micro); SwitchEvent event(state, timestamp, micro, &d); QCOMPARE(event.state(), state); QCOMPARE(event.timestamp(), ulong(timestamp)); QCOMPARE(event.timestampMicroseconds(), micro); 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 1dd354261..efc1781a3 100644 --- a/autotests/libinput/mock_libinput.cpp +++ b/autotests/libinput/mock_libinput.cpp @@ -1,885 +1,899 @@ /******************************************************************** 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; case LIBINPUT_DEVICE_CAP_SWITCH: return device->switchDevice; 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; } enum libinput_config_status libinput_device_config_dwt_set_enabled(struct libinput_device *device, enum libinput_config_dwt_state state) { if (device->setDisableWhileTypingReturnValue == 0) { if (!device->supportsDisableWhileTyping) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->disableWhileTyping = state; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_dwt_state libinput_device_config_dwt_get_enabled(struct libinput_device *device) { return device->disableWhileTyping; } enum libinput_config_dwt_state libinput_device_config_dwt_get_default_enabled(struct libinput_device *device) { return device->disableWhileTypingEnabledByDefault; } 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; } enum libinput_config_status libinput_device_config_calibration_set_matrix(struct libinput_device *device, const float matrix[6]) { for (std::size_t i = 0; i < 6; i++) { device->calibrationMatrix[i] = matrix[i]; } return LIBINPUT_CONFIG_STATUS_SUCCESS; } int libinput_device_config_calibration_get_default_matrix(struct libinput_device *device, float matrix[6]) { for (std::size_t i = 0; i < 6; i++) { matrix[i] = device->defaultCalibrationMatrix[i]; } return device->defaultCalibrationMatrixIsIdentity ? 0 : 1; } int libinput_device_config_left_handed_is_available(struct libinput_device *device) { return device->supportsLeftHanded; } 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_default_speed(struct libinput_device *device) { return device->defaultPointerAcceleration; } int libinput_device_config_left_handed_get_default(struct libinput_device *device) { return device->leftHandedEnabledByDefault; } double libinput_device_config_accel_get_speed(struct libinput_device *device) { return device->pointerAcceleration; } uint32_t libinput_device_config_accel_get_profiles(struct libinput_device *device) { return device->supportedPointerAccelerationProfiles; } enum libinput_config_accel_profile libinput_device_config_accel_get_default_profile(struct libinput_device *device) { return device->defaultPointerAccelerationProfile; } enum libinput_config_status libinput_device_config_accel_set_profile(struct libinput_device *device, enum libinput_config_accel_profile profile) { if (device->setPointerAccelerationProfileReturnValue == 0) { if (!(device->supportedPointerAccelerationProfiles & profile) && profile!= LIBINPUT_CONFIG_ACCEL_PROFILE_NONE) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->pointerAccelerationProfile = profile; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_accel_profile libinput_device_config_accel_get_profile(struct libinput_device *device) { return device->pointerAccelerationProfile; } uint32_t libinput_device_config_click_get_methods(struct libinput_device *device) { return device->supportedClickMethods; } enum libinput_config_click_method libinput_device_config_click_get_default_method(struct libinput_device *device) { return device->defaultClickMethod; } enum libinput_config_click_method libinput_device_config_click_get_method(struct libinput_device *device) { return device->clickMethod; } enum libinput_config_status libinput_device_config_click_set_method(struct libinput_device *device, enum libinput_config_click_method method) { if (device->setClickMethodReturnValue == 0) { if (!(device->supportedClickMethods & method) && method != LIBINPUT_CONFIG_CLICK_METHOD_NONE) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->clickMethod = method; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } 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; } } +double libinput_event_pointer_get_axis_value_discrete(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) +{ + if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { + return event->verticalDiscreteAxisValue; + } else { + return event->horizontalDiscreteAxisValue; + } +} + +enum libinput_pointer_axis_source libinput_event_pointer_get_axis_source(struct libinput_event_pointer *event) +{ + return event->axisSource; +} + 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; } int libinput_device_config_middle_emulation_is_available(struct libinput_device *device) { return device->supportsMiddleEmulation; } enum libinput_config_status libinput_device_config_middle_emulation_set_enabled(struct libinput_device *device, enum libinput_config_middle_emulation_state enable) { if (device->setMiddleEmulationReturnValue == 0) { if (!device->supportsMiddleEmulation) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->middleEmulation = (enable == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_middle_emulation_state libinput_device_config_middle_emulation_get_enabled(struct libinput_device *device) { if (device->middleEmulation) { return LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; } else { return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } } enum libinput_config_middle_emulation_state libinput_device_config_middle_emulation_get_default_enabled(struct libinput_device *device) { if (device->middleEmulationEnabledByDefault) { return LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; } else { return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; } } int libinput_device_config_scroll_has_natural_scroll(struct libinput_device *device) { return device->supportsNaturalScroll; } enum libinput_config_status libinput_device_config_scroll_set_natural_scroll_enabled(struct libinput_device *device, int enable) { if (device->setNaturalScrollReturnValue == 0) { if (!device->supportsNaturalScroll) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->naturalScroll = enable; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } int libinput_device_config_scroll_get_natural_scroll_enabled(struct libinput_device *device) { return device->naturalScroll; } int libinput_device_config_scroll_get_default_natural_scroll_enabled(struct libinput_device *device) { return device->naturalScrollEnabledByDefault; } enum libinput_config_tap_button_map libinput_device_config_tap_get_default_button_map(struct libinput_device *device) { return device->defaultTapButtonMap; } enum libinput_config_status libinput_device_config_tap_set_button_map(struct libinput_device *device, enum libinput_config_tap_button_map map) { if (device->setTapButtonMapReturnValue == 0) { if (device->tapFingerCount == 0) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->tapButtonMap = map; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_tap_button_map libinput_device_config_tap_get_button_map(struct libinput_device *device) { return device->tapButtonMap; } uint32_t libinput_device_config_scroll_get_methods(struct libinput_device *device) { return device->supportedScrollMethods; } enum libinput_config_scroll_method libinput_device_config_scroll_get_default_method(struct libinput_device *device) { return device->defaultScrollMethod; } enum libinput_config_status libinput_device_config_scroll_set_method(struct libinput_device *device, enum libinput_config_scroll_method method) { if (device->setScrollMethodReturnValue == 0) { if (!(device->supportedScrollMethods & method) && method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) { return LIBINPUT_CONFIG_STATUS_INVALID; } device->scrollMethod = method; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } enum libinput_config_scroll_method libinput_device_config_scroll_get_method(struct libinput_device *device) { return device->scrollMethod; } enum libinput_config_status libinput_device_config_scroll_set_button(struct libinput_device *device, uint32_t button) { if (device->setScrollButtonReturnValue == 0) { if (!(device->supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)) { return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; } device->scrollButton = button; return LIBINPUT_CONFIG_STATUS_SUCCESS; } return LIBINPUT_CONFIG_STATUS_INVALID; } uint32_t libinput_device_config_scroll_get_button(struct libinput_device *device) { return device->scrollButton; } uint32_t libinput_device_config_scroll_get_default_button(struct libinput_device *device) { return device->defaultScrollButton; } int libinput_device_switch_has_switch(struct libinput_device *device, enum libinput_switch sw) { switch (sw) { case LIBINPUT_SWITCH_LID: return device->lidSwitch; case LIBINPUT_SWITCH_TABLET_MODE: return device->tabletModeSwitch; default: Q_UNREACHABLE(); } return 0; } struct libinput_event_switch *libinput_event_get_switch_event(struct libinput_event *event) { if (event->type == LIBINPUT_EVENT_SWITCH_TOGGLE) { return reinterpret_cast(event); } else { return nullptr; } } enum libinput_switch_state libinput_event_switch_get_switch_state(struct libinput_event_switch *event) { switch (event->state) { case libinput_event_switch::State::On: return LIBINPUT_SWITCH_STATE_ON; case libinput_event_switch::State::Off: return LIBINPUT_SWITCH_STATE_OFF; default: Q_UNREACHABLE(); } } uint32_t libinput_event_switch_get_time(struct libinput_event_switch *event) { return event->time;; } uint64_t libinput_event_switch_get_time_usec(struct libinput_event_switch *event) { return event->timeMicroseconds; } diff --git a/autotests/libinput/mock_libinput.h b/autotests/libinput/mock_libinput.h index efbc730aa..2ee789297 100644 --- a/autotests/libinput/mock_libinput.h +++ b/autotests/libinput/mock_libinput.h @@ -1,163 +1,166 @@ /******************************************************************** 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 MOCK_LIBINPUT_H #define MOCK_LIBINPUT_H #include #include #include #include #include #include struct libinput_device { bool keyboard = false; bool pointer = false; bool touch = false; bool tabletTool = false; bool gestureSupported = false; bool switchDevice = false; QByteArray name; QByteArray sysName = QByteArrayLiteral("event0"); QByteArray outputName; quint32 product = 0; quint32 vendor = 0; int tapFingerCount = 0; QSizeF deviceSize; int deviceSizeReturnValue = 0; bool tapEnabledByDefault = false; bool tapToClick = false; bool tapAndDragEnabledByDefault = false; bool tapAndDrag = false; bool tapDragLockEnabledByDefault = false; bool tapDragLock = false; bool supportsDisableWhileTyping = false; bool supportsPointerAcceleration = false; bool supportsLeftHanded = false; bool supportsCalibrationMatrix = false; bool supportsDisableEvents = false; bool supportsDisableEventsOnExternalMouse = false; bool supportsMiddleEmulation = false; bool supportsNaturalScroll = false; quint32 supportedScrollMethods = 0; bool middleEmulationEnabledByDefault = false; bool middleEmulation = false; enum libinput_config_tap_button_map defaultTapButtonMap = LIBINPUT_CONFIG_TAP_MAP_LRM; enum libinput_config_tap_button_map tapButtonMap = LIBINPUT_CONFIG_TAP_MAP_LRM; int setTapButtonMapReturnValue = 0; enum libinput_config_dwt_state disableWhileTypingEnabledByDefault = LIBINPUT_CONFIG_DWT_DISABLED; enum libinput_config_dwt_state disableWhileTyping = LIBINPUT_CONFIG_DWT_DISABLED; int setDisableWhileTypingReturnValue = 0; qreal defaultPointerAcceleration = 0.0; qreal pointerAcceleration = 0.0; int setPointerAccelerationReturnValue = 0; bool leftHandedEnabledByDefault = false; bool leftHanded = false; int setLeftHandedReturnValue = 0; bool naturalScrollEnabledByDefault = false; bool naturalScroll = false; int setNaturalScrollReturnValue = 0; enum libinput_config_scroll_method defaultScrollMethod = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; enum libinput_config_scroll_method scrollMethod = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; int setScrollMethodReturnValue = 0; quint32 defaultScrollButton = 0; quint32 scrollButton = 0; int setScrollButtonReturnValue = 0; Qt::MouseButtons supportedButtons; QVector keys; bool enabled = true; int setEnableModeReturnValue = 0; int setTapToClickReturnValue = 0; int setTapAndDragReturnValue = 0; int setTapDragLockReturnValue = 0; int setMiddleEmulationReturnValue = 0; quint32 supportedPointerAccelerationProfiles = 0; enum libinput_config_accel_profile defaultPointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; enum libinput_config_accel_profile pointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; bool setPointerAccelerationProfileReturnValue = 0; std::array defaultCalibrationMatrix{{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f}}; std::array calibrationMatrix{{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f}}; bool defaultCalibrationMatrixIsIdentity = true; bool lidSwitch = false; bool tabletModeSwitch = false; quint32 supportedClickMethods = 0; enum libinput_config_click_method defaultClickMethod = LIBINPUT_CONFIG_CLICK_METHOD_NONE; enum libinput_config_click_method clickMethod = LIBINPUT_CONFIG_CLICK_METHOD_NONE; bool setClickMethodReturnValue = 0; }; struct libinput_event { libinput_device *device = nullptr; libinput_event_type type = LIBINPUT_EVENT_NONE; quint32 time = 0; }; struct libinput_event_keyboard : libinput_event { libinput_event_keyboard() { type = LIBINPUT_EVENT_KEYBOARD_KEY; } libinput_key_state state = LIBINPUT_KEY_STATE_RELEASED; quint32 key = 0; }; struct libinput_event_pointer : libinput_event { libinput_button_state buttonState = LIBINPUT_BUTTON_STATE_RELEASED; quint32 button = 0; bool verticalAxis = false; bool horizontalAxis = false; qreal horizontalAxisValue = 0.0; qreal verticalAxisValue = 0.0; + qreal horizontalDiscreteAxisValue = 0.0; + qreal verticalDiscreteAxisValue = 0.0; + libinput_pointer_axis_source axisSource = {}; QSizeF delta; QPointF absolutePos; }; struct libinput_event_touch : libinput_event { qint32 slot = -1; QPointF absolutePos; }; struct libinput_event_gesture : libinput_event { int fingerCount = 0; bool cancelled = false; QSizeF delta = QSizeF(0, 0); qreal scale = 0.0; qreal angleDelta = 0.0; }; struct libinput_event_switch : libinput_event { enum class State { Off, On }; State state = State::Off; quint64 timeMicroseconds = 0; }; struct libinput { int refCount = 1; QByteArray seat; int assignSeatRetVal = 0; }; #endif diff --git a/autotests/libinput/pointer_event_test.cpp b/autotests/libinput/pointer_event_test.cpp index bad852076..ff4e2e0eb 100644 --- a/autotests/libinput/pointer_event_test.cpp +++ b/autotests/libinput/pointer_event_test.cpp @@ -1,209 +1,232 @@ /******************************************************************** 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) +Q_DECLARE_METATYPE(libinput_pointer_axis_source) 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("discreteValue"); + QTest::addColumn("axisSource"); + QTest::addColumn("expectedAxisSource"); 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; + QTest::newRow("wheel/horizontal") << true << false << QPointF(3.0, 0.0) << QPoint(1, 0) << LIBINPUT_POINTER_AXIS_SOURCE_WHEEL << KWin::InputRedirection::PointerAxisSourceWheel << 100u; + QTest::newRow("wheel/vertical") << false << true << QPointF(0.0, 2.5) << QPoint(0, 1) << LIBINPUT_POINTER_AXIS_SOURCE_WHEEL << KWin::InputRedirection::PointerAxisSourceWheel << 200u; + QTest::newRow("wheel/both") << true << true << QPointF(1.1, 4.2) << QPoint(1, 1) << LIBINPUT_POINTER_AXIS_SOURCE_WHEEL << KWin::InputRedirection::PointerAxisSourceWheel << 300u; + + QTest::newRow("finger/horizontal") << true << false << QPointF(3.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 400u; + QTest::newRow("stop finger/horizontal") << true << false << QPointF(0.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 500u; + QTest::newRow("finger/vertical") << false << true << QPointF(0.0, 2.5) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 600u; + QTest::newRow("stop finger/vertical") << false << true << QPointF(0.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 700u; + QTest::newRow("finger/both") << true << true << QPointF(1.1, 4.2) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 800u; + QTest::newRow("stop finger/both") << true << true << QPointF(0.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 900u; + + QTest::newRow("continuous/horizontal") << true << false << QPointF(3.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS << KWin::InputRedirection::PointerAxisSourceContinuous << 1000u; + QTest::newRow("continuous/vertical") << false << true << QPointF(0.0, 2.5) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS << KWin::InputRedirection::PointerAxisSourceContinuous << 1100u; + QTest::newRow("continuous/both") << true << true << QPointF(1.1, 4.2) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS << KWin::InputRedirection::PointerAxisSourceContinuous << 1200u; } 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(QPoint, discreteValue); + QFETCH(libinput_pointer_axis_source, axisSource); QFETCH(quint32, time); pointerEvent->horizontalAxis = horizontal; pointerEvent->verticalAxis = vertical; pointerEvent->horizontalAxisValue = value.x(); pointerEvent->verticalAxisValue = value.y(); + pointerEvent->horizontalDiscreteAxisValue = discreteValue.x(); + pointerEvent->verticalDiscreteAxisValue = discreteValue.y(); + pointerEvent->axisSource = axisSource; 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->discreteAxisValue(KWin::InputRedirection::PointerAxisHorizontal), discreteValue.x()); + QCOMPARE(pe->discreteAxisValue(KWin::InputRedirection::PointerAxisVertical), discreteValue.y()); + QTEST(pe->axisSource(), "expectedAxisSource"); 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(), 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/input.cpp b/input.cpp index 955af932b..5013cb286 100644 --- a/input.cpp +++ b/input.cpp @@ -1,2389 +1,2409 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin Copyright (C) 2018 Roman Gilg +Copyright (C) 2019 Vlad Zagorodniy 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 "input_event_spy.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "touch_hide_cursor_spy.h" #include "client.h" #include "effects.h" #include "gestures.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" #include "libinput/connection.h" #include "libinput/device.h" #include "platform.h" #include "popup_input_filter.h" #include "shell_client.h" #include "wayland_server.h" #include "xwl/xwayland_interface.h" #include #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; } bool InputEventFilter::switchEvent(SwitchEvent *event) { Q_UNUSED(event) 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; } } 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; } }; 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 (pointerSurfaceAllowed()) { // TODO: should the pointer position always stay in sync, i.e. not do the check? seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { // TODO: can we leak presses/releases here when we move the mouse in between from an allowed surface to // disallowed one or vice versa? 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 (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; } bool pinchGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool pinchGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(fingerCount) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(delta) Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) // no touchpad multi-finger gestures on lock screen return waylandServer()->isScreenLocked(); } 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()->moveResizeClient(); 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()->moveResizeClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->moveResizeClient(); 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; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(pos) Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (!m_set) { m_id = id; m_set = true; } if (m_id == id) { c->updateMoveResize(pos.toPoint()); } return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) AbstractClient *c = workspace()->moveResizeClient(); if (!c) { return false; } if (m_id == id || !m_set) { c->endMoveResize(); m_set = false; // pass through to update decoration filter later on return false; } m_set = false; return true; } private: quint32 m_id = 0; bool m_set = false; }; class WindowSelectorFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!m_active) { return false; } switch (event->type()) { case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { if (event->button() == Qt::RightButton) { cancel(); } else { accept(event->globalPos()); } } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while selecting a window return m_active; } bool keyEvent(QKeyEvent *event) override { Q_UNUSED(event) if (!m_active) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { // x11 variant does this on key press, so do the same if (event->key() == Qt::Key_Escape) { cancel(); } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { accept(input()->globalPointer()); } if (input()->supportsPointerWarping()) { int mx = 0; int my = 0; if (event->key() == Qt::Key_Left) { mx = -10; } if (event->key() == Qt::Key_Right) { mx = 10; } if (event->key() == Qt::Key_Up) { my = -10; } if (event->key() == Qt::Key_Down) { my = 10; } if (event->modifiers() & Qt::ControlModifier) { mx /= 10; my /= 10; } input()->warpPointer(input()->globalPointer() + QPointF(mx, my)); } } // filter out while selecting a window return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } m_touchPoints.insert(id, pos); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { *it = pos; } return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) if (!isActive()) { return false; } auto it = m_touchPoints.find(id); if (it != m_touchPoints.end()) { const auto pos = it.value(); m_touchPoints.erase(it); if (m_touchPoints.isEmpty()) { accept(pos); } } return true; } bool isActive() const { return m_active; } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_callback = callback; input()->keyboard()->update(); input()->cancelTouch(); } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_pointSelectionFallback = callback; input()->keyboard()->update(); input()->cancelTouch(); } private: void deactivate() { m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); input()->pointer()->removeWindowSelectionCursor(); input()->keyboard()->update(); m_touchPoints.clear(); } void cancel() { if (m_callback) { m_callback(nullptr); } if (m_pointSelectionFallback) { m_pointSelectionFallback(QPoint(-1, -1)); } deactivate(); } void accept(const QPoint &pos) { if (m_callback) { // TODO: this ignores shaped windows m_callback(input()->findToplevel(pos)); } if (m_pointSelectionFallback) { m_pointSelectionFallback(pos); } deactivate(); } void accept(const QPointF &pos) { accept(pos.toPoint()); } bool m_active = false; std::function m_callback; std::function m_pointSelectionFallback; QMap m_touchPoints; }; 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(static_cast(event)->modifiersRelevantForGlobalShortcuts(), event->key()); } return false; } bool swipeGestureBegin(int fingerCount, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeStart(fingerCount); return false; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeUpdate(delta); return false; } bool swipeGestureCancelled(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeCancel(); return false; } bool swipeGestureEnd(quint32 time) override { Q_UNUSED(time) input()->shortcuts()->processSwipeEnd(); return false; } }; namespace { enum class MouseAction { ModifierOnly, ModifierAndWindow }; std::pair performClientMouseAction(QMouseEvent *event, AbstractClient *client, MouseAction action = MouseAction::ModifierOnly) { Options::MouseCommand command = Options::MouseNothing; bool wasAction = false; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { if (!input()->pointer()->isConstrained() && !workspace()->globalShortcutsDisabled()) { 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 { if (action == MouseAction::ModifierAndWindow) { command = client->getMouseCommand(event->button(), &wasAction); } } if (wasAction) { return std::make_pair(wasAction, !client->performMouseCommand(command, event->globalPos())); } return std::make_pair(wasAction, false); } std::pair performClientWheelAction(QWheelEvent *event, AbstractClient *c, MouseAction action = MouseAction::ModifierOnly) { bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { if (!input()->pointer()->isConstrained() && !workspace()->globalShortcutsDisabled()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } } else { if (action == MouseAction::ModifierAndWindow) { command = c->getWheelCommand(Qt::Vertical, &wasAction); } } if (wasAction) { return std::make_pair(wasAction, !c->performMouseCommand(command, event->globalPos())); } return std::make_pair(wasAction, false); } } class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } // find client switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { auto s = waylandServer()->findClient(internal); if (s && s->isDecorated()) { // only perform mouse commands on decorated internal windows const auto actionResult = performClientMouseAction(event, s); if (actionResult.first) { return actionResult.second; } } break; } default: break; } 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; } if (event->angleDelta().y() != 0) { auto s = waylandServer()->findClient(internal); if (s && s->isDecorated()) { // client window action only on vertical scrolling const auto actionResult = performClientWheelAction(event, s); if (actionResult.first) { return actionResult.second; } } } 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() * -1, delta * -1, 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; } if (w->property("outputOnly").toBool()) { continue; } if (w->flags().testFlag(Qt::ToolTip)) { continue; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } auto xkb = input()->keyboard()->xkb(); Qt::Key key = xkb->toQtKey(xkb->toKeysym(event->nativeScanCode())); if (key == Qt::Key_Super_L || key == Qt::Key_Super_R) { // workaround for QTBUG-62102 key = Qt::Key_Meta; } QKeyEvent internalEvent(event->type(), key, event->modifiers(), event->nativeScanCode(), event->nativeVirtualKey(), event->nativeModifiers(), event->text()); internalEvent.setAccepted(false); if (QCoreApplication::sendEvent(found, &internalEvent)) { 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 internal window, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); 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()); QEnterEvent enterEvent(m_lastLocalTouchPos, m_lastLocalTouchPos, pos); QCoreApplication::sendEvent(internal.data(), &enterEvent); 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); QEvent leaveEvent(QEvent::Leave); QCoreApplication::sendEvent(internal.data(), &leaveEvent); 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: { 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: { const auto actionResult = performClientMouseAction(event, decoration->client()); if (actionResult.first) { return actionResult.second; } 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; } if (event->angleDelta().y() != 0) { // client window action only on vertical scrolling const auto actionResult = performClientWheelAction(event, decoration->client()); if (actionResult.first) { return actionResult.second; } } 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); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent hoverEvent(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::sendEvent(decoration->decoration(), &hoverEvent); 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); QHoverEvent leaveEvent(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::sendEvent(decoration->decoration(), &leaveEvent); 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); input()->pointer()->setEnableConstraints(false); // 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 (static_cast(event)->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; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) // TODO: better check whether a touch sequence is in progress if (m_touchInProgress || waylandServer()->seat()->isTouchSequence()) { // cancel existing touch ScreenEdges::self()->gestureRecognizer()->cancelSwipeGesture(); m_touchInProgress = false; m_id = 0; return false; } if (ScreenEdges::self()->gestureRecognizer()->startSwipeGesture(pos) > 0) { m_touchInProgress = true; m_id = id; m_lastPos = pos; return true; } return false; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->updateSwipeGesture(QSizeF(pos.x() - m_lastPos.x(), pos.y() - m_lastPos.y())); m_lastPos = pos; return true; } return false; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time) if (m_touchInProgress && m_id == id) { ScreenEdges::self()->gestureRecognizer()->endSwipeGesture(); m_touchInProgress = false; return true; } return false; } private: bool m_touchInProgress = false; quint32 m_id = 0; QPointF m_lastPos; }; /** * 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()->focus().data()); if (!c) { return false; } const auto actionResult = performClientMouseAction(event, c, MouseAction::ModifierAndWindow); if (actionResult.first) { return actionResult.second; } 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()->focus().data()); if (!c) { return false; } const auto actionResult = performClientWheelAction(event, c, MouseAction::ModifierAndWindow); if (actionResult.first) { return actionResult.second; } 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; } AbstractClient *c = dynamic_cast(input()->touch()->focus().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: { 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); 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()); + auto _event = static_cast(event); + KWayland::Server::PointerAxisSource source; + switch (_event->axisSource()) { + case KWin::InputRedirection::PointerAxisSourceWheel: + source = KWayland::Server::PointerAxisSource::Wheel; + break; + case KWin::InputRedirection::PointerAxisSourceFinger: + source = KWayland::Server::PointerAxisSource::Finger; + break; + case KWin::InputRedirection::PointerAxisSourceContinuous: + source = KWayland::Server::PointerAxisSource::Continuous; + break; + case KWin::InputRedirection::PointerAxisSourceWheelTilt: + source = KWayland::Server::PointerAxisSource::WheelTilt; + break; + case KWin::InputRedirection::PointerAxisSourceUnknown: + default: + source = KWayland::Server::PointerAxisSource::Unknown; + break; + } + seat->pointerAxisV5(_event->orientation(), _event->delta(), _event->discreteDelta(), source); 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); 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; } bool pinchGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerPinchGesture(fingerCount); return true; } bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerPinchGesture(delta, scale, angleDelta); return true; } bool pinchGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerPinchGesture(); return true; } bool pinchGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerPinchGesture(); return true; } bool swipeGestureBegin(int fingerCount, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->startPointerSwipeGesture(fingerCount); return true; } bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->updatePointerSwipeGesture(delta); return true; } bool swipeGestureEnd(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->endPointerSwipeGesture(); return true; } bool swipeGestureCancelled(quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); seat->cancelPointerSwipeGesture(); return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } if (seat->isDragTouch()) { return true; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { const auto pos = input()->globalPointer(); seat->setPointerPos(pos); const auto eventPos = event->globalPos(); // TODO: use InputDeviceHandler::at() here and check isClient()? Toplevel *t = input()->findManagedToplevel(eventPos); if (auto *xwl = xwayland()) { const auto ret = xwl->dragMoveFilter(t, eventPos); if (ret == Xwl::DragEventReply::Ignore) { return false; } else if (ret == Xwl::DragEventReply::Take) { break; } } if (t) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->activateClient(c); } seat->setDragTarget(t->surface(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } 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; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isDragPointer()) { return true; } if (!seat->isDragTouch()) { return false; } if (m_touchId != id) { return true; } seat->setTimestamp(time); input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isDragPointer()) { return true; } if (!seat->isDragTouch()) { return false; } if (m_touchId < 0) { // We take for now the first id appearing as a move after a drag // started. We can optimize by specifying the id the drag is // associated with by implementing a key-value getter in KWayland. m_touchId = id; } if (m_touchId != id) { return true; } seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId == -1) { return true; } seat->touchMove(kwaylandId, pos); if (Toplevel *t = input()->findToplevel(pos.toPoint())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->activateClient(c); } seat->setDragTarget(t->surface(), pos, t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } return true; } bool touchUp(quint32 id, quint32 time) override { auto seat = waylandServer()->seat(); if (!seat->isDragTouch()) { return false; } seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } if (m_touchId == id) { m_touchId = -1; } return true; } private: qint32 m_touchId = -1; }; KWIN_SINGLETON_FACTORY(InputRedirection) static const QString s_touchpadComponent = QStringLiteral("kcm_touchpad"); 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 (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { LibInput::Connection::createThread(); 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(); } } ); } } connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); qDeleteAll(m_spies); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters << filter; } void InputRedirection::prependInputEventFilter(InputEventFilter *filter) { Q_ASSERT(!m_filters.contains(filter)); m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeOne(filter); } void InputRedirection::installInputEventSpy(InputEventSpy *spy) { m_spies << spy; } void InputRedirection::uninstallInputEventSpy(InputEventSpy *spy) { m_spies.removeOne(spy); } 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); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerMotionAbsoluteRequested, this, [this] (const QPointF &pos) { // TODO: Fix time m_pointer->processMotion(pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); waylandServer()->simulateUserActivity(); } ); 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); + m_pointer->processAxis(axis, delta, 0, InputRedirection::PointerAxisSourceUnknown, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); waylandServer()->simulateUserActivity(); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { // TODO: Fix time m_touch->processUp(id, 0); waylandServer()->simulateUserActivity(); } ); 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() { const bool hasGlobalShortcutSupport = !waylandServer() || waylandServer()->hasGlobalShortcutSupport(); if (LogindIntegration::self()->hasSessionControl() && hasGlobalShortcutSupport) { installInputEventFilter(new VirtualTerminalFilter); } if (waylandServer()) { installInputEventSpy(new TouchHideCursorSpy); if (hasGlobalShortcutSupport) { installInputEventFilter(new TerminateServerFilter); } installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); installInputEventFilter(new PopupInputFilter); m_windowSelector = new WindowSelectorFilter; installInputEventFilter(m_windowSelector); } if (hasGlobalShortcutSupport) { installInputEventFilter(new ScreenEdgeInputFilter); } installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif if (hasGlobalShortcutSupport) { installInputEventFilter(new GlobalShortcutFilter); } installInputEventFilter(new DecorationEventFilter); installInputEventFilter(new InternalWindowEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { if (Application::usesLibinput()) { auto inputConfig = kwinApp()->inputConfig(); inputConfig->reparseConfiguration(); const auto config = 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); } } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { 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(kwinApp()->inputConfig()); conn->updateLEDs(m_keyboard->xkb()->leds()); connect(m_keyboard, &KeyboardInputRedirection::ledsChanged, conn, &LibInput::Connection::updateLEDs); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); conn->setup(); 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] (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); auto handleSwitchEvent = [this] (SwitchEvent::State state, quint32 time, quint64 timeMicroseconds, LibInput::Device *device) { SwitchEvent event(state, time, timeMicroseconds, device); processSpies(std::bind(&InputEventSpy::switchEvent, std::placeholders::_1, &event)); processFilters(std::bind(&InputEventFilter::switchEvent, std::placeholders::_1, &event)); }; connect(conn, &LibInput::Connection::switchToggledOn, this, std::bind(handleSwitchEvent, SwitchEvent::State::On, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); connect(conn, &LibInput::Connection::switchToggledOff, this, std::bind(handleSwitchEvent, SwitchEvent::State::Off, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); 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::hasTabletModeSwitchChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } emit hasTabletModeSwitchChanged(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(); } } ); } setupTouchpadShortcuts(); } void InputRedirection::setupTouchpadShortcuts() { if (!m_libInput) { return; } 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 registerShortcut(Qt::Key_TouchpadToggle, touchpadToggleAction); registerShortcut(Qt::Key_TouchpadOn, touchpadOnAction); registerShortcut(Qt::Key_TouchpadOff, touchpadOffAction); #endif connect(touchpadToggleAction, &QAction::triggered, m_libInput, &LibInput::Connection::toggleTouchpads); connect(touchpadOnAction, &QAction::triggered, m_libInput, &LibInput::Connection::enableTouchpads); connect(touchpadOffAction, &QAction::triggered, m_libInput, &LibInput::Connection::disableTouchpads); } bool InputRedirection::hasAlphaNumericKeyboard() { if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } return true; } bool InputRedirection::hasTabletModeSwitch() { if (m_libInput) { return m_libInput->hasTabletModeSwitch(); } return false; } void InputRedirection::setupLibInputWithScreens() { if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); m_libInput->updateScreens(); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); connect(screens(), &Screens::changed, m_libInput, &LibInput::Connection::updateScreens); } 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) +void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, PointerAxisSource source, uint32_t time) { - m_pointer->processAxis(axis, delta, time); + m_pointer->processAxis(axis, delta, discreteDelta, source, 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; } } } return findManagedToplevel(pos); } Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); 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(); } Qt::KeyboardModifiers InputRedirection::modifiersRelevantForGlobalShortcuts() const { return m_keyboard->modifiersRelevantForGlobalShortcuts(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) kwinApp()->platform()->setupActionForGlobalAccel(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::registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action) { m_shortcuts->registerTouchpadSwipe(action, direction); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } 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(); } void InputRedirection::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(nullptr); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(cursorName); } void InputRedirection::startInteractivePositionSelection(std::function callback) { if (!m_windowSelector || m_windowSelector->isActive()) { callback(QPoint(-1, -1)); return; } m_windowSelector->start(callback); m_pointer->setWindowSelectionCursor(QByteArray()); } bool InputRedirection::isSelectingWindow() const { return m_windowSelector ? m_windowSelector->isActive() : false; } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::init() { connect(workspace(), &Workspace::stackingOrderChanged, this, &InputDeviceHandler::update); connect(workspace(), &Workspace::clientMinimizedChanged, this, &InputDeviceHandler::update); connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &InputDeviceHandler::update); } bool InputDeviceHandler::setAt(Toplevel *toplevel) { if (m_at == toplevel) { return false; } auto old = m_at; m_at = toplevel; emit atChanged(old, toplevel); return true; } void InputDeviceHandler::setFocus(Toplevel *toplevel) { m_focus.focus = toplevel; //TODO: call focusUpdate? } void InputDeviceHandler::setDecoration(QPointer decoration) { auto oldDeco = m_focus.decoration; m_focus.decoration = decoration; cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); emit decorationChanged(); } void InputDeviceHandler::setInternalWindow(QWindow *window) { m_focus.internalWindow = window; //TODO: call internalWindowUpdate? } void InputDeviceHandler::updateFocus() { auto oldFocus = m_focus.focus; m_focus.focus = m_at; focusUpdate(oldFocus, m_focus.focus); } bool InputDeviceHandler::updateDecoration() { const auto oldDeco = m_focus.decoration; m_focus.decoration = nullptr; auto *ac = qobject_cast(m_at); if (ac && ac->decoratedClient()) { const QRect clientRect = QRect(ac->clientPos(), ac->clientSize()).translated(ac->pos()); if (!clientRect.contains(position().toPoint())) { // input device above decoration m_focus.decoration = ac->decoratedClient(); } } if (m_focus.decoration == oldDeco) { // no change to decoration return false; } cleanupDecoration(oldDeco.data(), m_focus.decoration.data()); emit decorationChanged(); return true; } void InputDeviceHandler::updateInternalWindow(QWindow *window) { if (m_focus.internalWindow == window) { // no change return; } const auto oldInternal = m_focus.internalWindow; m_focus.internalWindow = window; cleanupInternalWindow(oldInternal, window); } void InputDeviceHandler::update() { if (!m_inited) { return; } Toplevel *toplevel = nullptr; QWindow *internalWindow = nullptr; if (!positionValid()) { const auto pos = position().toPoint(); internalWindow = findInternalWindow(pos); if (internalWindow) { toplevel = waylandServer()->findClient(internalWindow); } else { toplevel = input()->findToplevel(pos); } } // Always set the toplevel at the position of the input device. setAt(toplevel); if (focusUpdatesBlocked()) { return; } if (internalWindow) { if (m_focus.internalWindow != internalWindow) { // changed internal window updateDecoration(); updateInternalWindow(internalWindow); updateFocus(); } else if (updateDecoration()) { // went onto or off from decoration, update focus updateFocus(); } return; } updateInternalWindow(nullptr); if (m_focus.focus != m_at) { // focus change updateDecoration(); updateFocus(); return; } // check if switched to/from decoration while staying on the same Toplevel if (updateDecoration()) { // went onto or off from decoration, update focus updateFocus(); } } QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const { if (waylandServer()->isScreenLocked()) { return nullptr; } const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return nullptr; } auto it = internalClients.end(); do { --it; QWindow *w = (*it)->internalWindow(); if (!w || !w->isVisible()) { continue; } if (!(*it)->geometry().contains(pos)) { continue; } // check input mask const QRegion mask = w->mask().translated(w->geometry().topLeft()); if (!mask.isEmpty() && !mask.contains(pos)) { continue; } if (w->property("outputOnly").toBool()) { continue; } return w; } while (it != internalClients.begin()); return nullptr; } } // namespace diff --git a/input.h b/input.h index 706850da7..588674d79 100644 --- a/input.h +++ b/input.h @@ -1,483 +1,492 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin Copyright (C) 2018 Roman Gilg +Copyright (C) 2019 Vlad Zagorodniy 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_H #define KWIN_INPUT_H #include #include #include #include #include #include #include #include class KGlobalAccelInterface; class QKeySequence; class QMouseEvent; class QKeyEvent; class QWheelEvent; namespace KWin { class GlobalShortcutsManager; class Toplevel; class InputEventFilter; class InputEventSpy; class KeyboardInputRedirection; class PointerInputRedirection; class TouchInputRedirection; class WindowSelectorFilter; class SwitchEvent; namespace Decoration { class DecoratedClientImpl; } namespace LibInput { class Connection; } /** * @brief This class is responsible for redirecting incoming input to the surface which currently * has input or send enter/leave events. * * In addition input is intercepted before passed to the surfaces to have KWin internal areas * getting input first (e.g. screen edges) and filter the input event out if we currently have * a full input grab. **/ class KWIN_EXPORT InputRedirection : public QObject { Q_OBJECT public: enum PointerButtonState { PointerButtonReleased, PointerButtonPressed }; enum PointerAxis { PointerAxisVertical, PointerAxisHorizontal }; + enum PointerAxisSource { + PointerAxisSourceUnknown, + PointerAxisSourceWheel, + PointerAxisSourceFinger, + PointerAxisSourceContinuous, + PointerAxisSourceWheelTilt + }; enum KeyboardKeyState { KeyboardKeyReleased, KeyboardKeyPressed, KeyboardKeyAutoRepeat }; virtual ~InputRedirection(); void init(); /** * @return const QPointF& The current global pointer position **/ QPointF globalPointer() const; Qt::MouseButtons qtButtonStates() const; Qt::KeyboardModifiers keyboardModifiers() const; Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const; void registerShortcut(const QKeySequence &shortcut, QAction *action); /** * @overload * * Like registerShortcut, but also connects QAction::triggered to the @p slot on @p receiver. * It's recommended to use this method as it ensures that the X11 timestamp is updated prior * to the @p slot being invoked. If not using this overload it's required to ensure that * registerShortcut is called before connecting to QAction's triggered signal. **/ template void registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, Slot slot); void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action); void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action); void registerTouchpadSwipeShortcut(SwipeDirection direction, QAction *action); void registerGlobalAccel(KGlobalAccelInterface *interface); /** * @internal **/ void processPointerMotion(const QPointF &pos, uint32_t time); /** * @internal **/ void processPointerButton(uint32_t button, PointerButtonState state, uint32_t time); /** * @internal **/ - void processPointerAxis(PointerAxis axis, qreal delta, uint32_t time); + void processPointerAxis(PointerAxis axis, qreal delta, qint32 discreteDelta, PointerAxisSource source, uint32_t time); /** * @internal **/ void processKeyboardKey(uint32_t key, KeyboardKeyState state, uint32_t time); /** * @internal **/ void processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); /** * @internal **/ void processKeymapChange(int fd, uint32_t size); void processTouchDown(qint32 id, const QPointF &pos, quint32 time); void processTouchUp(qint32 id, quint32 time); void processTouchMotion(qint32 id, const QPointF &pos, quint32 time); void cancelTouch(); void touchFrame(); bool supportsPointerWarping() const; void warpPointer(const QPointF &pos); /** * Adds the @p filter to the list of event filters and makes it the first * event filter in processing. * * Note: the event filter will get events before the lock screen can get them, thus * this is a security relevant method. **/ void prependInputEventFilter(InputEventFilter *filter); void uninstallInputEventFilter(InputEventFilter *filter); /** * Installs the @p spy for spying on events. **/ void installInputEventSpy(InputEventSpy *spy); /** * Uninstalls the @p spy. This happens automatically when deleting an InputEventSpy. **/ void uninstallInputEventSpy(InputEventSpy *spy); Toplevel *findToplevel(const QPoint &pos); Toplevel *findManagedToplevel(const QPoint &pos); GlobalShortcutsManager *shortcuts() const { return m_shortcuts; } /** * Sends an event through all InputFilters. * The method @p function is invoked on each input filter. Processing is stopped if * a filter returns @c true for @p function. * * The UnaryPredicate is defined like the UnaryPredicate of std::any_of. * The signature of the function should be equivalent to the following: * @code * bool function(const InputEventFilter *spy); * @endcode * * The intended usage is to std::bind the method to invoke on the filter with all arguments * bind. **/ template void processFilters(UnaryPredicate function) { std::any_of(m_filters.constBegin(), m_filters.constEnd(), function); } /** * Sends an event through all input event spies. * The @p function is invoked on each InputEventSpy. * * The UnaryFunction is defined like the UnaryFunction of std::for_each. * The signature of the function should be equivalent to the following: * @code * void function(const InputEventSpy *spy); * @endcode * * The intended usage is to std::bind the method to invoke on the spies with all arguments * bind. **/ template void processSpies(UnaryFunction function) { std::for_each(m_spies.constBegin(), m_spies.constEnd(), function); } KeyboardInputRedirection *keyboard() const { return m_keyboard; } PointerInputRedirection *pointer() const { return m_pointer; } TouchInputRedirection *touch() const { return m_touch; } bool hasAlphaNumericKeyboard(); bool hasTabletModeSwitch(); void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName); void startInteractivePositionSelection(std::function callback); bool isSelectingWindow() const; Q_SIGNALS: /** * @brief Emitted when the global pointer position changed * * @param pos The new global pointer position. **/ void globalPointerChanged(const QPointF &pos); /** * @brief Emitted when the state of a pointer button changed. * * @param button The button which changed * @param state The new button state **/ void pointerButtonStateChanged(uint32_t button, InputRedirection::PointerButtonState state); /** * @brief Emitted when a pointer axis changed * * @param axis The axis on which the even occurred * @param delta The delta of the event. **/ void pointerAxisChanged(InputRedirection::PointerAxis axis, qreal delta); /** * @brief Emitted when the modifiers changes. * * Only emitted for the mask which is provided by Qt::KeyboardModifiers, if other modifiers * change signal is not emitted * * @param newMods The new modifiers state * @param oldMods The previous modifiers state **/ void keyboardModifiersChanged(Qt::KeyboardModifiers newMods, Qt::KeyboardModifiers oldMods); /** * @brief Emitted when the state of a key changed. * * @param keyCode The keycode of the key which changed * @param state The new key state **/ void keyStateChanged(quint32 keyCode, InputRedirection::KeyboardKeyState state); void hasAlphaNumericKeyboardChanged(bool set); void hasTabletModeSwitchChanged(bool set); private: void setupLibInput(); void setupTouchpadShortcuts(); void setupLibInputWithScreens(); void setupWorkspace(); void reconfigure(); void setupInputFilters(); void installInputEventFilter(InputEventFilter *filter); KeyboardInputRedirection *m_keyboard; PointerInputRedirection *m_pointer; TouchInputRedirection *m_touch; GlobalShortcutsManager *m_shortcuts; LibInput::Connection *m_libInput = nullptr; WindowSelectorFilter *m_windowSelector = nullptr; QVector m_filters; QVector m_spies; KWIN_SINGLETON(InputRedirection) friend InputRedirection *input(); friend class DecorationEventFilter; friend class InternalWindowEventFilter; friend class ForwardInputFilter; }; /** * Base class for filtering input events inside InputRedirection. * * The idea behind the InputEventFilter is to have task oriented * filters. E.g. there is one filter taking care of a locked screen, * one to take care of interacting with window decorations, etc. * * A concrete subclass can reimplement the virtual methods and decide * whether an event should be filtered out or not by returning either * @c true or @c false. E.g. the lock screen filter can easily ensure * that all events are filtered out. * * As soon as a filter returns @c true the processing is stopped. If * a filter returns @c false the next one is invoked. This means a filter * installed early gets to see more events than a filter installed later on. * * Deleting an instance of InputEventFilter automatically uninstalls it from * InputRedirection. **/ class KWIN_EXPORT InputEventFilter { public: InputEventFilter(); virtual ~InputEventFilter(); /** * Event filter for pointer events which can be described by a QMouseEvent. * * Please note that the button translation in QMouseEvent cannot cover all * possible buttons. Because of that also the @p nativeButton code is passed * through the filter. For internal areas it's fine to use @p event, but for * passing to client windows the @p nativeButton should be used. * * @param event The event information about the move or button press/release * @param nativeButton The native key code of the button, for move events 0 * @return @c true to stop further event processing, @c false to pass to next filter **/ virtual bool pointerEvent(QMouseEvent *event, quint32 nativeButton); /** * Event filter for pointer axis events. * * @param event The event information about the axis event * @return @c true to stop further event processing, @c false to pass to next filter **/ virtual bool wheelEvent(QWheelEvent *event); /** * Event filter for keyboard events. * * @param event The event information about the key event * @return @c tru to stop further event processing, @c false to pass to next filter. **/ virtual bool keyEvent(QKeyEvent *event); virtual bool touchDown(quint32 id, const QPointF &pos, quint32 time); virtual bool touchMotion(quint32 id, const QPointF &pos, quint32 time); virtual bool touchUp(quint32 id, quint32 time); virtual bool pinchGestureBegin(int fingerCount, quint32 time); virtual bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); virtual bool pinchGestureEnd(quint32 time); virtual bool pinchGestureCancelled(quint32 time); virtual bool swipeGestureBegin(int fingerCount, quint32 time); virtual bool swipeGestureUpdate(const QSizeF &delta, quint32 time); virtual bool swipeGestureEnd(quint32 time); virtual bool swipeGestureCancelled(quint32 time); virtual bool switchEvent(SwitchEvent *event); protected: void passToWaylandServer(QKeyEvent *event); }; class KWIN_EXPORT InputDeviceHandler : public QObject { Q_OBJECT public: virtual ~InputDeviceHandler(); virtual void init(); void update(); /** * @brief First Toplevel currently at the position of the input device * according to the stacking order. * @return Toplevel* at device position. **/ QPointer at() const { return m_at; } /** * @brief Toplevel currently having pointer input focus (this might * be different from the Toplevel at the position of the pointer). * @return Toplevel* with pointer focus. **/ QPointer focus() const { return m_focus.focus; } /** * @brief The Decoration currently receiving events. * @return decoration with pointer focus. **/ QPointer decoration() const { return m_focus.decoration; } /** * @brief The internal window currently receiving events. * @return QWindow with pointer focus. **/ QPointer internalWindow() const { return m_focus.internalWindow; } virtual QPointF position() const = 0; void setFocus(Toplevel *toplevel); void setDecoration(QPointer decoration); void setInternalWindow(QWindow *window); Q_SIGNALS: void atChanged(Toplevel *old, Toplevel *now); void decorationChanged(); protected: explicit InputDeviceHandler(InputRedirection *parent); virtual void cleanupInternalWindow(QWindow *old, QWindow *now) = 0; virtual void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) = 0; virtual void focusUpdate(Toplevel *old, Toplevel *now) = 0; /** * Certain input devices can be in a state of having no valid * position. An example are touch screens when no finger/pen * is resting on the surface (no touch point). **/ virtual bool positionValid() const { return false; } virtual bool focusUpdatesBlocked() { return false; } inline bool inited() const { return m_inited; } inline void setInited(bool set) { m_inited = set; } private: bool setAt(Toplevel *toplevel); void updateFocus(); bool updateDecoration(); void updateInternalWindow(QWindow *window); QWindow* findInternalWindow(const QPoint &pos) const; QPointer m_at; struct { QPointer focus; QPointer decoration; QPointer internalWindow; } m_focus; bool m_inited = false; }; inline InputRedirection *input() { return InputRedirection::s_self; } template inline void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, Slot slot) { registerShortcut(shortcut, action); connect(action, &QAction::triggered, receiver, slot); } } // namespace KWin Q_DECLARE_METATYPE(KWin::InputRedirection::KeyboardKeyState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerButtonState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerAxis) +Q_DECLARE_METATYPE(KWin::InputRedirection::PointerAxisSource) #endif // KWIN_INPUT_H diff --git a/input_event.cpp b/input_event.cpp index b8a2d7c97..a5efeef4d 100644 --- a/input_event.cpp +++ b/input_event.cpp @@ -1,63 +1,68 @@ /******************************************************************** 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, 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) +WheelEvent::WheelEvent(const QPointF &pos, qreal delta, qint32 discreteDelta, Qt::Orientation orientation, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, InputRedirection::PointerAxisSource source, + 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) + , m_orientation(orientation) + , m_delta(delta) + , m_discreteDelta(discreteDelta) + , m_source(source) { 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); } SwitchEvent::SwitchEvent(State state, quint32 timestamp, quint64 timestampMicroseconds, LibInput::Device* device) : QInputEvent(QEvent::User) , m_state(state) , m_timestampMicroseconds(timestampMicroseconds) , m_device(device) { setTimestamp(timestamp); } } diff --git a/input_event.h b/input_event.h index 7854a1088..8d2bc2648 100644 --- a/input_event.h +++ b/input_event.h @@ -1,156 +1,181 @@ /******************************************************************** 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 "input.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, 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; } Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const { return m_modifiersRelevantForShortcuts; } void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods) { m_modifiersRelevantForShortcuts = mods; } quint32 nativeButton() const { return m_nativeButton; } void setNativeButton(quint32 button) { m_nativeButton = button; } private: QSizeF m_delta; QSizeF m_deltaUnccelerated; quint64 m_timestampMicroseconds; LibInput::Device *m_device; Qt::KeyboardModifiers m_modifiersRelevantForShortcuts = Qt::KeyboardModifiers(); quint32 m_nativeButton = 0; }; +// TODO: Don't derive from QWheelEvent, this event is quite domain specific. 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); + explicit WheelEvent(const QPointF &pos, qreal delta, qint32 discreteDelta, Qt::Orientation orientation, + Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, InputRedirection::PointerAxisSource source, + quint32 timestamp, LibInput::Device *device); + + Qt::Orientation orientation() const { + return m_orientation; + } + + qreal delta() const { + return m_delta; + } + + qint32 discreteDelta() const { + return m_discreteDelta; + } + + InputRedirection::PointerAxisSource axisSource() const { + return m_source; + } LibInput::Device *device() const { return m_device; } Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const { return m_modifiersRelevantForShortcuts; } void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods) { m_modifiersRelevantForShortcuts = mods; } private: LibInput::Device *m_device; + Qt::Orientation m_orientation; + qreal m_delta; + qint32 m_discreteDelta; + InputRedirection::PointerAxisSource m_source; Qt::KeyboardModifiers m_modifiersRelevantForShortcuts = Qt::KeyboardModifiers(); }; 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; } Qt::KeyboardModifiers modifiersRelevantForGlobalShortcuts() const { return m_modifiersRelevantForShortcuts; } void setModifiersRelevantForGlobalShortcuts(const Qt::KeyboardModifiers &mods) { m_modifiersRelevantForShortcuts = mods; } private: LibInput::Device *m_device; Qt::KeyboardModifiers m_modifiersRelevantForShortcuts = Qt::KeyboardModifiers(); }; class SwitchEvent : public QInputEvent { public: enum class State { Off, On }; explicit SwitchEvent(State state, quint32 timestamp, quint64 timestampMicroseconds, LibInput::Device *device); State state() const { return m_state; } quint64 timestampMicroseconds() const { return m_timestampMicroseconds; } LibInput::Device *device() const { return m_device; } private: State m_state; quint64 m_timestampMicroseconds; LibInput::Device *m_device; }; } #endif diff --git a/libinput/connection.cpp b/libinput/connection.cpp index 7664659d6..104847aba 100644 --- a/libinput/connection.cpp +++ b/libinput/connection.cpp @@ -1,685 +1,664 @@ /******************************************************************** 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" #ifndef KWIN_BUILD_TESTING #include "../screens.h" #endif #include "../logind.h" #include "../udev.h" #include "libinput_logging.h" #include #include #include #include #include #include #include #include namespace KWin { namespace LibInput { class ConnectionAdaptor : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.InputDeviceManager") Q_PROPERTY(QStringList devicesSysNames READ devicesSysNames CONSTANT) private: Connection *m_con; public: ConnectionAdaptor(Connection *con) : m_con(con) { connect(con, &Connection::deviceAddedSysName, this, &ConnectionAdaptor::deviceAdded, Qt::QueuedConnection); connect(con, &Connection::deviceRemovedSysName, this, &ConnectionAdaptor::deviceRemoved, Qt::QueuedConnection); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin/InputDevice"), QStringLiteral("org.kde.KWin.InputDeviceManager"), this, QDBusConnection::ExportAllProperties | QDBusConnection::ExportAllSignals ); } ~ConnectionAdaptor() { QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/org/kde/KWin/InputDeviceManager")); } QStringList devicesSysNames() { // TODO: is this allowed? directly calling function of object in another thread!? // otherwise use signal-slot mechanism return m_con->devicesSysNames(); } Q_SIGNALS: void deviceAdded(QString sysName); void deviceRemoved(QString sysName); }; Connection *Connection::s_self = nullptr; QPointer Connection::s_thread; static ConnectionAdaptor *s_adaptor = nullptr; static Context *s_context = nullptr; static quint32 toLibinputLEDS(Xkb::LEDs leds) { quint32 libinputLeds = 0; if (leds.testFlag(Xkb::LED::NumLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_NUM_LOCK; } if (leds.testFlag(Xkb::LED::CapsLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_CAPS_LOCK; } if (leds.testFlag(Xkb::LED::ScrollLock)) { libinputLeds = libinputLeds | LIBINPUT_LED_SCROLL_LOCK; } return libinputLeds; } Connection::Connection(QObject *parent) : Connection(nullptr, parent) { // only here to fix build, using will crash, BUG 343529 } void Connection::createThread() { if (s_thread) { return; } s_thread = new QThread(); s_thread->setObjectName(QStringLiteral("libinput-connection")); s_thread->start(); } 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; } if (!s_context->assignSeat(LogindIntegration::self()->seat().toUtf8().constData())) { qCWarning(KWIN_LIBINPUT) << "Failed to assign seat" << LogindIntegration::self()->seat(); delete s_context; s_context = nullptr; return nullptr; } } Connection::createThread(); s_self = new Connection(s_context); s_self->moveToThread(s_thread); 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); if (!s_adaptor) { s_adaptor = new ConnectionAdaptor(s_self); } return s_self; } Connection::Connection(Context *input, QObject *parent) : QObject(parent) , m_input(input) , m_notifier(nullptr) , m_mutex(QMutex::Recursive) , m_leds() { Q_ASSERT(m_input); // 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))); } Connection::~Connection() { delete s_adaptor; s_adaptor = nullptr; s_self = nullptr; delete s_context; s_context = nullptr; } void Connection::setup() { QMetaObject::invokeMethod(this, "doSetup", Qt::QueuedConnection); } void Connection::doSetup() { connect(s_self, &Connection::deviceAdded, s_self, [](Device* device) { emit s_self->deviceAddedSysName(device->sysName()); }); connect(s_self, &Connection::deviceRemoved, s_self, [](Device* device) { emit s_self->deviceRemovedSysName(device->sysName()); }); 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_tabletModeSwitchBeforeSuspend = hasTabletModeSwitch(); 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); } } if (device->isTabletModeSwitch()) { m_tabletModeSwitch++; if (m_tabletModeSwitch == 1) { emit hasTabletModeSwitchChanged(true); } } applyDeviceConfig(device); applyScreenToDevice(device); // enable possible leds libinput_device_led_update(device->device(), static_cast(toLibinputLEDS(m_leds))); 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); } } if (device->isTabletModeSwitch()) { m_tabletModeSwitch--; if (m_tabletModeSwitch == 0) { emit hasTabletModeSwitchChanged(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()); + const auto axes = pe->axis(); + for (const InputRedirection::PointerAxis &axis : axes) { + emit pointerAxisChanged(axis, pe->axisValue(axis), pe->discreteAxisValue(axis), + pe->axisSource(), pe->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()); 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, 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: { #ifndef KWIN_BUILD_TESTING TouchEvent *te = static_cast(event.data()); const auto &geo = screens()->geometry(te->device()->screenId()); emit touchDown(te->id(), geo.topLeft() + te->absolutePos(geo.size()), te->time(), te->device()); break; #endif } case LIBINPUT_EVENT_TOUCH_UP: { TouchEvent *te = static_cast(event.data()); emit touchUp(te->id(), te->time(), te->device()); break; } case LIBINPUT_EVENT_TOUCH_MOTION: { #ifndef KWIN_BUILD_TESTING TouchEvent *te = static_cast(event.data()); const auto &geo = screens()->geometry(te->device()->screenId()); emit touchMotion(te->id(), geo.topLeft() + te->absolutePos(geo.size()), te->time(), te->device()); break; #endif } case LIBINPUT_EVENT_TOUCH_CANCEL: { emit touchCanceled(event->device()); 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; } case LIBINPUT_EVENT_SWITCH_TOGGLE: { SwitchEvent *se = static_cast(event.data()); switch (se->state()) { case SwitchEvent::State::Off: emit switchToggledOff(se->time(), se->timeMicroseconds(), se->device()); break; case SwitchEvent::State::On: emit switchToggledOn(se->time(), se->timeMicroseconds(), se->device()); break; default: Q_UNREACHABLE(); } break; } 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); } if (m_tabletModeSwitchBeforeSuspend && !m_tabletModeSwitch) { emit hasTabletModeSwitchChanged(false); } wasSuspended = false; } } void Connection::setScreenSize(const QSize &size) { m_size = size; } void Connection::updateScreens() { QMutexLocker locker(&m_mutex); for (auto device: qAsConst(m_devices)) { applyScreenToDevice(device); } } void Connection::applyScreenToDevice(Device *device) { #ifndef KWIN_BUILD_TESTING QMutexLocker locker(&m_mutex); if (!device->isTouch()) { return; } int id = -1; // let's try to find a screen for it if (screens()->count() == 1) { id = 0; } if (id == -1 && !device->outputName().isEmpty()) { // we have an output name, try to find a screen with matching name for (int i = 0; i < screens()->count(); i++) { if (screens()->name(i) == device->outputName()) { id = i; break; } } } if (id == -1) { // do we have an internal screen? int internalId = -1; for (int i = 0; i < screens()->count(); i++) { if (screens()->isInternal(i)) { internalId = i; break; } } auto testScreenMatches = [device] (int id) { const auto &size = device->size(); const auto &screenSize = screens()->physicalSize(id); return std::round(size.width()) == std::round(screenSize.width()) && std::round(size.height()) == std::round(screenSize.height()); }; if (internalId != -1 && testScreenMatches(internalId)) { id = internalId; } // let's compare all screens for size for (int i = 0; i < screens()->count(); i++) { if (testScreenMatches(i)) { id = i; break; } } if (id == -1) { // still not found if (internalId != -1) { // we have an internal id, so let's use that id = internalId; } else { // just take first screen, we have no clue id = 0; } } } device->setScreenId(id); device->setOrientation(screens()->orientation(id)); #else Q_UNUSED(device) #endif } bool Connection::isSuspended() const { if (!s_context) { return false; } return s_context->isSuspended(); } void Connection::applyDeviceConfig(Device *device) { // pass configuration to Device device->setConfig(m_config->group("Libinput").group(QString::number(device->vendor())).group(QString::number(device->product())).group(device->name())); device->loadConfiguration(); } 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->isTouchpad()) { continue; } 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); } } void Connection::enableTouchpads() { if (m_touchpadsEnabled) { return; } toggleTouchpads(); } void Connection::disableTouchpads() { if (!m_touchpadsEnabled) { return; } toggleTouchpads(); } void Connection::updateLEDs(Xkb::LEDs leds) { if (m_leds == leds) { return; } m_leds = leds; // update on devices const libinput_led l = static_cast(toLibinputLEDS(leds)); for (auto it = m_devices.constBegin(), end = m_devices.constEnd(); it != end; ++it) { libinput_device_led_update((*it)->device(), l); } } QStringList Connection::devicesSysNames() const { QStringList sl; foreach (Device *d, m_devices) { sl.append(d->sysName()); } return sl; } } } #include "connection.moc" diff --git a/libinput/connection.h b/libinput/connection.h index b45beecae..9c701141d 100644 --- a/libinput/connection.h +++ b/libinput/connection.h @@ -1,172 +1,173 @@ /******************************************************************** 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 "../keyboard_input.h" #include #include #include #include #include #include #include class QSocketNotifier; class QThread; namespace KWin { namespace LibInput { class Event; class Device; class Context; class KWIN_EXPORT 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); void updateScreens(); 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 hasTabletModeSwitch() const { return m_tabletModeSwitch > 0; } bool isSuspended() const; void deactivate(); void processEvents(); void toggleTouchpads(); void enableTouchpads(); void disableTouchpads(); QVector devices() const { return m_devices; } QStringList devicesSysNames() const; void updateLEDs(KWin::Xkb::LEDs leds); static void createThread(); 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(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 pointerAxisChanged(KWin::InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, + KWin::InputRedirection::PointerAxisSource source, 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 hasTabletModeSwitchChanged(bool); void deviceAdded(KWin::LibInput::Device *); void deviceRemoved(KWin::LibInput::Device *); void deviceAddedSysName(QString); void deviceRemovedSysName(QString); 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 switchToggledOn(quint32 time, quint64 timeMicroseconds, KWin::LibInput::Device *device); void switchToggledOff(quint32 time, quint64 timeMicroseconds, 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); void applyScreenToDevice(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; int m_tabletModeSwitch = 0; bool m_keyboardBeforeSuspend = false; bool m_alphaNumericKeyboardBeforeSuspend = false; bool m_pointerBeforeSuspend = false; bool m_touchBeforeSuspend = false; bool m_tabletModeSwitchBeforeSuspend = false; QMutex m_mutex; QVector m_eventQueue; bool wasSuspended = false; QVector m_devices; KSharedConfigPtr m_config; bool m_touchpadsEnabled = true; Xkb::LEDs m_leds; KWIN_SINGLETON(Connection) static QPointer s_thread; }; } } #endif diff --git a/libinput/events.cpp b/libinput/events.cpp index 3f79062f5..4077b8ec8 100644 --- a/libinput/events.cpp +++ b/libinput/events.cpp @@ -1,330 +1,356 @@ /******************************************************************** 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); case LIBINPUT_EVENT_SWITCH_TOGGLE: return new SwitchEvent(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(nullptr) { } Event::~Event() { libinput_event_destroy(m_event); } Device *Event::device() const { if (!m_device) { m_device = Device::getDevice(libinput_event_get_device(m_event)); } return m_device; } 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())); } QSizeF PointerEvent::delta() const { Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION); 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); } +qint32 PointerEvent::discreteAxisValue(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_discrete(m_pointerEvent, a); +} + +InputRedirection::PointerAxisSource PointerEvent::axisSource() const +{ + Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS); + switch (libinput_event_pointer_get_axis_source(m_pointerEvent)) { + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + return InputRedirection::PointerAxisSourceWheel; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + return InputRedirection::PointerAxisSourceFinger; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + return InputRedirection::PointerAxisSourceContinuous; + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: + return InputRedirection::PointerAxisSourceWheelTilt; + default: + return InputRedirection::PointerAxisSourceUnknown; + } +} + 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); const qint32 slot = libinput_event_touch_get_slot(m_touchEvent); return slot == -1 ? 0 : slot; } 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; SwitchEvent::SwitchEvent(libinput_event *event, libinput_event_type type) : Event(event, type) , m_switchEvent(libinput_event_get_switch_event(event)) { } SwitchEvent::~SwitchEvent() = default; SwitchEvent::State SwitchEvent::state() const { switch (libinput_event_switch_get_switch_state(m_switchEvent)) { case LIBINPUT_SWITCH_STATE_OFF: return State::Off; case LIBINPUT_SWITCH_STATE_ON: return State::On; default: Q_UNREACHABLE(); } return State::Off; } quint32 SwitchEvent::time() const { return libinput_event_switch_get_time(m_switchEvent); } quint64 SwitchEvent::timeMicroseconds() const { return libinput_event_switch_get_time_usec(m_switchEvent); } } } diff --git a/libinput/events.h b/libinput/events.h index c16fae2dd..6f8ab7094 100644 --- a/libinput/events.h +++ b/libinput/events.h @@ -1,201 +1,203 @@ /******************************************************************** 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; 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; mutable 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; 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; + qint32 discreteAxisValue(InputRedirection::PointerAxis axis) const; + InputRedirection::PointerAxisSource axisSource() 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(); }; class SwitchEvent : public Event { public: SwitchEvent(libinput_event *event, libinput_event_type type); ~SwitchEvent() override; enum class State { Off, On }; State state() const; quint32 time() const; quint64 timeMicroseconds() const; private: libinput_event_switch *m_switchEvent; }; inline libinput_event_type Event::type() const { return m_type; } } } #endif diff --git a/platform.cpp b/platform.cpp index 899b31c4e..84a9e1044 100644 --- a/platform.cpp +++ b/platform.cpp @@ -1,488 +1,487 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 "platform.h" #include #include "composite.h" #include "cursor.h" #include "effects.h" -#include "input.h" #include #include "overlaywindow.h" #include "outline.h" #include "pointer_input.h" #include "scene.h" #include "screenedge.h" #include "wayland_server.h" #include "colorcorrection/manager.h" #include namespace KWin { Platform::Platform(QObject *parent) : QObject(parent) , m_eglDisplay(EGL_NO_DISPLAY) { setSoftWareCursor(false); m_colorCorrect = new ColorCorrect::Manager(this); } Platform::~Platform() { if (m_eglDisplay != EGL_NO_DISPLAY) { eglTerminate(m_eglDisplay); } } QImage Platform::softwareCursor() const { return input()->pointer()->cursorImage(); } QPoint Platform::softwareCursorHotspot() const { return input()->pointer()->cursorHotSpot(); } PlatformCursorImage Platform::cursorImage() const { return PlatformCursorImage(softwareCursor(), softwareCursorHotspot()); } void Platform::hideCursor() { m_hideCursorCounter++; if (m_hideCursorCounter == 1) { doHideCursor(); } } void Platform::doHideCursor() { } void Platform::showCursor() { m_hideCursorCounter--; if (m_hideCursorCounter == 0) { doShowCursor(); } } void Platform::doShowCursor() { } Screens *Platform::createScreens(QObject *parent) { Q_UNUSED(parent) return nullptr; } OpenGLBackend *Platform::createOpenGLBackend() { return nullptr; } QPainterBackend *Platform::createQPainterBackend() { return nullptr; } Edge *Platform::createScreenEdge(ScreenEdges *edges) { return new Edge(edges); } void Platform::createPlatformCursor(QObject *parent) { new InputRedirectionCursor(parent); } void Platform::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { qCWarning(KWIN_CORE) << "This backend does not support configuration changes."; config->setFailed(); } void Platform::setSoftWareCursor(bool set) { if (qEnvironmentVariableIsSet("KWIN_FORCE_SW_CURSOR")) { set = true; } if (m_softWareCursor == set) { return; } m_softWareCursor = set; if (m_softWareCursor) { connect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); connect(this, &Platform::cursorChanged, this, &Platform::triggerCursorRepaint); } else { disconnect(Cursor::self(), &Cursor::posChanged, this, &Platform::triggerCursorRepaint); disconnect(this, &Platform::cursorChanged, this, &Platform::triggerCursorRepaint); } } void Platform::triggerCursorRepaint() { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(m_cursor.lastRenderedGeometry); Compositor::self()->addRepaint(QRect(Cursor::pos() - softwareCursorHotspot(), softwareCursor().size())); } void Platform::markCursorAsRendered() { if (m_softWareCursor) { m_cursor.lastRenderedGeometry = QRect(Cursor::pos() - softwareCursorHotspot(), softwareCursor().size()); } if (input()->pointer()) { input()->pointer()->markCursorAsRendered(); } } void Platform::keyboardKeyPressed(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyPressed, time); } void Platform::keyboardKeyReleased(quint32 key, quint32 time) { if (!input()) { return; } input()->processKeyboardKey(key, InputRedirection::KeyboardKeyReleased, time); } void Platform::keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!input()) { return; } input()->processKeyboardModifiers(modsDepressed, modsLatched, modsLocked, group); } void Platform::keymapChange(int fd, uint32_t size) { if (!input()) { return; } input()->processKeymapChange(fd, size); } -void Platform::pointerAxisHorizontal(qreal delta, quint32 time) +void Platform::pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source) { if (!input()) { return; } - input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, time); + input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, discreteDelta, source, time); } -void Platform::pointerAxisVertical(qreal delta, quint32 time) +void Platform::pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source) { if (!input()) { return; } - input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, time); + input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, discreteDelta, source, time); } void Platform::pointerButtonPressed(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonPressed, time); } void Platform::pointerButtonReleased(quint32 button, quint32 time) { if (!input()) { return; } input()->processPointerButton(button, InputRedirection::PointerButtonReleased, time); } void Platform::pointerMotion(const QPointF &position, quint32 time) { if (!input()) { return; } input()->processPointerMotion(position, time); } void Platform::touchCancel() { if (!input()) { return; } input()->cancelTouch(); } void Platform::touchDown(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchDown(id, pos, time); } void Platform::touchFrame() { if (!input()) { return; } input()->touchFrame(); } void Platform::touchMotion(qint32 id, const QPointF &pos, quint32 time) { if (!input()) { return; } input()->processTouchMotion(id, pos, time); } void Platform::touchUp(qint32 id, quint32 time) { if (!input()) { return; } input()->processTouchUp(id, time); } void Platform::processSwipeGestureBegin(int fingerCount, quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureBegin(fingerCount, time); } void Platform::processSwipeGestureUpdate(const QSizeF &delta, quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureUpdate(delta, time); } void Platform::processSwipeGestureEnd(quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureEnd(time); } void Platform::processSwipeGestureCancelled(quint32 time) { if (!input()) { return; } input()->pointer()->processSwipeGestureCancelled(time); } void Platform::processPinchGestureBegin(int fingerCount, quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureBegin(fingerCount, time); } void Platform::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureUpdate(scale, angleDelta, delta, time); } void Platform::processPinchGestureEnd(quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureEnd(time); } void Platform::processPinchGestureCancelled(quint32 time) { if (!input()) { return; } input()->pointer()->processPinchGestureCancelled(time); } void Platform::repaint(const QRect &rect) { if (!Compositor::self()) { return; } Compositor::self()->addRepaint(rect); } void Platform::setReady(bool ready) { if (m_ready == ready) { return; } m_ready = ready; emit readyChanged(m_ready); } void Platform::warpPointer(const QPointF &globalPos) { Q_UNUSED(globalPos) } bool Platform::supportsQpaContext() const { if (Compositor *c = Compositor::self()) { return c->scene()->openGLPlatformInterfaceExtensions().contains(QByteArrayLiteral("EGL_KHR_surfaceless_context")); } return false; } EGLDisplay KWin::Platform::sceneEglDisplay() const { return m_eglDisplay; } void Platform::setSceneEglDisplay(EGLDisplay display) { m_eglDisplay = display; } QSize Platform::screenSize() const { return QSize(); } QVector Platform::screenGeometries() const { return QVector({QRect(QPoint(0, 0), screenSize())}); } QVector Platform::screenScales() const { return QVector({1}); } bool Platform::requiresCompositing() const { return true; } bool Platform::compositingPossible() const { return true; } QString Platform::compositingNotPossibleReason() const { return QString(); } bool Platform::openGLCompositingIsBroken() const { return false; } void Platform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { Q_UNUSED(safePoint) } void Platform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!input()) { callback(nullptr); return; } input()->startInteractiveWindowSelection(callback, cursorName); } void Platform::startInteractivePositionSelection(std::function callback) { if (!input()) { callback(QPoint(-1, -1)); return; } input()->startInteractivePositionSelection(callback); } void Platform::setupActionForGlobalAccel(QAction *action) { Q_UNUSED(action) } OverlayWindow *Platform::createOverlayWindow() { return nullptr; } void Platform::updateXTime() { } OutlineVisual *Platform::createOutline(Outline *outline) { if (Compositor::compositing()) { return new CompositedOutlineVisual(outline); } return nullptr; } Decoration::Renderer *Platform::createDecorationRenderer(Decoration::DecoratedClientImpl *client) { if (Compositor::self()->hasScene()) { return Compositor::self()->scene()->createDecorationRenderer(client); } return nullptr; } void Platform::invertScreen() { if (effects) { if (Effect *inverter = static_cast(effects)->provides(Effect::ScreenInversion)) { qCDebug(KWIN_CORE) << "inverting screen using Effect plugin"; QMetaObject::invokeMethod(inverter, "toggleScreenInversion", Qt::DirectConnection); } } } void Platform::createEffectsHandler(Compositor *compositor, Scene *scene) { new EffectsHandlerImpl(compositor, scene); } QString Platform::supportInformation() const { return QStringLiteral("Name: %1\n").arg(metaObject()->className()); } } diff --git a/platform.h b/platform.h index e2fdc00cb..b509a636d 100644 --- a/platform.h +++ b/platform.h @@ -1,560 +1,563 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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_PLATFORM_H #define KWIN_PLATFORM_H #include #include #include +#include #include "fixqopengl.h" +#include "input.h" -#include #include #include #include class QAction; namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { namespace ColorCorrect { class Manager; } class AbstractOutput; class Edge; class Compositor; class OverlayWindow; class OpenGLBackend; class Outline; class OutlineVisual; class QPainterBackend; class Scene; class Screens; class ScreenEdges; class Toplevel; class WaylandCursorTheme; namespace Decoration { class Renderer; class DecoratedClientImpl; } class KWIN_EXPORT Outputs : public QVector { public: Outputs(){}; template Outputs(const QVector &other) { resize(other.size()); std::copy(other.constBegin(), other.constEnd(), begin()); } }; class KWIN_EXPORT Platform : public QObject { Q_OBJECT public: virtual ~Platform(); virtual void init() = 0; virtual Screens *createScreens(QObject *parent = nullptr); virtual OpenGLBackend *createOpenGLBackend(); virtual QPainterBackend *createQPainterBackend(); /** * Allows the platform to create a platform specific screen edge. * The default implementation creates a Edge. **/ virtual Edge *createScreenEdge(ScreenEdges *parent); /** * Allows the platform to create a platform specific Cursor. * The default implementation creates an InputRedirectionCursor. **/ virtual void createPlatformCursor(QObject *parent = nullptr); virtual void warpPointer(const QPointF &globalPos); /** * Whether our Compositing EGL display allows a surface less context * so that a sharing context could be created. **/ virtual bool supportsQpaContext() const; /** * The EGLDisplay used by the compositing scene. **/ EGLDisplay sceneEglDisplay() const; void setSceneEglDisplay(EGLDisplay display); /** * The EGLContext used by the compositing scene. **/ virtual EGLContext sceneEglContext() const { return m_context; } /** * Sets the @p context used by the compositing scene. **/ void setSceneEglContext(EGLContext context) { m_context = context; } /** * The first (in case of multiple) EGLSurface used by the compositing scene. **/ EGLSurface sceneEglSurface() const { return m_surface; } /** * Sets the first @p surface used by the compositing scene. * @see sceneEglSurface **/ void setSceneEglSurface(EGLSurface surface) { m_surface = surface; } /** * The EglConfig used by the compositing scene. **/ EGLConfig sceneEglConfig() const { return m_eglConfig; } /** * Sets the @p config used by the compositing scene. * @see sceneEglConfig **/ void setSceneEglConfig(EGLConfig config) { m_eglConfig = config; } /** * Implementing subclasses should provide a size in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns an invalid size. **/ virtual QSize screenSize() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns one QRect positioned at 0/0 with screenSize() as size. **/ virtual QVector screenGeometries() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns a screen with a scale of 1. **/ virtual QVector screenScales() const; /** * Implement this method to receive configuration change requests through KWayland's * OutputManagement interface. * * Base implementation warns that the current backend does not implement this * functionality. **/ virtual void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); /** * Whether the Platform requires compositing for rendering. * Default implementation returns @c true. If the implementing Platform allows to be used * without compositing (e.g. rendering is done by the windowing system), re-implement this method. **/ virtual bool requiresCompositing() const; /** * Whether Compositing is possible in the Platform. * Returning @c false in this method makes only sense if requiresCompositing returns @c false. * * The default implementation returns @c true. * @see requiresCompositing **/ virtual bool compositingPossible() const; /** * Returns a user facing text explaining why compositing is not possible in case * compositingPossible returns @c false. * * The default implementation returns an empty string. * @see compositingPossible **/ virtual QString compositingNotPossibleReason() const; /** * Whether OpenGL compositing is broken. * The Platform can implement this method if it is able to detect whether OpenGL compositing * broke (e.g. triggered a crash in a previous run). * * Default implementation returns @c false. * @see createOpenGLSafePoint **/ virtual bool openGLCompositingIsBroken() const; enum class OpenGLSafePoint { PreInit, PostInit, PreFrame, PostFrame, PostLastGuardedFrame }; /** * This method is invoked before and after creating the OpenGL rendering Scene. * An implementing Platform can use it to detect crashes triggered by the OpenGL implementation. * This can be used for openGLCompositingIsBroken. * * The default implementation does nothing. * @see openGLCompositingIsBroken. **/ virtual void createOpenGLSafePoint(OpenGLSafePoint safePoint); /** * Starts an interactive window selection process. * * Once the user selected a window the @p callback is invoked with the selected Toplevel as * argument. In case the user cancels the interactive window selection or selecting a window is currently * not possible (e.g. screen locked) the @p callback is invoked with a @c nullptr argument. * * During the interactive window selection the cursor is turned into a crosshair cursor unless * @p cursorName is provided. The argument @p cursorName is a QByteArray instead of Qt::CursorShape * to support the "pirate" cursor for kill window which is not wrapped by Qt::CursorShape. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive window selection ends * @param cursorName The optional name of the cursor shape to use, default is crosshair **/ virtual void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName = QByteArray()); /** * Starts an interactive position selection process. * * Once the user selected a position on the screen the @p callback is invoked with * the selected point as argument. In case the user cancels the interactive position selection * or selecting a position is currently not possible (e.g. screen locked) the @p callback * is invoked with a point at @c -1 as x and y argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive position selection ends **/ virtual void startInteractivePositionSelection(std::function callback); /** * Platform specific preparation for an @p action which is used for KGlobalAccel. * * A platform might need to do preparation for an @p action before * it can be used with KGlobalAccel. * * Code using KGlobalAccel should invoke this method for the @p action * prior to setting up any shortcuts and connections. * * The default implementation does nothing. * * @param action The action which will be used with KGlobalAccel. * @since 5.10 **/ virtual void setupActionForGlobalAccel(QAction *action); bool usesSoftwareCursor() const { return m_softWareCursor; } QImage softwareCursor() const; QPoint softwareCursorHotspot() const; void markCursorAsRendered(); /** * Returns a PlatformCursorImage. By default this is created by softwareCursor and * softwareCursorHotspot. An implementing subclass can use this to provide a better * suited PlatformCursorImage. * * @see softwareCursor * @see softwareCursorHotspot * @since 5.9 **/ virtual PlatformCursorImage cursorImage() const; /** * The Platform cursor image should be hidden. * @see showCursor * @see doHideCursor * @see isCursorHidden * @since 5.9 **/ void hideCursor(); /** * The Platform cursor image should be shown again. * @see hideCursor * @see doShowCursor * @see isCursorHidden * @since 5.9 **/ void showCursor(); /** * Whether the cursor is currently hidden. * @see showCursor * @see hideCursor * @since 5.9 **/ bool isCursorHidden() const { return m_hideCursorCounter > 0; } bool handlesOutputs() const { return m_handlesOutputs; } bool isReady() const { return m_ready; } void setInitialWindowSize(const QSize &size) { m_initialWindowSize = size; } void setDeviceIdentifier(const QByteArray &identifier) { m_deviceIdentifier = identifier; } bool supportsPointerWarping() const { return m_pointerWarping; } bool areOutputsEnabled() const { return m_outputsEnabled; } void setOutputsEnabled(bool enabled) { m_outputsEnabled = enabled; } int initialOutputCount() const { return m_initialOutputCount; } void setInitialOutputCount(int count) { m_initialOutputCount = count; } qreal initialOutputScale() const { return m_initialOutputScale; } void setInitialOutputScale(qreal scale) { m_initialOutputScale = scale; } /** * Creates the OverlayWindow required for X11 based compositors. * Default implementation returns @c nullptr. **/ virtual OverlayWindow *createOverlayWindow(); /** * Allows a platform to update the X11 timestamp. * Mostly for the X11 standalone platform to interact with QX11Info. * * Default implementation does nothing. This means code relying on the X timestamp being up to date, * might not be working. E.g. synced X11 window resizing **/ virtual void updateXTime(); /** * Creates the OutlineVisual for the given @p outline. * Default implementation creates an OutlineVisual suited for composited usage. **/ virtual OutlineVisual *createOutline(Outline *outline); /** * Creates the Decoration::Renderer for the given @p client. * * The default implementation creates a Renderer suited for the Compositor, @c nullptr if there is no Compositor. **/ virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *client); /** * Platform specific way to invert the screen. * Default implementation invokes the invert effect **/ virtual void invertScreen(); /** * Default implementation creates an EffectsHandlerImp; **/ virtual void createEffectsHandler(Compositor *compositor, Scene *scene); /** * The CompositingTypes supported by the Platform. * The first item should be the most preferred one. * @since 5.11 **/ virtual QVector supportedCompositors() const = 0; /** * Whether gamma control is supported by the backend. * @since 5.12 **/ bool supportsGammaControl() const { return m_supportsGammaControl; } ColorCorrect::Manager *colorCorrectManager() { return m_colorCorrect; } // outputs with connections (org_kde_kwin_outputdevice) virtual Outputs outputs() const { return Outputs(); } // actively compositing outputs (wl_output) virtual Outputs enabledOutputs() const { return Outputs(); } /** * A string of information to include in kwin debug output * It should not be translated. * * The base implementation prints the name. * @since 5.12 **/ virtual QString supportInformation() const; /** * The compositor plugin which got selected from @link{supportedCompositors}. * Prior to selecting a compositor this returns @c NoCompositing. * * This method allows the platforms to limit the offerings in @link{supportedCompositors} * in case they do not support runtime compositor switching **/ CompositingType selectedCompositor() const { return m_selectedCompositor; } /** * Used by Compositor to set the used compositor. **/ void setSelectedCompositor(CompositingType type) { m_selectedCompositor = type; } public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); void pointerButtonReleased(quint32 button, quint32 time); - void pointerAxisHorizontal(qreal delta, quint32 time); - void pointerAxisVertical(qreal delta, quint32 time); + void pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta = 0, + InputRedirection::PointerAxisSource source = InputRedirection::PointerAxisSourceUnknown); + void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta = 0, + InputRedirection::PointerAxisSource source = InputRedirection::PointerAxisSourceUnknown); void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void keymapChange(int fd, uint32_t size); void touchDown(qint32 id, const QPointF &pos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &pos, quint32 time); void touchCancel(); void touchFrame(); void processSwipeGestureBegin(int fingerCount, quint32 time); void processSwipeGestureUpdate(const QSizeF &delta, quint32 time); void processSwipeGestureEnd(quint32 time); void processSwipeGestureCancelled(quint32 time); void processPinchGestureBegin(int fingerCount, quint32 time); void processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); void processPinchGestureEnd(quint32 time); void processPinchGestureCancelled(quint32 time); Q_SIGNALS: void screensQueried(); void initFailed(); void cursorChanged(); void readyChanged(bool); /** * Emitted by backends using a one screen (nested window) approach and when the size of that changes. **/ void screenSizeChanged(); protected: explicit Platform(QObject *parent = nullptr); void setSoftWareCursor(bool set); void handleOutputs() { m_handlesOutputs = true; } void repaint(const QRect &rect); void setReady(bool ready); QSize initialWindowSize() const { return m_initialWindowSize; } QByteArray deviceIdentifier() const { return m_deviceIdentifier; } void setSupportsPointerWarping(bool set) { m_pointerWarping = set; } void setSupportsGammaControl(bool set) { m_supportsGammaControl = set; } /** * Actual platform specific way to hide the cursor. * Sub-classes need to implement if they support hiding the cursor. * * This method is invoked by hideCursor if the cursor needs to be hidden. * The default implementation does nothing. * * @see doShowCursor * @see hideCursor * @see showCursor **/ virtual void doHideCursor(); /** * Actual platform specific way to show the cursor. * Sub-classes need to implement if they support showing the cursor. * * This method is invoked by showCursor if the cursor needs to be shown again. * * @see doShowCursor * @see hideCursor * @see showCursor **/ virtual void doShowCursor(); private: void triggerCursorRepaint(); bool m_softWareCursor = false; struct { QRect lastRenderedGeometry; } m_cursor; bool m_handlesOutputs = false; bool m_ready = false; QSize m_initialWindowSize; QByteArray m_deviceIdentifier; bool m_pointerWarping = false; bool m_outputsEnabled = true; int m_initialOutputCount = 1; qreal m_initialOutputScale = 1; EGLDisplay m_eglDisplay; EGLConfig m_eglConfig = nullptr; EGLContext m_context = EGL_NO_CONTEXT; EGLSurface m_surface = EGL_NO_SURFACE; int m_hideCursorCounter = 0; ColorCorrect::Manager *m_colorCorrect = nullptr; bool m_supportsGammaControl = false; CompositingType m_selectedCompositor = NoCompositing; }; } Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/plugins/platforms/wayland/wayland_backend.cpp b/plugins/platforms/wayland/wayland_backend.cpp index c7e4f7ca3..313eb580d 100644 --- a/plugins/platforms/wayland/wayland_backend.cpp +++ b/plugins/platforms/wayland/wayland_backend.cpp @@ -1,851 +1,852 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg Copyright 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 "wayland_backend.h" #if HAVE_WAYLAND_EGL #include "egl_wayland_backend.h" #endif #include "logging.h" #include "scene_qpainter_wayland_backend.h" #include "wayland_output.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "main.h" #include "outputscreens.h" #include "pointer_input.h" #include "screens.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KWin { namespace Wayland { using namespace KWayland::Client; WaylandCursor::WaylandCursor(WaylandBackend *backend) : QObject(backend) , m_backend(backend) { resetSurface(); } void WaylandCursor::resetSurface() { delete m_surface; m_surface = backend()->compositor()->createSurface(this); } void WaylandCursor::init() { installImage(); } WaylandCursor::~WaylandCursor() { delete m_surface; } void WaylandCursor::installImage() { const QImage image = m_backend->softwareCursor(); if (image.isNull() || image.size().isEmpty()) { doInstallImage(nullptr, QSize()); return; } wl_buffer *imageBuffer = *(m_backend->shmPool()->createBuffer(image).data()); doInstallImage(imageBuffer, image.size()); } void WaylandCursor::doInstallImage(wl_buffer *image, const QSize &size) { auto *pointer = m_backend->seat()->pointer(); if (!pointer || !pointer->isValid()) { return; } pointer->setCursor(m_surface, image ? m_backend->softwareCursorHotspot() : QPoint()); drawSurface(image, size); } void WaylandCursor::drawSurface(wl_buffer *image, const QSize &size) { m_surface->attachBuffer(image); m_surface->damage(QRect(QPoint(0,0), size)); m_surface->commit(Surface::CommitFlag::None); m_backend->flush(); } WaylandSubSurfaceCursor::WaylandSubSurfaceCursor(WaylandBackend *backend) : WaylandCursor(backend) { } void WaylandSubSurfaceCursor::init() { if (auto *pointer = backend()->seat()->pointer()) { pointer->hideCursor(); } } WaylandSubSurfaceCursor::~WaylandSubSurfaceCursor() { delete m_subSurface; } void WaylandSubSurfaceCursor::changeOutput(WaylandOutput *output) { delete m_subSurface; m_subSurface = nullptr; m_output = output; if (!output) { return; } createSubSurface(); surface()->commit(); } void WaylandSubSurfaceCursor::createSubSurface() { if (m_subSurface) { return; } if (!m_output) { return; } resetSurface(); m_subSurface = backend()->subCompositor()->createSubSurface(surface(), m_output->surface(), this); m_subSurface->setMode(SubSurface::Mode::Desynchronized); } void WaylandSubSurfaceCursor::doInstallImage(wl_buffer *image, const QSize &size) { if (!image) { delete m_subSurface; m_subSurface = nullptr; return; } createSubSurface(); // cursor position might have changed due to different cursor hot spot move(input()->pointer()->pos()); drawSurface(image, size); } QPointF WaylandSubSurfaceCursor::absoluteToRelativePosition(const QPointF &position) { auto ret = position - m_output->geometry().topLeft() - backend()->softwareCursorHotspot(); return ret; } void WaylandSubSurfaceCursor::move(const QPointF &globalPosition) { auto *output = backend()->getOutputAt(globalPosition.toPoint()); if (!m_output || (output && m_output != output)) { changeOutput(output); if (!m_output) { // cursor might be off the grid return; } installImage(); return; } if (!m_subSurface) { return; } // place the sub-surface relative to the output it is on and factor in the hotspot const auto relativePosition = globalPosition.toPoint() - backend()->softwareCursorHotspot() - m_output->geometry().topLeft(); m_subSurface->setPosition(relativePosition); Compositor::self()->addRepaintFull(); } WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend) : QObject(NULL) , m_seat(new Seat(this)) , m_pointer(NULL) , m_keyboard(NULL) , m_touch(nullptr) , m_enteredSerial(0) , m_backend(backend) { m_seat->setup(seat); connect(m_seat, &Seat::hasKeyboardChanged, this, [this](bool hasKeyboard) { if (hasKeyboard) { m_keyboard = m_seat->createKeyboard(this); connect(m_keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState state, quint32 time) { switch (state) { case Keyboard::KeyState::Pressed: if (key == KEY_RIGHTCTRL) { m_backend->togglePointerLock(); } m_backend->keyboardKeyPressed(key, time); break; case Keyboard::KeyState::Released: m_backend->keyboardKeyReleased(key, time); break; default: Q_UNREACHABLE(); } } ); connect(m_keyboard, &Keyboard::modifiersChanged, this, [this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) { m_backend->keyboardModifiers(depressed, latched, locked, group); } ); connect(m_keyboard, &Keyboard::keymapChanged, this, [this](int fd, quint32 size) { m_backend->keymapChange(fd, size); } ); } else { destroyKeyboard(); } } ); connect(m_seat, &Seat::hasPointerChanged, this, [this](bool hasPointer) { if (hasPointer && !m_pointer) { m_pointer = m_seat->createPointer(this); setupPointerGestures(); connect(m_pointer, &Pointer::entered, this, [this](quint32 serial, const QPointF &relativeToSurface) { Q_UNUSED(relativeToSurface) m_enteredSerial = serial; } ); connect(m_pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) { m_backend->pointerMotionRelativeToOutput(relativeToSurface, time); } ); connect(m_pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState state) { Q_UNUSED(serial) switch (state) { case Pointer::ButtonState::Pressed: m_backend->pointerButtonPressed(button, time); break; case Pointer::ButtonState::Released: m_backend->pointerButtonReleased(button, time); break; default: Q_UNREACHABLE(); } } ); + // TODO: Send discreteDelta and source as well. connect(m_pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis axis, qreal delta) { switch (axis) { case Pointer::Axis::Horizontal: m_backend->pointerAxisHorizontal(delta, time); break; case Pointer::Axis::Vertical: m_backend->pointerAxisVertical(delta, time); break; default: Q_UNREACHABLE(); } } ); } else { destroyPointer(); } } ); connect(m_seat, &Seat::hasTouchChanged, [this] (bool hasTouch) { if (hasTouch && !m_touch) { m_touch = m_seat->createTouch(this); connect(m_touch, &Touch::sequenceCanceled, m_backend, &Platform::touchCancel); connect(m_touch, &Touch::frameEnded, m_backend, &Platform::touchFrame); connect(m_touch, &Touch::sequenceStarted, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointAdded, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointRemoved, this, [this] (TouchPoint *tp) { m_backend->touchUp(tp->id(), tp->time()); } ); connect(m_touch, &Touch::pointMoved, this, [this] (TouchPoint *tp) { m_backend->touchMotion(tp->id(), tp->position(), tp->time()); } ); } else { destroyTouch(); } } ); WaylandServer *server = waylandServer(); if (server) { using namespace KWayland::Server; SeatInterface *si = server->seat(); connect(m_seat, &Seat::hasKeyboardChanged, si, &SeatInterface::setHasKeyboard); connect(m_seat, &Seat::hasPointerChanged, si, &SeatInterface::setHasPointer); connect(m_seat, &Seat::hasTouchChanged, si, &SeatInterface::setHasTouch); connect(m_seat, &Seat::nameChanged, si, &SeatInterface::setName); } } void WaylandBackend::pointerMotionRelativeToOutput(const QPointF &position, quint32 time) { auto outputIt = std::find_if(m_outputs.begin(), m_outputs.end(), [this](WaylandOutput *wo) { return wo->surface() == m_seat->pointer()->enteredSurface(); }); Q_ASSERT(outputIt != m_outputs.end()); const QPointF outputPosition = (*outputIt)->geometry().topLeft() + position; Platform::pointerMotion(outputPosition, time); } WaylandSeat::~WaylandSeat() { destroyPointer(); destroyKeyboard(); destroyTouch(); } void WaylandSeat::destroyPointer() { delete m_pinchGesture; m_pinchGesture = nullptr; delete m_swipeGesture; m_swipeGesture = nullptr; delete m_pointer; m_pointer = nullptr; } void WaylandSeat::destroyKeyboard() { delete m_keyboard; m_keyboard = nullptr; } void WaylandSeat::destroyTouch() { delete m_touch; m_touch = nullptr; } void WaylandSeat::setupPointerGestures() { if (!m_pointer || !m_gesturesInterface) { return; } if (m_pinchGesture || m_swipeGesture) { return; } m_pinchGesture = m_gesturesInterface->createPinchGesture(m_pointer, this); m_swipeGesture = m_gesturesInterface->createSwipeGesture(m_pointer, this); connect(m_pinchGesture, &PointerPinchGesture::started, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial); m_backend->processPinchGestureBegin(m_pinchGesture->fingerCount(), time); } ); connect(m_pinchGesture, &PointerPinchGesture::updated, m_backend, [this] (const QSizeF &delta, qreal scale, qreal rotation, quint32 time) { m_backend->processPinchGestureUpdate(scale, rotation, delta, time); } ); connect(m_pinchGesture, &PointerPinchGesture::ended, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processPinchGestureEnd(time); } ); connect(m_pinchGesture, &PointerPinchGesture::cancelled, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processPinchGestureCancelled(time); } ); connect(m_swipeGesture, &PointerSwipeGesture::started, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureBegin(m_swipeGesture->fingerCount(), time); } ); connect(m_swipeGesture, &PointerSwipeGesture::updated, m_backend, &Platform::processSwipeGestureUpdate); connect(m_swipeGesture, &PointerSwipeGesture::ended, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureEnd(time); } ); connect(m_swipeGesture, &PointerSwipeGesture::cancelled, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureCancelled(time); } ); } WaylandBackend::WaylandBackend(QObject *parent) : Platform(parent) , m_display(nullptr) , m_eventQueue(new EventQueue(this)) , m_registry(new Registry(this)) , m_compositor(new KWayland::Client::Compositor(this)) , m_subCompositor(new KWayland::Client::SubCompositor(this)) , m_shell(new Shell(this)) , m_shm(new ShmPool(this)) , m_connectionThreadObject(new ConnectionThread(nullptr)) , m_connectionThread(nullptr) { connect(this, &WaylandBackend::connectionFailed, this, &WaylandBackend::initFailed); } WaylandBackend::~WaylandBackend() { if (m_pointerConstraints) { m_pointerConstraints->release(); } delete m_waylandCursor; qDeleteAll(m_outputs); if (m_xdgShell) { m_xdgShell->release(); } m_shell->release(); m_subCompositor->release(); m_compositor->release(); m_registry->release(); delete m_seat; m_shm->release(); m_eventQueue->release(); m_connectionThreadObject->deleteLater(); m_connectionThread->quit(); m_connectionThread->wait(); qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display"; } void WaylandBackend::init() { connect(m_registry, &Registry::compositorAnnounced, this, [this](quint32 name) { m_compositor->setup(m_registry->bindCompositor(name, 1)); } ); connect(m_registry, &Registry::subCompositorAnnounced, this, [this](quint32 name) { m_subCompositor->setup(m_registry->bindSubCompositor(name, 1)); } ); connect(m_registry, &Registry::shellAnnounced, this, [this](quint32 name) { m_shell->setup(m_registry->bindShell(name, 1)); } ); connect(m_registry, &Registry::seatAnnounced, this, [this](quint32 name) { if (Application::usesLibinput()) { return; } m_seat = new WaylandSeat(m_registry->bindSeat(name, 2), this); } ); connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name) { m_shm->setup(m_registry->bindShm(name, 1)); } ); connect(m_registry, &Registry::relativePointerManagerUnstableV1Announced, this, [this](quint32 name, quint32 version) { if (m_relativePointerManager) { return; } m_relativePointerManager = m_registry->createRelativePointerManager(name, version, this); if (m_pointerConstraints) { emit pointerLockSupportedChanged(); } } ); connect(m_registry, &Registry::pointerConstraintsUnstableV1Announced, this, [this](quint32 name, quint32 version) { if (m_pointerConstraints) { return; } m_pointerConstraints = m_registry->createPointerConstraints(name, version, this); if (m_relativePointerManager) { emit pointerLockSupportedChanged(); } } ); connect(m_registry, &Registry::interfacesAnnounced, this, &WaylandBackend::createOutputs); connect(m_registry, &Registry::interfacesAnnounced, this, [this] { if (!m_seat) { return; } const auto gi = m_registry->interface(Registry::Interface::PointerGesturesUnstableV1); if (gi.name == 0) { return; } auto gesturesInterface = m_registry->createPointerGestures(gi.name, gi.version, m_seat); m_seat->installGesturesInterface(gesturesInterface); m_waylandCursor = new WaylandCursor(this); } ); if (!deviceIdentifier().isEmpty()) { m_connectionThreadObject->setSocketName(deviceIdentifier()); } connect(this, &WaylandBackend::cursorChanged, this, [this] { if (!m_seat) { return; } m_waylandCursor->installImage(); markCursorAsRendered(); } ); connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) { delete m_waylandCursor; if (locked) { Q_ASSERT(!m_relativePointer); m_waylandCursor = new WaylandSubSurfaceCursor(this); m_waylandCursor->move(input()->pointer()->pos()); m_relativePointer = m_relativePointerManager->createRelativePointer(m_seat->pointer(), this); if (!m_relativePointer->isValid()) { return; } connect(m_relativePointer, &RelativePointer::relativeMotion, this, &WaylandBackend::relativeMotionHandler); } else { delete m_relativePointer; m_relativePointer = nullptr; m_waylandCursor = new WaylandCursor(this); } m_waylandCursor->init(); }); initConnection(); } void WaylandBackend::relativeMotionHandler(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) { Q_UNUSED(deltaNonAccelerated) Q_ASSERT(m_waylandCursor); const auto oldGlobalPos = input()->pointer()->pos(); const QPointF newPos = oldGlobalPos + QPointF(delta.width(), delta.height()); m_waylandCursor->move(newPos); Platform::pointerMotion(newPos, timestamp); } void WaylandBackend::initConnection() { connect(m_connectionThreadObject, &ConnectionThread::connected, this, [this]() { // create the event queue for the main gui thread m_display = m_connectionThreadObject->display(); m_eventQueue->setup(m_connectionThreadObject); m_registry->setEventQueue(m_eventQueue); // setup registry m_registry->create(m_display); m_registry->setup(); }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::connectionDied, this, [this]() { setReady(false); emit systemCompositorDied(); delete m_seat; m_shm->destroy(); qDeleteAll(m_outputs); m_outputs.clear(); if (m_shell) { m_shell->destroy(); } if (m_xdgShell) { m_xdgShell->destroy(); } m_subCompositor->destroy(); m_compositor->destroy(); m_registry->destroy(); m_eventQueue->destroy(); if (m_display) { m_display = nullptr; } }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::failed, this, &WaylandBackend::connectionFailed, Qt::QueuedConnection); m_connectionThread = new QThread(this); m_connectionThreadObject->moveToThread(m_connectionThread); m_connectionThread->start(); m_connectionThreadObject->initConnection(); } void WaylandBackend::updateScreenSize(WaylandOutput *output) { auto it = std::find(m_outputs.begin(), m_outputs.end(), output); int nextLogicalPosition = output->geometry().topRight().x(); while (++it != m_outputs.end()) { const QRect geo = (*it)->geometry(); (*it)->setGeometry(QPoint(nextLogicalPosition, 0), geo.size()); nextLogicalPosition = geo.topRight().x(); } } void WaylandBackend::createOutputs() { using namespace KWayland::Client; const auto ssdManagerIface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); ServerSideDecorationManager *ssdManager = ssdManagerIface.name == 0 ? nullptr : m_registry->createServerSideDecorationManager(ssdManagerIface.name, ssdManagerIface.version, this); const auto xdgIface = m_registry->interface(Registry::Interface::XdgShellUnstableV6); if (xdgIface.name != 0) { m_xdgShell = m_registry->createXdgShell(xdgIface.name, xdgIface.version, this); } // we need to multiply the initial window size with the scale in order to // create an output window of this size in the end const int pixelWidth = initialWindowSize().width() * initialOutputScale() + 0.5; const int pixelHeight = initialWindowSize().height() * initialOutputScale() + 0.5; const int logicalWidth = initialWindowSize().width(); int logicalWidthSum = 0; for (int i = 0; i < initialOutputCount(); i++) { auto surface = m_compositor->createSurface(this); if (!surface || !surface->isValid()) { qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; return; } if (ssdManager) { auto decoration = ssdManager->create(surface, this); connect(decoration, &ServerSideDecoration::modeChanged, this, [this, decoration] { if (decoration->mode() != ServerSideDecoration::Mode::Server) { decoration->requestMode(ServerSideDecoration::Mode::Server); } } ); } WaylandOutput *waylandOutput = nullptr; if (m_xdgShell && m_xdgShell->isValid()) { waylandOutput = new XdgShellOutput(surface, m_xdgShell, this, i+1); } else if (m_shell->isValid()) { waylandOutput = new ShellOutput(surface, m_shell, this); } if (!waylandOutput) { qCCritical(KWIN_WAYLAND_BACKEND) << "Binding to all shell interfaces failed for output" << i; return; } waylandOutput->setScale(initialOutputScale()); waylandOutput->setGeometry(QPoint(logicalWidthSum, 0), QSize(pixelWidth, pixelHeight)); connect(waylandOutput, &WaylandOutput::sizeChanged, this, [this, waylandOutput](const QSize &size) { Q_UNUSED(size) updateScreenSize(waylandOutput); Compositor::self()->addRepaintFull(); }); connect(waylandOutput, &WaylandOutput::frameRendered, this, &WaylandBackend::checkBufferSwap); logicalWidthSum += logicalWidth; m_outputs << waylandOutput; } setReady(true); emit screensQueried(); } Screens *WaylandBackend::createScreens(QObject *parent) { return new OutputScreens(this, parent); } OpenGLBackend *WaylandBackend::createOpenGLBackend() { #if HAVE_WAYLAND_EGL return new EglWaylandBackend(this); #else return nullptr; #endif } QPainterBackend *WaylandBackend::createQPainterBackend() { return new WaylandQPainterBackend(this); } void WaylandBackend::checkBufferSwap() { const bool allRendered = std::all_of(m_outputs.begin(), m_outputs.end(), [](WaylandOutput *o) { return o->rendered(); }); if (!allRendered) { // need to wait more // TODO: what if one does not need to be rendered (no damage)? return; } for (auto *output : m_outputs) { if (!output->rendered()) { return; } } Compositor::self()->bufferSwapComplete(); for (auto *output : m_outputs) { output->resetRendered(); } } void WaylandBackend::flush() { if (m_connectionThreadObject) { m_connectionThreadObject->flush(); } } WaylandOutput* WaylandBackend::getOutputAt(const QPointF globalPosition) { const auto pos = globalPosition.toPoint(); auto checkPosition = [pos](WaylandOutput *output) { return output->geometry().contains(pos); }; auto it = std::find_if(m_outputs.begin(), m_outputs.end(), checkPosition); return it == m_outputs.end() ? nullptr : *it; } bool WaylandBackend::supportsPointerLock() { return m_pointerConstraints && m_relativePointerManager; } void WaylandBackend::togglePointerLock() { if (!m_pointerConstraints) { return; } if (!m_relativePointerManager) { return; } if (!m_seat) { return; } auto pointer = m_seat->pointer(); if (!pointer) { return; } if (m_outputs.isEmpty()) { return; } for (auto output : m_outputs) { output->lockPointer(m_seat->pointer(), !m_pointerLockRequested); } m_pointerLockRequested = !m_pointerLockRequested; flush(); } bool WaylandBackend::pointerIsLocked() { for (auto *output : m_outputs) { if (output->pointerIsLocked()) { return true; } } return false; } QVector WaylandBackend::supportedCompositors() const { if (selectedCompositor() != NoCompositing) { return {selectedCompositor()}; } #if HAVE_WAYLAND_EGL return QVector{OpenGLCompositing, QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } Outputs WaylandBackend::outputs() const { return m_outputs; } Outputs WaylandBackend::enabledOutputs() const { // all outputs are enabled return m_outputs; } } } // KWin diff --git a/plugins/platforms/x11/windowed/x11windowed_backend.cpp b/plugins/platforms/x11/windowed/x11windowed_backend.cpp index 22d47fb3e..9282c9482 100644 --- a/plugins/platforms/x11/windowed/x11windowed_backend.cpp +++ b/plugins/platforms/x11/windowed/x11windowed_backend.cpp @@ -1,607 +1,607 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 "x11windowed_backend.h" #include "scene_qpainter_x11_backend.h" #include "logging.h" #include "wayland_server.h" #include "xcbutils.h" #include "egl_x11_backend.h" #include "screens.h" #include // KDE #include #include #include #include #include #include // kwayland #include #include #include #include // xcb #include // X11 #if HAVE_X11_XINPUT #include "ge_event_mem_mover.h" #include #include #endif // system #include #include namespace KWin { X11WindowedBackend::X11WindowedBackend(QObject *parent) : Platform(parent) { setSupportsPointerWarping(true); connect(this, &X11WindowedBackend::sizeChanged, this, &X11WindowedBackend::screenSizeChanged); } X11WindowedBackend::~X11WindowedBackend() { if (m_connection) { if (m_keySymbols) { xcb_key_symbols_free(m_keySymbols); } for (auto it = m_windows.begin(); it != m_windows.end(); ++it) { xcb_unmap_window(m_connection, (*it).window); xcb_destroy_window(m_connection, (*it).window); delete (*it).winInfo; } if (m_cursor) { xcb_free_cursor(m_connection, m_cursor); } xcb_disconnect(m_connection); } } void X11WindowedBackend::init() { int screen = 0; xcb_connection_t *c = nullptr; Display *xDisplay = XOpenDisplay(deviceIdentifier().constData()); if (xDisplay) { c = XGetXCBConnection(xDisplay); XSetEventQueueOwner(xDisplay, XCBOwnsEventQueue); screen = XDefaultScreen(xDisplay); } if (c && !xcb_connection_has_error(c)) { m_connection = c; m_screenNumber = screen; m_display = xDisplay; for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(m_connection)); it.rem; --screen, xcb_screen_next(&it)) { if (screen == m_screenNumber) { m_screen = it.data; } } initXInput(); XRenderUtils::init(m_connection, m_screen->root); createWindow(); connect(kwinApp(), &Application::workspaceCreated, this, &X11WindowedBackend::startEventReading); connect(this, &X11WindowedBackend::cursorChanged, this, [this] { createCursor(softwareCursor(), softwareCursorHotspot()); } ); setReady(true); waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasKeyboard(true); if (m_hasXInput) { waylandServer()->seat()->setHasTouch(true); } emit screensQueried(); } else { emit initFailed(); } } void X11WindowedBackend::initXInput() { #if HAVE_X11_XINPUT int xi_opcode, event, error; // init XInput extension if (!XQueryExtension(m_display, "XInputExtension", &xi_opcode, &event, &error)) { qCDebug(KWIN_X11WINDOWED) << "XInputExtension not present"; return; } // verify that the XInput extension is at at least version 2.0 int major = 2, minor = 2; int result = XIQueryVersion(m_display, &major, &minor); if (result != Success) { qCDebug(KWIN_X11WINDOWED) << "Failed to init XInput 2.2, trying 2.0"; minor = 0; if (XIQueryVersion(m_display, &major, &minor) != Success) { qCDebug(KWIN_X11WINDOWED) << "Failed to init XInput"; return; } } m_xiOpcode = xi_opcode; m_majorVersion = major; m_minorVersion = minor; m_hasXInput = m_majorVersion >=2 && m_minorVersion >= 2; #endif } void X11WindowedBackend::createWindow() { Xcb::Atom protocolsAtom(QByteArrayLiteral("WM_PROTOCOLS"), false, m_connection); Xcb::Atom deleteWindowAtom(QByteArrayLiteral("WM_DELETE_WINDOW"), false, m_connection); for (int i = 0; i < initialOutputCount(); ++i) { Output o; o.window = xcb_generate_id(m_connection); uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; const uint32_t values[] = { m_screen->black_pixel, XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_EXPOSURE }; o.scale = initialOutputScale(); o.size = initialWindowSize() * o.scale; if (!m_windows.isEmpty()) { const auto &p = m_windows.last(); o.internalPosition = QPoint(p.internalPosition.x() + p.size.width() / p.scale, 0); } xcb_create_window(m_connection, XCB_COPY_FROM_PARENT, o.window, m_screen->root, 0, 0, o.size.width(), o.size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, mask, values); // select xinput 2 events initXInputForWindow(o.window); o.winInfo = new NETWinInfo(m_connection, o.window, m_screen->root, NET::WMWindowType, NET::Properties2()); o.winInfo->setWindowType(NET::Normal); o.winInfo->setPid(QCoreApplication::applicationPid()); QIcon windowIcon = QIcon::fromTheme(QStringLiteral("kwin")); auto addIcon = [&o, &windowIcon] (const QSize &size) { if (windowIcon.actualSize(size) != size) { return; } NETIcon icon; icon.data = windowIcon.pixmap(size).toImage().bits(); icon.size.width = size.width(); icon.size.height = size.height(); o.winInfo->setIcon(icon, false); }; addIcon(QSize(16, 16)); addIcon(QSize(32, 32)); addIcon(QSize(48, 48)); xcb_map_window(m_connection, o.window); m_protocols = protocolsAtom; m_deleteWindowProtocol = deleteWindowAtom; xcb_change_property(m_connection, XCB_PROP_MODE_REPLACE, o.window, m_protocols, XCB_ATOM_ATOM, 32, 1, &m_deleteWindowProtocol); m_windows << o; } updateWindowTitle(); xcb_flush(m_connection); } void X11WindowedBackend::initXInputForWindow(xcb_window_t window) { if (!m_hasXInput) { return; } #if HAVE_X11_XINPUT XIEventMask evmasks[1]; unsigned char mask1[XIMaskLen(XI_LASTEVENT)]; memset(mask1, 0, sizeof(mask1)); XISetMask(mask1, XI_TouchBegin); XISetMask(mask1, XI_TouchUpdate); XISetMask(mask1, XI_TouchOwnership); XISetMask(mask1, XI_TouchEnd); evmasks[0].deviceid = XIAllMasterDevices; evmasks[0].mask_len = sizeof(mask1); evmasks[0].mask = mask1; XISelectEvents(m_display, window, evmasks, 1); #else Q_UNUSED(window) #endif } void X11WindowedBackend::startEventReading() { QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this); auto processXcbEvents = [this] { while (auto event = xcb_poll_for_event(m_connection)) { handleEvent(event); free(event); } xcb_flush(m_connection); }; connect(notifier, &QSocketNotifier::activated, this, processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents); } #if HAVE_X11_XINPUT static inline qreal fixed1616ToReal(FP1616 val) { return (val) * 1.0 / (1 << 16); } #endif void X11WindowedBackend::handleEvent(xcb_generic_event_t *e) { const uint8_t eventType = e->response_type & ~0x80; switch (eventType) { case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: handleButtonPress(reinterpret_cast(e)); break; case XCB_MOTION_NOTIFY: { auto event = reinterpret_cast(e); auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [event] (const Output &o) { return o.window == event->event; }); if (it == m_windows.constEnd()) { break; } //generally we don't need to normalise input to the output scale; however because we're getting input //from a host window that doesn't understand scaling, we need to apply it ourselves so the cursor matches pointerMotion(QPointF(event->root_x - (*it).xPosition.x() + (*it).internalPosition.x(), event->root_y - (*it).xPosition.y() + (*it).internalPosition.y()) / it->scale, event->time); } break; case XCB_KEY_PRESS: case XCB_KEY_RELEASE: { auto event = reinterpret_cast(e); if (eventType == XCB_KEY_PRESS) { if (!m_keySymbols) { m_keySymbols = xcb_key_symbols_alloc(m_connection); } const xcb_keysym_t kc = xcb_key_symbols_get_keysym(m_keySymbols, event->detail, 0); if (kc == XK_Control_R) { grabKeyboard(event->time); } keyboardKeyPressed(event->detail - 8, event->time); } else { keyboardKeyReleased(event->detail - 8, event->time); } } break; case XCB_CONFIGURE_NOTIFY: updateSize(reinterpret_cast(e)); break; case XCB_ENTER_NOTIFY: { auto event = reinterpret_cast(e); auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [event] (const Output &o) { return o.window == event->event; }); if (it == m_windows.constEnd()) { break; } pointerMotion(QPointF(event->root_x - (*it).xPosition.x() + (*it).internalPosition.x(), event->root_y - (*it).xPosition.y() + (*it).internalPosition.y()) / it->scale, event->time); } break; case XCB_CLIENT_MESSAGE: handleClientMessage(reinterpret_cast(e)); break; case XCB_EXPOSE: handleExpose(reinterpret_cast(e)); break; case XCB_MAPPING_NOTIFY: if (m_keySymbols) { xcb_refresh_keyboard_mapping(m_keySymbols, reinterpret_cast(e)); } break; #if HAVE_X11_XINPUT case XCB_GE_GENERIC: { GeEventMemMover ge(e); auto te = reinterpret_cast(e); auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [te] (const Output &o) { return o.window == te->event; }); if (it == m_windows.constEnd()) { break; } QPointF position{ fixed1616ToReal(te->root_x) - (*it).xPosition.x() + (*it).internalPosition.x(), fixed1616ToReal(te->root_y) - (*it).xPosition.y() + (*it).internalPosition.y() }; position /= it->scale; switch (ge->event_type) { case XI_TouchBegin: { touchDown(te->detail, position, te->time); touchFrame(); break; } case XI_TouchUpdate: { touchMotion(te->detail, position, te->time); touchFrame(); break; } case XI_TouchEnd: { touchUp(te->detail, te->time); touchFrame(); break; } case XI_TouchOwnership: { auto te = reinterpret_cast(e); XIAllowTouchEvents(m_display, te->deviceid, te->sourceid, te->touchid, XIAcceptTouch); break; } } break; } #endif default: break; } } void X11WindowedBackend::grabKeyboard(xcb_timestamp_t time) { const bool oldState = m_keyboardGrabbed; if (m_keyboardGrabbed) { xcb_ungrab_keyboard(m_connection, time); xcb_ungrab_pointer(m_connection, time); m_keyboardGrabbed = false; } else { const auto c = xcb_grab_keyboard_unchecked(m_connection, false, window(), time, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); ScopedCPointer grab(xcb_grab_keyboard_reply(m_connection, c, nullptr)); if (grab.isNull()) { return; } if (grab->status == XCB_GRAB_STATUS_SUCCESS) { const auto c = xcb_grab_pointer_unchecked(m_connection, false, window(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, window(), XCB_CURSOR_NONE, time); ScopedCPointer grab(xcb_grab_pointer_reply(m_connection, c, nullptr)); if (grab.isNull() || grab->status != XCB_GRAB_STATUS_SUCCESS) { xcb_ungrab_keyboard(m_connection, time); return; } m_keyboardGrabbed = true; } } if (oldState != m_keyboardGrabbed) { updateWindowTitle(); xcb_flush(m_connection); } } void X11WindowedBackend::updateWindowTitle() { const QString grab = m_keyboardGrabbed ? i18n("Press right control to ungrab input") : i18n("Press right control key to grab input"); const QString title = QStringLiteral("%1 (%2) - %3").arg(i18n("KDE Wayland Compositor")) .arg(waylandServer()->display()->socketName()) .arg(grab); for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { (*it).winInfo->setName(title.toUtf8().constData()); } } void X11WindowedBackend::handleClientMessage(xcb_client_message_event_t *event) { auto it = std::find_if(m_windows.begin(), m_windows.end(), [event] (const Output &o) { return o.window == event->window; }); if (it == m_windows.end()) { return; } if (event->type == m_protocols && m_protocols != XCB_ATOM_NONE) { if (event->data.data32[0] == m_deleteWindowProtocol && m_deleteWindowProtocol != XCB_ATOM_NONE) { if (m_windows.count() == 1) { qCDebug(KWIN_X11WINDOWED) << "Backend window is going to be closed, shutting down."; QCoreApplication::quit(); } else { // remove the window qCDebug(KWIN_X11WINDOWED) << "Removing one output window."; auto o = *it; it = m_windows.erase(it); xcb_unmap_window(m_connection, o.window); xcb_destroy_window(m_connection, o.window); delete o.winInfo; // update the sizes int x = o.internalPosition.x(); for (; it != m_windows.end(); ++it) { (*it).internalPosition.setX(x); x += (*it).size.width(); } QMetaObject::invokeMethod(screens(), "updateCount"); } } } } void X11WindowedBackend::handleButtonPress(xcb_button_press_event_t *event) { auto it = std::find_if(m_windows.constBegin(), m_windows.constEnd(), [event] (const Output &o) { return o.window == event->event; }); if (it == m_windows.constEnd()) { return; } bool const pressed = (event->response_type & ~0x80) == XCB_BUTTON_PRESS; if (event->detail >= XCB_BUTTON_INDEX_4 && event->detail <= 7) { // wheel if (!pressed) { return; } const int delta = (event->detail == XCB_BUTTON_INDEX_4 || event->detail == 6) ? -1 : 1; static const qreal s_defaultAxisStepDistance = 10.0; if (event->detail > 5) { - pointerAxisHorizontal(delta * s_defaultAxisStepDistance, event->time); + pointerAxisHorizontal(delta * s_defaultAxisStepDistance, event->time, delta); } else { - pointerAxisVertical(delta * s_defaultAxisStepDistance, event->time); + pointerAxisVertical(delta * s_defaultAxisStepDistance, event->time, delta); } return; } uint32_t button = 0; switch (event->detail) { case XCB_BUTTON_INDEX_1: button = BTN_LEFT; break; case XCB_BUTTON_INDEX_2: button = BTN_MIDDLE; break; case XCB_BUTTON_INDEX_3: button = BTN_RIGHT; break; default: button = event->detail + BTN_LEFT - 1; return; } pointerMotion(QPointF(event->root_x - (*it).xPosition.x() + (*it).internalPosition.x(), event->root_y - (*it).xPosition.y() + (*it).internalPosition.y()) / it->scale, event->time); if (pressed) { pointerButtonPressed(button, event->time); } else { pointerButtonReleased(button, event->time); } } void X11WindowedBackend::handleExpose(xcb_expose_event_t *event) { repaint(QRect(event->x, event->y, event->width, event->height)); } void X11WindowedBackend::updateSize(xcb_configure_notify_event_t *event) { auto it = std::find_if(m_windows.begin(), m_windows.end(), [event] (const Output &o) { return o.window == event->window; }); if (it == m_windows.end()) { return; } (*it).xPosition = QPoint(event->x, event->y); QSize s = QSize(event->width, event->height); if (s != (*it).size) { (*it).size = s; int x = (*it).internalPosition.x() + (*it).size.width() / (*it).scale; it++; for (; it != m_windows.end(); ++it) { (*it).internalPosition.setX(x); x += (*it).size.width() / (*it).scale; } emit sizeChanged(); } } void X11WindowedBackend::createCursor(const QImage &srcImage, const QPoint &hotspot) { const xcb_pixmap_t pix = xcb_generate_id(m_connection); const xcb_gcontext_t gc = xcb_generate_id(m_connection); const xcb_cursor_t cid = xcb_generate_id(m_connection); //right now on X we only have one scale between all screens, and we know we will have at least one screen const qreal outputScale = screenScales().first(); const QSize targetSize = srcImage.size() * outputScale / srcImage.devicePixelRatio(); const QImage img = srcImage.scaled(targetSize, Qt::KeepAspectRatio); xcb_create_pixmap(m_connection, 32, pix, m_screen->root, img.width(), img.height()); xcb_create_gc(m_connection, gc, pix, 0, nullptr); xcb_put_image(m_connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, img.width(), img.height(), 0, 0, 0, 32, img.byteCount(), img.constBits()); XRenderPicture pic(pix, 32); xcb_render_create_cursor(m_connection, cid, pic, qRound(hotspot.x() * outputScale), qRound(hotspot.y() * outputScale)); for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { xcb_change_window_attributes(m_connection, (*it).window, XCB_CW_CURSOR, &cid); } xcb_free_pixmap(m_connection, pix); xcb_free_gc(m_connection, gc); if (m_cursor) { xcb_free_cursor(m_connection, m_cursor); } m_cursor = cid; xcb_flush(m_connection); markCursorAsRendered(); } xcb_window_t X11WindowedBackend::rootWindow() const { if (!m_screen) { return XCB_WINDOW_NONE; } return m_screen->root; } Screens *X11WindowedBackend::createScreens(QObject *parent) { return new BasicScreens(this, parent); } OpenGLBackend *X11WindowedBackend::createOpenGLBackend() { return new EglX11Backend(this); } QPainterBackend *X11WindowedBackend::createQPainterBackend() { return new X11WindowedQPainterBackend(this); } void X11WindowedBackend::warpPointer(const QPointF &globalPos) { const xcb_window_t w = m_windows.at(0).window; xcb_warp_pointer(m_connection, w, w, 0, 0, 0, 0, globalPos.x(), globalPos.y()); xcb_flush(m_connection); } xcb_window_t X11WindowedBackend::windowForScreen(int screen) const { if (screen > m_windows.count()) { return XCB_WINDOW_NONE; } return m_windows.at(screen).window; } QVector X11WindowedBackend::screenGeometries() const { QVector ret; for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { ret << QRect((*it).internalPosition, (*it).size / (*it).scale); } return ret; } QVector X11WindowedBackend::screenScales() const { QVector ret; for (auto it = m_windows.constBegin(); it != m_windows.constEnd(); ++it) { ret << (*it).scale; } return ret; } } diff --git a/pointer_input.cpp b/pointer_input.cpp index 8175e8843..70bddb2dc 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -1,1412 +1,1411 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin Copyright (C) 2018 Roman Gilg +Copyright (C) 2019 Vlad Zagorodniy 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 "client.h" #include "effects.h" #include "input_event.h" #include "input_event_spy.h" #include "osd.h" #include "screens.h" #include "shell_client.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" #include "screens.h" // KDecoration #include // KWayland #include #include #include #include #include #include #include #include // screenlocker #include #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; } static QPointF confineToBoundingBox(const QPointF &pos, const QRectF &boundingBox) { return QPointF( qBound(boundingBox.left(), pos.x(), boundingBox.right() - 1.0), qBound(boundingBox.top(), pos.y(), boundingBox.bottom() - 1.0) ); } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) : InputDeviceHandler(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { } PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { Q_ASSERT(!inited()); m_cursor = new CursorImage(this); setInited(true); InputDeviceHandler::init(); connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); if (waylandServer()->hasScreenLockerIntegration()) { connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, [this] { waylandServer()->seat()->cancelPointerPinchGesture(); waylandServer()->seat()->cancelPointerSwipeGesture(); update(); } ); } connect(workspace(), &QObject::destroyed, this, [this] { setInited(false); }); connect(waylandServer(), &QObject::destroyed, this, [this] { setInited(false); }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); setFocus(nullptr); update(); } ); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::clientStartUserMovedResized, this, &PointerInputRedirection::updateOnStartMoveResize); connect(c, &AbstractClient::clientFinishUserMovedResized, this, &PointerInputRedirection::update); }; 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); // warp the cursor to center of screen warp(screens()->geometry().center()); updateAfterScreenChange(); } void PointerInputRedirection::updateOnStartMoveResize() { breakPointerConstraints(focus() ? focus()->surface() : nullptr); disconnectPointerConstraintsConnection(); setFocus(nullptr); waylandServer()->seat()->setFocusedPointerSurface(nullptr); } void PointerInputRedirection::updateToReset() { if (internalWindow()) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(internalWindow().data(), &event); setInternalWindow(nullptr); } if (decoration()) { QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); setDecoration(nullptr); } if (focus()) { if (AbstractClient *c = qobject_cast(focus().data())) { c->leaveEvent(); } disconnect(m_focusGeometryConnection); m_focusGeometryConnection = QMetaObject::Connection(); breakPointerConstraints(focus()->surface()); disconnectPointerConstraintsConnection(); setFocus(nullptr); } waylandServer()->seat()->setFocusedPointerSurface(nullptr); } void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time, LibInput::Device *device) { processMotion(pos, QSizeF(), QSizeF(), time, 0, device); } class PositionUpdateBlocker { public: PositionUpdateBlocker(PointerInputRedirection *pointer) : m_pointer(pointer) { s_counter++; } ~PositionUpdateBlocker() { s_counter--; if (s_counter == 0) { if (!s_scheduledPositions.isEmpty()) { const auto pos = s_scheduledPositions.takeFirst(); m_pointer->processMotion(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, pos.timeUsec, nullptr); } } } static bool isPositionBlocked() { return s_counter > 0; } static void schedulePosition(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec) { s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time, timeUsec}); } private: static int s_counter; struct ScheduledPosition { QPointF pos; QSizeF delta; QSizeF deltaNonAccelerated; quint32 time; quint64 timeUsec; }; static QVector s_scheduledPositions; PointerInputRedirection *m_pointer; }; int PositionUpdateBlocker::s_counter = 0; QVector PositionUpdateBlocker::s_scheduledPositions; void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device) { if (!inited()) { return; } if (PositionUpdateBlocker::isPositionBlocked()) { PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time, timeUsec); return; } PositionUpdateBlocker blocker(this); updatePosition(pos); MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons, input()->keyboardModifiers(), time, delta, deltaNonAccelerated, timeUsec, device); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); update(); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, 0)); } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time, LibInput::Device *device) { QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: type = QEvent::MouseButtonRelease; break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; update(); break; default: Q_UNREACHABLE(); return; } updateButton(button, state); MouseEvent event(type, m_pos, buttonToQtMouseButton(button), m_qtButtons, input()->keyboardModifiers(), time, QSizeF(), QSizeF(), 0, device); event.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); event.setNativeButton(button); input()->processSpies(std::bind(&InputEventSpy::pointerEvent, std::placeholders::_1, &event)); if (!inited()) { return; } input()->processFilters(std::bind(&InputEventFilter::pointerEvent, std::placeholders::_1, &event, button)); if (state == InputRedirection::PointerButtonReleased) { update(); } } -void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time, LibInput::Device *device) +void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, + InputRedirection::PointerAxisSource source, uint32_t time, LibInput::Device *device) { - if (delta == 0) { - return; - } update(); emit input()->pointerAxisChanged(axis, delta); - WheelEvent wheelEvent(m_pos, delta, + WheelEvent wheelEvent(m_pos, delta, discreteDelta, (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, - m_qtButtons, input()->keyboardModifiers(), time, device); + m_qtButtons, input()->keyboardModifiers(), source, time, device); wheelEvent.setModifiersRelevantForGlobalShortcuts(input()->modifiersRelevantForGlobalShortcuts()); input()->processSpies(std::bind(&InputEventSpy::wheelEvent, std::placeholders::_1, &wheelEvent)); if (!inited()) { return; } input()->processFilters(std::bind(&InputEventFilter::wheelEvent, std::placeholders::_1, &wheelEvent)); } void PointerInputRedirection::processSwipeGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } input()->processSpies(std::bind(&InputEventSpy::swipeGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureBegin, std::placeholders::_1, fingerCount, time)); } void PointerInputRedirection::processSwipeGestureUpdate(const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureUpdate, std::placeholders::_1, delta, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureUpdate, std::placeholders::_1, delta, time)); } void PointerInputRedirection::processSwipeGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureEnd, std::placeholders::_1, time)); } void PointerInputRedirection::processSwipeGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::swipeGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::swipeGestureCancelled, std::placeholders::_1, time)); } void PointerInputRedirection::processPinchGestureBegin(int fingerCount, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureBegin, std::placeholders::_1, fingerCount, time)); } void PointerInputRedirection::processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureUpdate, std::placeholders::_1, scale, angleDelta, delta, time)); } void PointerInputRedirection::processPinchGestureEnd(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureEnd, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureEnd, std::placeholders::_1, time)); } void PointerInputRedirection::processPinchGestureCancelled(quint32 time, KWin::LibInput::Device *device) { Q_UNUSED(device) if (!inited()) { return; } update(); input()->processSpies(std::bind(&InputEventSpy::pinchGestureCancelled, std::placeholders::_1, time)); input()->processFilters(std::bind(&InputEventFilter::pinchGestureCancelled, std::placeholders::_1, time)); } bool PointerInputRedirection::areButtonsPressed() const { for (auto state : m_buttons) { if (state == InputRedirection::PointerButtonPressed) { return true; } } return false; } bool PointerInputRedirection::focusUpdatesBlocked() { if (!inited()) { return true; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop return true; } if (waylandServer()->seat()->isTouchSequence()) { // ignore during touch operations return true; } if (input()->isSelectingWindow()) { return true; } if (areButtonsPressed()) { return true; } return false; } void PointerInputRedirection::cleanupInternalWindow(QWindow *old, QWindow *now) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); if (old) { // leave internal window // TODO: do this instead via Wayland protocol as below QEvent leaveEvent(QEvent::Leave); QCoreApplication::sendEvent(old, &leaveEvent); } if (now) { m_internalWindowConnection = connect(internalWindow().data(), &QWindow::visibleChanged, this, [this] (bool visible) { if (!visible) { update(); } } ); } } void PointerInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) { disconnect(m_decorationGeometryConnection); m_decorationGeometryConnection = QMetaObject::Connection(); workspace()->updateFocusMousePosition(position().toPoint()); if (old) { // send leave event to old decoration QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(old->decoration(), &event); } if (!now) { // left decoration return; } waylandServer()->seat()->setFocusedPointerSurface(nullptr); auto pos = m_pos - now->client()->pos(); QHoverEvent event(QEvent::HoverEnter, pos, pos); QCoreApplication::instance()->sendEvent(now->decoration(), &event); now->client()->processDecorationMove(pos.toPoint(), m_pos.toPoint()); m_decorationGeometryConnection = connect(decoration()->client(), &AbstractClient::geometryChanged, this, [this] { // ensure maximize button gets the leave event when maximizing/restore a window, see BUG 385140 const auto oldDeco = decoration(); update(); if (oldDeco && oldDeco == decoration() && !decoration()->client()->isMove() && !decoration()->client()->isResize() && !areButtonsPressed()) { // position of window did not change, we need to send HoverMotion manually const QPointF p = m_pos - decoration()->client()->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration()->decoration(), &event); } }, Qt::QueuedConnection); } static bool s_cursorUpdateBlocking = false; void PointerInputRedirection::focusUpdate(Toplevel *focusOld, Toplevel *focusNow) { if (AbstractClient *ac = qobject_cast(focusOld)) { ac->leaveEvent(); breakPointerConstraints(ac->surface()); disconnectPointerConstraintsConnection(); } disconnect(m_focusGeometryConnection); m_focusGeometryConnection = QMetaObject::Connection(); if (AbstractClient *ac = qobject_cast(focusNow)) { ac->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } auto seat = waylandServer()->seat(); if (!focusNow || !focusNow->surface() || decoration()) { // no new surface or internal window or on decoration -> cleanup warpXcbOnSurfaceLeft(nullptr); seat->setFocusedPointerSurface(nullptr); return; } if (internalWindow()) { // enter internal window // TODO: do this instead via Wayland protocol as below const auto pos = at()->pos(); QEnterEvent enterEvent(pos, pos, m_pos); QCoreApplication::sendEvent(internalWindow().data(), &enterEvent); } // TODO: add convenient API to update global pos together with updating focused surface warpXcbOnSurfaceLeft(focusNow->surface()); // TODO: why? in order to reset the cursor icon? s_cursorUpdateBlocking = true; seat->setFocusedPointerSurface(nullptr); s_cursorUpdateBlocking = false; seat->setPointerPos(m_pos.toPoint()); seat->setFocusedPointerSurface(focusNow->surface(), focusNow->inputTransformation()); m_focusGeometryConnection = connect(focusNow, &Toplevel::geometryChanged, this, [this] { // TODO: why no assert possible? if (!focus()) { return; } // TODO: can we check on the client instead? if (workspace()->moveResizeClient()) { // don't update while moving return; } auto seat = waylandServer()->seat(); if (focus()->surface() != seat->focusedPointerSurface()) { return; } seat->setFocusedPointerSurfaceTransformation(focus()->inputTransformation()); } ); m_constraintsConnection = connect(focusNow->surface(), &KWayland::Server::SurfaceInterface::pointerConstraintsChanged, this, &PointerInputRedirection::updatePointerConstraints); m_constraintsActivatedConnection = connect(workspace(), &Workspace::clientActivated, this, &PointerInputRedirection::updatePointerConstraints); updatePointerConstraints(); } void PointerInputRedirection::breakPointerConstraints(KWayland::Server::SurfaceInterface *surface) { // cancel pointer constraints if (surface) { auto c = surface->confinedPointer(); if (c && c->isConfined()) { c->setConfined(false); } auto l = surface->lockedPointer(); if (l && l->isLocked()) { l->setLocked(false); } } disconnectConfinedPointerRegionConnection(); m_confined = false; m_locked = false; } void PointerInputRedirection::disconnectConfinedPointerRegionConnection() { disconnect(m_confinedPointerRegionConnection); m_confinedPointerRegionConnection = QMetaObject::Connection(); } void PointerInputRedirection::disconnectLockedPointerAboutToBeUnboundConnection() { disconnect(m_lockedPointerAboutToBeUnboundConnection); m_lockedPointerAboutToBeUnboundConnection = QMetaObject::Connection(); } void PointerInputRedirection::disconnectPointerConstraintsConnection() { disconnect(m_constraintsConnection); m_constraintsConnection = QMetaObject::Connection(); disconnect(m_constraintsActivatedConnection); m_constraintsActivatedConnection = QMetaObject::Connection(); } template static QRegion getConstraintRegion(Toplevel *t, T *constraint) { const QRegion windowShape = t->inputShape(); const QRegion windowRegion = windowShape.isEmpty() ? QRegion(0, 0, t->clientSize().width(), t->clientSize().height()) : windowShape; const QRegion intersected = constraint->region().isEmpty() ? windowRegion : windowRegion.intersected(constraint->region()); return intersected.translated(t->pos() + t->clientPos()); } void PointerInputRedirection::setEnableConstraints(bool set) { if (m_enableConstraints == set) { return; } m_enableConstraints = set; updatePointerConstraints(); } void PointerInputRedirection::updatePointerConstraints() { if (focus().isNull()) { return; } const auto s = focus()->surface(); if (!s) { return; } if (s != waylandServer()->seat()->focusedPointerSurface()) { return; } if (!supportsWarping()) { return; } const bool canConstrain = m_enableConstraints && focus() == workspace()->activeClient(); const auto cf = s->confinedPointer(); if (cf) { if (cf->isConfined()) { if (!canConstrain) { cf->setConfined(false); m_confined = false; disconnectConfinedPointerRegionConnection(); } return; } const QRegion r = getConstraintRegion(focus().data(), cf.data()); if (canConstrain && r.contains(m_pos.toPoint())) { cf->setConfined(true); m_confined = true; m_confinedPointerRegionConnection = connect(cf.data(), &KWayland::Server::ConfinedPointerInterface::regionChanged, this, [this] { if (!focus()) { return; } const auto s = focus()->surface(); if (!s) { return; } const auto cf = s->confinedPointer(); if (!getConstraintRegion(focus().data(), cf.data()).contains(m_pos.toPoint())) { // pointer no longer in confined region, break the confinement cf->setConfined(false); m_confined = false; } else { if (!cf->isConfined()) { cf->setConfined(true); m_confined = true; } } } ); return; } } else { m_confined = false; disconnectConfinedPointerRegionConnection(); } const auto lock = s->lockedPointer(); if (lock) { if (lock->isLocked()) { if (!canConstrain) { const auto hint = lock->cursorPositionHint(); lock->setLocked(false); m_locked = false; disconnectLockedPointerAboutToBeUnboundConnection(); if (! (hint.x() < 0 || hint.y() < 0) && focus()) { processMotion(focus()->pos() - focus()->clientContentPos() + hint, waylandServer()->seat()->timestamp()); } } return; } const QRegion r = getConstraintRegion(focus().data(), lock.data()); if (canConstrain && r.contains(m_pos.toPoint())) { lock->setLocked(true); m_locked = true; // The client might cancel pointer locking from its side by unbinding the LockedPointerInterface. // In this case the cached cursor position hint must be fetched before the resource goes away m_lockedPointerAboutToBeUnboundConnection = connect(lock.data(), &KWayland::Server::LockedPointerInterface::aboutToBeUnbound, this, [this, lock]() { const auto hint = lock->cursorPositionHint(); if (hint.x() < 0 || hint.y() < 0 || !focus()) { return; } auto globalHint = focus()->pos() - focus()->clientContentPos() + hint; // When the resource finally goes away, reposition the cursor according to the hint connect(lock.data(), &KWayland::Server::LockedPointerInterface::unbound, this, [this, globalHint]() { processMotion(globalHint, waylandServer()->seat()->timestamp()); }); } ); // TODO: connect to region change - is it needed at all? If the pointer is locked it's always in the region } } else { m_locked = false; disconnectLockedPointerAboutToBeUnboundConnection(); } } void PointerInputRedirection::warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInterface *newSurface) { auto xc = waylandServer()->xWaylandConnection(); if (!xc) { // No XWayland, no point in warping the x cursor return; } const auto c = kwinApp()->x11Connection(); if (!c) { return; } static bool s_hasXWayland119 = xcb_get_setup(c)->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(c, XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 0, 0), xcb_flush(c); } QPointF PointerInputRedirection::applyPointerConfinement(const QPointF &pos) const { if (!focus()) { return pos; } auto s = focus()->surface(); if (!s) { return pos; } auto cf = s->confinedPointer(); if (!cf) { return pos; } if (!cf->isConfined()) { return pos; } const QRegion confinementRegion = getConstraintRegion(focus().data(), cf.data()); if (confinementRegion.contains(pos.toPoint())) { return pos; } QPointF p = pos; // allow either x or y to pass p = QPointF(m_pos.x(), pos.y()); if (confinementRegion.contains(p.toPoint())) { return p; } p = QPointF(pos.x(), m_pos.y()); if (confinementRegion.contains(p.toPoint())) { return p; } return m_pos; } void PointerInputRedirection::updatePosition(const QPointF &pos) { if (m_locked) { // locked pointer should not move return; } // verify that at least one screen contains the pointer position QPointF p = pos; if (!screenContainsPos(p)) { const QRectF unitedScreensGeometry = screens()->geometry(); p = confineToBoundingBox(p, unitedScreensGeometry); if (!screenContainsPos(p)) { const QRectF currentScreenGeometry = screens()->geometry(screens()->number(m_pos.toPoint())); p = confineToBoundingBox(p, currentScreenGeometry); } } p = applyPointerConfinement(p); if (p == m_pos) { // didn't change due to confinement return; } // verify screen confinement if (!screenContainsPos(p)) { return; } m_pos = p; emit 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 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 (!inited()) { return false; } if (m_supportsWarping) { return true; } if (kwinApp()->platform()->supportsPointerWarping()) { return true; } return false; } void PointerInputRedirection::updateAfterScreenChange() { if (!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 (!inited()) { return QImage(); } return m_cursor->image(); } QPoint PointerInputRedirection::cursorHotSpot() const { if (!inited()) { return QPoint(); } return m_cursor->hotSpot(); } void PointerInputRedirection::markCursorAsRendered() { if (!inited()) { return; } m_cursor->markAsRendered(); } QPointF PointerInputRedirection::position() const { return m_pos.toPoint(); } void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { if (!inited()) { return; } // current pointer focus window should get a leave event update(); m_cursor->setEffectsOverrideCursor(shape); } void PointerInputRedirection::removeEffectsOverrideCursor() { if (!inited()) { return; } // cursor position might have changed while there was an effect in place update(); m_cursor->removeEffectsOverrideCursor(); } void PointerInputRedirection::setWindowSelectionCursor(const QByteArray &shape) { if (!inited()) { return; } // send leave to current pointer focus window updateToReset(); m_cursor->setWindowSelectionCursor(shape); } void PointerInputRedirection::removeWindowSelectionCursor() { if (!inited()) { return; } update(); m_cursor->removeWindowSelectionCursor(); } 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); connect(c, &AbstractClient::moveResizeCursorChanged, 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(); m_cursorsByName.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() { if (s_cursorUpdateBlocking) { return; } 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(); reevaluteSource(); } } 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()->moveResizeClient()) { loadThemeCursor(c->cursor(), &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(); m_serverCursor.image.setDevicePixelRatio(cursorSurface->scale()); 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::setWindowSelectionCursor(const QByteArray &shape) { if (shape.isEmpty()) { loadThemeCursor(Qt::CrossCursor, &m_windowSelectionCursor); } else { loadThemeCursor(shape, &m_windowSelectionCursor); } if (m_currentSource == CursorSource::WindowSelector) { emit changed(); } reevaluteSource(); } void CursorImage::removeWindowSelectionCursor() { 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(CursorShape shape, Image *image) { loadThemeCursor(shape, m_cursors, image); } void CursorImage::loadThemeCursor(const QByteArray &shape, Image *image) { loadThemeCursor(shape, m_cursorsByName, image); } template void CursorImage::loadThemeCursor(const T &shape, QHash &cursors, Image *image) { loadTheme(); if (!m_cursorTheme) { return; } auto it = cursors.constFind(shape); if (it == 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; } auto scale = screens()->maxScale(); int hotSpotX = qRound(cursor->hotspot_x / scale); int hotSpotY = qRound(cursor->hotspot_y / scale); QImage img = buffer->data().copy(); img.setDevicePixelRatio(scale); it = decltype(it)(cursors.insert(shape, {img, QPoint(hotSpotX, hotSpotY)})); } 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 (input()->isSelectingWindow()) { setSource(CursorSource::WindowSelector); return; } if (effects && static_cast(effects)->isMouseInterception()) { setSource(CursorSource::EffectsOverride); return; } if (workspace() && workspace()->moveResizeClient()) { setSource(CursorSource::MoveResize); return; } if (!m_pointer->decoration().isNull()) { setSource(CursorSource::Decoration); return; } if (!m_pointer->focus().isNull() && waylandServer()->seat()->focusedPointer()) { 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; case CursorSource::WindowSelector: return m_windowSelectionCursor.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; case CursorSource::WindowSelector: return m_windowSelectionCursor.hotSpot; default: Q_UNREACHABLE(); } } } diff --git a/pointer_input.h b/pointer_input.h index 2d2bc9fc7..e3322762e 100644 --- a/pointer_input.h +++ b/pointer_input.h @@ -1,258 +1,259 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin Copyright (C) 2018 Roman Gilg +Copyright (C) 2019 Vlad Zagorodniy 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; class CursorShape; 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 updateAfterScreenChange(); bool supportsWarping() const; void warp(const QPointF &pos); QPointF pos() const { return m_pos; } Qt::MouseButtons buttons() const { return m_qtButtons; } bool areButtonsPressed() const; QImage cursorImage() const; QPoint cursorHotSpot() const; void markCursorAsRendered(); void setEffectsOverrideCursor(Qt::CursorShape shape); void removeEffectsOverrideCursor(); void setWindowSelectionCursor(const QByteArray &shape); void removeWindowSelectionCursor(); void updatePointerConstraints(); void setEnableConstraints(bool set); bool isConstrained() const { return m_confined || m_locked; } bool focusUpdatesBlocked() override; /** * @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); + void processAxis(InputRedirection::PointerAxis axis, qreal delta, qint32 discreteDelta, InputRedirection::PointerAxisSource source, 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 cleanupInternalWindow(QWindow *old, QWindow *now) override; void cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now) override; void focusUpdate(Toplevel *focusOld, Toplevel *focusNow) override; QPointF position() const override; void updateOnStartMoveResize(); void updateToReset(); void updatePosition(const QPointF &pos); void updateButton(uint32_t button, InputRedirection::PointerButtonState state); void warpXcbOnSurfaceLeft(KWayland::Server::SurfaceInterface *surface); QPointF applyPointerConfinement(const QPointF &pos) const; void disconnectConfinedPointerRegionConnection(); void disconnectLockedPointerAboutToBeUnboundConnection(); void disconnectPointerConstraintsConnection(); void breakPointerConstraints(KWayland::Server::SurfaceInterface *surface); CursorImage *m_cursor; bool m_supportsWarping; QPointF m_pos; QHash m_buttons; Qt::MouseButtons m_qtButtons; QMetaObject::Connection m_focusGeometryConnection; QMetaObject::Connection m_internalWindowConnection; QMetaObject::Connection m_constraintsConnection; QMetaObject::Connection m_constraintsActivatedConnection; QMetaObject::Connection m_confinedPointerRegionConnection; QMetaObject::Connection m_lockedPointerAboutToBeUnboundConnection; QMetaObject::Connection m_decorationGeometryConnection; bool m_confined = false; bool m_locked = false; bool m_enableConstraints = true; }; class CursorImage : public QObject { Q_OBJECT public: explicit CursorImage(PointerInputRedirection *parent = nullptr); virtual ~CursorImage(); void setEffectsOverrideCursor(Qt::CursorShape shape); void removeEffectsOverrideCursor(); void setWindowSelectionCursor(const QByteArray &shape); void removeWindowSelectionCursor(); 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(CursorShape shape, Image *image); void loadThemeCursor(const QByteArray &shape, Image *image); template void loadThemeCursor(const T &shape, QHash &cursors, Image *image); enum class CursorSource { LockScreen, EffectsOverride, MoveResize, PointerSurface, Decoration, DragAndDrop, Fallback, WindowSelector }; 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; Image m_windowSelectionCursor; QHash m_cursors; QHash m_cursorsByName; QElapsedTimer m_surfaceRenderedTimer; struct { Image cursor; QMetaObject::Connection connection; } m_drag; }; } #endif