Warning, file /plasma/kwin/autotests/integration/modifier_only_shortcut_test.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include <config-kwin.h>
0010 
0011 #include "kwin_wayland_test.h"
0012 
0013 #include "core/outputbackend.h"
0014 #include "cursor.h"
0015 #include "input.h"
0016 #include "keyboard_input.h"
0017 #include "wayland_server.h"
0018 #include "workspace.h"
0019 #include "xkb.h"
0020 
0021 #include <KConfigGroup>
0022 
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_modifier_only_shortcut-0");
0030 static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut");
0031 static const QString s_path = QStringLiteral("/Test");
0032 
0033 class ModifierOnlyShortcutTest : public QObject
0034 {
0035     Q_OBJECT
0036 private Q_SLOTS:
0037     void initTestCase();
0038     void init();
0039     void cleanup();
0040 
0041     void testTrigger_data();
0042     void testTrigger();
0043     void testCapsLock();
0044     void testGlobalShortcutsDisabled_data();
0045     void testGlobalShortcutsDisabled();
0046 };
0047 
0048 class Target : public QObject
0049 {
0050     Q_OBJECT
0051     Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut")
0052 
0053 public:
0054     Target();
0055     ~Target() override;
0056 
0057 public Q_SLOTS:
0058     Q_SCRIPTABLE void shortcut();
0059 
0060 Q_SIGNALS:
0061     void shortcutTriggered();
0062 };
0063 
0064 Target::Target()
0065     : QObject()
0066 {
0067     QDBusConnection::sessionBus().registerService(s_serviceName);
0068     QDBusConnection::sessionBus().registerObject(s_path, s_serviceName, this, QDBusConnection::ExportScriptableSlots);
0069 }
0070 
0071 Target::~Target()
0072 {
0073     QDBusConnection::sessionBus().unregisterObject(s_path);
0074     QDBusConnection::sessionBus().unregisterService(s_serviceName);
0075 }
0076 
0077 void Target::shortcut()
0078 {
0079     Q_EMIT shortcutTriggered();
0080 }
0081 
0082 void ModifierOnlyShortcutTest::initTestCase()
0083 {
0084     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0085     QVERIFY(waylandServer()->init(s_socketName));
0086     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0087 
0088     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0089     qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1");
0090     qputenv("XKB_DEFAULT_RULES", "evdev");
0091 
0092     kwinApp()->start();
0093     QVERIFY(applicationStartedSpy.wait());
0094 }
0095 
0096 void ModifierOnlyShortcutTest::init()
0097 {
0098     workspace()->setActiveOutput(QPoint(640, 512));
0099     KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512));
0100 }
0101 
0102 void ModifierOnlyShortcutTest::cleanup()
0103 {
0104 }
0105 
0106 void ModifierOnlyShortcutTest::testTrigger_data()
0107 {
0108     QTest::addColumn<QStringList>("metaConfig");
0109     QTest::addColumn<QStringList>("altConfig");
0110     QTest::addColumn<QStringList>("controlConfig");
0111     QTest::addColumn<QStringList>("shiftConfig");
0112     QTest::addColumn<int>("modifier");
0113     QTest::addColumn<QList<int>>("nonTriggeringMods");
0114 
0115     const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")};
0116     const QStringList e = QStringList();
0117 
0118     QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0119     QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0120     QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT << QList<int>{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0121     QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT << QList<int>{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0122     QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0123     QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
0124     QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA};
0125     QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA};
0126 }
0127 
0128 void ModifierOnlyShortcutTest::testTrigger()
0129 {
0130     // this test verifies that modifier only shortcut triggers correctly
0131     Target target;
0132     QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
0133 
0134     KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts");
0135     QFETCH(QStringList, metaConfig);
0136     QFETCH(QStringList, altConfig);
0137     QFETCH(QStringList, shiftConfig);
0138     QFETCH(QStringList, controlConfig);
0139     group.writeEntry("Meta", metaConfig);
0140     group.writeEntry("Alt", altConfig);
0141     group.writeEntry("Shift", shiftConfig);
0142     group.writeEntry("Control", controlConfig);
0143     group.sync();
0144     workspace()->slotReconfigure();
0145 
0146     // configured shortcut should trigger
0147     quint32 timestamp = 1;
0148     QFETCH(int, modifier);
0149     Test::keyboardKeyPressed(modifier, timestamp++);
0150     Test::keyboardKeyReleased(modifier, timestamp++);
0151     QCOMPARE(triggeredSpy.count(), 1);
0152 
0153     // the other shortcuts should not trigger
0154     QFETCH(QList<int>, nonTriggeringMods);
0155     for (auto it = nonTriggeringMods.constBegin(), end = nonTriggeringMods.constEnd(); it != end; it++) {
0156         Test::keyboardKeyPressed(*it, timestamp++);
0157         Test::keyboardKeyReleased(*it, timestamp++);
0158         QCOMPARE(triggeredSpy.count(), 1);
0159     }
0160 
0161     // try configured again
0162     Test::keyboardKeyPressed(modifier, timestamp++);
0163     Test::keyboardKeyReleased(modifier, timestamp++);
0164     QCOMPARE(triggeredSpy.count(), 2);
0165 
0166     // click another key while modifier is held
0167     Test::keyboardKeyPressed(modifier, timestamp++);
0168     Test::keyboardKeyPressed(KEY_A, timestamp++);
0169     Test::keyboardKeyReleased(KEY_A, timestamp++);
0170     Test::keyboardKeyReleased(modifier, timestamp++);
0171     QCOMPARE(triggeredSpy.count(), 2);
0172 
0173     // release other key after modifier release
0174     Test::keyboardKeyPressed(modifier, timestamp++);
0175     Test::keyboardKeyPressed(KEY_A, timestamp++);
0176     Test::keyboardKeyReleased(modifier, timestamp++);
0177     Test::keyboardKeyReleased(KEY_A, timestamp++);
0178     QCOMPARE(triggeredSpy.count(), 2);
0179 
0180     // press key before pressing modifier
0181     Test::keyboardKeyPressed(KEY_A, timestamp++);
0182     Test::keyboardKeyPressed(modifier, timestamp++);
0183     Test::keyboardKeyReleased(modifier, timestamp++);
0184     Test::keyboardKeyReleased(KEY_A, timestamp++);
0185     QCOMPARE(triggeredSpy.count(), 2);
0186 
0187     // mouse button pressed before clicking modifier
0188     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0189     QCOMPARE(input()->qtButtonStates(), Qt::LeftButton);
0190     Test::keyboardKeyPressed(modifier, timestamp++);
0191     Test::keyboardKeyReleased(modifier, timestamp++);
0192     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0193     QCOMPARE(input()->qtButtonStates(), Qt::NoButton);
0194     QCOMPARE(triggeredSpy.count(), 2);
0195 
0196     // mouse button press before mod press, release before mod release
0197     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0198     QCOMPARE(input()->qtButtonStates(), Qt::LeftButton);
0199     Test::keyboardKeyPressed(modifier, timestamp++);
0200     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0201     Test::keyboardKeyReleased(modifier, timestamp++);
0202     QCOMPARE(input()->qtButtonStates(), Qt::NoButton);
0203     QCOMPARE(triggeredSpy.count(), 2);
0204 
0205     // mouse button click while mod is pressed
0206     Test::keyboardKeyPressed(modifier, timestamp++);
0207     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0208     QCOMPARE(input()->qtButtonStates(), Qt::LeftButton);
0209     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0210     Test::keyboardKeyReleased(modifier, timestamp++);
0211     QCOMPARE(input()->qtButtonStates(), Qt::NoButton);
0212     QCOMPARE(triggeredSpy.count(), 2);
0213 
0214     // scroll while mod is pressed
0215     Test::keyboardKeyPressed(modifier, timestamp++);
0216     Test::pointerAxisVertical(5.0, timestamp++);
0217     Test::keyboardKeyReleased(modifier, timestamp++);
0218     QCOMPARE(triggeredSpy.count(), 2);
0219 
0220     // same for horizontal
0221     Test::keyboardKeyPressed(modifier, timestamp++);
0222     Test::pointerAxisHorizontal(5.0, timestamp++);
0223     Test::keyboardKeyReleased(modifier, timestamp++);
0224     QCOMPARE(triggeredSpy.count(), 2);
0225 
0226 #if KWIN_BUILD_SCREENLOCKER
0227     // now try to lock the screen while modifier key is pressed
0228     Test::keyboardKeyPressed(modifier, timestamp++);
0229     QVERIFY(Test::lockScreen());
0230     Test::keyboardKeyReleased(modifier, timestamp++);
0231     QCOMPARE(triggeredSpy.count(), 2);
0232 
0233     // now trigger while screen is locked, should also not work
0234     Test::keyboardKeyPressed(modifier, timestamp++);
0235     Test::keyboardKeyReleased(modifier, timestamp++);
0236     QCOMPARE(triggeredSpy.count(), 2);
0237 
0238     QVERIFY(Test::unlockScreen());
0239 #endif
0240 }
0241 
0242 void ModifierOnlyShortcutTest::testCapsLock()
0243 {
0244     // this test verifies that Capslock does not trigger the shift shortcut
0245     // but other shortcuts still trigger even when Capslock is on
0246     Target target;
0247     QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
0248 
0249     KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts");
0250     group.writeEntry("Meta", QStringList());
0251     group.writeEntry("Alt", QStringList());
0252     group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")});
0253     group.writeEntry("Control", QStringList());
0254     group.sync();
0255     workspace()->slotReconfigure();
0256 
0257     // first test that the normal shortcut triggers
0258     quint32 timestamp = 1;
0259     const int modifier = KEY_LEFTSHIFT;
0260     Test::keyboardKeyPressed(modifier, timestamp++);
0261     Test::keyboardKeyReleased(modifier, timestamp++);
0262     QCOMPARE(triggeredSpy.count(), 1);
0263 
0264     // now capslock
0265     Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
0266     Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
0267     QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier);
0268     QCOMPARE(triggeredSpy.count(), 1);
0269 
0270     // currently caps lock is on
0271     // shift still triggers
0272     Test::keyboardKeyPressed(modifier, timestamp++);
0273     Test::keyboardKeyReleased(modifier, timestamp++);
0274     QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier);
0275     QCOMPARE(triggeredSpy.count(), 2);
0276 
0277     // meta should also trigger
0278     group.writeEntry("Meta", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")});
0279     group.writeEntry("Alt", QStringList());
0280     group.writeEntry("Shift", QStringList{});
0281     group.writeEntry("Control", QStringList());
0282     group.sync();
0283     workspace()->slotReconfigure();
0284     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0285     QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier);
0286     QCOMPARE(input()->keyboard()->xkb()->modifiersRelevantForGlobalShortcuts(), Qt::MetaModifier);
0287     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0288     QCOMPARE(triggeredSpy.count(), 3);
0289 
0290     // set back to shift to ensure we don't trigger with capslock
0291     group.writeEntry("Meta", QStringList());
0292     group.writeEntry("Alt", QStringList());
0293     group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")});
0294     group.writeEntry("Control", QStringList());
0295     group.sync();
0296     workspace()->slotReconfigure();
0297 
0298     // release caps lock
0299     Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
0300     Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
0301     QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier);
0302     QCOMPARE(triggeredSpy.count(), 3);
0303 }
0304 
0305 void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled_data()
0306 {
0307     QTest::addColumn<QStringList>("metaConfig");
0308     QTest::addColumn<QStringList>("altConfig");
0309     QTest::addColumn<QStringList>("controlConfig");
0310     QTest::addColumn<QStringList>("shiftConfig");
0311     QTest::addColumn<int>("modifier");
0312 
0313     const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")};
0314     const QStringList e = QStringList();
0315 
0316     QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA;
0317     QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA;
0318     QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT;
0319     QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT;
0320     QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL;
0321     QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL;
0322     QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT;
0323     QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT;
0324 }
0325 
0326 void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled()
0327 {
0328     // this test verifies that when global shortcuts are disabled inside KWin (e.g. through a window rule)
0329     // the modifier only shortcuts do not trigger.
0330     // see BUG: 370146
0331     Target target;
0332     QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
0333 
0334     KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts");
0335     QFETCH(QStringList, metaConfig);
0336     QFETCH(QStringList, altConfig);
0337     QFETCH(QStringList, shiftConfig);
0338     QFETCH(QStringList, controlConfig);
0339     group.writeEntry("Meta", metaConfig);
0340     group.writeEntry("Alt", altConfig);
0341     group.writeEntry("Shift", shiftConfig);
0342     group.writeEntry("Control", controlConfig);
0343     group.sync();
0344     workspace()->slotReconfigure();
0345 
0346     // trigger once to verify the shortcut works
0347     quint32 timestamp = 1;
0348     QFETCH(int, modifier);
0349     QVERIFY(!workspace()->globalShortcutsDisabled());
0350     Test::keyboardKeyPressed(modifier, timestamp++);
0351     Test::keyboardKeyReleased(modifier, timestamp++);
0352     QCOMPARE(triggeredSpy.count(), 1);
0353     triggeredSpy.clear();
0354 
0355     // now disable global shortcuts
0356     workspace()->disableGlobalShortcutsForClient(true);
0357     QVERIFY(workspace()->globalShortcutsDisabled());
0358     // Should not get triggered
0359     Test::keyboardKeyPressed(modifier, timestamp++);
0360     Test::keyboardKeyReleased(modifier, timestamp++);
0361     QCOMPARE(triggeredSpy.count(), 0);
0362     triggeredSpy.clear();
0363 
0364     // enable again
0365     workspace()->disableGlobalShortcutsForClient(false);
0366     QVERIFY(!workspace()->globalShortcutsDisabled());
0367     // should get triggered again
0368     Test::keyboardKeyPressed(modifier, timestamp++);
0369     Test::keyboardKeyReleased(modifier, timestamp++);
0370     QCOMPARE(triggeredSpy.count(), 1);
0371 }
0372 
0373 WAYLANDTEST_MAIN(ModifierOnlyShortcutTest)
0374 #include "modifier_only_shortcut_test.moc"