KWin
Loading...
Searching...
No Matches
modifier_only_shortcut_test.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include <config-kwin.h>
10
11#include "kwin_wayland_test.h"
12
13#include "input.h"
14#include "keyboard_input.h"
15#include "pointer_input.h"
16#include "wayland_server.h"
17#include "workspace.h"
18#include "xkb.h"
19
20#include <KConfigGroup>
21
22#include <QDBusConnection>
23
24#include <linux/input.h>
25
26using namespace KWin;
27
28static const QString s_socketName = QStringLiteral("wayland_test_kwin_modifier_only_shortcut-0");
29static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut");
30static const QString s_path = QStringLiteral("/Test");
31
32class ModifierOnlyShortcutTest : public QObject
33{
34 Q_OBJECT
35private Q_SLOTS:
36 void initTestCase();
37 void init();
38 void cleanup();
39
40 void testTrigger_data();
41 void testTrigger();
42 void testCapsLock();
43 void testGlobalShortcutsDisabled_data();
44 void testGlobalShortcutsDisabled();
45};
46
47class Target : public QObject
48{
49 Q_OBJECT
50 Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut")
51
52public:
53 Target();
54 ~Target() override;
55
56public Q_SLOTS:
57 Q_SCRIPTABLE void shortcut();
58
59Q_SIGNALS:
61};
62
64 : QObject()
65{
66 QDBusConnection::sessionBus().registerService(s_serviceName);
67 QDBusConnection::sessionBus().registerObject(s_path, s_serviceName, this, QDBusConnection::ExportScriptableSlots);
68}
69
71{
72 QDBusConnection::sessionBus().unregisterObject(s_path);
73 QDBusConnection::sessionBus().unregisterService(s_serviceName);
74}
75
77{
78 Q_EMIT shortcutTriggered();
79}
80
81void ModifierOnlyShortcutTest::initTestCase()
82{
83 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
84 QVERIFY(waylandServer()->init(s_socketName));
86 QRect(0, 0, 1280, 1024),
87 QRect(1280, 0, 1280, 1024),
88 });
89
90 kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
91 qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1");
92 qputenv("XKB_DEFAULT_RULES", "evdev");
93
94 kwinApp()->start();
95 QVERIFY(applicationStartedSpy.wait());
96}
97
98void ModifierOnlyShortcutTest::init()
99{
100 workspace()->setActiveOutput(QPoint(640, 512));
101 KWin::input()->pointer()->warp(QPoint(640, 512));
102}
103
104void ModifierOnlyShortcutTest::cleanup()
105{
106}
107
108void ModifierOnlyShortcutTest::testTrigger_data()
109{
110 QTest::addColumn<QStringList>("metaConfig");
111 QTest::addColumn<QStringList>("altConfig");
112 QTest::addColumn<QStringList>("controlConfig");
113 QTest::addColumn<QStringList>("shiftConfig");
114 QTest::addColumn<int>("modifier");
115 QTest::addColumn<QList<int>>("nonTriggeringMods");
116
117 const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")};
118 const QStringList e = QStringList();
119
120 QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
121 QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
122 QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT << QList<int>{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
123 QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT << QList<int>{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
124 QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
125 QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT};
126 QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA};
127 QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT << QList<int>{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA};
128}
129
130void ModifierOnlyShortcutTest::testTrigger()
131{
132 // this test verifies that modifier only shortcut triggers correctly
133 Target target;
134 QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
135
136 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("ModifierOnlyShortcuts"));
137 QFETCH(QStringList, metaConfig);
138 QFETCH(QStringList, altConfig);
139 QFETCH(QStringList, shiftConfig);
140 QFETCH(QStringList, controlConfig);
141 group.writeEntry("Meta", metaConfig);
142 group.writeEntry("Alt", altConfig);
143 group.writeEntry("Shift", shiftConfig);
144 group.writeEntry("Control", controlConfig);
145 group.sync();
147
148 // configured shortcut should trigger
149 quint32 timestamp = 1;
150 QFETCH(int, modifier);
151 Test::keyboardKeyPressed(modifier, timestamp++);
152 Test::keyboardKeyReleased(modifier, timestamp++);
153 QCOMPARE(triggeredSpy.count(), 1);
154
155 // the other shortcuts should not trigger
156 QFETCH(QList<int>, nonTriggeringMods);
157 for (auto it = nonTriggeringMods.constBegin(), end = nonTriggeringMods.constEnd(); it != end; it++) {
158 Test::keyboardKeyPressed(*it, timestamp++);
159 Test::keyboardKeyReleased(*it, timestamp++);
160 QCOMPARE(triggeredSpy.count(), 1);
161 }
162
163 // try configured again
164 Test::keyboardKeyPressed(modifier, timestamp++);
165 Test::keyboardKeyReleased(modifier, timestamp++);
166 QCOMPARE(triggeredSpy.count(), 2);
167
168 // click another key while modifier is held
169 Test::keyboardKeyPressed(modifier, timestamp++);
170 Test::keyboardKeyPressed(KEY_A, timestamp++);
171 Test::keyboardKeyReleased(KEY_A, timestamp++);
172 Test::keyboardKeyReleased(modifier, timestamp++);
173 QCOMPARE(triggeredSpy.count(), 2);
174
175 // release other key after modifier release
176 Test::keyboardKeyPressed(modifier, timestamp++);
177 Test::keyboardKeyPressed(KEY_A, timestamp++);
178 Test::keyboardKeyReleased(modifier, timestamp++);
179 Test::keyboardKeyReleased(KEY_A, timestamp++);
180 QCOMPARE(triggeredSpy.count(), 2);
181
182 // press key before pressing modifier
183 Test::keyboardKeyPressed(KEY_A, timestamp++);
184 Test::keyboardKeyPressed(modifier, timestamp++);
185 Test::keyboardKeyReleased(modifier, timestamp++);
186 Test::keyboardKeyReleased(KEY_A, timestamp++);
187 QCOMPARE(triggeredSpy.count(), 2);
188
189 // mouse button pressed before clicking modifier
190 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
191 QCOMPARE(input()->qtButtonStates(), Qt::LeftButton);
192 Test::keyboardKeyPressed(modifier, timestamp++);
193 Test::keyboardKeyReleased(modifier, timestamp++);
194 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
195 QCOMPARE(input()->qtButtonStates(), Qt::NoButton);
196 QCOMPARE(triggeredSpy.count(), 2);
197
198 // mouse button press before mod press, release before mod release
199 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
200 QCOMPARE(input()->qtButtonStates(), Qt::LeftButton);
201 Test::keyboardKeyPressed(modifier, timestamp++);
202 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
203 Test::keyboardKeyReleased(modifier, timestamp++);
204 QCOMPARE(input()->qtButtonStates(), Qt::NoButton);
205 QCOMPARE(triggeredSpy.count(), 2);
206
207 // mouse button click while mod is pressed
208 Test::keyboardKeyPressed(modifier, timestamp++);
209 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
210 QCOMPARE(input()->qtButtonStates(), Qt::LeftButton);
211 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
212 Test::keyboardKeyReleased(modifier, timestamp++);
213 QCOMPARE(input()->qtButtonStates(), Qt::NoButton);
214 QCOMPARE(triggeredSpy.count(), 2);
215
216 // scroll while mod is pressed
217 Test::keyboardKeyPressed(modifier, timestamp++);
218 Test::pointerAxisVertical(5.0, timestamp++);
219 Test::keyboardKeyReleased(modifier, timestamp++);
220 QCOMPARE(triggeredSpy.count(), 2);
221
222 // same for horizontal
223 Test::keyboardKeyPressed(modifier, timestamp++);
224 Test::pointerAxisHorizontal(5.0, timestamp++);
225 Test::keyboardKeyReleased(modifier, timestamp++);
226 QCOMPARE(triggeredSpy.count(), 2);
227
228#if KWIN_BUILD_SCREENLOCKER
229 // now try to lock the screen while modifier key is pressed
230 Test::keyboardKeyPressed(modifier, timestamp++);
231 QVERIFY(Test::lockScreen());
232 Test::keyboardKeyReleased(modifier, timestamp++);
233 QCOMPARE(triggeredSpy.count(), 2);
234
235 // now trigger while screen is locked, should also not work
236 Test::keyboardKeyPressed(modifier, timestamp++);
237 Test::keyboardKeyReleased(modifier, timestamp++);
238 QCOMPARE(triggeredSpy.count(), 2);
239
240 QVERIFY(Test::unlockScreen());
241#endif
242}
243
244void ModifierOnlyShortcutTest::testCapsLock()
245{
246 // this test verifies that Capslock does not trigger the shift shortcut
247 // but other shortcuts still trigger even when Capslock is on
248 Target target;
249 QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
250
251 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("ModifierOnlyShortcuts"));
252 group.writeEntry("Meta", QStringList());
253 group.writeEntry("Alt", QStringList());
254 group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")});
255 group.writeEntry("Control", QStringList());
256 group.sync();
258
259 // first test that the normal shortcut triggers
260 quint32 timestamp = 1;
261 const int modifier = KEY_LEFTSHIFT;
262 Test::keyboardKeyPressed(modifier, timestamp++);
263 Test::keyboardKeyReleased(modifier, timestamp++);
264 QCOMPARE(triggeredSpy.count(), 1);
265
266 // now capslock
267 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
268 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
269 QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier);
270 QCOMPARE(triggeredSpy.count(), 1);
271
272 // currently caps lock is on
273 // shift still triggers
274 Test::keyboardKeyPressed(modifier, timestamp++);
275 Test::keyboardKeyReleased(modifier, timestamp++);
276 QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier);
277 QCOMPARE(triggeredSpy.count(), 2);
278
279 // meta should also trigger
280 group.writeEntry("Meta", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")});
281 group.writeEntry("Alt", QStringList());
282 group.writeEntry("Shift", QStringList{});
283 group.writeEntry("Control", QStringList());
284 group.sync();
286 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
287 QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier);
288 QCOMPARE(input()->keyboard()->xkb()->modifiersRelevantForGlobalShortcuts(), Qt::MetaModifier);
289 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
290 QCOMPARE(triggeredSpy.count(), 3);
291
292 // set back to shift to ensure we don't trigger with capslock
293 group.writeEntry("Meta", QStringList());
294 group.writeEntry("Alt", QStringList());
295 group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")});
296 group.writeEntry("Control", QStringList());
297 group.sync();
299
300 // release caps lock
301 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
302 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
303 QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier);
304 QCOMPARE(triggeredSpy.count(), 3);
305}
306
307void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled_data()
308{
309 QTest::addColumn<QStringList>("metaConfig");
310 QTest::addColumn<QStringList>("altConfig");
311 QTest::addColumn<QStringList>("controlConfig");
312 QTest::addColumn<QStringList>("shiftConfig");
313 QTest::addColumn<int>("modifier");
314
315 const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")};
316 const QStringList e = QStringList();
317
318 QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA;
319 QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA;
320 QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT;
321 QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT;
322 QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL;
323 QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL;
324 QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT;
325 QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT;
326}
327
328void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled()
329{
330 // this test verifies that when global shortcuts are disabled inside KWin (e.g. through a window rule)
331 // the modifier only shortcuts do not trigger.
332 // see BUG: 370146
333 Target target;
334 QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered);
335
336 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("ModifierOnlyShortcuts"));
337 QFETCH(QStringList, metaConfig);
338 QFETCH(QStringList, altConfig);
339 QFETCH(QStringList, shiftConfig);
340 QFETCH(QStringList, controlConfig);
341 group.writeEntry("Meta", metaConfig);
342 group.writeEntry("Alt", altConfig);
343 group.writeEntry("Shift", shiftConfig);
344 group.writeEntry("Control", controlConfig);
345 group.sync();
347
348 // trigger once to verify the shortcut works
349 quint32 timestamp = 1;
350 QFETCH(int, modifier);
351 QVERIFY(!workspace()->globalShortcutsDisabled());
352 Test::keyboardKeyPressed(modifier, timestamp++);
353 Test::keyboardKeyReleased(modifier, timestamp++);
354 QCOMPARE(triggeredSpy.count(), 1);
355 triggeredSpy.clear();
356
357 // now disable global shortcuts
359 QVERIFY(workspace()->globalShortcutsDisabled());
360 // Should not get triggered
361 Test::keyboardKeyPressed(modifier, timestamp++);
362 Test::keyboardKeyReleased(modifier, timestamp++);
363 QCOMPARE(triggeredSpy.count(), 0);
364 triggeredSpy.clear();
365
366 // enable again
368 QVERIFY(!workspace()->globalShortcutsDisabled());
369 // should get triggered again
370 Test::keyboardKeyPressed(modifier, timestamp++);
371 Test::keyboardKeyReleased(modifier, timestamp++);
372 QCOMPARE(triggeredSpy.count(), 1);
373}
374
376#include "modifier_only_shortcut_test.moc"
PointerInputRedirection * pointer() const
Definition input.h:220
void warp(const QPointF &pos)
void setActiveOutput(Output *output)
void disableGlobalShortcutsForClient(bool disable)
void slotReconfigure()
void shortcutTriggered()
Q_SCRIPTABLE void shortcut()
const QString s_path
#define WAYLANDTEST_MAIN(TestObject)
void keyboardKeyReleased(quint32 key, quint32 time)
void setOutputConfig(const QList< QRect > &geometries)
void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta=0, InputRedirection::PointerAxisSource source=InputRedirection::PointerAxisSourceUnknown)
void keyboardKeyPressed(quint32 key, quint32 time)
void pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta=0, InputRedirection::PointerAxisSource source=InputRedirection::PointerAxisSourceUnknown)
void pointerButtonPressed(quint32 button, quint32 time)
bool unlockScreen()
bool lockScreen()
void pointerButtonReleased(quint32 button, quint32 time)
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
InputRedirection * input()
Definition input.h:549