diff --git a/autotests/integration/globalshortcuts_test.cpp b/autotests/integration/globalshortcuts_test.cpp
index 41b8d27251..3a6478772f 100644
--- a/autotests/integration/globalshortcuts_test.cpp
+++ b/autotests/integration/globalshortcuts_test.cpp
@@ -23,9 +23,12 @@ along with this program. If not, see .
#include "platform.h"
#include "screens.h"
#include "shell_client.h"
+#include "useractions.h"
#include "wayland_server.h"
#include "workspace.h"
+#include
+#include
#include
#include
@@ -46,6 +49,7 @@ private Q_SLOTS:
void testConsumedShift();
void testRepeatedTrigger();
+ void testUserActionsMenu();
};
void GlobalShortcutsTest::initTestCase()
@@ -67,12 +71,14 @@ void GlobalShortcutsTest::initTestCase()
void GlobalShortcutsTest::init()
{
+ QVERIFY(Test::setupWaylandConnection(s_socketName));
screens()->setCurrent(0);
KWin::Cursor::setPos(QPoint(640, 512));
}
void GlobalShortcutsTest::cleanup()
{
+ Test::destroyWaylandConnection();
}
void GlobalShortcutsTest::testConsumedShift()
@@ -127,7 +133,6 @@ void GlobalShortcutsTest::testRepeatedTrigger()
QVERIFY(triggeredSpy.wait());
// now release the key
kwinApp()->platform()->keyboardKeyReleased(KEY_5, timestamp++);
- QEXPECT_FAIL("", "BUG 369091", Continue);
QVERIFY(!triggeredSpy.wait(500));
kwinApp()->platform()->keyboardKeyReleased(KEY_WAKEUP, timestamp++);
@@ -137,5 +142,31 @@ void GlobalShortcutsTest::testRepeatedTrigger()
kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++);
}
+void GlobalShortcutsTest::testUserActionsMenu()
+{
+ // this test tries to trigger the user actions menu with Alt+F3
+ // the problem here is that pressing F3 consumes modifiers as it's part of the
+ // Ctrl+alt+F3 keysym for vt switching. xkbcommon considers all modifiers as consumed
+ // which a transformation to any keysym would cause
+ // for more information see:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=92818
+ // https://github.com/xkbcommon/libxkbcommon/issues/17
+
+ // first create a window
+ QScopedPointer surface(Test::createSurface());
+ QScopedPointer shellSurface(Test::createShellSurface(surface.data()));
+ auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
+ QVERIFY(c);
+ QVERIFY(c->isActive());
+
+ quint32 timestamp = 0;
+ QVERIFY(!workspace()->userActionsMenu()->isShown());
+ kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++);
+ kwinApp()->platform()->keyboardKeyPressed(KEY_F3, timestamp++);
+ kwinApp()->platform()->keyboardKeyReleased(KEY_F3, timestamp++);
+ QTRY_VERIFY(workspace()->userActionsMenu()->isShown());
+ kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++);
+}
+
WAYLANDTEST_MAIN(GlobalShortcutsTest)
#include "globalshortcuts_test.moc"
diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp
index 95e7c330de..ccdd078d6c 100644
--- a/autotests/integration/kwin_wayland_test.cpp
+++ b/autotests/integration/kwin_wayland_test.cpp
@@ -46,6 +46,7 @@ static void readDisplay(int pipe);
WaylandTestApplication::WaylandTestApplication(int &argc, char **argv)
: Application(OperationModeXwayland, argc, argv)
{
+ QStandardPaths::setTestModeEnabled(true);
#ifdef KWIN_BUILD_ACTIVITIES
setUseKActivities(false);
#endif
diff --git a/autotests/integration/modifier_only_shortcut_test.cpp b/autotests/integration/modifier_only_shortcut_test.cpp
index f38318551c..68feb70ac1 100644
--- a/autotests/integration/modifier_only_shortcut_test.cpp
+++ b/autotests/integration/modifier_only_shortcut_test.cpp
@@ -49,6 +49,8 @@ private Q_SLOTS:
void testTrigger_data();
void testTrigger();
void testCapsLock();
+ void testGlobalShortcutsDisabled_data();
+ void testGlobalShortcutsDisabled();
};
class Target : public QObject
@@ -288,5 +290,74 @@ void ModifierOnlyShortcutTest::testCapsLock()
QCOMPARE(triggeredSpy.count(), 1);
}
+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_input.cpp b/keyboard_input.cpp
index a2138a3f74..6c3a457042 100644
--- a/keyboard_input.cpp
+++ b/keyboard_input.cpp
@@ -310,7 +310,8 @@ void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state)
} else {
m_modOnlyShortcut.pressCount--;
if (m_modOnlyShortcut.pressCount == 0 &&
- m_modifiers == Qt::NoModifier) {
+ m_modifiers == Qt::NoModifier &&
+ !workspace()->globalShortcutsDisabled()) {
if (m_modOnlyShortcut.modifier != Qt::NoModifier) {
const auto list = options->modifierOnlyDBusShortcut(m_modOnlyShortcut.modifier);
if (list.size() >= 4) {
@@ -403,6 +404,24 @@ Qt::KeyboardModifiers Xkb::modifiersRelevantForGlobalShortcuts() const
if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
mods |= Qt::MetaModifier;
}
+
+ // workaround xkbcommon limitation concerning consumed modifiers
+ // if a key could be turned into a keysym with a modifier xkbcommon
+ // considers the modifier as consumed even if not pressed
+ // e.g. alt+F3 considers alt as consumed as there is a keysym generated
+ // with ctrl+alt+F3 (vt switching)
+ // For more information see:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=92818
+ // https://github.com/xkbcommon/libxkbcommon/issues/17
+
+ // the workaround is to not consider the modifiers as consumed
+ // if they are not a currently
+ // this might have other side effects, though. The only proper way to
+ // handle this is through new API in xkbcommon which doesn't exist yet
+ if (m_consumedModifiers & ~m_modifiers) {
+ return mods;
+ }
+
return mods & ~m_consumedModifiers;
}
@@ -463,17 +482,25 @@ KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent)
{
}
-KeyboardInputRedirection::~KeyboardInputRedirection()
-{
- qDeleteAll(m_repeatTimers);
- m_repeatTimers.clear();
-}
+KeyboardInputRedirection::~KeyboardInputRedirection() = default;
void KeyboardInputRedirection::init()
{
Q_ASSERT(!m_inited);
m_inited = true;
+ // setup key repeat
+ m_keyRepeat.timer = new QTimer(this);
+ connect(m_keyRepeat.timer, &QTimer::timeout, this,
+ [this] {
+ if (waylandServer()->seat()->keyRepeatRate() != 0) {
+ m_keyRepeat.timer->setInterval(1000 / waylandServer()->seat()->keyRepeatRate());
+ }
+ // TODO: better time
+ processKey(m_keyRepeat.key, InputRedirection::KeyboardKeyAutoRepeat, m_keyRepeat.time);
+ }
+ );
+
connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; });
connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; });
connect(workspace(), &Workspace::clientActivated, this,
@@ -615,26 +642,14 @@ void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::Keyboa
device);
if (state == InputRedirection::KeyboardKeyPressed) {
if (m_xkb->shouldKeyRepeat(key) && waylandServer()->seat()->keyRepeatDelay() != 0) {
- QTimer *timer = new QTimer;
- timer->setInterval(waylandServer()->seat()->keyRepeatDelay());
- connect(timer, &QTimer::timeout, this,
- [this, timer, time, key] {
- const int delay = 1000 / waylandServer()->seat()->keyRepeatRate();
- if (timer->interval() != delay) {
- timer->setInterval(delay);
- }
- // TODO: better time
- processKey(key, InputRedirection::KeyboardKeyAutoRepeat, time);
- }
- );
- m_repeatTimers.insert(key, timer);
- timer->start();
+ m_keyRepeat.timer->setInterval(waylandServer()->seat()->keyRepeatDelay());
+ m_keyRepeat.key = key;
+ m_keyRepeat.time = time;
+ m_keyRepeat.timer->start();
}
} else if (state == InputRedirection::KeyboardKeyReleased) {
- auto it = m_repeatTimers.find(key);
- if (it != m_repeatTimers.end()) {
- delete it.value();
- m_repeatTimers.erase(it);
+ if (key == m_keyRepeat.key) {
+ m_keyRepeat.timer->stop();
}
}
diff --git a/keyboard_input.h b/keyboard_input.h
index 3e9808d937..dd43326556 100644
--- a/keyboard_input.h
+++ b/keyboard_input.h
@@ -140,8 +140,12 @@ private:
InputRedirection *m_input;
bool m_inited = false;
QScopedPointer m_xkb;
- QHash m_repeatTimers;
QMetaObject::Connection m_activeClientSurfaceChangedConnection;
+ struct {
+ quint32 key = 0;
+ quint32 time = 0;
+ QTimer *timer = nullptr;
+ } m_keyRepeat;
};
inline
diff --git a/useractions.h b/useractions.h
index 2a7927e9da..9f489346e7 100644
--- a/useractions.h
+++ b/useractions.h
@@ -21,6 +21,8 @@ along with this program. If not, see .
#define KWIN_USERACTIONS_H
#include "ui_shortcutdialog.h"
+#include
+
// Qt
#include
#include
@@ -52,7 +54,7 @@ class Client;
*
* @author Martin Gräßlin
**/
-class UserActionsMenu : public QObject
+class KWIN_EXPORT UserActionsMenu : public QObject
{
Q_OBJECT
public: