File indexing completed on 2024-05-19 09:23:09

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