File indexing completed on 2024-05-05 17:35:52

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2018 Martin Flöser <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "kwin_wayland_test.h"
0010 
0011 #include "core/outputbackend.h"
0012 #include "cursor.h"
0013 #include "input.h"
0014 #include "keyboard_input.h"
0015 #include "screenedge.h"
0016 #include "wayland_server.h"
0017 #include "workspace.h"
0018 
0019 #include <KConfigGroup>
0020 #include <KGlobalAccel>
0021 
0022 #include <QAction>
0023 #include <QDBusConnection>
0024 
0025 #include <linux/input.h>
0026 
0027 using namespace KWin;
0028 
0029 static const QString s_socketName = QStringLiteral("wayland_test_kwin_no_global_shortcuts-0");
0030 static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut");
0031 static const QString s_path = QStringLiteral("/Test");
0032 
0033 Q_DECLARE_METATYPE(KWin::ElectricBorder)
0034 
0035 /**
0036  * This test verifies the NoGlobalShortcuts initialization flag
0037  */
0038 class NoGlobalShortcutsTest : public QObject
0039 {
0040     Q_OBJECT
0041 private Q_SLOTS:
0042     void initTestCase();
0043     void init();
0044     void cleanup();
0045 
0046     void testTrigger_data();
0047     void testTrigger();
0048     void testKGlobalAccel();
0049     void testPointerShortcut();
0050     void testAxisShortcut_data();
0051     void testAxisShortcut();
0052     void testScreenEdge();
0053 };
0054 
0055 class Target : public QObject
0056 {
0057     Q_OBJECT
0058     Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut")
0059 
0060 public:
0061     Target();
0062     ~Target() override;
0063 
0064 public Q_SLOTS:
0065     Q_SCRIPTABLE void shortcut();
0066 
0067 Q_SIGNALS:
0068     void shortcutTriggered();
0069 };
0070 
0071 Target::Target()
0072     : QObject()
0073 {
0074     QDBusConnection::sessionBus().registerService(s_serviceName);
0075     QDBusConnection::sessionBus().registerObject(s_path, s_serviceName, this, QDBusConnection::ExportScriptableSlots);
0076 }
0077 
0078 Target::~Target()
0079 {
0080     QDBusConnection::sessionBus().unregisterObject(s_path);
0081     QDBusConnection::sessionBus().unregisterService(s_serviceName);
0082 }
0083 
0084 void Target::shortcut()
0085 {
0086     Q_EMIT shortcutTriggered();
0087 }
0088 
0089 void NoGlobalShortcutsTest::initTestCase()
0090 {
0091     qRegisterMetaType<KWin::ElectricBorder>("ElectricBorder");
0092     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0093     QVERIFY(waylandServer()->init(s_socketName, KWin::WaylandServer::InitializationFlag::NoGlobalShortcuts));
0094     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0095 
0096     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0097     qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1");
0098     qputenv("XKB_DEFAULT_RULES", "evdev");
0099 
0100     kwinApp()->start();
0101     QVERIFY(applicationStartedSpy.wait());
0102 }
0103 
0104 void NoGlobalShortcutsTest::init()
0105 {
0106     workspace()->setActiveOutput(QPoint(640, 512));
0107     KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512));
0108 }
0109 
0110 void NoGlobalShortcutsTest::cleanup()
0111 {
0112 }
0113 
0114 void NoGlobalShortcutsTest::testTrigger_data()
0115 {
0116     QTest::addColumn<QStringList>("metaConfig");
0117     QTest::addColumn<QStringList>("altConfig");
0118     QTest::addColumn<QStringList>("controlConfig");
0119     QTest::addColumn<QStringList>("shiftConfig");
0120     QTest::addColumn<int>("modifier");
0121     QTest::addColumn<QList<int>>("nonTriggeringMods");
0122 
0123     const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")};
0124     const QStringList e = QStringList();
0125 
0126     QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0127     QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0128     QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT << QList<int>{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0129     QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT << QList<int>{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0130     QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0131     QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0132     QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA};
0133     QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA};
0134 }
0135 
0136 void NoGlobalShortcutsTest::testTrigger()
0137 {
0138     // test based on ModifierOnlyShortcutTest::testTrigger
0139     Target target;
0140     QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
0141 
0142     KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts");
0143     QFETCH(QStringList, metaConfig);
0144     QFETCH(QStringList, altConfig);
0145     QFETCH(QStringList, shiftConfig);
0146     QFETCH(QStringList, controlConfig);
0147     group.writeEntry("Meta", metaConfig);
0148     group.writeEntry("Alt", altConfig);
0149     group.writeEntry("Shift", shiftConfig);
0150     group.writeEntry("Control", controlConfig);
0151     group.sync();
0152     workspace()->slotReconfigure();
0153 
0154     // configured shortcut should trigger
0155     quint32 timestamp = 1;
0156     QFETCH(int, modifier);
0157     Test::keyboardKeyPressed(modifier, timestamp++);
0158     Test::keyboardKeyReleased(modifier, timestamp++);
0159     QCOMPARE(triggeredSpy.count(), 0);
0160 
0161     // the other shortcuts should not trigger
0162     QFETCH(QList<int>, nonTriggeringMods);
0163     for (auto it = nonTriggeringMods.constBegin(), end = nonTriggeringMods.constEnd(); it != end; it++) {
0164         Test::keyboardKeyPressed(*it, timestamp++);
0165         Test::keyboardKeyReleased(*it, timestamp++);
0166         QCOMPARE(triggeredSpy.count(), 0);
0167     }
0168 }
0169 
0170 void NoGlobalShortcutsTest::testKGlobalAccel()
0171 {
0172     std::unique_ptr<QAction> action(new QAction(nullptr));
0173     action->setProperty("componentName", QStringLiteral("kwin"));
0174     action->setObjectName(QStringLiteral("globalshortcuts-test-meta-shift-w"));
0175     QSignalSpy triggeredSpy(action.get(), &QAction::triggered);
0176     KGlobalAccel::self()->setShortcut(action.get(), QList<QKeySequence>{Qt::META | Qt::SHIFT | Qt::Key_W}, KGlobalAccel::NoAutoloading);
0177 
0178     // press meta+shift+w
0179     quint32 timestamp = 0;
0180     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0181     QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier);
0182     Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++);
0183     QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier);
0184     Test::keyboardKeyPressed(KEY_W, timestamp++);
0185     Test::keyboardKeyReleased(KEY_W, timestamp++);
0186 
0187     // release meta+shift
0188     Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++);
0189     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0190 
0191     QVERIFY(!triggeredSpy.wait(100));
0192     QCOMPARE(triggeredSpy.count(), 0);
0193 }
0194 
0195 void NoGlobalShortcutsTest::testPointerShortcut()
0196 {
0197     // based on LockScreenTest::testPointerShortcut
0198     std::unique_ptr<QAction> action(new QAction(nullptr));
0199     QSignalSpy actionSpy(action.get(), &QAction::triggered);
0200     input()->registerPointerShortcut(Qt::MetaModifier, Qt::LeftButton, action.get());
0201 
0202     // try to trigger the shortcut
0203     quint32 timestamp = 1;
0204     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0205     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0206     QCoreApplication::instance()->processEvents();
0207     QCOMPARE(actionSpy.count(), 0);
0208     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0209     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0210     QCoreApplication::instance()->processEvents();
0211     QCOMPARE(actionSpy.count(), 0);
0212 }
0213 
0214 void NoGlobalShortcutsTest::testAxisShortcut_data()
0215 {
0216     QTest::addColumn<Qt::Orientation>("direction");
0217     QTest::addColumn<int>("sign");
0218 
0219     QTest::newRow("up") << Qt::Vertical << 1;
0220     QTest::newRow("down") << Qt::Vertical << -1;
0221     QTest::newRow("left") << Qt::Horizontal << 1;
0222     QTest::newRow("right") << Qt::Horizontal << -1;
0223 }
0224 
0225 void NoGlobalShortcutsTest::testAxisShortcut()
0226 {
0227     // based on LockScreenTest::testAxisShortcut
0228     std::unique_ptr<QAction> action(new QAction(nullptr));
0229     QSignalSpy actionSpy(action.get(), &QAction::triggered);
0230     QFETCH(Qt::Orientation, direction);
0231     QFETCH(int, sign);
0232     PointerAxisDirection axisDirection = PointerAxisUp;
0233     if (direction == Qt::Vertical) {
0234         axisDirection = sign > 0 ? PointerAxisUp : PointerAxisDown;
0235     } else {
0236         axisDirection = sign > 0 ? PointerAxisLeft : PointerAxisRight;
0237     }
0238     input()->registerAxisShortcut(Qt::MetaModifier, axisDirection, action.get());
0239 
0240     // try to trigger the shortcut
0241     quint32 timestamp = 1;
0242     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0243     if (direction == Qt::Vertical) {
0244         Test::pointerAxisVertical(sign * 5.0, timestamp++);
0245     } else {
0246         Test::pointerAxisHorizontal(sign * 5.0, timestamp++);
0247     }
0248     QCoreApplication::instance()->processEvents();
0249     QCOMPARE(actionSpy.count(), 0);
0250     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0251     QCoreApplication::instance()->processEvents();
0252     QCOMPARE(actionSpy.count(), 0);
0253 }
0254 
0255 void NoGlobalShortcutsTest::testScreenEdge()
0256 {
0257     // based on LockScreenTest::testScreenEdge
0258     QSignalSpy screenEdgeSpy(workspace()->screenEdges(), &ScreenEdges::approaching);
0259     QCOMPARE(screenEdgeSpy.count(), 0);
0260 
0261     quint32 timestamp = 1;
0262     Test::pointerMotion({5, 5}, timestamp++);
0263     QCOMPARE(screenEdgeSpy.count(), 0);
0264 }
0265 
0266 WAYLANDTEST_MAIN(NoGlobalShortcutsTest)
0267 #include "no_global_shortcuts_test.moc"