diff --git a/autotests/test_screen_edges.cpp b/autotests/test_screen_edges.cpp index 0595f53c9..8fe9bd094 100644 --- a/autotests/test_screen_edges.cpp +++ b/autotests/test_screen_edges.cpp @@ -1,868 +1,863 @@ /******************************************************************** 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 . *********************************************************************/ // kwin #include "../atoms.h" #include "../cursor.h" #include "../input.h" #include "../main.h" #include "../screenedge.h" #include "../screens.h" #include "../utils.h" #include "../virtualdesktops.h" #include "../xcbutils.h" #include "mock_client.h" #include "mock_screens.h" #include "mock_workspace.h" #include "testutils.h" // Frameworks #include // Qt #include #include // xcb #include Q_DECLARE_METATYPE(KWin::ElectricBorder) Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core") namespace KWin { Atoms* atoms; int screen_number = 0; Cursor *Cursor::s_self = nullptr; static QPoint s_cursorPos = QPoint(); QPoint Cursor::pos() { return s_cursorPos; } void Cursor::setPos(const QPoint &pos) { s_cursorPos = pos; } void Cursor::setPos(int x, int y) { setPos(QPoint(x, y)); } void Cursor::startMousePolling() { } void Cursor::stopMousePolling() { } InputRedirection *InputRedirection::s_self = nullptr; void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) Q_UNUSED(action) } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { Q_UNUSED(modifiers) Q_UNUSED(axis) Q_UNUSED(action) } -void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) -{ - Q_UNUSED(action) -} - void updateXTime() { } class TestObject : public QObject { Q_OBJECT public Q_SLOTS: bool callback(ElectricBorder border); Q_SIGNALS: void gotCallback(KWin::ElectricBorder); }; bool TestObject::callback(KWin::ElectricBorder border) { emit gotCallback(border); return true; } } class TestScreenEdges : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testInit(); void testCreatingInitialEdges(); void testCallback(); void testCallbackWithCheck(); void testPushBack_data(); void testPushBack(); void testFullScreenBlocking(); void testClientEdge(); }; void TestScreenEdges::initTestCase() { qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); KWin::atoms = new KWin::Atoms; qRegisterMetaType(); } void TestScreenEdges::cleanupTestCase() { delete KWin::atoms; } void TestScreenEdges::init() { using namespace KWin; new MockWorkspace; auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); Screens::create(); auto vd = VirtualDesktopManager::create(); vd->setConfig(config); vd->load(); auto s = ScreenEdges::create(); s->setConfig(config); } void TestScreenEdges::cleanup() { using namespace KWin; delete ScreenEdges::self(); delete VirtualDesktopManager::self(); delete Screens::self(); delete workspace(); } void TestScreenEdges::testInit() { using namespace KWin; auto s = ScreenEdges::self(); s->init(); QCOMPARE(s->isDesktopSwitching(), false); QCOMPARE(s->isDesktopSwitchingMovingClients(), false); QCOMPARE(s->timeThreshold(), 150); QCOMPARE(s->reActivationThreshold(), 350); QCOMPARE(s->cursorPushBackDistance(), QSize(1, 1)); QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone); QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 8); for (auto e : edges) { QVERIFY(!e->isReserved()); QVERIFY(e->inherits("KWin::WindowBasedEdge")); QVERIFY(!e->inherits("KWin::AreaBasedEdge")); QVERIFY(!e->client()); QVERIFY(!e->isApproaching()); } Edge *te = edges.at(0); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(te->isLeft()); QVERIFY(te->isTop()); QVERIFY(!te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricTopLeft); te = edges.at(1); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(!te->isRight()); QVERIFY(te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricBottomLeft); te = edges.at(2); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(!te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricLeft); te = edges.at(3); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(te->isTop()); QVERIFY(te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricTopRight); te = edges.at(4); QVERIFY(te->isCorner()); QVERIFY(!te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(te->isRight()); QVERIFY(te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricBottomRight); te = edges.at(5); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricRight); te = edges.at(6); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(te->isTop()); QVERIFY(!te->isRight()); QVERIFY(!te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricTop); te = edges.at(7); QVERIFY(!te->isCorner()); QVERIFY(te->isScreenEdge()); QVERIFY(!te->isLeft()); QVERIFY(!te->isTop()); QVERIFY(!te->isRight()); QVERIFY(te->isBottom()); QCOMPARE(te->border(), ElectricBorder::ElectricBottom); // we shouldn't have any x windows, though QCOMPARE(s->windows().size(), 0); } void TestScreenEdges::testCreatingInitialEdges() { using namespace KWin; auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorders", 2/*ElectricAlways*/); config->sync(); auto s = ScreenEdges::self(); s->setConfig(config); s->init(); // we don't have multiple desktops, so it's returning false QCOMPARE(s->isDesktopSwitching(), true); QCOMPARE(s->isDesktopSwitchingMovingClients(), true); QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone); QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone); QEXPECT_FAIL("", "needs fixing", Continue); QCOMPARE(s->windows().size(), 0); // set some reasonable virtual desktops config->group("Desktops").writeEntry("Number", 4); config->sync(); auto vd = VirtualDesktopManager::self(); vd->setConfig(config); vd->load(); QCOMPARE(vd->count(), 4u); QCOMPARE(vd->grid().width(), 2); QCOMPARE(vd->grid().height(), 2); // approach windows for edges not created as screen too small s->updateLayout(); auto edgeWindows = s->windows(); #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 6, 1)) if (!Xcb::Extensions::self()->isRandrAvailable()) { QEXPECT_FAIL("", "Broken on no xrandr systems in Qt 5.5", Abort); } #endif #endif QCOMPARE(edgeWindows.size(), 12); auto testWindowGeometry = [&](int index) { Xcb::WindowGeometry geo(edgeWindows[index]); return geo.rect(); }; QRect sg = screens()->geometry(); const int co = s->cornerOffset(); QList expectedGeometries{ QRect(0, 0, 1, 1), QRect(0, 0, co, co), QRect(0, sg.bottom(), 1, 1), QRect(0, sg.height() - co, co, co), QRect(0, co, 1, sg.height() - co*2), // QRect(0, co * 2 + 1, co, sg.height() - co*4), QRect(sg.right(), 0, 1, 1), QRect(sg.right() - co + 1, 0, co, co), QRect(sg.right(), sg.bottom(), 1, 1), QRect(sg.right() - co + 1, sg.bottom() - co + 1, co, co), QRect(sg.right(), co, 1, sg.height() - co*2), // QRect(sg.right() - co + 1, co * 2, co, sg.height() - co*4), QRect(co, 0, sg.width() - co * 2, 1), // QRect(co * 2, 0, sg.width() - co * 4, co), QRect(co, sg.bottom(), sg.width() - co * 2, 1), // QRect(co * 2, sg.height() - co, sg.width() - co * 4, co) }; for (int i = 0; i < 12; ++i) { QCOMPARE(testWindowGeometry(i), expectedGeometries.at(i)); } QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 8); for (auto e : edges) { QVERIFY(e->isReserved()); } static_cast(screens())->setGeometries(QList{QRect{0, 0, 1024, 768}}); QSignalSpy changedSpy(screens(), SIGNAL(changed())); QVERIFY(changedSpy.isValid()); // first is before it's updated QVERIFY(changedSpy.wait()); // second is after it's updated QVERIFY(changedSpy.wait()); // let's update the layout and verify that we have edges s->recreateEdges(); edgeWindows = s->windows(); QCOMPARE(edgeWindows.size(), 16); sg = screens()->geometry(); expectedGeometries = QList{ QRect(0, 0, 1, 1), QRect(0, 0, co, co), QRect(0, sg.bottom(), 1, 1), QRect(0, sg.height() - co, co, co), QRect(0, co, 1, sg.height() - co*2), QRect(0, co * 2 + 1, co, sg.height() - co*4), QRect(sg.right(), 0, 1, 1), QRect(sg.right() - co + 1, 0, co, co), QRect(sg.right(), sg.bottom(), 1, 1), QRect(sg.right() - co + 1, sg.bottom() - co + 1, co, co), QRect(sg.right(), co, 1, sg.height() - co*2), QRect(sg.right() - co + 1, co * 2, co, sg.height() - co*4), QRect(co, 0, sg.width() - co * 2, 1), QRect(co * 2, 0, sg.width() - co * 4, co), QRect(co, sg.bottom(), sg.width() - co * 2, 1), QRect(co * 2, sg.height() - co, sg.width() - co * 4, co) }; for (int i = 0; i < 16; ++i) { QCOMPARE(testWindowGeometry(i), expectedGeometries.at(i)); } // disable desktop switching again config->group("Windows").writeEntry("ElectricBorders", 1/*ElectricMoveOnly*/); s->reconfigure(); QCOMPARE(s->isDesktopSwitching(), false); QCOMPARE(s->isDesktopSwitchingMovingClients(), true); QCOMPARE(s->windows().size(), 0); edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 8); for (int i = 0; i < 8; ++i) { auto e = edges.at(i); QVERIFY(!e->isReserved()); QCOMPARE(e->approachGeometry(), expectedGeometries.at(i*2+1)); } } void TestScreenEdges::testCallback() { using namespace KWin; MockWorkspace ws; static_cast(screens())->setGeometries(QList{QRect{0, 0, 1024, 768}, QRect{200, 768, 1024, 768}}); QSignalSpy changedSpy(screens(), SIGNAL(changed())); QVERIFY(changedSpy.isValid()); // first is before it's updated QVERIFY(changedSpy.wait()); // second is after it's updated QVERIFY(changedSpy.wait()); auto s = ScreenEdges::self(); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); s->reserve(ElectricLeft, &callback, "callback"); s->reserve(ElectricTopLeft, &callback, "callback"); s->reserve(ElectricTop, &callback, "callback"); s->reserve(ElectricTopRight, &callback, "callback"); s->reserve(ElectricRight, &callback, "callback"); s->reserve(ElectricBottomRight, &callback, "callback"); s->reserve(ElectricBottom, &callback, "callback"); s->reserve(ElectricBottomLeft, &callback, "callback"); QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(edges.size(), 10); for (auto e: edges) { QVERIFY(e->isReserved()); } auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) { return e->isScreenEdge() && e->isLeft() && e->approachGeometry().bottom() < 768; }); #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 6, 1)) if (!Xcb::Extensions::self()->isRandrAvailable()) { QEXPECT_FAIL("", "Broken on no xrandr systems in Qt 5.5", Abort); } #endif #endif QVERIFY(it != edges.constEnd()); xcb_enter_notify_event_t event; auto setPos = [&event] (const QPoint &pos) { Cursor::setPos(pos); event.root_x = pos.x(); event.root_y = pos.y(); event.event_x = pos.x(); event.event_y = pos.y(); }; event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = (*it)->window(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); setPos(QPoint(0, 50)); QVERIFY(s->isEntered(&event)); // doesn't trigger as the edge was not triggered yet QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 50)); // test doesn't trigger due to too much offset QTest::qWait(160); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // doesn't trigger as we are waiting too long already QTest::qWait(200); setPos(QPoint(0, 101)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 101)); // doesn't activate as we are waiting too short QTest::qWait(50); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // and this one triggers QTest::qWait(110); setPos(QPoint(0, 101)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(!spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 101)); // now let's try to trigger again QTest::qWait(351); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 1); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // it's still under the reactivation QTest::qWait(50); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 1); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // now it should trigger again QTest::qWait(250); setPos(QPoint(0, 100)); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 2); QCOMPARE(spy.first().first().value(), ElectricLeft); QCOMPARE(spy.last().first().value(), ElectricLeft); QCOMPARE(Cursor::pos(), QPoint(1, 100)); // let's disable pushback auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 0); config->sync(); s->setConfig(config); s->reconfigure(); // it should trigger directly QTest::qWait(350); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QCOMPARE(spy.count(), 3); QCOMPARE(spy.at(0).first().value(), ElectricLeft); QCOMPARE(spy.at(1).first().value(), ElectricLeft); QCOMPARE(spy.at(2).first().value(), ElectricLeft); QCOMPARE(Cursor::pos(), QPoint(0, 100)); // now let's unreserve again s->unreserve(ElectricTopLeft, &callback); s->unreserve(ElectricTop, &callback); s->unreserve(ElectricTopRight, &callback); s->unreserve(ElectricRight, &callback); s->unreserve(ElectricBottomRight, &callback); s->unreserve(ElectricBottom, &callback); s->unreserve(ElectricBottomLeft, &callback); s->unreserve(ElectricLeft, &callback); for (auto e: s->findChildren(QString(), Qt::FindDirectChildrenOnly)) { QVERIFY(!e->isReserved()); } } void TestScreenEdges::testCallbackWithCheck() { using namespace KWin; auto s = ScreenEdges::self(); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); s->reserve(ElectricLeft, &callback, "callback"); // check activating a different edge doesn't do anything s->check(QPoint(50, 0), QDateTime::currentDateTime(), true); QVERIFY(spy.isEmpty()); // try a direct activate without pushback Cursor::setPos(0, 50); s->check(QPoint(0, 50), QDateTime::currentDateTime(), true); QCOMPARE(spy.count(), 1); QEXPECT_FAIL("", "Argument says force no pushback, but it gets pushed back. Needs investigation", Continue); QCOMPARE(Cursor::pos(), QPoint(0, 50)); // use a different edge, this time with pushback s->reserve(KWin::ElectricRight, &callback, "callback"); Cursor::setPos(99, 50); s->check(QPoint(99, 50), QDateTime::currentDateTime()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.last().first().value(), ElectricLeft); QCOMPARE(Cursor::pos(), QPoint(98, 50)); // and trigger it again QTest::qWait(160); Cursor::setPos(99, 50); s->check(QPoint(99, 50), QDateTime::currentDateTime()); QCOMPARE(spy.count(), 2); QCOMPARE(spy.last().first().value(), ElectricRight); QCOMPARE(Cursor::pos(), QPoint(98, 50)); } void TestScreenEdges::testPushBack_data() { QTest::addColumn("border"); QTest::addColumn("pushback"); QTest::addColumn("trigger"); QTest::addColumn("expected"); QTest::newRow("topleft-3") << KWin::ElectricTopLeft << 3 << QPoint(0, 0) << QPoint(3, 3); QTest::newRow("top-5") << KWin::ElectricTop << 5 << QPoint(50, 0) << QPoint(50, 5); QTest::newRow("toprigth-2") << KWin::ElectricTopRight << 2 << QPoint(99, 0) << QPoint(97, 2); QTest::newRow("right-10") << KWin::ElectricRight << 10 << QPoint(99, 50) << QPoint(89, 50); QTest::newRow("bottomright-5") << KWin::ElectricBottomRight << 5 << QPoint(99, 99) << QPoint(94, 94); QTest::newRow("bottom-10") << KWin::ElectricBottom << 10 << QPoint(50, 99) << QPoint(50, 89); QTest::newRow("bottomleft-3") << KWin::ElectricBottomLeft << 3 << QPoint(0, 99) << QPoint(3, 96); QTest::newRow("left-10") << KWin::ElectricLeft << 10 << QPoint(0, 50) << QPoint(10, 50); QTest::newRow("invalid") << KWin::ElectricLeft << 10 << QPoint(50, 0) << QPoint(50, 0); } void TestScreenEdges::testPushBack() { using namespace KWin; QFETCH(int, pushback); auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorderPushbackPixels", pushback); config->sync(); // TODO: add screens auto s = ScreenEdges::self(); s->setConfig(config); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); QFETCH(ElectricBorder, border); s->reserve(border, &callback, "callback"); QFETCH(QPoint, trigger); Cursor::setPos(trigger); xcb_enter_notify_event_t event; event.root_x = trigger.x(); event.root_y = trigger.y(); event.event_x = trigger.x(); event.event_y = trigger.y(); event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = s->windows().first(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QTEST(Cursor::pos(), "expected"); // do the same without the event, but the check method Cursor::setPos(trigger); s->check(trigger, QDateTime::currentDateTime()); QVERIFY(spy.isEmpty()); QTEST(Cursor::pos(), "expected"); } void TestScreenEdges::testFullScreenBlocking() { using namespace KWin; MockWorkspace ws; Client client(&ws); auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 1); config->sync(); auto s = ScreenEdges::self(); s->setConfig(config); s->init(); TestObject callback; QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder))); QVERIFY(spy.isValid()); s->reserve(KWin::ElectricLeft, &callback, "callback"); s->reserve(KWin::ElectricBottomRight, &callback, "callback"); // currently there is no active client yet, so check blocking shouldn't do anything emit s->checkBlocking(); xcb_enter_notify_event_t event; Cursor::setPos(0, 50); event.root_x = 0; event.root_y = 50; event.event_x = 0; event.event_y = 50; event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = s->windows().first(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 50)); client.setGeometry(screens()->geometry()); client.setActive(true); client.setFullScreen(true); ws.setActiveClient(&client); emit s->checkBlocking(); // the signal doesn't trigger for corners, let's go over all windows just to be sure that it doesn't call for corners for (auto e: s->findChildren()) { e->checkBlocking(); } // calling again should not trigger QTest::qWait(160); Cursor::setPos(0, 50); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and no pushback QCOMPARE(Cursor::pos(), QPoint(0, 50)); // let's make the client not fullscreen, which should trigger client.setFullScreen(false); emit s->checkBlocking(); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(!spy.isEmpty()); QCOMPARE(Cursor::pos(), QPoint(1, 50)); // let's make the client fullscreen again, but with a geometry not intersecting the left edge QTest::qWait(351); client.setFullScreen(true); client.setGeometry(client.geometry().translated(10, 0)); emit s->checkBlocking(); spy.clear(); Cursor::setPos(0, 50); event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and a pushback QCOMPARE(Cursor::pos(), QPoint(1, 50)); // just to be sure, let's set geometry back client.setGeometry(screens()->geometry()); emit s->checkBlocking(); Cursor::setPos(0, 50); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and no pushback QCOMPARE(Cursor::pos(), QPoint(0, 50)); // the corner should always trigger s->unreserve(KWin::ElectricLeft, &callback); event.event_x = 99; event.event_y = 99; event.root_x = 99; event.root_y = 99; event.event = s->windows().first(); event.time = QDateTime::currentMSecsSinceEpoch(); Cursor::setPos(99, 99); QVERIFY(s->isEntered(&event)); QVERIFY(spy.isEmpty()); // and pushback QCOMPARE(Cursor::pos(), QPoint(98, 98)); QTest::qWait(160); event.time = QDateTime::currentMSecsSinceEpoch(); Cursor::setPos(99, 99); QVERIFY(s->isEntered(&event)); QVERIFY(!spy.isEmpty()); } void TestScreenEdges::testClientEdge() { using namespace KWin; Client client(workspace()); client.setGeometry(QRect(10, 50, 10, 50)); auto s = ScreenEdges::self(); s->init(); s->reserve(&client, KWin::ElectricBottom); // let's set the client to be hidden client.setHiddenInternal(true); QPointer edge = s->findChildren().last(); s->reserve(&client, KWin::ElectricBottom); QCOMPARE(edge.data(), s->findChildren().last()); QCOMPARE(edge->isReserved(), true); //remove old reserves and resize to be in the middle of the screen s->reserve(&client, KWin::ElectricNone); client.setGeometry(QRect(2, 2, 20, 20)); // for none of the edges it should be able to be set for (int i = 0; i < ELECTRIC_COUNT; ++i) { client.setHiddenInternal(true); s->reserve(&client, static_cast(i)); QCOMPARE(client.isHiddenInternal(), false); } // now let's try to set it and activate it client.setGeometry(screens()->geometry()); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricLeft); QCOMPARE(client.isHiddenInternal(), true); xcb_enter_notify_event_t event; Cursor::setPos(0, 50); event.root_x = 0; event.root_y = 50; event.event_x = 0; event.event_y = 50; event.root = XCB_WINDOW_NONE; event.child = XCB_WINDOW_NONE; event.event = s->windows().first(); event.same_screen_focus = 1; event.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event)); // autohiding panels shall activate instantly QCOMPARE(client.isHiddenInternal(), false); QCOMPARE(Cursor::pos(), QPoint(1, 50)); // now let's reserve the client for each of the edges, in the end for the right one client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricTop); s->reserve(&client, KWin::ElectricBottom); QCOMPARE(client.isHiddenInternal(), true); // corners shouldn't get reserved s->reserve(&client, KWin::ElectricTopLeft); QCOMPARE(client.isHiddenInternal(), false); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricTopRight); QCOMPARE(client.isHiddenInternal(), false); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricBottomRight); QCOMPARE(client.isHiddenInternal(), false); client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricBottomLeft); QCOMPARE(client.isHiddenInternal(), false); // now finally reserve on right one client.setHiddenInternal(true); s->reserve(&client, KWin::ElectricRight); QCOMPARE(client.isHiddenInternal(), true); // now let's emulate the removal of a Client through Workspace emit workspace()->clientRemoved(&client); for (auto e : s->findChildren()) { QVERIFY(!e->client()); } QCOMPARE(client.isHiddenInternal(), true); // now let's try to trigger the client showing with the check method instead of enter notify s->reserve(&client, KWin::ElectricTop); QCOMPARE(client.isHiddenInternal(), true); Cursor::setPos(50, 0); s->check(QPoint(50, 0), QDateTime::currentDateTime()); QCOMPARE(client.isHiddenInternal(), false); QCOMPARE(Cursor::pos(), QPoint(50, 1)); // unreserve by setting to none edge s->reserve(&client, KWin::ElectricNone); // check on previous edge again, should fail client.setHiddenInternal(true); Cursor::setPos(50, 0); s->check(QPoint(50, 0), QDateTime::currentDateTime()); QCOMPARE(client.isHiddenInternal(), true); QCOMPARE(Cursor::pos(), QPoint(50, 0)); // set to windows can cover client.setGeometry(screens()->geometry()); client.setHiddenInternal(false); client.setKeepBelow(true); s->reserve(&client, KWin::ElectricLeft); QCOMPARE(client.keepBelow(), true); QCOMPARE(client.isHiddenInternal(), false); xcb_enter_notify_event_t event2; Cursor::setPos(0, 50); event2.root_x = 0; event2.root_y = 50; event2.event_x = 0; event2.event_y = 50; event2.root = XCB_WINDOW_NONE; event2.child = XCB_WINDOW_NONE; event2.event = s->windows().first(); event2.same_screen_focus = 1; event2.time = QDateTime::currentMSecsSinceEpoch(); QVERIFY(s->isEntered(&event2)); QCOMPARE(client.keepBelow(), false); QCOMPARE(client.isHiddenInternal(), false); QCOMPARE(Cursor::pos(), QPoint(1, 50)); } Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestScreenEdges) #include "test_screen_edges.moc" diff --git a/autotests/test_virtual_desktops.cpp b/autotests/test_virtual_desktops.cpp index 2626f6432..03fcb5a75 100644 --- a/autotests/test_virtual_desktops.cpp +++ b/autotests/test_virtual_desktops.cpp @@ -1,657 +1,652 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 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 "../virtualdesktops.h" #include "../input.h" // KDE #include #include #include namespace KWin { int screen_number = 0; InputRedirection *InputRedirection::s_self = nullptr; void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { Q_UNUSED(shortcut) Q_UNUSED(action) } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { Q_UNUSED(modifiers) Q_UNUSED(axis) Q_UNUSED(action) } -void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) -{ - Q_UNUSED(action) -} - } Q_DECLARE_METATYPE(Qt::Orientation) using namespace KWin; class TestVirtualDesktops : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void count_data(); void count(); void navigationWrapsAround_data(); void navigationWrapsAround(); void current_data(); void current(); void currentChangeOnCountChange_data(); void currentChangeOnCountChange(); void next_data(); void next(); void previous_data(); void previous(); void left_data(); void left(); void right_data(); void right(); void above_data(); void above(); void below_data(); void below(); void updateGrid_data(); void updateGrid(); void updateLayout_data(); void updateLayout(); void name_data(); void name(); void switchToShortcuts(); void load(); void save(); private: void addDirectionColumns(); template void testDirection(const QString &actionName); }; void TestVirtualDesktops::init() { VirtualDesktopManager::create(); screen_number = 0; } void TestVirtualDesktops::cleanup() { delete VirtualDesktopManager::self(); } static const uint s_countInitValue = 2; void TestVirtualDesktops::count_data() { QTest::addColumn("request"); QTest::addColumn("result"); QTest::addColumn("signal"); QTest::addColumn("removedSignal"); QTest::newRow("Minimum") << (uint)1 << (uint)1 << true << true; QTest::newRow("Below Minimum") << (uint)0 << (uint)1 << true << true; QTest::newRow("Normal Value") << (uint)10 << (uint)10 << true << false; QTest::newRow("Maximum") << VirtualDesktopManager::maximum() << VirtualDesktopManager::maximum() << true << false; QTest::newRow("Above Maximum") << VirtualDesktopManager::maximum() + 1 << VirtualDesktopManager::maximum() << true << false; QTest::newRow("Unchanged") << s_countInitValue << s_countInitValue << false << false; } void TestVirtualDesktops::count() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QCOMPARE(vds->count(), (uint)0); // start with a useful desktop count vds->setCount(s_countInitValue); QSignalSpy spy(vds, SIGNAL(countChanged(uint,uint))); QSignalSpy desktopsRemoved(vds, SIGNAL(desktopsRemoved(uint))); QFETCH(uint, request); QFETCH(uint, result); QFETCH(bool, signal); QFETCH(bool, removedSignal); vds->setCount(request); QCOMPARE(vds->count(), result); QCOMPARE(spy.isEmpty(), !signal); if (!spy.isEmpty()) { QList arguments = spy.takeFirst(); QCOMPARE(arguments.count(), 2); QCOMPARE(arguments.at(0).type(), QVariant::UInt); QCOMPARE(arguments.at(1).type(), QVariant::UInt); QCOMPARE(arguments.at(0).toUInt(), s_countInitValue); QCOMPARE(arguments.at(1).toUInt(), result); } QCOMPARE(desktopsRemoved.isEmpty(), !removedSignal); if (!desktopsRemoved.isEmpty()) { QList arguments = desktopsRemoved.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.at(0).type(), QVariant::UInt); QCOMPARE(arguments.at(0).toUInt(), s_countInitValue); } } void TestVirtualDesktops::navigationWrapsAround_data() { QTest::addColumn("init"); QTest::addColumn("request"); QTest::addColumn("result"); QTest::addColumn("signal"); QTest::newRow("enable") << false << true << true << true; QTest::newRow("disable") << true << false << false << true; QTest::newRow("keep enabled") << true << true << true << false; QTest::newRow("keep disabled") << false << false << false << false; } void TestVirtualDesktops::navigationWrapsAround() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QCOMPARE(vds->isNavigationWrappingAround(), false); QFETCH(bool, init); QFETCH(bool, request); QFETCH(bool, result); QFETCH(bool, signal); // set to init value vds->setNavigationWrappingAround(init); QCOMPARE(vds->isNavigationWrappingAround(), init); QSignalSpy spy(vds, SIGNAL(navigationWrappingAroundChanged())); vds->setNavigationWrappingAround(request); QCOMPARE(vds->isNavigationWrappingAround(), result); QCOMPARE(spy.isEmpty(), !signal); } void TestVirtualDesktops::current_data() { QTest::addColumn("count"); QTest::addColumn("init"); QTest::addColumn("request"); QTest::addColumn("result"); QTest::addColumn("signal"); QTest::newRow("lower") << (uint)4 << (uint)3 << (uint)2 << (uint)2 << true; QTest::newRow("higher") << (uint)4 << (uint)1 << (uint)2 << (uint)2 << true; QTest::newRow("maximum") << (uint)4 << (uint)1 << (uint)4 << (uint)4 << true; QTest::newRow("above maximum") << (uint)4 << (uint)1 << (uint)5 << (uint)1 << false; QTest::newRow("minimum") << (uint)4 << (uint)2 << (uint)1 << (uint)1 << true; QTest::newRow("below minimum") << (uint)4 << (uint)2 << (uint)0 << (uint)2 << false; QTest::newRow("unchanged") << (uint)4 << (uint)2 << (uint)2 << (uint)2 << false; } void TestVirtualDesktops::current() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QCOMPARE(vds->current(), (uint)0); QFETCH(uint, count); vds->setCount(count); QFETCH(uint, init); QVERIFY(vds->setCurrent(init)); QCOMPARE(vds->current(), init); QSignalSpy spy(vds, SIGNAL(currentChanged(uint,uint))); QFETCH(uint, request); QFETCH(uint, result); QFETCH(bool, signal); QCOMPARE(vds->setCurrent(request), signal); QCOMPARE(vds->current(), result); QCOMPARE(spy.isEmpty(), !signal); if (!spy.isEmpty()) { QList arguments = spy.takeFirst(); QCOMPARE(arguments.count(), 2); QCOMPARE(arguments.at(0).type(), QVariant::UInt); QCOMPARE(arguments.at(1).type(), QVariant::UInt); QCOMPARE(arguments.at(0).toUInt(), init); QCOMPARE(arguments.at(1).toUInt(), result); } } void TestVirtualDesktops::currentChangeOnCountChange_data() { QTest::addColumn("initCount"); QTest::addColumn("initCurrent"); QTest::addColumn("request"); QTest::addColumn("current"); QTest::addColumn("signal"); QTest::newRow("increment") << (uint)4 << (uint)2 << (uint)5 << (uint)2 << false; QTest::newRow("increment on last") << (uint)4 << (uint)4 << (uint)5 << (uint)4 << false; QTest::newRow("decrement") << (uint)4 << (uint)2 << (uint)3 << (uint)2 << false; QTest::newRow("decrement on second last") << (uint)4 << (uint)3 << (uint)3 << (uint)3 << false; QTest::newRow("decrement on last") << (uint)4 << (uint)4 << (uint)3 << (uint)3 << true; QTest::newRow("multiple decrement") << (uint)4 << (uint)2 << (uint)1 << (uint)1 << true; } void TestVirtualDesktops::currentChangeOnCountChange() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QFETCH(uint, initCount); QFETCH(uint, initCurrent); vds->setCount(initCount); vds->setCurrent(initCurrent); QSignalSpy spy(vds, SIGNAL(currentChanged(uint,uint))); QFETCH(uint, request); QFETCH(uint, current); QFETCH(bool, signal); vds->setCount(request); QCOMPARE(vds->current(), current); QCOMPARE(spy.isEmpty(), !signal); } void TestVirtualDesktops::addDirectionColumns() { QTest::addColumn("initCount"); QTest::addColumn("initCurrent"); QTest::addColumn("wrap"); QTest::addColumn("result"); } template void TestVirtualDesktops::testDirection(const QString &actionName) { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QFETCH(uint, initCount); QFETCH(uint, initCurrent); vds->setCount(initCount); vds->setCurrent(initCurrent); QFETCH(bool, wrap); QFETCH(uint, result); T functor; QCOMPARE(functor(nullptr, wrap)->x11DesktopNumber(), result); vds->setNavigationWrappingAround(wrap); vds->initShortcuts(); QAction *action = vds->findChild(actionName); QVERIFY(action); action->trigger(); QCOMPARE(vds->current(), result); QCOMPARE(functor(initCurrent, wrap), result); } void TestVirtualDesktops::next_data() { addDirectionColumns(); QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; QTest::newRow("desktops, wrap") << (uint)4 << (uint)1 << true << (uint)2; QTest::newRow("desktops, no wrap") << (uint)4 << (uint)1 << false << (uint)2; QTest::newRow("desktops at end, wrap") << (uint)4 << (uint)4 << true << (uint)1; QTest::newRow("desktops at end, no wrap") << (uint)4 << (uint)4 << false << (uint)4; } void TestVirtualDesktops::next() { testDirection(QStringLiteral("Switch to Next Desktop")); } void TestVirtualDesktops::previous_data() { addDirectionColumns(); QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; QTest::newRow("desktops, wrap") << (uint)4 << (uint)3 << true << (uint)2; QTest::newRow("desktops, no wrap") << (uint)4 << (uint)3 << false << (uint)2; QTest::newRow("desktops at start, wrap") << (uint)4 << (uint)1 << true << (uint)4; QTest::newRow("desktops at start, no wrap") << (uint)4 << (uint)1 << false << (uint)1; } void TestVirtualDesktops::previous() { testDirection(QStringLiteral("Switch to Previous Desktop")); } void TestVirtualDesktops::left_data() { addDirectionColumns(); QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; QTest::newRow("desktops, wrap, 1st row") << (uint)4 << (uint)2 << true << (uint)1; QTest::newRow("desktops, no wrap, 1st row") << (uint)4 << (uint)2 << false << (uint)1; QTest::newRow("desktops, wrap, 2nd row") << (uint)4 << (uint)4 << true << (uint)3; QTest::newRow("desktops, no wrap, 2nd row") << (uint)4 << (uint)4 << false << (uint)3; QTest::newRow("desktops at start, wrap, 1st row") << (uint)4 << (uint)1 << true << (uint)2; QTest::newRow("desktops at start, no wrap, 1st row") << (uint)4 << (uint)1 << false << (uint)1; QTest::newRow("desktops at start, wrap, 2nd row") << (uint)4 << (uint)3 << true << (uint)4; QTest::newRow("desktops at start, no wrap, 2nd row") << (uint)4 << (uint)3 << false << (uint)3; QTest::newRow("non symmetric, start") << (uint)5 << (uint)5 << false << (uint)4; QTest::newRow("non symmetric, end, no wrap") << (uint)5 << (uint)4 << false << (uint)4; QTest::newRow("non symmetric, end, wrap") << (uint)5 << (uint)4 << true << (uint)5; } void TestVirtualDesktops::left() { testDirection(QStringLiteral("Switch One Desktop to the Left")); } void TestVirtualDesktops::right_data() { addDirectionColumns(); QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; QTest::newRow("desktops, wrap, 1st row") << (uint)4 << (uint)1 << true << (uint)2; QTest::newRow("desktops, no wrap, 1st row") << (uint)4 << (uint)1 << false << (uint)2; QTest::newRow("desktops, wrap, 2nd row") << (uint)4 << (uint)3 << true << (uint)4; QTest::newRow("desktops, no wrap, 2nd row") << (uint)4 << (uint)3 << false << (uint)4; QTest::newRow("desktops at start, wrap, 1st row") << (uint)4 << (uint)2 << true << (uint)1; QTest::newRow("desktops at start, no wrap, 1st row") << (uint)4 << (uint)2 << false << (uint)2; QTest::newRow("desktops at start, wrap, 2nd row") << (uint)4 << (uint)4 << true << (uint)3; QTest::newRow("desktops at start, no wrap, 2nd row") << (uint)4 << (uint)4 << false << (uint)4; QTest::newRow("non symmetric, start") << (uint)5 << (uint)4 << false << (uint)5; QTest::newRow("non symmetric, end, no wrap") << (uint)5 << (uint)5 << false << (uint)5; QTest::newRow("non symmetric, end, wrap") << (uint)5 << (uint)5 << true << (uint)4; } void TestVirtualDesktops::right() { testDirection(QStringLiteral("Switch One Desktop to the Right")); } void TestVirtualDesktops::above_data() { addDirectionColumns(); QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; QTest::newRow("desktops, wrap, 1st column") << (uint)4 << (uint)3 << true << (uint)1; QTest::newRow("desktops, no wrap, 1st column") << (uint)4 << (uint)3 << false << (uint)1; QTest::newRow("desktops, wrap, 2nd column") << (uint)4 << (uint)4 << true << (uint)2; QTest::newRow("desktops, no wrap, 2nd column") << (uint)4 << (uint)4 << false << (uint)2; QTest::newRow("desktops at start, wrap, 1st column") << (uint)4 << (uint)1 << true << (uint)3; QTest::newRow("desktops at start, no wrap, 1st column") << (uint)4 << (uint)1 << false << (uint)1; QTest::newRow("desktops at start, wrap, 2nd column") << (uint)4 << (uint)2 << true << (uint)4; QTest::newRow("desktops at start, no wrap, 2nd column") << (uint)4 << (uint)2 << false << (uint)2; } void TestVirtualDesktops::above() { testDirection(QStringLiteral("Switch One Desktop Up")); } void TestVirtualDesktops::below_data() { addDirectionColumns(); QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; QTest::newRow("desktops, wrap, 1st column") << (uint)4 << (uint)1 << true << (uint)3; QTest::newRow("desktops, no wrap, 1st column") << (uint)4 << (uint)1 << false << (uint)3; QTest::newRow("desktops, wrap, 2nd column") << (uint)4 << (uint)2 << true << (uint)4; QTest::newRow("desktops, no wrap, 2nd column") << (uint)4 << (uint)2 << false << (uint)4; QTest::newRow("desktops at start, wrap, 1st column") << (uint)4 << (uint)3 << true << (uint)1; QTest::newRow("desktops at start, no wrap, 1st column") << (uint)4 << (uint)3 << false << (uint)3; QTest::newRow("desktops at start, wrap, 2nd column") << (uint)4 << (uint)4 << true << (uint)2; QTest::newRow("desktops at start, no wrap, 2nd column") << (uint)4 << (uint)4 << false << (uint)4; } void TestVirtualDesktops::below() { testDirection(QStringLiteral("Switch One Desktop Down")); } void TestVirtualDesktops::updateGrid_data() { QTest::addColumn("initCount"); QTest::addColumn("size"); QTest::addColumn("orientation"); QTest::addColumn("coords"); QTest::addColumn("desktop"); const Qt::Orientation h = Qt::Horizontal; const Qt::Orientation v = Qt::Vertical; QTest::newRow("one desktop, h") << (uint)1 << QSize(1, 1) << h << QPoint(0, 0) << (uint)1; QTest::newRow("one desktop, v") << (uint)1 << QSize(1, 1) << v << QPoint(0, 0) << (uint)1; QTest::newRow("one desktop, h, 0") << (uint)1 << QSize(1, 1) << h << QPoint(1, 0) << (uint)0; QTest::newRow("one desktop, v, 0") << (uint)1 << QSize(1, 1) << v << QPoint(0, 1) << (uint)0; QTest::newRow("two desktops, h, 1") << (uint)2 << QSize(2, 1) << h << QPoint(0, 0) << (uint)1; QTest::newRow("two desktops, h, 2") << (uint)2 << QSize(2, 1) << h << QPoint(1, 0) << (uint)2; QTest::newRow("two desktops, h, 3") << (uint)2 << QSize(2, 1) << h << QPoint(0, 1) << (uint)0; QTest::newRow("two desktops, h, 4") << (uint)2 << QSize(2, 1) << h << QPoint(2, 0) << (uint)0; QTest::newRow("two desktops, v, 1") << (uint)2 << QSize(2, 1) << v << QPoint(0, 0) << (uint)1; QTest::newRow("two desktops, v, 2") << (uint)2 << QSize(2, 1) << v << QPoint(1, 0) << (uint)2; QTest::newRow("two desktops, v, 3") << (uint)2 << QSize(2, 1) << v << QPoint(0, 1) << (uint)0; QTest::newRow("two desktops, v, 4") << (uint)2 << QSize(2, 1) << v << QPoint(2, 0) << (uint)0; QTest::newRow("four desktops, h, one row, 1") << (uint)4 << QSize(4, 1) << h << QPoint(0, 0) << (uint)1; QTest::newRow("four desktops, h, one row, 2") << (uint)4 << QSize(4, 1) << h << QPoint(1, 0) << (uint)2; QTest::newRow("four desktops, h, one row, 3") << (uint)4 << QSize(4, 1) << h << QPoint(2, 0) << (uint)3; QTest::newRow("four desktops, h, one row, 4") << (uint)4 << QSize(4, 1) << h << QPoint(3, 0) << (uint)4; QTest::newRow("four desktops, v, one column, 1") << (uint)4 << QSize(1, 4) << v << QPoint(0, 0) << (uint)1; QTest::newRow("four desktops, v, one column, 2") << (uint)4 << QSize(1, 4) << v << QPoint(0, 1) << (uint)2; QTest::newRow("four desktops, v, one column, 3") << (uint)4 << QSize(1, 4) << v << QPoint(0, 2) << (uint)3; QTest::newRow("four desktops, v, one column, 4") << (uint)4 << QSize(1, 4) << v << QPoint(0, 3) << (uint)4; QTest::newRow("four desktops, h, grid, 1") << (uint)4 << QSize(2, 2) << h << QPoint(0, 0) << (uint)1; QTest::newRow("four desktops, h, grid, 2") << (uint)4 << QSize(2, 2) << h << QPoint(1, 0) << (uint)2; QTest::newRow("four desktops, h, grid, 3") << (uint)4 << QSize(2, 2) << h << QPoint(0, 1) << (uint)3; QTest::newRow("four desktops, h, grid, 4") << (uint)4 << QSize(2, 2) << h << QPoint(1, 1) << (uint)4; QTest::newRow("four desktops, h, grid, 0/3") << (uint)4 << QSize(2, 2) << h << QPoint(0, 3) << (uint)0; QTest::newRow("three desktops, h, grid, 1") << (uint)3 << QSize(2, 2) << h << QPoint(0, 0) << (uint)1; QTest::newRow("three desktops, h, grid, 2") << (uint)3 << QSize(2, 2) << h << QPoint(1, 0) << (uint)2; QTest::newRow("three desktops, h, grid, 3") << (uint)3 << QSize(2, 2) << h << QPoint(0, 1) << (uint)3; QTest::newRow("three desktops, h, grid, 4") << (uint)3 << QSize(2, 2) << h << QPoint(1, 1) << (uint)0; } void TestVirtualDesktops::updateGrid() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QFETCH(uint, initCount); vds->setCount(initCount); VirtualDesktopGrid grid; QFETCH(QSize, size); QFETCH(Qt::Orientation, orientation); QCOMPARE(vds->desktops().count(), int(initCount)); grid.update(size, orientation, vds->desktops()); QCOMPARE(grid.size(), size); QCOMPARE(grid.width(), size.width()); QCOMPARE(grid.height(), size.height()); QFETCH(QPoint, coords); QFETCH(uint, desktop); QCOMPARE(grid.at(coords), vds->desktopForX11Id(desktop)); if (desktop != 0) { QCOMPARE(grid.gridCoords(desktop), coords); } } void TestVirtualDesktops::updateLayout_data() { QTest::addColumn("desktop"); QTest::addColumn("result"); QTest::newRow("01") << (uint)1 << QSize(1, 1); QTest::newRow("02") << (uint)2 << QSize(1, 2); QTest::newRow("03") << (uint)3 << QSize(2, 2); QTest::newRow("04") << (uint)4 << QSize(2, 2); QTest::newRow("05") << (uint)5 << QSize(3, 2); QTest::newRow("06") << (uint)6 << QSize(3, 2); QTest::newRow("07") << (uint)7 << QSize(4, 2); QTest::newRow("08") << (uint)8 << QSize(4, 2); QTest::newRow("09") << (uint)9 << QSize(5, 2); QTest::newRow("10") << (uint)10 << QSize(5, 2); QTest::newRow("11") << (uint)11 << QSize(6, 2); QTest::newRow("12") << (uint)12 << QSize(6, 2); QTest::newRow("13") << (uint)13 << QSize(7, 2); QTest::newRow("14") << (uint)14 << QSize(7, 2); QTest::newRow("15") << (uint)15 << QSize(8, 2); QTest::newRow("16") << (uint)16 << QSize(8, 2); QTest::newRow("17") << (uint)17 << QSize(9, 2); QTest::newRow("18") << (uint)18 << QSize(9, 2); QTest::newRow("19") << (uint)19 << QSize(10, 2); QTest::newRow("20") << (uint)20 << QSize(10, 2); } void TestVirtualDesktops::updateLayout() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QSignalSpy spy(vds, SIGNAL(layoutChanged(int,int))); // call update layout - implicitly through setCount QFETCH(uint, desktop); QFETCH(QSize, result); vds->setCount(desktop); QCOMPARE(vds->grid().size(), result); QCOMPARE(spy.count(), 1); const QVariantList &arguments = spy.at(0); QCOMPARE(arguments.at(0).toInt(), result.width()); QCOMPARE(arguments.at(1).toInt(), result.height()); // calling update layout again should not change anything vds->updateLayout(); QCOMPARE(vds->grid().size(), result); QCOMPARE(spy.count(), 2); const QVariantList &arguments2 = spy.at(1); QCOMPARE(arguments2.at(0).toInt(), result.width()); QCOMPARE(arguments2.at(1).toInt(), result.height()); } void TestVirtualDesktops::name_data() { QTest::addColumn("initCount"); QTest::addColumn("desktop"); QTest::addColumn("desktopName"); QTest::newRow("desktop 1") << (uint)4 << (uint)1 << "Desktop 1"; QTest::newRow("desktop 2") << (uint)4 << (uint)2 << "Desktop 2"; QTest::newRow("desktop 3") << (uint)4 << (uint)3 << "Desktop 3"; QTest::newRow("desktop 4") << (uint)4 << (uint)4 << "Desktop 4"; QTest::newRow("desktop 5") << (uint)4 << (uint)5 << "Desktop 5"; } void TestVirtualDesktops::name() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); QFETCH(uint, initCount); vds->setCount(initCount); QFETCH(uint, desktop); QTEST(vds->name(desktop), "desktopName"); } void TestVirtualDesktops::switchToShortcuts() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); vds->setCount(vds->maximum()); vds->setCurrent(vds->maximum()); QCOMPARE(vds->current(), vds->maximum()); vds->initShortcuts(); const QString toDesktop = QStringLiteral("Switch to Desktop %1"); for (uint i=1; i<=vds->maximum(); ++i) { const QString desktop(toDesktop.arg(i)); QAction *action = vds->findChild(desktop); QVERIFY2(action, desktop.toUtf8().constData()); action->trigger(); QCOMPARE(vds->current(), i); } // invoke switchTo not from a QAction QMetaObject::invokeMethod(vds, "slotSwitchTo"); // should still be on max QCOMPARE(vds->current(), vds->maximum()); } void TestVirtualDesktops::load() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); // no config yet, load should not change anything vds->load(); QCOMPARE(vds->count(), (uint)0); // empty config should create one desktop KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); vds->setConfig(config); vds->load(); QCOMPARE(vds->count(), (uint)1); // setting a sensible number config->group("Desktops").writeEntry("Number", 4); vds->load(); QCOMPARE(vds->count(), (uint)4); // setting the screen number should reset to one desktop as config value is missing screen_number = 2; vds->load(); QCOMPARE(vds->count(), (uint)1); // creating the respective group should properly load config->group("Desktops-screen-2").writeEntry("Number", 5); vds->load(); QCOMPARE(vds->count(), (uint)5); } void TestVirtualDesktops::save() { VirtualDesktopManager *vds = VirtualDesktopManager::self(); vds->setCount(4); // no config yet, just to ensure it actually works vds->save(); KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); vds->setConfig(config); // now save should create the group "Desktops" QCOMPARE(config->hasGroup("Desktops"), false); vds->save(); QCOMPARE(config->hasGroup("Desktops"), true); KConfigGroup desktops = config->group("Desktops"); QCOMPARE(desktops.readEntry("Number", 1), 4); QCOMPARE(desktops.hasKey("Name_1"), false); QCOMPARE(desktops.hasKey("Name_2"), false); QCOMPARE(desktops.hasKey("Name_3"), false); QCOMPARE(desktops.hasKey("Name_4"), false); // change screen number screen_number = 3; QCOMPARE(config->hasGroup("Desktops-screen-3"), false); vds->setCount(3); vds->save(); QCOMPARE(config->hasGroup("Desktops-screen-3"), true); // old one should be unchanged desktops = config->group("Desktops"); QCOMPARE(desktops.readEntry("Number", 1), 4); desktops = config->group("Desktops-screen-3"); QCOMPARE(desktops.readEntry("Number", 1), 3); QCOMPARE(desktops.hasKey("Name_1"), false); QCOMPARE(desktops.hasKey("Name_2"), false); QCOMPARE(desktops.hasKey("Name_3"), false); QCOMPARE(desktops.hasKey("Name_4"), false); } QTEST_MAIN(TestVirtualDesktops) #include "test_virtual_desktops.moc" diff --git a/input.cpp b/input.cpp index ad432624e..199e893cc 100644 --- a/input.cpp +++ b/input.cpp @@ -1,1995 +1,1983 @@ /******************************************************************** 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 "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include #include #include #include #include #include //screenlocker #include // Qt #include #include namespace KWin { InputEventFilter::InputEventFilter() = default; InputEventFilter::~InputEventFilter() { if (input()) { input()->uninstallInputEventFilter(this); } } bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) return false; } bool InputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::keyEvent(QKeyEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::touchDown(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchMotion(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { Q_UNUSED(scale) Q_UNUSED(angleDelta) Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::pinchGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureBegin(int fingerCount, quint32 time) { Q_UNUSED(fingerCount) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { Q_UNUSED(delta) Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureEnd(quint32 time) { Q_UNUSED(time) return false; } bool InputEventFilter::swipeGestureCancelled(quint32 time) { Q_UNUSED(time) return false; } void InputEventFilter::passToWaylandServer(QKeyEvent *event) { Q_ASSERT(waylandServer()); if (event->isAutoRepeat()) { return; } switch (event->type()) { case QEvent::KeyPress: waylandServer()->seat()->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: waylandServer()->seat()->keyReleased(event->nativeScanCode()); break; default: break; } } #if HAVE_INPUT class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; #endif class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (pointerSurfaceAllowed()) { seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } 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] { 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); input()->keyboard()->update(); m_keyCode = event->nativeScanCode(); passToWaylandServer(event); return true; } } 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(); } } 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(); } 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 isActive() const { return m_active; } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_callback = callback; input()->keyboard()->update(); } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_pointSelectionFallback = callback; input()->keyboard()->update(); } private: void deactivate() { m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); input()->pointer()->removeWindowSelectionCursor(); input()->keyboard()->update(); } void cancel() { if (m_callback) { m_callback(nullptr); } if (m_pointSelectionFallback) { m_pointSelectionFallback(QPoint(-1, -1)); } deactivate(); } void accept() { if (m_callback) { // TODO: this ignores shaped windows m_callback(input()->findToplevel(input()->globalPointer().toPoint())); } if (m_pointSelectionFallback) { m_pointSelectionFallback(input()->globalPointer().toPoint()); } deactivate(); } bool m_active = false; std::function m_callback; std::function m_pointSelectionFallback; }; 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->nativeVirtualKey(), event->key()); } return false; } }; class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (!internal) { return false; } QMouseEvent e(event->type(), event->pos() - internal->position(), event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool wheelEvent(QWheelEvent *event) override { auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y()); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta() * -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; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } event->setAccepted(false); if (QCoreApplication::sendEvent(found, event)) { waylandServer()->seat()->setFocusedKeyboardSurface(nullptr); passToWaylandServer(event); return true; } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { // something else is getting the events return false; } auto touch = input()->touch(); if (touch->internalPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } // a new touch point seat->setTimestamp(time); touch->update(pos); auto internal = touch->internalWindow(); if (!internal) { return false; } touch->setInternalPressId(id); // Qt's touch event API is rather complex, let's do fake mouse events instead m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y()); QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); QCoreApplication::instance()->sendEvent(internal.data(), &e); return true; } bool touchUp(quint32 id, quint32 time) override { auto touch = input()->touch(); auto internal = touch->internalWindow(); if (!internal) { return false; } if (touch->internalPressId() == -1) { return false; } waylandServer()->seat()->setTimestamp(time); if (touch->internalPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setInternalPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; class DecorationEventFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { return false; } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) { decoration->client()->processDecorationButtonPress(&e); } if (event->type() == QEvent::MouseButtonRelease) { decoration->client()->processDecorationButtonRelease(&e); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF localPos = event->globalPosF() - decoration->client()->pos(); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration.data(), &e); if (e.isAccepted()) { return true; } if ((orientation == Qt::Vertical) && decoration->client()->titlebarPositionUnderMouse()) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } if (input()->touch()->decorationPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } seat->setTimestamp(time); input()->touch()->update(pos); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } // send mouse up QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationButtonRelease(&e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } auto seat = waylandServer()->seat(); seat->setFocusedKeyboardSurface(nullptr); // pass the key event to the seat, so that it has a proper model of the currently hold keys // this is important for combinations like alt+shift to ensure that shift is not considered pressed passToWaylandServer(event); if (event->type() == QEvent::KeyPress) { TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); } else if (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; } }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event **/ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (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 { command = c->getMouseCommand(event->button(), &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (static_cast(event)->modifiersRelevantForGlobalShortcuts() == options->commandAllModifier()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } else { command = c->getWheelCommand(Qt::Vertical, &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } input()->touch()->update(pos); AbstractClient *c = dynamic_cast(input()->touch()->window().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows **/ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { 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) InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #if HAVE_INPUT if (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { if (LogindIntegration::self()->isConnected()) { LogindIntegration::self()->takeControl(); } else { connect(LogindIntegration::self(), &LogindIntegration::connectedChanged, LogindIntegration::self(), &LogindIntegration::takeControl); } connect(LogindIntegration::self(), &LogindIntegration::hasSessionControlChanged, this, [this] (bool sessionControl) { if (sessionControl) { setupLibInput(); } } ); } m_inputConfig = KSharedConfig::openConfig(QStringLiteral("kcminputrc")); } #endif connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); 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); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { // TODO: Fix time m_touch->processUp(id, 0); } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] () { m_touch->cancel(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] () { m_touch->frame(); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { #if HAVE_INPUT if (LogindIntegration::self()->hasSessionControl()) { installInputEventFilter(new VirtualTerminalFilter); } #endif if (waylandServer()) { installInputEventFilter(new TerminateServerFilter); installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); 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()) { m_inputConfig->reparseConfiguration(); const auto config = m_inputConfig->group(QStringLiteral("keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } #endif } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { #if HAVE_INPUT if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { if (waylandServer()) { // create relative pointer manager waylandServer()->display()->createRelativePointerManager(KWayland::Server::RelativePointerInterfaceVersion::UnstableV1, waylandServer()->display())->create(); } conn->setInputConfig(m_inputConfig); conn->updateLEDs(m_keyboard->xkb()->leds()); conn->setup(); connect(m_keyboard, &KeyboardInputRedirection::ledsChanged, conn, &LibInput::Connection::updateLEDs); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::pinchGestureBegin, m_pointer, &PointerInputRedirection::processPinchGestureBegin); connect(conn, &LibInput::Connection::pinchGestureUpdate, m_pointer, &PointerInputRedirection::processPinchGestureUpdate); connect(conn, &LibInput::Connection::pinchGestureEnd, m_pointer, &PointerInputRedirection::processPinchGestureEnd); connect(conn, &LibInput::Connection::pinchGestureCancelled, m_pointer, &PointerInputRedirection::processPinchGestureCancelled); connect(conn, &LibInput::Connection::swipeGestureBegin, m_pointer, &PointerInputRedirection::processSwipeGestureBegin); connect(conn, &LibInput::Connection::swipeGestureUpdate, m_pointer, &PointerInputRedirection::processSwipeGestureUpdate); connect(conn, &LibInput::Connection::swipeGestureEnd, m_pointer, &PointerInputRedirection::processSwipeGestureEnd); connect(conn, &LibInput::Connection::swipeGestureCancelled, m_pointer, &PointerInputRedirection::processSwipeGestureCancelled); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, [this] (const QSizeF &delta, const QSizeF &deltaNonAccel, uint32_t time, quint64 timeMicroseconds, LibInput::Device *device) { m_pointer->processMotion(m_pointer->pos() + QPointF(delta.width(), delta.height()), delta, deltaNonAccel, time, timeMicroseconds, device); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time, LibInput::Device *device) { Q_UNUSED(orig) m_pointer->processMotion(screen, time, device); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard s->setHasKeyboard(true); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasAlphaNumericKeyboardChanged, this, [this] (bool set) { if (m_libInput->isSuspended()) { return; } // TODO: this should update the seat, only workaround for QTBUG-54371 emit hasAlphaNumericKeyboardChanged(set); } ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } #endif } bool InputRedirection::hasAlphaNumericKeyboard() { #if HAVE_INPUT if (m_libInput) { return m_libInput->hasAlphaNumericKeyboard(); } #endif return true; } void InputRedirection::setupLibInputWithScreens() { #if HAVE_INPUT if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); #endif } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { m_pointer->processAxis(axis, delta, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } Qt::KeyboardModifiers InputRedirection::modifiersRelevantForGlobalShortcuts() const { return m_keyboard->modifiersRelevantForGlobalShortcuts(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { m_shortcuts->registerShortcut(action, shortcut); - registerShortcutForGlobalAccelTimestamp(action); + 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::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } -void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) -{ - connect(action, &QAction::triggered, kwinApp(), [action] { - QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); - bool ok = false; - const quint32 t = timestamp.toULongLong(&ok); - if (ok) { - kwinApp()->setX11Time(t); - } - }); -} - void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } 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; } m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { QEnterEvent event(pos - m_internalWindow->position(), pos - m_internalWindow->position(), pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); } emit internalWindowChanged(); } } } // namespace diff --git a/input.h b/input.h index fa554d9b2..88eacb4f4 100644 --- a/input.h +++ b/input.h @@ -1,422 +1,421 @@ /******************************************************************** 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 . *********************************************************************/ #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 PointerConstraintsFilter; class PointerInputRedirection; class TouchInputRedirection; class WindowSelectorFilter; 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 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, void (T::*slot)()); void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action); void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, 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); /** * @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); 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(); void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName); void startInteractivePositionSelection(std::function callback); bool isSelectingWindow() const; bool isBreakingPointerConstraints() 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 oldMods The new key state */ void keyStateChanged(quint32 keyCode, InputRedirection::KeyboardKeyState state); void hasAlphaNumericKeyboardChanged(bool set); private: void setupLibInput(); void setupLibInputWithScreens(); - void registerShortcutForGlobalAccelTimestamp(QAction *action); 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; PointerConstraintsFilter *m_pointerConstraintsFilter = nullptr; QVector m_filters; QVector m_spies; KSharedConfigPtr m_inputConfig; 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); protected: void passToWaylandServer(QKeyEvent *event); }; class InputDeviceHandler : public QObject { Q_OBJECT public: virtual ~InputDeviceHandler(); QPointer window() const { return m_window; } QPointer decoration() const { return m_decoration; } QPointer internalWindow() const { return m_internalWindow; } Q_SIGNALS: void decorationChanged(); void internalWindowChanged(); protected: explicit InputDeviceHandler(InputRedirection *parent); void updateDecoration(Toplevel *t, const QPointF &pos); void updateInternalWindow(const QPointF &pos); InputRedirection *m_input; /** * @brief The Toplevel which currently receives events */ QPointer m_window; /** * @brief The Decoration which currently receives events. **/ QPointer m_decoration; QPointer m_internalWindow; }; inline InputRedirection *input() { return InputRedirection::s_self; } template inline void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, void (T::*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) #endif // KWIN_INPUT_H diff --git a/platform.cpp b/platform.cpp index 57f24c80f..0a29a7f28 100644 --- a/platform.cpp +++ b/platform.cpp @@ -1,389 +1,394 @@ /******************************************************************** 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 "abstract_egl_backend.h" #include "composite.h" #include "cursor.h" #include "input.h" #include "pointer_input.h" #include "scene_opengl.h" #include "screenedge.h" #include "wayland_server.h" namespace KWin { Platform::Platform(QObject *parent) : QObject(parent) , m_eglDisplay(EGL_NO_DISPLAY) { } 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) { Q_UNUSED(config) qCWarning(KWIN_CORE) << "This backend does not support configuration changes."; } void Platform::setSoftWareCursor(bool set) { 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) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisHorizontal, delta, time); } void Platform::pointerAxisVertical(qreal delta, quint32 time) { if (!input()) { return; } input()->processPointerAxis(InputRedirection::PointerAxisVertical, delta, 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::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()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return s->backend()->hasExtension(QByteArrayLiteral("EGL_KHR_surfaceless_context")); } } return false; } EGLDisplay KWin::Platform::sceneEglDisplay() const { return m_eglDisplay; } void Platform::setSceneEglDisplay(EGLDisplay display) { m_eglDisplay = display; } EGLContext Platform::sceneEglContext() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->context(); } } return EGL_NO_CONTEXT; } EGLSurface Platform::sceneEglSurface() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->surface(); } } return EGL_NO_SURFACE; } EGLConfig Platform::sceneEglConfig() const { if (Compositor *c = Compositor::self()) { if (SceneOpenGL *s = dynamic_cast(c->scene())) { return static_cast(s->backend())->config(); } } return nullptr; } QSize Platform::screenSize() const { return QSize(); } QVector Platform::screenGeometries() const { return QVector({QRect(QPoint(0, 0), screenSize())}); } 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) +} + } diff --git a/platform.h b/platform.h index 87ba3658d..f4eb5fca2 100644 --- a/platform.h +++ b/platform.h @@ -1,361 +1,379 @@ /******************************************************************** 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 #include #include +class QAction; + namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { class Edge; class OpenGLBackend; class QPainterBackend; class Screens; class ScreenEdges; class Toplevel; class WaylandCursorTheme; 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; /** * The first (in case of multiple) EGLSurface used by the compositing scene. **/ EGLSurface sceneEglSurface() const; /** * The EglConfig used by the compositing scene. **/ EGLConfig sceneEglConfig() const; /** * 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; /** * 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 @link{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 * @link{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 @link{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; } 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 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(); 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; } /** * 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; EGLDisplay m_eglDisplay; int m_hideCursorCounter = 0; }; } Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/plugins/platforms/x11/standalone/x11_platform.cpp b/plugins/platforms/x11/standalone/x11_platform.cpp index 36f1a1866..41e816591 100644 --- a/plugins/platforms/x11/standalone/x11_platform.cpp +++ b/plugins/platforms/x11/standalone/x11_platform.cpp @@ -1,285 +1,297 @@ /******************************************************************** 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 "x11_platform.h" #include "x11cursor.h" #include "edge.h" #include "windowselector.h" #include #include #if HAVE_EPOXY_GLX #include "glxbackend.h" #endif #if HAVE_X11_XINPUT #include "xinputintegration.h" #endif #include "eglonxbackend.h" #include "keyboard_input.h" #include "logging.h" #include "screens_xrandr.h" #include "options.h" #include #include #include #include #include namespace KWin { X11StandalonePlatform::X11StandalonePlatform(QObject *parent) : Platform(parent) , m_x11Display(QX11Info::display()) { #if HAVE_X11_XINPUT if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) { m_xinputIntegration = new XInputIntegration(m_x11Display, this); m_xinputIntegration->init(); if (!m_xinputIntegration->hasXinput()) { delete m_xinputIntegration; m_xinputIntegration = nullptr; } else { connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration, &XInputIntegration::startListening); } } #endif } X11StandalonePlatform::~X11StandalonePlatform() = default; void X11StandalonePlatform::init() { if (!QX11Info::isPlatformX11()) { emit initFailed(); return; } setReady(true); emit screensQueried(); } Screens *X11StandalonePlatform::createScreens(QObject *parent) { return new XRandRScreens(parent); } OpenGLBackend *X11StandalonePlatform::createOpenGLBackend() { switch (options->glPlatformInterface()) { #if HAVE_EPOXY_GLX case GlxPlatformInterface: if (hasGlx()) { return new GlxBackend(m_x11Display); } else { qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead."; // no break, needs fall-through } #endif case EglPlatformInterface: return new EglOnXBackend(m_x11Display); default: // no backend available return nullptr; } } Edge *X11StandalonePlatform::createScreenEdge(ScreenEdges *edges) { return new WindowBasedEdge(edges); } void X11StandalonePlatform::createPlatformCursor(QObject *parent) { auto c = new X11Cursor(parent, m_xinputIntegration != nullptr); #if HAVE_X11_XINPUT if (m_xinputIntegration) { m_xinputIntegration->setCursor(c); // we know we have xkb already auto xkb = input()->keyboard()->xkb(); m_xinputIntegration->setXkb(xkb); xkb->reconfigure(); } #endif } bool X11StandalonePlatform::requiresCompositing() const { return false; } bool X11StandalonePlatform::openGLCompositingIsBroken() const { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); return KConfigGroup(kwinApp()->config(), "Compositing").readEntry(unsafeKey, false); } QString X11StandalonePlatform::compositingNotPossibleReason() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return i18n("OpenGL compositing (the default) has crashed KWin in the past.
" "This was most likely due to a driver bug." "

