File indexing completed on 2025-03-23 13:48:00
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"