diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp index 9a2407c8b..58bbb3687 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -1,595 +1,677 @@ /******************************************************************** 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 "kwin_wayland_test.h" #include "platform.h" #include "cursor.h" #include "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include using namespace KWayland::Client; namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0"); class InternalWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testEnterLeave(); void testPointerPressRelease(); void testPointerAxis(); void testKeyboard_data(); void testKeyboard(); void testKeyboardShowWithoutActivating(); void testKeyboardTriggersLeave(); void testTouch(); void testOpacity(); void testMove(); void testSkipCloseAnimation_data(); void testSkipCloseAnimation(); + void testModifierClickUnrestrictedMove(); + void testModifierScroll(); }; class HelperWindow : public QRasterWindow { Q_OBJECT public: HelperWindow(); ~HelperWindow(); QPoint latestGlobalMousePos() const { return m_latestGlobalMousePos; } Qt::MouseButtons pressedButtons() const { return m_pressedButtons; } Q_SIGNALS: void entered(); void left(); void mouseMoved(const QPoint &global); void mousePressed(); void mouseReleased(); void wheel(); void keyPressed(); void keyReleased(); protected: void paintEvent(QPaintEvent *event) override; bool event(QEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; private: QPoint m_latestGlobalMousePos; Qt::MouseButtons m_pressedButtons = Qt::MouseButtons(); }; HelperWindow::HelperWindow() : QRasterWindow(nullptr) { setFlags(Qt::FramelessWindowHint); } HelperWindow::~HelperWindow() = default; void HelperWindow::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); p.fillRect(0, 0, width(), height(), Qt::red); } bool HelperWindow::event(QEvent *event) { if (event->type() == QEvent::Enter) { emit entered(); } if (event->type() == QEvent::Leave) { emit left(); } return QRasterWindow::event(event); } void HelperWindow::mouseMoveEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); emit mouseMoved(event->globalPos()); } void HelperWindow::mousePressEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); m_pressedButtons = event->buttons(); emit mousePressed(); } void HelperWindow::mouseReleaseEvent(QMouseEvent *event) { m_latestGlobalMousePos = event->globalPos(); m_pressedButtons = event->buttons(); emit mouseReleased(); } void HelperWindow::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) emit wheel(); } void HelperWindow::keyPressEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyPressed(); } void HelperWindow::keyReleaseEvent(QKeyEvent *event) { Q_UNUSED(event) emit keyReleased(); } void InternalWindowTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QCOMPARE(screens()->count(), 2); QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); waylandServer()->initWorkspace(); } void InternalWindowTest::init() { Cursor::setPos(QPoint(1280, 512)); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandKeyboard()); } void InternalWindowTest::cleanup() { Test::destroyWaylandConnection(); } void InternalWindowTest::testEnterLeave() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; QVERIFY(!workspace()->findToplevel(nullptr)); QVERIFY(!workspace()->findToplevel(&win)); win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QVERIFY(!workspace()->activeClient()); ShellClient *c = clientAddedSpy.first().first().value(); QVERIFY(c->isInternal()); QCOMPARE(c->icon().name(), QStringLiteral("wayland")); + QVERIFY(!c->isDecorated()); QCOMPARE(workspace()->findToplevel(&win), c); QCOMPARE(c->geometry(), QRect(0, 0, 100, 100)); QVERIFY(c->isShown(false)); QVERIFY(workspace()->xStackingOrder().contains(c)); QSignalSpy enterSpy(&win, &HelperWindow::entered); QVERIFY(enterSpy.isValid()); QSignalSpy leaveSpy(&win, &HelperWindow::left); QVERIFY(leaveSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); QTRY_COMPARE(enterSpy.count(), 1); kwinApp()->platform()->pointerMotion(QPoint(60, 50), timestamp++); QTRY_COMPARE(moveSpy.count(), 1); QCOMPARE(moveSpy.first().first().toPoint(), QPoint(60, 50)); kwinApp()->platform()->pointerMotion(QPoint(101, 50), timestamp++); QTRY_COMPARE(leaveSpy.count(), 1); // set a mask on the window win.setMask(QRegion(10, 20, 30, 40)); // outside the mask we should not get an enter kwinApp()->platform()->pointerMotion(QPoint(5, 5), timestamp++); QVERIFY(!enterSpy.wait(100)); QCOMPARE(enterSpy.count(), 1); // inside the mask we should still get an enter kwinApp()->platform()->pointerMotion(QPoint(25, 27), timestamp++); QTRY_COMPARE(enterSpy.count(), 2); // hide the window, which should be removed from the stacking order win.hide(); QTRY_VERIFY(!c->isShown(false)); QVERIFY(!workspace()->xStackingOrder().contains(c)); // show again win.show(); QTRY_VERIFY(c->isShown(false)); QVERIFY(workspace()->xStackingOrder().contains(c)); } void InternalWindowTest::testPointerPressRelease() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); } void InternalWindowTest::testPointerAxis() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy wheelSpy(&win, &HelperWindow::wheel); QVERIFY(wheelSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(QPoint(50, 50), timestamp++); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 1); kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); QTRY_COMPARE(wheelSpy.count(), 2); } void InternalWindowTest::testKeyboard_data() { QTest::addColumn("cursorPos"); QTest::newRow("on Window") << QPoint(50, 50); QTest::newRow("outside Window") << QPoint(250, 250); } void InternalWindowTest::testKeyboard() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); quint32 timestamp = 1; QFETCH(QPoint, cursorPos); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QTRY_COMPARE(pressSpy.count(), 1); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); QCOMPARE(pressSpy.count(), 1); } void InternalWindowTest::testKeyboardShowWithoutActivating() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setProperty("_q_showWithoutActivating", true); win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); quint32 timestamp = 1; const QPoint cursorPos = QPoint(50, 50); kwinApp()->platform()->pointerMotion(cursorPos, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QCOMPARE(pressSpy.count(), 0); QVERIFY(!pressSpy.wait(100)); QCOMPARE(releaseSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QCOMPARE(releaseSpy.count(), 0); QVERIFY(!releaseSpy.wait(100)); QCOMPARE(pressSpy.count(), 0); } void InternalWindowTest::testKeyboardTriggersLeave() { // this test verifies that a leave event is sent to a client when an internal window // gets a key event QScopedPointer keyboard(Test::waylandSeat()->createKeyboard()); QVERIFY(!keyboard.isNull()); QVERIFY(keyboard->isValid()); QSignalSpy enteredSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data())); // now let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isInternal()); if (enteredSpy.isEmpty()) { QVERIFY(enteredSpy.wait()); } QCOMPARE(enteredSpy.count(), 1); // create internal window QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); QVERIFY(releaseSpy.isValid()); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QVERIFY(internalClient->readyForPainting()); QVERIFY(leftSpy.isEmpty()); QVERIFY(!leftSpy.wait(100)); // now let's trigger a key, which should result in a leave quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(leftSpy.wait()); QCOMPARE(pressSpy.count(), 1); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QTRY_COMPARE(releaseSpy.count(), 1); // after hiding the internal window, next key press should trigger an enter win.hide(); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); QVERIFY(enteredSpy.wait()); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); } void InternalWindowTest::testTouch() { // touch events for internal windows are emulated through mouse events QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); QVERIFY(pressSpy.isValid()); QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); QVERIFY(releaseSpy.isValid()); QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); QVERIFY(moveSpy.isValid()); quint32 timestamp = 1; QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); kwinApp()->platform()->touchDown(0, QPointF(50, 50), timestamp++); QCOMPARE(pressSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // further touch down should not trigger kwinApp()->platform()->touchDown(1, QPointF(75, 75), timestamp++); QCOMPARE(pressSpy.count(), 1); kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(releaseSpy.count(), 0); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // another press kwinApp()->platform()->touchDown(1, QPointF(10, 10), timestamp++); QCOMPARE(pressSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // simulate the move QCOMPARE(moveSpy.count(), 0); kwinApp()->platform()->touchMotion(0, QPointF(80, 90), timestamp++); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // move on other ID should not do anything kwinApp()->platform()->touchMotion(1, QPointF(20, 30), timestamp++); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); // now up our main point kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(releaseSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); // and up the additional point kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(releaseSpy.count(), 1); QCOMPARE(moveSpy.count(), 1); QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); } void InternalWindowTest::testOpacity() { // this test verifies that opacity is properly synced from QWindow to ShellClient QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QCOMPARE(internalClient->opacity(), 0.5); QSignalSpy opacityChangedSpy(internalClient, &ShellClient::opacityChanged); QVERIFY(opacityChangedSpy.isValid()); win.setOpacity(0.75); QCOMPARE(opacityChangedSpy.count(), 1); QCOMPARE(internalClient->opacity(), 0.75); } void InternalWindowTest::testMove() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->geometry(), QRect(0, 0, 100, 100)); // normal move should be synced internalClient->move(5, 10); QCOMPARE(internalClient->geometry(), QRect(5, 10, 100, 100)); QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); // another move should also be synced internalClient->move(10, 20); QCOMPARE(internalClient->geometry(), QRect(10, 20, 100, 100)); QTRY_COMPARE(win.geometry(), QRect(10, 20, 100, 100)); // now move with a Geometry update blocker { GeometryUpdatesBlocker blocker(internalClient); internalClient->move(5, 10); // not synced! QCOMPARE(win.geometry(), QRect(10, 20, 100, 100)); } // after destroying the blocker it should be synced QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); } void InternalWindowTest::testSkipCloseAnimation_data() { QTest::addColumn("initial"); QTest::newRow("set") << true; QTest::newRow("not set") << false; } void InternalWindowTest::testSkipCloseAnimation() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); QFETCH(bool, initial); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->skipsCloseAnimation(), initial); QSignalSpy skipCloseChangedSpy(internalClient, &Toplevel::skipCloseAnimationChanged); QVERIFY(skipCloseChangedSpy.isValid()); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", !initial); QCOMPARE(skipCloseChangedSpy.count(), 1); QCOMPARE(internalClient->skipsCloseAnimation(), !initial); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); QCOMPARE(skipCloseChangedSpy.count(), 2); QCOMPARE(internalClient->skipsCloseAnimation(), initial); } +void InternalWindowTest::testModifierClickUnrestrictedMove() +{ + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.setFlags(win.flags() & ~Qt::FramelessWindowHint); + win.show(); + QVERIFY(clientAddedSpy.wait()); + QCOMPARE(clientAddedSpy.count(), 1); + auto internalClient = clientAddedSpy.first().first().value(); + QVERIFY(internalClient); + QVERIFY(internalClient->isDecorated()); + + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Alt"); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::AltModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // move cursor on window + Cursor::setPos(internalClient->geometry().center()); + + // simulate modifier+click + quint32 timestamp = 1; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + QVERIFY(!internalClient->isMove()); + kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(internalClient->isMove()); + // release modifier should not change it + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + QVERIFY(internalClient->isMove()); + // but releasing the key should end move/resize + kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(!internalClient->isMove()); +} + +void InternalWindowTest::testModifierScroll() +{ + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.setFlags(win.flags() & ~Qt::FramelessWindowHint); + win.show(); + QVERIFY(clientAddedSpy.wait()); + QCOMPARE(clientAddedSpy.count(), 1); + auto internalClient = clientAddedSpy.first().first().value(); + QVERIFY(internalClient); + QVERIFY(internalClient->isDecorated()); + + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Alt"); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + + // move cursor on window + Cursor::setPos(internalClient->geometry().center()); + + // set the opacity to 0.5 + internalClient->setOpacity(0.5); + QCOMPARE(internalClient->opacity(), 0.5); + quint32 timestamp = 1; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); + QCOMPARE(internalClient->opacity(), 0.6); + kwinApp()->platform()->pointerAxisVertical(5, timestamp++); + QCOMPARE(internalClient->opacity(), 0.5); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); +} + } WAYLANDTEST_MAIN(KWin::InternalWindowTest) #include "internal_window.moc" diff --git a/input.cpp b/input.cpp index bd12702ea..d6fce65b5 100644 --- a/input.cpp +++ b/input.cpp @@ -1,2231 +1,2260 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "input.h" #include "input_event.h" #include "input_event_spy.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.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" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include "platform.h" #include "popup_input_filter.h" #include "shell_client.h" #include "wayland_server.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; } } #if HAVE_INPUT class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; #endif class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { input()->pointer()->update(); if (pointerSurfaceAllowed()) { seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } 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 PointerConstraintsFilter : public InputEventFilter { public: explicit PointerConstraintsFilter() : InputEventFilter() , m_timer(new QTimer) { QObject::connect(m_timer.data(), &QTimer::timeout, [this] { if (waylandServer()) { // break keyboard focus, this cancels the pressed ESC waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); } input()->pointer()->breakPointerConstraints(); input()->pointer()->blockPointerConstraints(); // TODO: show notification waylandServer()->seat()->keyReleased(m_keyCode); cancel(); } ); } bool keyEvent(QKeyEvent *event) override { if (isActive()) { if (event->type() == QEvent::KeyPress) { // is that another key that gets pressed? if (!event->isAutoRepeat() && event->key() != Qt::Key_Escape) { cancel(); return false; } if (event->isAutoRepeat() && event->key() == Qt::Key_Escape) { // filter out return true; } } else { cancel(); return false; } } else if (input()->pointer()->isConstrained()) { if (event->type() == QEvent::KeyPress && event->key() == Qt::Key_Escape && static_cast(event)->modifiersRelevantForGlobalShortcuts() == Qt::KeyboardModifiers()) { // TODO: don't hard code m_timer->start(3000); m_keyCode = event->nativeScanCode(); return false; } } return false; } void cancel() { if (!isActive()) { return; } m_timer->stop(); input()->keyboard()->update(); } bool isActive() const { return m_timer->isActive(); } private: QScopedPointer m_timer; int m_keyCode = 0; }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchDown(id, pos, time); } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchMotion(id, pos, time); } bool touchUp(quint32 id, quint32 time) override { if (!effects) { return false; } return static_cast< EffectsHandlerImpl* >(effects)->touchUp(id, time); } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } switch (event->type()) { case QEvent::MouseMove: c->updateMoveResize(event->screenPos().toPoint()); break; case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { c->endMoveResize(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while moving a window return workspace()->getMovingClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } if (event->type() == QEvent::KeyPress) { c->keyPressEvent(event->key() | event->modifiers()); if (c->isMove() || c->isResize()) { // only update if mode didn't end c->updateMoveResize(input()->globalPointer()); } } return true; } }; class 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()) { + 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()) { + 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; } if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } 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; } 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 a decoration, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); touch->update(pos); auto internal = touch->internalWindow(); if (!internal) { return false; } touch->setInternalPressId(id); // Qt's touch event API is rather complex, let's do fake mouse events instead m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QCoreApplication::instance()->sendEvent(internal.data(), &e); return true; } bool touchUp(quint32 id, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; -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()) { - 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()) { - 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 DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { return false; } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { 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); input()->touch()->update(pos); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); if (auto c = workspace()->getMovingClient()) { c->updateMoveResize(pos); } else { 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 if (auto c = workspace()->getMovingClient()) { c->endMoveResize(); } else { 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); if (input()->pointer()->decoration() == decoration) { // send motion to current pointer position const QPointF p = input()->pointer()->pos() - decoration->client()->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &event); } else { // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(decoration->decoration(), &event); } } m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } auto seat = waylandServer()->seat(); seat->setFocusedKeyboardSurface(nullptr); // pass the key event to the seat, so that it has a proper model of the currently hold keys // this is important for combinations like alt+shift to ensure that shift is not considered pressed passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); } else if (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 progess 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()->window().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()->window().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; } input()->touch()->update(pos); AbstractClient *c = dynamic_cast(input()->touch()->window().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows **/ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); input()->pointer()->enablePointerConstraints(); } seat->setPointerPos(event->globalPos()); MouseEvent *e = static_cast(event); if (e->delta() != QSizeF()) { seat->relativePointerMotion(e->delta(), e->deltaUnaccelerated(), e->timestampMicroseconds()); } break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); if (event->buttons() == Qt::NoButton) { input()->pointer()->update(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); passToWaylandServer(event); return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } return true; } bool touchUp(quint32 id, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } 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; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (Toplevel *t = input()->findToplevel(event->globalPos())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->raiseClient(c); } seat->setPointerPos(event->globalPos()); seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } seat->setPointerPos(event->globalPos()); break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } }; KWIN_SINGLETON_FACTORY(InputRedirection) 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 HAVE_INPUT 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(); } } ); } } #endif 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::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); 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() { #if HAVE_INPUT if (LogindIntegration::self()->hasSessionControl()) { installInputEventFilter(new VirtualTerminalFilter); } #endif if (waylandServer()) { installInputEventFilter(new TerminateServerFilter); installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); installInputEventFilter(new PopupInputFilter); m_pointerConstraintsFilter = new PointerConstraintsFilter; installInputEventFilter(m_pointerConstraintsFilter); m_windowSelector = new WindowSelectorFilter; installInputEventFilter(m_windowSelector); } installInputEventFilter(new ScreenEdgeInputFilter); installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif installInputEventFilter(new GlobalShortcutFilter); installInputEventFilter(new InternalWindowEventFilter); installInputEventFilter(new DecorationEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { #if HAVE_INPUT if (Application::usesLibinput()) { 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); } #endif } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { #if HAVE_INPUT if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { if (waylandServer()) { // create relative pointer manager waylandServer()->display()->createRelativePointerManager(KWayland::Server::RelativePointerInterfaceVersion::UnstableV1, waylandServer()->display())->create(); } conn->setInputConfig(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::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(); #endif } void InputRedirection::setupTouchpadShortcuts() { if (!m_libInput) { return; } #if HAVE_INPUT 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); #endif } bool InputRedirection::hasAlphaNumericKeyboard() { #if HAVE_INPUT if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } #endif return true; } void InputRedirection::setupLibInputWithScreens() { #if HAVE_INPUT if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); m_libInput->updateScreens(); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); connect(screens(), &Screens::changed, m_libInput, &LibInput::Connection::updateScreens); #endif } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { m_pointer->processAxis(axis, delta, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } 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; } bool InputRedirection::isBreakingPointerConstraints() const { return m_pointerConstraintsFilter ? m_pointerConstraintsFilter->isActive() : false; } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) , m_input(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(pos.toPoint()); workspace()->updateFocusMousePosition(pos.toPoint()); } const QPointF p = pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), pos.toPoint()); } } void InputDeviceHandler::updateInternalWindow(const QPointF &pos) { const auto oldInternalWindow = m_internalWindow; bool found = false; // TODO: screen locked check without going through wayland server bool needsReset = waylandServer()->isScreenLocked(); const auto &internalClients = waylandServer()->internalClients(); const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (!internalClients.isEmpty() && change) { auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if ((*it)->geometry().contains(pos.toPoint())) { // check input mask const QRegion mask = w->mask().translated(w->geometry().topLeft()); if (!mask.isEmpty() && !mask.contains(pos.toPoint())) { continue; } if (w->property("outputOnly").toBool()) { continue; } m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { QEnterEvent event(pos - m_internalWindow->position(), pos - m_internalWindow->position(), pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); } emit internalWindowChanged(); } } } // namespace