If you think that you have meanwhile upgraded to a stable driver,
" "you can reset this protection but be aware that this might result in an immediate crash!

" "

Alternatively, you might want to use the XRender backend instead.

"); if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) { return i18n("Required X extensions (XComposite and XDamage) are not available."); } #if !defined( KWIN_HAVE_XRENDER_COMPOSITING ) if (!hasGlx()) return i18n("GLX/OpenGL are not available and only OpenGL support is compiled."); #else if (!(hasGlx() || (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()))) { return i18n("GLX/OpenGL and XRender/XFixes are not available."); } #endif return QString(); } bool X11StandalonePlatform::compositingPossible() const { // first off, check whether we figured that we'll crash on detection because of a buggy driver KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing"); const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && gl_workaround_group.readEntry(unsafeKey, false)) return false; if (!Xcb::Extensions::self()->isCompositeAvailable()) { qCDebug(KWIN_CORE) << "No composite extension available"; return false; } if (!Xcb::Extensions::self()->isDamageAvailable()) { qCDebug(KWIN_CORE) << "No damage extension available"; return false; } if (hasGlx()) return true; #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()) return true; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return true; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } qCDebug(KWIN_CORE) << "No OpenGL or XRender/XFixes support"; return false; } bool X11StandalonePlatform::hasGlx() { return Xcb::Extensions::self()->hasGlx(); } void X11StandalonePlatform::createOpenGLSafePoint(OpenGLSafePoint safePoint) { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); switch (safePoint) { case OpenGLSafePoint::PreInit: group.writeEntry(unsafeKey, true); group.sync(); // Deliberately continue with PreFrame case OpenGLSafePoint::PreFrame: if (m_openGLFreezeProtectionThread == nullptr) { Q_ASSERT(m_openGLFreezeProtection == nullptr); m_openGLFreezeProtectionThread = new QThread(this); m_openGLFreezeProtectionThread->setObjectName("FreezeDetector"); m_openGLFreezeProtectionThread->start(); m_openGLFreezeProtection = new QTimer; m_openGLFreezeProtection->setInterval(15000); m_openGLFreezeProtection->setSingleShot(true); m_openGLFreezeProtection->start(); m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread); connect(m_openGLFreezeProtection, &QTimer::timeout, m_openGLFreezeProtection, [] { const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString())); auto group = KConfigGroup(kwinApp()->config(), "Compositing"); group.writeEntry(unsafeKey, true); group.sync(); qFatal("Freeze in OpenGL initialization detected"); }, Qt::DirectConnection); } else { Q_ASSERT(m_openGLFreezeProtection); QMetaObject::invokeMethod(m_openGLFreezeProtection, "start", Qt::QueuedConnection); } break; case OpenGLSafePoint::PostInit: group.writeEntry(unsafeKey, false); group.sync(); // Deliberately continue with PostFrame case OpenGLSafePoint::PostFrame: QMetaObject::invokeMethod(m_openGLFreezeProtection, "stop", Qt::QueuedConnection); break; case OpenGLSafePoint::PostLastGuardedFrame: m_openGLFreezeProtection->deleteLater(); m_openGLFreezeProtection = nullptr; m_openGLFreezeProtectionThread->quit(); m_openGLFreezeProtectionThread->wait(); delete m_openGLFreezeProtectionThread; m_openGLFreezeProtectionThread = nullptr; break; } } PlatformCursorImage X11StandalonePlatform::cursorImage() const { auto c = kwinApp()->x11Connection(); QScopedPointer cursor( xcb_xfixes_get_cursor_image_reply(c, xcb_xfixes_get_cursor_image_unchecked(c), nullptr)); if (cursor.isNull()) { return PlatformCursorImage(); } QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); // deep copy of image as the data is going to be freed return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot)); } void X11StandalonePlatform::doHideCursor() { xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::doShowCursor() { xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } void X11StandalonePlatform::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (m_windowSelector.isNull()) { m_windowSelector.reset(new WindowSelector); } m_windowSelector->start(callback, cursorName); } +void X11StandalonePlatform::setupActionForGlobalAccel(QAction *action) +{ + connect(action, &QAction::triggered, kwinApp(), [action] { + QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); + bool ok = false; + const quint32 t = timestamp.toULongLong(&ok); + if (ok) { + kwinApp()->setX11Time(t); + } + }); +} + } diff --git a/plugins/platforms/x11/standalone/x11_platform.h b/plugins/platforms/x11/standalone/x11_platform.h index 719c94a98..7fa55261d 100644 --- a/plugins/platforms/x11/standalone/x11_platform.h +++ b/plugins/platforms/x11/standalone/x11_platform.h @@ -1,82 +1,84 @@ /******************************************************************** 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_X11STANDALONE_PLATFORM_H #define KWIN_X11STANDALONE_PLATFORM_H #include "platform.h" #include #include namespace KWin { class XInputIntegration; class WindowSelector; class KWIN_EXPORT X11StandalonePlatform : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "x11.json") public: X11StandalonePlatform(QObject *parent = nullptr); virtual ~X11StandalonePlatform(); void init() override; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; Edge *createScreenEdge(ScreenEdges *parent) override; void createPlatformCursor(QObject *parent = nullptr) override; bool requiresCompositing() const override; bool compositingPossible() const override; QString compositingNotPossibleReason() const override; bool openGLCompositingIsBroken() const override; void createOpenGLSafePoint(OpenGLSafePoint safePoint) override; void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName = QByteArray()) override; PlatformCursorImage cursorImage() const override; + void setupActionForGlobalAccel(QAction *action) override; + protected: void doHideCursor() override; void doShowCursor() override; private: /** * Tests whether GLX is supported and returns @c true * in case KWin is compiled with OpenGL support and GLX * is available. * * If KWin is compiled with OpenGL ES or without OpenGL at * all, @c false is returned. * @returns @c true if GLX is available, @c false otherwise and if not build with OpenGL support. **/ static bool hasGlx(); XInputIntegration *m_xinputIntegration = nullptr; QThread *m_openGLFreezeProtectionThread = nullptr; QTimer *m_openGLFreezeProtection = nullptr; Display *m_x11Display; QScopedPointer m_windowSelector; }; } #endif