diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -58,6 +58,7 @@ integrationTest(WAYLAND_ONLY NAME testColorCorrectNightColor SRCS colorcorrect_nightcolor_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashCursorPhysicalSizeEmpty SRCS dont_crash_cursor_physical_size_empty.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashReinitializeCompositor SRCS dont_crash_reinitialize_compositor.cpp) +integrationTest(WAYLAND_ONLY NAME testNoGlobalShortcuts SRCS no_global_shortcuts_test.cpp) if (XCB_ICCCM_FOUND) integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) diff --git a/autotests/integration/no_global_shortcuts_test.cpp b/autotests/integration/no_global_shortcuts_test.cpp new file mode 100644 --- /dev/null +++ b/autotests/integration/no_global_shortcuts_test.cpp @@ -0,0 +1,285 @@ +/******************************************************************** +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(); + 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 NoGlobalShortcutsTest::initTestCase() +{ + qRegisterMetaType("ElectricBorder"); + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit(), KWin::WaylandServer::InitalizationFlag::NoGlobalShortcuts)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); + qputenv("XKB_DEFAULT_RULES", "evdev"); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + waylandServer()->initWorkspace(); +} + +void NoGlobalShortcutsTest::init() +{ + screens()->setCurrent(0); + KWin::Cursor::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" diff --git a/autotests/integration/start_test.cpp b/autotests/integration/start_test.cpp --- a/autotests/integration/start_test.cpp +++ b/autotests/integration/start_test.cpp @@ -52,6 +52,7 @@ QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + QVERIFY(waylandServer()->hasGlobalShortcutSupport()); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); } diff --git a/input.cpp b/input.cpp --- a/input.cpp +++ b/input.cpp @@ -1751,25 +1751,32 @@ void InputRedirection::setupInputFilters() { - if (LogindIntegration::self()->hasSessionControl()) { + const bool hasGlobalShortcutSupport = !waylandServer() || waylandServer()->hasGlobalShortcutSupport(); + if (LogindIntegration::self()->hasSessionControl() && hasGlobalShortcutSupport) { installInputEventFilter(new VirtualTerminalFilter); } if (waylandServer()) { installInputEventSpy(new TouchHideCursorSpy); - installInputEventFilter(new TerminateServerFilter); + if (hasGlobalShortcutSupport) { + installInputEventFilter(new TerminateServerFilter); + } installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); installInputEventFilter(new PopupInputFilter); m_windowSelector = new WindowSelectorFilter; installInputEventFilter(m_windowSelector); } - installInputEventFilter(new ScreenEdgeInputFilter); + if (hasGlobalShortcutSupport) { + installInputEventFilter(new ScreenEdgeInputFilter); + } installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif - installInputEventFilter(new GlobalShortcutFilter); + if (hasGlobalShortcutSupport) { + installInputEventFilter(new GlobalShortcutFilter); + } installInputEventFilter(new DecorationEventFilter); installInputEventFilter(new InternalWindowEventFilter); if (waylandServer()) { diff --git a/keyboard_input.cpp b/keyboard_input.cpp --- a/keyboard_input.cpp +++ b/keyboard_input.cpp @@ -122,7 +122,9 @@ m_keyboardLayout->init(); m_input->installInputEventSpy(m_keyboardLayout); - m_input->installInputEventSpy(new ModifierOnlyShortcuts); + if (waylandServer()->hasGlobalShortcutSupport()) { + m_input->installInputEventSpy(new ModifierOnlyShortcuts); + } KeyboardRepeat *keyRepeatSpy = new KeyboardRepeat(m_xkb.data()); connect(keyRepeatSpy, &KeyboardRepeat::keyRepeat, this, diff --git a/main_wayland.cpp b/main_wayland.cpp --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -676,6 +676,10 @@ i18n("Starts the session without lock screen support.")); parser.addOption(noScreenLockerOption); + QCommandLineOption noGlobalShortcutsOption(QStringLiteral("no-global-shortcuts"), + i18n("Starts the session without global shortcuts support.")); + parser.addOption(noScreenLockerOption); + QCommandLineOption exitWithSessionOption(QStringLiteral("exit-with-session"), i18n("Exit after the session application, which is started by KWin, closed."), QStringLiteral("/path/to/session")); @@ -792,6 +796,9 @@ } else if (parser.isSet(noScreenLockerOption)) { flags = KWin::WaylandServer::InitalizationFlag::NoLockScreenIntegration; } + if (parser.isSet(noGlobalShortcutsOption)) { + flags |= KWin::WaylandServer::InitalizationFlag::NoGlobalShortcuts; + } if (!server->init(parser.value(waylandSocketOption).toUtf8(), flags)) { std::cerr << "FATAL ERROR: could not create Wayland server" << std::endl; return 1; diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -79,7 +79,8 @@ enum class InitalizationFlag { NoOptions = 0x0, LockScreen = 0x1, - NoLockScreenIntegration = 0x2 + NoLockScreenIntegration = 0x2, + NoGlobalShortcuts = 0x4 }; Q_DECLARE_FLAGS(InitalizationFlags, InitalizationFlag) @@ -153,6 +154,11 @@ **/ bool hasScreenLockerIntegration() const; + /** + * @returns whether any kind of global shortcuts are supported. + **/ + bool hasGlobalShortcutSupport() const; + void createInternalConnection(); void initWorkspace(); diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -795,6 +795,11 @@ return !m_initFlags.testFlag(InitalizationFlag::NoLockScreenIntegration); } +bool WaylandServer::hasGlobalShortcutSupport() const +{ + return !m_initFlags.testFlag(InitalizationFlag::NoGlobalShortcuts); +} + void WaylandServer::simulateUserActivity() { if (m_idle) {