diff --git a/autotests/integration/modifier_only_shortcut_test.cpp b/autotests/integration/modifier_only_shortcut_test.cpp index 68feb70ac..1b304d86c 100644 --- a/autotests/integration/modifier_only_shortcut_test.cpp +++ b/autotests/integration/modifier_only_shortcut_test.cpp @@ -1,363 +1,379 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_modifier_only_shortcut-0"); static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut"); static const QString s_path = QStringLiteral("/Test"); class ModifierOnlyShortcutTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testTrigger_data(); void testTrigger(); void testCapsLock(); void testGlobalShortcutsDisabled_data(); void testGlobalShortcutsDisabled(); }; class Target : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut") public: Target(); virtual ~Target(); 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 ModifierOnlyShortcutTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void ModifierOnlyShortcutTest::init() { screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(640, 512)); } void ModifierOnlyShortcutTest::cleanup() { } void ModifierOnlyShortcutTest::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 ModifierOnlyShortcutTest::testTrigger() { // this test verifies that modifier only shortcut triggers correctly 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(), 1); // 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(), 1); } // try configured again kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // click another key while modifier is held kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // release other key after modifier release kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // press key before pressing modifier kwinApp()->platform()->keyboardKeyPressed(KEY_A, timestamp++); kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_A, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // mouse button pressed before clicking modifier kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QCOMPARE(input()->qtButtonStates(), Qt::LeftButton); kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QCOMPARE(input()->qtButtonStates(), Qt::NoButton); QCOMPARE(triggeredSpy.count(), 2); // mouse button press before mod press, release before mod release kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QCOMPARE(input()->qtButtonStates(), Qt::LeftButton); kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(input()->qtButtonStates(), Qt::NoButton); QCOMPARE(triggeredSpy.count(), 2); // mouse button click while mod is pressed kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QCOMPARE(input()->qtButtonStates(), Qt::LeftButton); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(input()->qtButtonStates(), Qt::NoButton); QCOMPARE(triggeredSpy.count(), 2); // scroll while mod is pressed kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->pointerAxisVertical(5.0, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // same for horizontal kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->pointerAxisHorizontal(5.0, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // now try to lock the screen while modifier key is pressed kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); QVERIFY(Test::lockScreen()); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 2); // now trigger while screen is locked, should also not work kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 2); QVERIFY(Test::unlockScreen()); } void ModifierOnlyShortcutTest::testCapsLock() { // this test verifies that Capslock does not trigger the shift shortcut // and that the shift modifier on capslock does not trigger either Target target; QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered); QVERIFY(triggeredSpy.isValid()); KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts"); group.writeEntry("Meta", QStringList()); group.writeEntry("Alt", QStringList()); group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}); group.writeEntry("Control", QStringList()); group.sync(); workspace()->slotReconfigure(); // first test that the normal shortcut triggers quint32 timestamp = 1; const int modifier = KEY_LEFTSHIFT; kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 1); // now capslock kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); QCOMPARE(triggeredSpy.count(), 1); // currently caps lock is on // shift is ignored kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); QCOMPARE(triggeredSpy.count(), 1); + // meta on the other hand should trigger + group.writeEntry("Meta", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}); + group.writeEntry("Alt", QStringList()); + group.writeEntry("Shift", QStringList{}); + group.writeEntry("Control", QStringList()); + group.sync(); + workspace()->slotReconfigure(); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); + QCOMPARE(input()->keyboard()->xkb()->modifiersRelevantForGlobalShortcuts(), Qt::MetaModifier); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QEXPECT_FAIL("", "BUG 375355", Continue); + QCOMPARE(triggeredSpy.count(), 2); + // release caps lock kwinApp()->platform()->keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier); - QCOMPARE(triggeredSpy.count(), 1); + QEXPECT_FAIL("", "BUG 375355", Continue); + QCOMPARE(triggeredSpy.count(), 2); } void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled_data() { QTest::addColumn("metaConfig"); QTest::addColumn("altConfig"); QTest::addColumn("controlConfig"); QTest::addColumn("shiftConfig"); QTest::addColumn("modifier"); 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; QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA; QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT; QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT; QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL; QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL; QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT; QTest::newRow("rightShift") << e << e << e << trigger <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(); // trigger once to verify the shortcut works quint32 timestamp = 1; QFETCH(int, modifier); QVERIFY(!workspace()->globalShortcutsDisabled()); kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 1); triggeredSpy.clear(); // now disable global shortcuts workspace()->disableGlobalShortcutsForClient(true); QVERIFY(workspace()->globalShortcutsDisabled()); // Should not get triggered kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 0); triggeredSpy.clear(); // enable again workspace()->disableGlobalShortcutsForClient(false); QVERIFY(!workspace()->globalShortcutsDisabled()); // should get triggered again kwinApp()->platform()->keyboardKeyPressed(modifier, timestamp++); kwinApp()->platform()->keyboardKeyReleased(modifier, timestamp++); QCOMPARE(triggeredSpy.count(), 1); } WAYLANDTEST_MAIN(ModifierOnlyShortcutTest) #include "modifier_only_shortcut_test.moc" diff --git a/keyboard_layout.cpp b/keyboard_layout.cpp index e778f3b91..78eac2c6b 100644 --- a/keyboard_layout.cpp +++ b/keyboard_layout.cpp @@ -1,115 +1,116 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016, 2017 Martin Gräßlin 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 "keyboard_layout.h" #include "keyboard_input.h" #include "input_event.h" #include "main.h" #include "platform.h" #include +#include #include #include #include #include namespace KWin { KeyboardLayout::KeyboardLayout(Xkb *xkb) : QObject() , m_xkb(xkb) { } KeyboardLayout::~KeyboardLayout() = default; void KeyboardLayout::init() { QAction *switchKeyboardAction = new QAction(this); switchKeyboardAction->setObjectName(QStringLiteral("Switch to Next Keyboard Layout")); switchKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher")); const QKeySequence sequence = QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_K); KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList({sequence})); KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList({sequence})); kwinApp()->platform()->setupActionForGlobalAccel(switchKeyboardAction); connect(switchKeyboardAction, &QAction::triggered, this, [this] { m_xkb->switchToNextLayout(); checkLayoutChange(); } ); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/Layouts"), QStringLiteral("org.kde.keyboard"), QStringLiteral("reloadConfig"), this, SLOT(reconfigure())); reconfigure(); } void KeyboardLayout::reconfigure() { m_xkb->reconfigure(); resetLayout(); } void KeyboardLayout::resetLayout() { m_layout = m_xkb->currentLayout(); } void KeyboardLayout::keyEvent(KeyEvent *event) { if (!event->isAutoRepeat()) { checkLayoutChange(); } } void KeyboardLayout::checkLayoutChange() { const auto layout = m_xkb->currentLayout(); if (m_layout == layout) { return; } m_layout = layout; notifyLayoutChange(); } void KeyboardLayout::notifyLayoutChange() { // notify OSD service about the new layout if (!kwinApp()->usesLibinput()) { return; } // only if kwin is in charge of keyboard input QDBusMessage msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.plasmashell"), QStringLiteral("/org/kde/osdService"), QStringLiteral("org.kde.osdService"), QStringLiteral("kbdLayoutChanged")); - msg << m_xkb->layoutName(); + msg << i18nd("xkeyboard-config", m_xkb->layoutName().toUtf8().constData()); QDBusConnection::sessionBus().asyncCall(msg); } }