File indexing completed on 2024-11-10 04:56:01
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"