/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 Martin Flöser This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "cursor.h" #include "input.h" #include "keyboard_input.h" #include "platform.h" #include "screenedge.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_no_global_shortcuts-0"); static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut"); static const QString s_path = QStringLiteral("/Test"); Q_DECLARE_METATYPE(KWin::ElectricBorder) /** * This test verifies the NoGlobalShortcuts initialization flag */ class NoGlobalShortcutsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testTrigger_data(); void testTrigger(); void testKGlobalAccel(); void testPointerShortcut(); void testAxisShortcut_data(); void testAxisShortcut(); void testScreenEdge(); }; class Target : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut") public: Target(); ~Target() override; public Q_SLOTS: Q_SCRIPTABLE void shortcut(); Q_SIGNALS: void shortcutTriggered(); }; Target::Target() : QObject() { QDBusConnection::sessionBus().registerService(s_serviceName); QDBusConnection::sessionBus().registerObject(s_path, s_serviceName, this, QDBusConnection::ExportScriptableSlots); } Target::~Target() { QDBusConnection::sessionBus().unregisterObject(s_path); QDBusConnection::sessionBus().unregisterService(s_serviceName); } void Target::shortcut() { emit shortcutTriggered(); } void NoGlobalShortcutsTest::initTestCase() { qRegisterMetaType("ElectricBorder"); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(applicationStartedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit(), KWin::WaylandServer::InitializationFlag::NoGlobalShortcuts)); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); qputenv("XKB_DEFAULT_RULES", "evdev"); kwinApp()->start(); QVERIFY(applicationStartedSpy.wait()); waylandServer()->initWorkspace(); } void NoGlobalShortcutsTest::init() { screens()->setCurrent(0); KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); } void NoGlobalShortcutsTest::cleanup() { } void NoGlobalShortcutsTest::testTrigger_data() { QTest::addColumn("metaConfig"); QTest::addColumn("altConfig"); QTest::addColumn("controlConfig"); QTest::addColumn("shiftConfig"); QTest::addColumn("modifier"); QTest::addColumn>("nonTriggeringMods"); const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}; const QStringList e = QStringList(); QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT << QList{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT << QList{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA}; QTest::newRow("rightShift") << e << e << e << trigger <{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA}; } void NoGlobalShortcutsTest::testTrigger() { // test based on ModifierOnlyShortcutTest::testTrigger Target target; QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered); QVERIFY(triggeredSpy.isValid()); KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts"); QFETCH(QStringList, metaConfig); QFETCH(QStringList, altConfig); QFETCH(QStringList, shiftConfig); QFETCH(QStringList, controlConfig); group.writeEntry("Meta", metaConfig); group.writeEntry("Alt", altConfig); group.writeEntry("Shift", shiftConfig); group.writeEntry("Control", controlConfig); group.sync(); workspace()->slotReconfigure(); // configured shortcut should trigger quint32 timestamp = 1; QFETCH(int, modifier); kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 0); // the other shortcuts should not trigger QFETCH(QList, nonTriggeringMods); for (auto it = nonTriggeringMods.constBegin(), end = nonTriggeringMods.constEnd(); it != end; it++) { kwinApp()->platform()->keyboardKeyPressed(*it, timestamp++); kwinApp()->platform()->keyboardKeyReleased(*it, timestamp++); QCOMPARE(triggeredSpy.count(), 0); } } void NoGlobalShortcutsTest::testKGlobalAccel() { QScopedPointer action(new QAction(nullptr)); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName(QStringLiteral("globalshortcuts-test-meta-shift-w")); QSignalSpy triggeredSpy(action.data(), &QAction::triggered); QVERIFY(triggeredSpy.isValid()); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::META + Qt::SHIFT + Qt::Key_W}, KGlobalAccel::NoAutoloading); input()->registerShortcut(Qt::META + Qt::SHIFT + Qt::Key_W, action.data()); // press meta+shift+w quint32 timestamp = 0; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); kwinApp()->platform()->keyboardKeyPressed(KEY_W, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_W, timestamp++); // release meta+shift kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); QVERIFY(!triggeredSpy.wait()); QCOMPARE(triggeredSpy.count(), 0); } void NoGlobalShortcutsTest::testPointerShortcut() { // based on LockScreenTest::testPointerShortcut QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); input()->registerPointerShortcut(Qt::MetaModifier, Qt::LeftButton, action.data()); // try to trigger the shortcut quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 0); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 0); } void NoGlobalShortcutsTest::testAxisShortcut_data() { QTest::addColumn("direction"); QTest::addColumn("sign"); QTest::newRow("up") << Qt::Vertical << 1; QTest::newRow("down") << Qt::Vertical << -1; QTest::newRow("left") << Qt::Horizontal << 1; QTest::newRow("right") << Qt::Horizontal << -1; } void NoGlobalShortcutsTest::testAxisShortcut() { // based on LockScreenTest::testAxisShortcut QScopedPointer action(new QAction(nullptr)); QSignalSpy actionSpy(action.data(), &QAction::triggered); QVERIFY(actionSpy.isValid()); QFETCH(Qt::Orientation, direction); QFETCH(int, sign); PointerAxisDirection axisDirection = PointerAxisUp; if (direction == Qt::Vertical) { axisDirection = sign > 0 ? PointerAxisUp : PointerAxisDown; } else { axisDirection = sign > 0 ? PointerAxisLeft : PointerAxisRight; } input()->registerAxisShortcut(Qt::MetaModifier, axisDirection, action.data()); // try to trigger the shortcut quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); if (direction == Qt::Vertical) kwinApp()->platform()->pointerAxisVertical(sign * 5.0, timestamp++); else kwinApp()->platform()->pointerAxisHorizontal(sign * 5.0, timestamp++); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 0); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); QCoreApplication::instance()->processEvents(); QCOMPARE(actionSpy.count(), 0); } void NoGlobalShortcutsTest::testScreenEdge() { // based on LockScreenTest::testScreenEdge QSignalSpy screenEdgeSpy(ScreenEdges::self(), &ScreenEdges::approaching); QVERIFY(screenEdgeSpy.isValid()); QCOMPARE(screenEdgeSpy.count(), 0); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion({5, 5}, timestamp++); QCOMPARE(screenEdgeSpy.count(), 0); } WAYLANDTEST_MAIN(NoGlobalShortcutsTest) #include "no_global_shortcuts_test.moc"